WebMagic指北
一、快速開始
WebMagic主要包含兩個jar包:webmagic-core-{version}.jar
和webmagic-extension-{version}.jar
。在項目中添加這兩個包的依賴全蝶,即可使用WebMagic狂男。
WebMagic默認使用Maven管理依賴古沥,但是你也可以不依賴Maven進行使用秒赤。
編寫基本爬蟲
二址晕、實現(xiàn)一個PageProcessor
這部分我們直接通過GithubRepoPageProcessor
這個例子來介紹PageProcessor
的編寫方式致讥。我將PageProcessor的定制分為三個部分仅仆,分別是爬蟲的配置、頁面元素的抽取和鏈接的發(fā)現(xiàn)拄踪。
public class GithubRepoPageProcessor implements PageProcessor {
// 部分一:抓取網(wǎng)站的相關(guān)配置蝇恶,包括編碼、抓取間隔惶桐、重試次數(shù)等
private Site site = Site.me().setRetryTimes(3).setSleepTime(1000);
@Override
// process是定制爬蟲邏輯的核心接口撮弧,在這里編寫抽取邏輯
public void process(Page page) {
// 部分二:定義如何抽取頁面信息,并保存下來
page.putField("author", page.getUrl().regex("https://github\\.com/(\\w+)/.*").toString());
page.putField("name", page.getHtml().xpath("http://h1[@class='entry-title public']/strong/a/text()").toString());
if (page.getResultItems().get("name") == null) {
//skip this page
page.setSkip(true);
}
page.putField("readme", page.getHtml().xpath("http://div[@id='readme']/tidyText()"));
// 部分三:從頁面發(fā)現(xiàn)后續(xù)的url地址來抓取
page.addTargetRequests(page.getHtml().links().regex("(https://github\\.com/[\\w\\-]+/[\\w\\-]+)").all());
}
@Override
public Site getSite() {
return site;
}
public static void main(String[] args) {
Spider.create(new GithubRepoPageProcessor())
//從"https://github.com/code4craft"開始抓
.addUrl("https://github.com/code4craft")
//開啟5個線程抓取
.thread(5)
//啟動爬蟲
.run();
}
}
1姚糊、爬蟲的配置
第一部分關(guān)于爬蟲的配置贿衍,包括編碼、抓取間隔救恨、超時時間贸辈、重試次數(shù)等,也包括一些模擬的參數(shù)肠槽,例如User Agent擎淤、cookie奢啥,以及代理的設(shè)置,我們會在第5章-“爬蟲的配置”里進行介紹嘴拢。在這里我們先簡單設(shè)置一下:重試次數(shù)為3次桩盲,抓取間隔為一秒。
2席吴、頁面元素的抽取
第二部分是爬蟲的核心部分:對于下載到的Html頁面赌结,你如何從中抽取到你想要的信息?WebMagic里主要使用了三種抽取技術(shù):XPath孝冒、正則表達式和CSS選擇器柬姚。另外,對于JSON格式的內(nèi)容庄涡,可使用JsonPath進行解析量承。
-
XPath
XPath本來是用于XML中獲取元素的一種查詢語言,但是用于Html也是比較方便的啼染。例如:
page.getHtml().xpath("http://h1[@class='entry-title public']/strong/a/text()")
這段代碼使用了XPath宴合,它的意思是“查找所有class屬性為'entry-title public'的h1元素,并找到他的strong子節(jié)點的a子節(jié)點迹鹅,并提取a節(jié)點的文本信息”卦洽。 對應(yīng)的Html是這樣子的:
-
CSS選擇器
CSS選擇器是與XPath類似的語言。如果大家做過前端開發(fā)斜棚,肯定知道$('h1.entry-title')這種寫法的含義阀蒂。客觀的說弟蚀,它比XPath寫起來要簡單一些蚤霞,但是如果寫復(fù)雜一點的抽取規(guī)則,就相對要麻煩一點义钉。
-
正則表達式
正則表達式則是一種通用的文本抽取語言昧绣。
page.addTargetRequests(page.getHtml().links().regex("(https://github\\.com/\\w+/\\w+)").all());
這段代碼就用到了正則表達式,它表示匹配所有"https://github.com/code4craft/webmagic"這樣的鏈接捶闸。
-
JsonPath
JsonPath是于XPath很類似的一個語言夜畴,它用于從Json中快速定位一條內(nèi)容。WebMagic中使用的JsonPath格式可以參考這里:https://code.google.com/p/json-path/
3删壮、鏈接的發(fā)現(xiàn)
有了處理頁面的邏輯贪绘,我們的爬蟲就接近完工了!
但是現(xiàn)在還有一個問題:一個站點的頁面是很多的央碟,一開始我們不可能全部列舉出來税灌,于是如何發(fā)現(xiàn)后續(xù)的鏈接,是一個爬蟲不可缺少的一部分。
page.addTargetRequests(page.getHtml().links().regex("(https://github\\.com/\\w+/\\w+)").all());
這段代碼的分為兩部分菱涤,page.getHtml().links().regex("(https://github\\.com/\\w+/\\w+)").all()
用于獲取所有滿足"(https:/ /github.com/\w+/\w+)"這個正則表達式的鏈接苞也,page.addTargetRequests()
則將這些鏈接加入到待抓取的隊列中去。
三粘秆、 使用Selectable抽取元素
Selectable
相關(guān)的抽取元素鏈式API是WebMagic的一個核心功能墩朦。使用Selectable接口,你可以直接完成頁面元素的鏈式抽取翻擒,也無需去關(guān)心抽取的細節(jié)。
在剛才的例子中可以看到牛哺,page.getHtml()返回的是一個Html
對象陋气,它實現(xiàn)了Selectable
接口。這個接口包含一些重要的方法引润,我將它分為兩類:抽取部分和獲取結(jié)果部分巩趁。
1、 抽取部分API:
方法 | 說明 | 示例 |
---|---|---|
xpath(String xpath) | 使用XPath選擇 | html.xpath("http://div[@class='title']") |
$(String selector) | 使用Css選擇器選擇 | html.$("div.title") |
$(String selector,String attr) | 使用Css選擇器選擇 | html.$("div.title","text") |
css(String selector) | 功能同$()淳附,使用Css選擇器選擇 | html.css("div.title") |
links() | 選擇所有鏈接 | html.links() |
regex(String regex) | 使用正則表達式抽取 | html.regex("(.*?)") |
regex(String regex,int group) | 使用正則表達式抽取议慰,并指定捕獲組 | html.regex("(.*?)",1) |
replace(String regex, String replacement) | 替換內(nèi)容 | html.replace("","") |
這部分抽取API返回的都是一個Selectable
接口,意思是說奴曙,抽取是支持鏈式調(diào)用的别凹。下面我用一個實例來講解鏈式API的使用。
例如洽糟,我現(xiàn)在要抓取github上所有的Java項目炉菲,這些項目可以在https://github.com/search?l=Java&p=1&q=stars%3A%3E1&s=stars&type=Repositories搜索結(jié)果中看到。
為了避免抓取范圍太寬坤溃,我指定只從分頁部分抓取鏈接拍霜。這個抓取規(guī)則是比較復(fù)雜的,我會要怎么寫呢薪介?
首先看到頁面的html結(jié)構(gòu)是這個樣子的:
那么我可以先用CSS選擇器提取出這個div祠饺,然后在取到所有的鏈接。為了保險起見汁政,我再使用正則表達式限定一下提取出的URL的格式道偷,那么最終的寫法是這樣子的:
List<String> urls = page.getHtml().css("div.pagination").links().regex(".*/search/\?l=java.*").all();
然后,我們可以把這些URL加到抓取列表中去:
List<String> urls = page.getHtml().css("div.pagination").links().regex(".*/search/\?l=java.*").all();
page.addTargetRequests(urls);
是不是比較簡單烂完?除了發(fā)現(xiàn)鏈接试疙,Selectable的鏈式抽取還可以完成很多工作。我們會在第9章示例中再講到抠蚣。
2祝旷、獲取結(jié)果的API:
當鏈式調(diào)用結(jié)束時,我們一般都想要拿到一個字符串類型的結(jié)果。這時候就需要用到獲取結(jié)果的API了怀跛。我們知道距贷,一條抽取規(guī)則,無論是XPath吻谋、CSS選擇器或者正則表達式忠蝗,總有可能抽取到多條元素。WebMagic對這些進行了統(tǒng)一漓拾,你可以通過不同的API獲取到一個或者多個元素阁最。
方法 | 說明 | 示例 |
---|---|---|
get() | 返回一條String類型的結(jié)果 | String link= html.links().get() |
toString() | 功能同get(),返回一條String類型的結(jié)果 | String link= html.links().toString() |
all() | 返回所有抽取結(jié)果 | List links= html.links().all() |
match() | 是否有匹配結(jié)果 | if (html.links().match()){ xxx; } |
例如骇两,我們知道頁面只會有一條結(jié)果速种,那么可以使用selectable.get()或者selectable.toString()拿到這條結(jié)果。
這里selectable.toString()采用了toString()這個接口低千,是為了在輸出以及和一些框架結(jié)合的時候配阵,更加方便。因為一般情況下示血,我們都只需要選擇一個元素棋傍!
selectable.all()則會獲取到所有元素。
好了难审,到現(xiàn)在為止瘫拣,在回過頭看看3.1中的GithubRepoPageProcessor,可能就覺得更加清晰了吧剔宪?指定main方法拂铡,已經(jīng)可以看到抓取結(jié)果在控制臺輸出了。
四葱绒、使用Pipeline保存結(jié)果
好了感帅,爬蟲編寫完成,現(xiàn)在我們可能還有一個問題:我如果想把抓取的結(jié)果保存下來地淀,要怎么做呢失球?WebMagic用于保存結(jié)果的組件叫做Pipeline
。例如我們通過“控制臺輸出結(jié)果”這件事也是通過一個內(nèi)置的Pipeline完成的帮毁,它叫做ConsolePipeline
实苞。那么,我現(xiàn)在想要把結(jié)果用Json的格式保存下來烈疚,怎么做呢黔牵?我只需要將Pipeline的實現(xiàn)換成"JsonFilePipeline"就可以了。
public static void main(String[] args) {
Spider.create(new GithubRepoPageProcessor())
//從"https://github.com/code4craft"開始抓
.addUrl("https://github.com/code4craft")
.addPipeline(new JsonFilePipeline("D:\\webmagic\\"))
//開啟5個線程抓取
.thread(5)
//啟動爬蟲
.run();
}
這樣子下載下來的文件就會保存在D盤的webmagic目錄中了爷肝。
通過定制Pipeline猾浦,我們還可以實現(xiàn)保存結(jié)果到文件陆错、數(shù)據(jù)庫等一系列功能。這個會在第7章“抽取結(jié)果的處理”中介紹金赦。
至此為止音瓷,我們已經(jīng)完成了一個基本爬蟲的編寫,也具有了一些定制功能夹抗。
五绳慎、爬蟲的配置、啟動和終止
1漠烧、Spider
Spider
是爬蟲啟動的入口杏愤。在啟動爬蟲之前,我們需要使用一個PageProcessor
創(chuàng)建一個Spider對象已脓,然后使用run()
進行啟動声邦。同時Spider的其他組件(Downloader、Scheduler摆舟、Pipeline)都可以通過set方法來進行設(shè)置。
方法 | 說明 | 示例 |
---|---|---|
create(PageProcessor) | 創(chuàng)建Spider | Spider.create(new GithubRepoProcessor()) |
addUrl(String…) | 添加初始的URL | spider .addUrl("http://webmagic.io/docs/") |
addRequest(Request...) | 添加初始的Request | spider .addRequest("http://webmagic.io/docs/") |
thread(n) | 開啟n個線程 | spider.thread(5) |
run() | 啟動邓了,會阻塞當前線程執(zhí)行 | spider.run() |
start()/runAsync() | 異步啟動恨诱,當前線程繼續(xù)執(zhí)行 | spider.start() |
stop() | 停止爬蟲 | spider.stop() |
test(String) | 抓取一個頁面進行測試 | spider .test("http://webmagic.io/docs/") |
addPipeline(Pipeline) | 添加一個Pipeline,一個Spider可以有多個Pipeline | spider .addPipeline(new ConsolePipeline()) |
setScheduler(Scheduler) | 設(shè)置Scheduler骗炉,一個Spider只能有個一個Scheduler | spider.setScheduler(new RedisScheduler()) |
setDownloader(Downloader) | 設(shè)置Downloader照宝,一個Spider只能有個一個Downloader | spider .setDownloader(new SeleniumDownloader()) |
get(String) | 同步調(diào)用,并直接取得結(jié)果 | ResultItems result = spider .get("http://webmagic.io/docs/") |
getAll(String…) | 同步調(diào)用句葵,并直接取得一堆結(jié)果 | List<ResultItems> results = spider .getAll("http://webmagic.io/docs/", "http://webmagic.io/xxx") |
2厕鹃、Site
對站點本身的一些配置信息,例如編碼乍丈、HTTP頭剂碴、超時時間、重試策略等轻专、代理等忆矛,都可以通過設(shè)置Site
對象來進行配置。
方法 | 說明 | 示例 |
---|---|---|
setCharset(String) | 設(shè)置編碼 | site.setCharset("utf-8") |
setUserAgent(String) | 設(shè)置UserAgent | site.setUserAgent("Spider") |
setTimeOut(int) | 設(shè)置超時時間请垛,單位是毫秒 | site.setTimeOut(3000) |
setRetryTimes(int) | 設(shè)置重試次數(shù) | site.setRetryTimes(3) |
setCycleRetryTimes(int) | 設(shè)置循環(huán)重試次數(shù) | site.setCycleRetryTimes(3) |
addCookie(String,String) | 添加一條cookie | site.addCookie("dotcomt_user","code4craft") |
setDomain(String) | 設(shè)置域名催训,需設(shè)置域名后,addCookie才可生效 | site.setDomain("github.com") |
addHeader(String,String) | 添加一條addHeader | site.addHeader("Referer","https://github.com") |
setHttpProxy(HttpHost) | 設(shè)置Http代理 | site.setHttpProxy(new HttpHost("127.0.0.1",8080)) |
其中循環(huán)重試cycleRetry是0.3.0版本加入的機制宗收。
該機制會將下載失敗的url重新放入隊列尾部重試漫拭,直到達到重試次數(shù),以保證不因為某些網(wǎng)絡(luò)原因漏抓頁面混稽。
配置代理
從0.7.1版本開始采驻,WebMagic開始使用了新的代理APIProxyProvider
审胚。因為相對于Site的“配置”,ProxyProvider定位更多是一個“組件”挑宠,所以代理不再從Site設(shè)置菲盾,而是由HttpClientDownloader
設(shè)置。
API | 說明 |
---|---|
HttpClientDownloader.setProxyProvider(ProxyProvider proxyProvider) | 設(shè)置代理 |
ProxyProvider
有一個默認實現(xiàn):SimpleProxyProvider
各淀。它是一個基于簡單Round-Robin的懒鉴、沒有失敗檢查的ProxyProvider∷榻剑可以配置任意個候選代理临谱,每次會按順序挑選一個代理使用。它適合用在自己搭建的比較穩(wěn)定的代理的場景奴璃。
代理示例:
- 設(shè)置單一的普通HTTP代理為101.101.101.101的8888端口悉默,并設(shè)置密碼為"username","password"
HttpClientDownloader httpClientDownloader = new HttpClientDownloader();
httpClientDownloader.setProxyProvider(SimpleProxyProvider.from(new Proxy("101.101.101.101",8888,"username","password")));
spider.setDownloader(httpClientDownloader);
- 設(shè)置代理池,其中包括101.101.101.101和102.102.102.102兩個IP苟穆,沒有密碼
HttpClientDownloader httpClientDownloader = new HttpClientDownloader();
httpClientDownloader.setProxyProvider(SimpleProxyProvider.from(
new Proxy("101.101.101.101",8888)
,new Proxy("102.102.102.102",8888)));