教你如何用Java簡(jiǎn)單爬取WebMagic
一、Java爬蟲(chóng)——WebMagic
1.1 WebMagic總體架構(gòu)圖
1.2 WebMagic核心組件
1.2.1 Downloader
該組件負(fù)責(zé)從互聯(lián)網(wǎng)上下載頁(yè)面。WebMagic默認(rèn)使用Apache HttpClient
作為下載工具。
1.2.2 PageProcessor
該組件負(fù)責(zé)解析頁(yè)面,根據(jù)我們的業(yè)務(wù)進(jìn)行抽取信息。WebMagic使用Jsoup作為HTML解析工具,并基于其開(kāi)發(fā)了解析Xpath的工具Xsoup。
1.2.3 Scheduler
該組件負(fù)責(zé)管理待抓取的URL,以及去重的工作。WebMagic默認(rèn)使用JDK內(nèi)存隊(duì)列管理URL,通過(guò)集合進(jìn)行去重。
支持使用Redis進(jìn)行分布式管理。
1.2.4 Pipeline
該組件負(fù)責(zé)抽取結(jié)果的處理,包括計(jì)算、持久化到文件、數(shù)據(jù)庫(kù)等等。
1.2.5 數(shù)據(jù)流轉(zhuǎn)對(duì)象
1. Request
Request
是對(duì)URL地址的一層封裝,一個(gè)Request對(duì)應(yīng)一個(gè)URL地址。
它是PageProcessor與Downloader交互的載體,也是PageProcessor控制Downloader唯一方式。
除了URL本身外,它還包含一個(gè)Key-Value結(jié)構(gòu)的字段extra
。你可以在extra中保存一些特殊的屬性,然后在其他地方讀取,以完成不同的功能。例如附加上一個(gè)頁(yè)面的一些信息等。
2. Page
Page
代表了從Downloader下載到的一個(gè)頁(yè)面——可能是HTML,也可能是JSON或者其他文本格式的內(nèi)容。
Page是WebMagic抽取過(guò)程的核心對(duì)象,它提供一些方法可供抽取、結(jié)果保存等。
3. ResultItems
ResultItems
相當(dāng)于一個(gè)Map,底層使用了LinkedHashMap
進(jìn)行存儲(chǔ),它保存PageProcessor處理的結(jié)果,供Pipeline使用。它的API與Map很類(lèi)似,值得注意的是它有一個(gè)字段skip
,若設(shè)置為true,則不被Pipeline處理,跳過(guò)。
1.2.6 Spider——WebMagic核心引擎
Spider是WebMagic內(nèi)部流程的核心。Downloader、PageProcessor、Scheduler、Pipeline都是Spider的一個(gè)屬性,這些屬性是可以自由設(shè)置的,通過(guò)設(shè)置這個(gè)屬性可以實(shí)現(xiàn)不同的功能。Spider也是WebMagic操作的入口,它封裝了爬蟲(chóng)的創(chuàng)建、啟動(dòng)、停止、多線(xiàn)程等功能。
1.3 練習(xí)Demo
需求是爬取一篇廈門(mén)限行文章,文章來(lái)源:http://xm.bendibao.com/traffic/2018116/54311.shtm
,具體需求如下:
1.刪除文章中超鏈接
2.文章中的圖片下載至本地
3.刪除文章末尾:溫馨提示...
1.3.1 定制Downloader
為預(yù)防頁(yè)面失效,定制一個(gè)Downloader
,當(dāng)鏈接地址不存在時(shí),打印日志。
public class MyHttpClientDownloader extends HttpClientDownloader { private Logger logger = LoggerFactory.getLogger(this.getClass()); /** * 提示頁(yè)面獲取狀態(tài)碼 */ @Override protected Page handleResponse(Request request, String charset, HttpResponse httpResponse, Task task) throws IOException { Page page = super.handleResponse(request, charset, httpResponse, task); if(httpResponse.getStatusLine().getStatusCode()!= ConstantsField.PAGE_STATUS_200){ page.setDownloadSuccess(false); logger.warn("頁(yè)面獲取狀態(tài)碼錯(cuò)誤,正在重試!"); } return page; } }
1.3.2 定制PageProcessor
該頁(yè)面處理器實(shí)現(xiàn)了對(duì)頁(yè)面的抽取,符合上面的需求。
將處理完成的數(shù)據(jù)添加進(jìn)入:Page
對(duì)象,并設(shè)置鍵分別為:imgList
與content
。
public class XmPageProcessor implements PageProcessor { /** * 抓取網(wǎng)站的相關(guān)配置,包括編碼、抓取間隔、重試次數(shù)等 */ private Site site = Site.me().setCycleRetryTimes(3).setSleepTime(1000); /** * 核心:編寫(xiě)抽取邏輯 */ @Override public void process(Page page) { // 抽取頁(yè)面文本數(shù)據(jù) Selectable selectable = page.getHtml().css(ConstantsField.PAGE_CSS_CONTENT); //處理圖片 List<String> pImgList = selectable.xpath(ConstantsField.XPATH_IMG).all(); List<String> imgUrl = new ArrayList<>(); if(pImgList.size()>0){ Pattern compile = Pattern.compile(ConstantsField.REX_IMG_SRC); for (String img : pImgList) { Matcher matcher = compile.matcher(img); while (matcher.find()){ imgUrl.add(matcher.group(1)); } } } if(imgUrl.size()>0){ page.putField("imgList",imgUrl); }else { page.putField("imgList",null); } //對(duì)內(nèi)容轉(zhuǎn)換為StringBuilder String content = selectable.toString(); StringBuilder stringBuilder = new StringBuilder(content); //處理超鏈接 StringBuilder newString = dealLink(stringBuilder); //處理末尾 int startIndex = newString.indexOf(ConstantsField.END_CONTENT); if(startIndex>0) { newString.delete(startIndex, stringBuilder.length()); newString.append("</div>"); } page.putField("content",newString.toString()); } @Override public Site getSite() { return site; } /** * 處理超鏈接 */ private static StringBuilder dealLink(StringBuilder stringBuilder){ StringBuilder newString = new StringBuilder(stringBuilder); int aIndex = newString.indexOf("<a href"); while (aIndex != -1){ int pStart = newString.lastIndexOf("<p>", aIndex); int pEnd = (newString.indexOf("</p>", aIndex) + 4); newString.delete(pStart,pEnd); aIndex = newString.indexOf("<a href"); } return newString; } }
1.3.3 定制Pipeline
Pipeline
是處理結(jié)果的地方,這里我們對(duì)結(jié)果進(jìn)行存儲(chǔ)文件的處理。網(wǎng)站文本存儲(chǔ)為:stm
格式,圖片文本存儲(chǔ)為其網(wǎng)站源文件的格式。
public class MyFilePipeline extends FilePersistentBase implements Pipeline { private Logger logger = LoggerFactory.getLogger(this.getClass()); private StringBuilder filepath; private MyFilePipeline() { this.setPath(ConstantsField.DEFAULT_SAVE_LOCATION); } public MyFilePipeline(String path) { if(path!=null){ this.setPath(path); }else { new MyFilePipeline(); } filepath = new StringBuilder().append(this.path). append(PATH_SEPERATOR).append(ConstantsField.FILE_NAME) .append(ConstantsField.FILE_POSTFIX); } @Override public void process(ResultItems resultItems, Task task) { //文件內(nèi)容覆蓋 try(PrintWriter printWriter = new PrintWriter(new FileWriter(getFile(filepath.toString()),false))) { printWriter.write(resultItems.get("content").toString()); logger.info("文件生成成功,存儲(chǔ)地址為:"+filepath); //下載圖片 List<String> imgList = resultItems.get("imgList"); if(imgList!=null&&imgList.size()>0){ boolean dowload = DownloadImgUtils.download(imgList, this.getPath()); if(dowload){ logger.info("圖片下載成功,存儲(chǔ)地址為:" + this.getPath()); } } } catch (IOException e) { logger.error("輸出文件出錯(cuò):" + e.getCause().toString()); } } }
這里實(shí)現(xiàn)了網(wǎng)頁(yè)存儲(chǔ)為stm
格式與圖片存儲(chǔ),圖片存儲(chǔ)使用了如下工具類(lèi)DownloadImgUtils
:
public class DownloadImgUtils { /** * 下載圖片 * @param imgList 圖片列表 * @param savePath 保存地址 * @return 成功返回true */ public static boolean download(List<String> imgList, String savePath) throws IOException { URL url; DataInputStream dataInputStream = null; FileOutputStream fileOutputStream = null; File file; try { for (String imgUrl : imgList) { //截取文件名 Pattern pat=Pattern.compile(ConstantsField.REX_IMG_SUFFIX); Matcher mc=pat.matcher(imgUrl); while(mc.find()) { String fileName= mc.group(); file = new File(savePath + fileName); file.createNewFile(); fileOutputStream = new FileOutputStream(savePath + fileName); } url = new URL(imgUrl); dataInputStream = new DataInputStream(url.openStream()); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int length; while ((length = dataInputStream.read(buffer))>0){ outputStream.write(buffer,0,length); } fileOutputStream.write(outputStream.toByteArray()); } return true; }catch (Exception e){ e.printStackTrace(); }finally { dataInputStream.close(); fileOutputStream.close(); } return false; } }
1.3.4 啟動(dòng)類(lèi)
public class WebMagicApplication { private String url; private String saveUrl; /** * 無(wú)參構(gòu)造使用默認(rèn)值 */ public WebMagicApplication() { this.url = ConstantsField.XM_BDB_URL; this.saveUrl = ConstantsField.DEFAULT_SAVE_LOCATION; } public WebMagicApplication(String url, String saveUrl) { this.url = url; this.saveUrl = saveUrl; } public void start(){ Spider.create(new XmPageProcessor()).addUrl(this.url).addPipeline(new MyFilePipeline(this.saveUrl)).setDownloader(new MyHttpClientDownloader()).run(); } public static void main(String[] args) { WebMagicApplication webMagicApplication = new WebMagicApplication("http://xm.bendibao.com/traffic/2018116/5431122.shtm","C:\\"); webMagicApplication.start(); } }
這里啟動(dòng)類(lèi)可以使用帶參構(gòu)造或無(wú)參構(gòu)造,無(wú)參構(gòu)造默認(rèn)使用URL與存儲(chǔ)地址為ConstantsField
類(lèi)中的XM_BDB_URL
屬性和DEFAULT_SAVE_LOCATION
。
練習(xí)中的ConstantsField
具體如下:
public final class ConstantsField { /** * 爬取的CSS的文本 */ public static final String PAGE_CSS_CONTENT = "div.content"; /** * 結(jié)束文本起始位置 */ public static final String END_CONTENT = "<div id=\"adInArticle\"></div>"; /** * 廈門(mén)本地寶URL */ public static final String XM_BDB_URL = "http://xm.bendibao.com/traffic/2018116/54311.shtm"; /** * 默認(rèn)文件保存地址 */ public static final String DEFAULT_SAVE_LOCATION = "C:\\"; /** * 文件名 */ public static final String FILE_NAME = "2021廈門(mén)限行最新消息(持續(xù)更新)"; /** * 文件后綴 */ public static final String FILE_POSTFIX = ".stm"; /** * 頁(yè)面訪(fǎng)問(wèn)狀態(tài)碼 */ public static final int PAGE_STATUS_200 = 200; /** * 正則匹配src */ public static final String REX_IMG_SRC = "src\\s*=\\s*\"?(.*?)(\"|>|\\s+)"; /** * 正則匹配文件后綴 */ public static final String REX_IMG_SUFFIX = "[\\w]+[\\.](jpeg|jpg|png)"; /** * 處理圖片XPATH */ public static final String XPATH_IMG = "http://*[@id=\"bo\"]/*/img"; }
1.3.5 源碼地址
練習(xí)Demo源碼地址: https://gitee.com/Xiaoxinnolabi/web-magic/settings
WebMagic中文文檔: http://webmagic.io/docs/zh/
WebMagic源碼地址: https://github.com/code4craft/webmagic/
到此這篇關(guān)于教你如何用Java簡(jiǎn)單爬取WebMagic的文章就介紹到這了,更多相關(guān)Java爬取WebMagic內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)順時(shí)針輸出螺旋二維數(shù)組的方法示例
這篇文章主要介紹了利用Java如何實(shí)現(xiàn)順時(shí)針輸出螺旋二維數(shù)組的方法示例,文中給出了詳細(xì)的示例代碼和注釋?zhuān)嘈艑?duì)大家具有一定的參考價(jià)值,有需要的朋友們下面來(lái)一起看看吧。2017-02-02Java實(shí)現(xiàn)中國(guó)象棋的示例代碼
中國(guó)象棋是起源于中國(guó)的一種棋,屬于二人對(duì)抗性游戲的一種,在中國(guó)有著悠久的歷史。由于用具簡(jiǎn)單,趣味性強(qiáng),成為流行極為廣泛的棋藝活動(dòng)。本文將利用Java實(shí)現(xiàn)這一經(jīng)典游戲,需要的可以參考一下2022-02-02Java Validation Api使用方法實(shí)例解析
這篇文章主要介紹了Java Validation Api使用方法實(shí)例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09SpringBoot整合mybatis使用Druid做連接池的方式
這篇文章主要介紹了SpringBoot整合mybatis使用Druid做連接池的方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08