webmagic是一個開源的Java垂直爬蟲架構,目标是簡化爬蟲的開發流程,讓開發者專注于邏輯功能的開發。webmagic的核心非常簡單,但是覆寫爬蟲的整個流程,也是很好的學習爬蟲開發的材料。作者曾經進行過一年的垂直爬蟲的開發,webmagic就是為了解決爬蟲開發的一些重複勞動而産生的架構。
web爬蟲是一種技術,webmagic緻力于将這種技術的實作成本降低,但是出于對資源提供者的尊重,webmagic不會做反封鎖的事情,包括:驗證碼破解、代理切換、自動登入、抓取靜态資源等。
webmagic的架構和設計參考了以下兩個項目,感謝以下兩個項目的作者:
python爬蟲 scrapy https://github.com/scrapy/scrapy
Java爬蟲 Spiderman https://gitcafe.com/laiweiwei/Spiderman
webmagic遵循Apache 2.0協定,你可以自由進行使用和修改。有使用不便或者問題,歡迎在github送出issue,或者在oschina讨論子產品提問。
快速開始
使用maven
webmagic使用maven管理依賴,你可以直接下載下傳webmagic源碼進行編譯:
git clone https://github.com/code4craft/webmagic.git
mvn clean install
安裝後,在項目中添加對應的依賴即可使用webmagic:
<dependency>
<groupId>us.codecraft</groupId>
<artifactId>webmagic-core</artifactId>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>us.codecraft</groupId>
<artifactId>webmagic-extension</artifactId>
<version>0.2.0</version>
</dependency>
項目結構
webmagic主要包括兩個包:
-
webmagic-core
webmagic核心部分,隻包含爬蟲基本子產品和基本抽取器。webmagic-core的目标是成為網頁爬蟲的一個教科書般的實作。
-
webmagic-extension
webmagic的擴充子產品,提供一些更友善的編寫爬蟲的工具。包括注解格式定義爬蟲、JSON、分布式等支援。
webmagic還包含兩個可用的擴充包,因為這兩個包都依賴了比較重量級的工具,是以從主要包中抽離出來:
-
webmagic-saxon
webmagic與Saxon結合的子產品。Saxon是一個XPath、XSLT的解析工具,webmagic依賴Saxon來進行XPath2.0文法解析支援。
-
webmagic-selenium
webmagic與Selenium結合的子產品。Selenium是一個模拟浏覽器進行頁面渲染的工具,webmagic依賴Selenium進行動态頁面的抓取。
在項目中,你可以根據需要依賴不同的包。
不使用maven
<<<<<<< HEAD
不使用maven的使用者,可以下載下傳這個二進制打包版本(感謝oschina):
=======
不使用maven的使用者,可以下載下傳附帶二進制jar包的版本(感謝oschina):
>>>>>>> 3f4368cf780251a8d9fdd9da8cfff73e1190a637
git clone http://git.oschina.net/flashsword20/webmagic-bin.git
在**bin/lib**目錄下,有項目依賴的所有jar包,直接在IDE裡import即可。
第一個爬蟲
定制PageProcessor
PageProcessor是webmagic-core的一部分,定制一個PageProcessor即可實作自己的爬蟲邏輯。以下是抓取osc部落格的一段代碼:
public class OschinaBlogPageProcesser implements PageProcessor {
private Site site = Site.me().setDomain("my.oschina.net")
.addStartUrl("http://my.oschina.net/flashsword/blog");
@Override
public void process(Page page) {
List<String> links = page.getHtml().links().regex("http://my\\.oschina\\.net/flashsword/blog/\\d+").all();
page.addTargetRequests(links);
page.putField("title", page.getHtml().xpath("//div[@class='BlogEntity']/div[@class='BlogTitle']/h1").toString());
page.putField("content", page.getHtml().$("div.content").toString());
page.putField("tags",page.getHtml().xpath("//div[@class='BlogTags']/a/text()").all());
}
@Override
public Site getSite() {
return site;
}
public static void main(String[] args) {
Spider.create(new OschinaBlogPageProcesser())
.pipeline(new ConsolePipeline()).run();
}
}
這裡通過page.addTargetRequests()方法來增加要抓取的URL,并通過page.putField()來儲存抽取結果。page.getHtml().xpath()則是按照某個規則對結果進行抽取,這裡抽取支援鍊式調用。調用結束後,toString()表示轉化為單個String,all()則轉化為一個String清單。
Spider是爬蟲的入口類。Pipeline是結果輸出和持久化的接口,這裡ConsolePipeline表示結果輸出到控制台。
執行這個main方法,即可在控制台看到抓取結果。webmagic預設有3秒抓取間隔,請耐心等待。
使用注解
webmagic-extension包括了注解方式編寫爬蟲的方法,隻需基于一個POJO增加注解即可完成一個爬蟲。以下仍然是抓取oschina部落格的一段代碼,功能與OschinaBlogPageProcesser完全相同:
@TargetUrl("http://my.oschina.net/flashsword/blog/\\d+")
public class OschinaBlog {
@ExtractBy("//title")
private String title;
@ExtractBy(value = "div.BlogContent",type = ExtractBy.Type.Css)
private String content;
@ExtractBy(value = "//div[@class='BlogTags']/a/text()", multi = true)
private List<String> tags;
public static void main(String[] args) {
OOSpider.create(
Site.me().addStartUrl("http://my.oschina.net/flashsword/blog"),
new ConsolePageModelPipeline(), OschinaBlog.class).run();
}
}
這個例子定義了一個Model類,Model類的字段'title'、'content'、'tags'均為要抽取的屬性。這個類在Pipeline裡是可以複用的。
注解的詳細使用方式見後文中得webmagic-extension注解子產品。
webmagic-core
webmagic-core是爬蟲的核心架構,隻包括一個爬蟲各功能子產品的核心功能。webmagic-core的目标是成為網頁爬蟲的一個教科書般的實作。
此節部分内容摘自作者的博文 webmagic的設計機制及原理-如何開發一個Java爬蟲。
webmagic-core的子產品劃分
webmagic-core參考了scrapy的子產品劃分,分為Spider(整個爬蟲的排程架構)、Downloader(頁面下載下傳)、PageProcessor(連結提取和頁面分析)、Scheduler(URL管理)、Pipeline(離線分析和持久化)幾部分。隻不過scrapy通過middleware實作擴充,而webmagic則通過定義這幾個接口,并将其不同的實作注入主架構類Spider來實作擴充。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLjl2Zh1mYld3LcNHdz9Gcvw1cldWYtl2Lc9WauIWdoRXan5CdmFmcjRTZk92Yvw1LcpDc0RHaiojIsJye.png)
Spider類(核心排程)
Spider是爬蟲的入口類,Spider的接口調用采用了鍊式的API設計,其他功能全部通過接口注入Spider實作,下面是啟動一個比較複雜的Spider的例子。
Spider.create(sinaBlogProcessor)
.scheduler(new FileCacheQueueScheduler("/data/temp/webmagic/cache/"))
.pipeline(new FilePipeline())
.thread(10).run();
Spider的核心處理流程非常簡單,代碼如下:
<!-- lang: java -->
private void processRequest(Request request) {
Page page = downloader.download(request, this);
if (page == null) {
sleep(site.getSleepTime());
return;
}
pageProcessor.process(page);
addRequest(page);
for (Pipeline pipeline : pipelines) {
pipeline.process(page, this);
}
sleep(site.getSleepTime());
}
<<<<<<< HEAD =======
Spider還包括一個方法test(String url),該方法隻抓取一個單獨的頁面,用于測試抽取效果。
>>>>>>> 3f4368cf780251a8d9fdd9da8cfff73e1190a637
PageProcessor(頁面分析及連結抽取)
頁面分析是垂直爬蟲中需要定制的部分。在webmagic-core裡,通過實作**PageProcessor**接口來實作定制爬蟲。PageProcessor有兩個核心方法:public void process(Page page)和public Site getSite() 。
-
public void process(Page page)
通過對**Page**對象的操作,實作爬蟲邏輯。Page對象包括兩個最重要的方法:addTargetRequests()可以添加URL到待抓取隊列,put()可以将結果儲存供後續處理。 Page的資料可以通過Page.getHtml()和Page.getUrl()擷取。
-
public Site getSite()
Site對象定義了爬蟲的域名、起始位址、抓取間隔、編碼等資訊。
Selector是webmagic為了簡化頁面抽取開發的獨立子產品,是webmagic-core的主要着力點。這裡整合了CSS Selector、XPath和正規表達式,并可以進行鍊式的抽取。
<!-- lang: java -->
//content是用别的爬蟲工具抽取到的正文
List<String> links = page.getHtml()
.$("div.title") //css 選擇,Java裡雖然很少有$符号出現,不過貌似$作為方法名是合法的
.xpath("//@href") //提取連結
.regex(".*blog.*") //正則比對過濾
.all(); //轉換為string清單
webmagic包括一個對于頁面正文的自動抽取的類**SmartContentSelector**。相信用過Evernote Clearly都會對其自動抽取正文的技術印象深刻。這個技術又叫**Readability**。當然webmagic對Readability的實作還比較粗略,但是仍有一些學習價值。
基于Saxon,webmagic提供了XPath2.0文法的支援。XPath2.0文法支援内部函數、邏輯控制等,是一門完整的語言,如果你熟悉XPath2.0文法,倒是不妨一試(需要引入**webmagic-saxon**包)。
webmagic-samples包裡有一些為某個站點定制的PageProcessor,供學習之用。
Downloader(頁面下載下傳)
Downloader是webmagic中下載下傳頁面的接口,主要方法:
-
public Page download(Request request, Task task)
Request對象封裝了待抓取的URL及其他資訊,而Page則包含了頁面下載下傳後的Html及其他資訊。Task是一個包裝了任務對應的Site資訊的抽象接口。
-
public void setThread(int thread)
因為Downloader一般會涉及連接配接池等功能,而這些功能與多線程密切相關,是以定義了此方法。
目前有幾個Downloader的實作:
-
HttpClientDownloader
內建了**Apache HttpClient**的Downloader。Apache HttpClient(4.0後整合到HttpCompenent項目中)是強大的Java http下載下傳器,它支援自定義HTTP頭(對于爬蟲比較有用的就是User-agent、cookie等)、自動redirect、連接配接複用、cookie保留、設定代理等諸多強大的功能。
-
SeleniumDownloader
對于一些Javascript動态加載的網頁,僅僅使用http模拟下載下傳工具,并不能取到頁面的内容。這方面的思路有兩種:一種是抽絲剝繭,分析js的邏輯,再用爬蟲去重制它;另一種就是:内置一個浏覽器,直接擷取最後加載完的頁面。**webmagic-selenium**包中整合了Selenium到SeleniumDownloader,可以直接進行動态加載頁面的抓取。使用selenium需要安裝一些native的工具,具體步驟可以參考作者的博文使用Selenium來抓取動态加載的頁面
Scheduler(URL管理)
Scheduler是webmagic的管理子產品,通過實作Scheduler可以定制自己的URL管理器。Scheduler包括兩個主要方法:
-
public void push(Request request,Task task)
将待抓取URL加入Scheduler。Request對象是對URL的一個封裝,還包括優先級、以及一個供存儲資料的Map。Task仍然用于區分不同任務,在多個任務公用一個Scheduler時可以此進行區分。
-
public Request poll(Task task)
從Scheduler裡取出一條請求,并進行後續執行。
webmagic目前有三個Scheduler的實作:
-
QueueScheduler
一個簡單的記憶體隊列,速度較快,并且是線程安全的。
-
FileCacheQueueScheduler
使用檔案儲存隊列,它可以用于耗時較長的下載下傳任務,在任務中途停止後(手動停止或者程式崩潰),下次執行仍然從中止的URL開始繼續爬取。
-
RedisScheduler
使用redis存儲URL隊列。通過使用同一台redis伺服器存儲URL,webmagic可以很容易的在多機部署,進而達到分布式爬蟲的效果。
Pipeline(後續處理和持久化)
Pipeline是最終抽取結果進行輸出和持久化的接口。它隻包括一個方法:
-
public void process(ResultItems resultItems,Task task)
ResultItems是內建了抽取結果的對象。通過ResultItems.get(key)可以擷取抽取結果。Task同樣是用于區分不同任務的對象。
webmagic包括以下幾個Pipeline的實作:
-
ConsolePipeline
直接輸出結果到控制台,測試時使用。
-
FilePipeline
輸出結果到檔案,每個URL單獨儲存到一個頁面,以URL的MD5結果作為檔案名。通過構造函數
定義存儲路徑,**以下使用檔案持久化的類,多數都使用此方法指定路徑**。public FilePipeline(String path)
-
JsonFilePipeline
以JSON輸出結果到檔案(.json字尾),其他與FilePipeline相同。
webmagic目前不支援持久化到資料庫,但是結合其他工具,持久化到資料庫也是很容易的。這裡不妨看一下webmagic結合JFinal持久化到資料庫的一段代碼。因為JFinal目前還不支援maven,是以這段代碼并沒有放到webmagic-samples裡來。
webmagic-extension
webmagic-extension是為了開發爬蟲更友善而實作的一些功能子產品。這些功能完全基于webmagic-core的架構,包括注解形式編寫爬蟲、分頁、分布式等功能。
注解子產品
webmagic-extension包括注解子產品。為什麼會有注解方式?
因為PageProcessor的方式靈活、強大,但是沒有解決兩個問題:
- 對于一個站點,如果想抓取多種格式的URL,那麼必須在PageProcesser中寫判斷邏輯,代碼難以管理。
- 抓取結果沒有對應Model,并不符合Java程式開發習慣,與一些架構也無法很好整合。
注解的核心是Model類,本身是一個POJO,這個Model類用于傳遞、儲存頁面最終抓取結果資料。注解方式直接将抽取與資料綁定,以便于編寫和維護。
注解方式其實也是通過一個PageProcessor的實作--ModelPageProcessor完成,是以對webmagic-core代碼沒有任何影響。仍然以抓取OschinaBlog的程式為例:
@TargetUrl("http://my.oschina.net/flashsword/blog/\\d+")
public class OschinaBlog {
@ExtractBy("//title")
private String title;
@ExtractBy(value = "div.BlogContent",type = ExtractBy.Type.Css)
private String content;
@ExtractBy(value = "//div[@class='BlogTags']/a/text()", multi = true)
private List<String> tags;
public static void main(String[] args) {
OOSpider.create(
Site.me().addStartUrl("http://my.oschina.net/flashsword/blog"),
new ConsolePageModelPipeline(), OschinaBlog.class).run();
}
}
注解部分包括以下内容:
-
TargetUrl
"TargetUrl"表示這個Model對應要抓取的URL,它包含兩層意思:符合這個條件的URL會被加入抓取隊列;符合這個條件的URL會被這個Model抓取。TargetUrl可以**sourceRegion**指定提取URL的區域(僅支援XPath)。
TargetUrl使用了正規表達式,比對 "http://my.oschina.net/flashsword/blog/150039" 格式的URL。webmagic對正規表達式進行了修改,"."僅表示字元"."而不代表任意字元,而"*"則代表了".*",例如"http://*.oschina.net/*"代表了oschina所有的二級域名下的URL。
與TargetUrl相似的還有**HelpUrl**,HelpUrl表示:僅僅抓取該URL用作連結提取,并不對它進行内容抽取。例如部落格正文頁對應TargetUrl,而清單頁則對應HelpUrl。
-
ExtractBy
-
用于字段
"ExtractBy"可用于類以及字段。用于字段時,定義了字段抽取的規則。抽取的規則預設使用XPath,也可以選擇使用CSS Selector、正規表達式(通過設定type)。
ExtractBy還有幾個擴充屬性。**multi**表示是否抽取清單,當然,設定為multi時,你需要一個List字段去容納它。**notnull**則表示,此字段不允許為null,若為null則放棄整個對象。
-
用于類
"ExtractBy"用于類時,則限定了字段抽取的區域。用于類時仍支援multi,multi則表示一個頁面可以抽取到多個對象。 -
ExtractByRaw & ExtractByUrl
在類使用"ExtractBy"修飾後,字段的"ExtractBy"使用的是其抽取的結果,如果仍然想要抽取原HTML,可以使用"ExtractByRaw"。與此類似的還有"ExtractByUrl",表示從URL中抽取資訊。ExtractByUrl隻支援正規表達式。 -
ExtractBy2 ExtractBy3
"ExtractBy"、"ExtractByRaw"支援鍊式抽取,通過增加注解"ExtractBy2"、"ExtractBy3"實作。
-
-
AfterExtractor
AfterExtractor接口是對注解方式抽取能力不足的補充。實作AfterExtractor接口後,會在**使用注解方式填充完字段後**調用**afterProcess()**方法,在這個方法中可以直接通路已抽取的字段、補充需要抽取的字段,甚至做一些簡單的輸出和持久化操作(并不是很建議這麼做)。這部分可以參考webmagic結合JFinal持久化到資料庫的一段代碼。 -
OOSpider
OOSpider是注解式爬蟲的入口,這裡調用**create()**方法将OschinaBlog這個類加入到爬蟲的抽取中,這裡是可以傳入多個類的,例如:
OOSpider會根據TargetUrl調用不同的Model進行解析。OOSpider.create( Site.me().addStartUrl("http://www.oschina.net"), new ConsolePageModelPipeline(), OschinaBlog.clas,OschinaAnswer.class).run();
-
PageModelPipeline
<<<<<<< HEAD
可以通過定義PageModelPipeline來選擇結果輸出方式。這裡new ConsolePageModelPipeline()是PageModelPipeline的一個實作,會将結果輸出到控制台。 PageModelPipeline還有一個實作**JsonFilePageModelPipeline**,可以将對象持久化以JSON格式輸出,并持久化到檔案。JsonFilePageModelPipeline預設使用對象的MD5值作為檔案名,你可以在Model中實作HasKey接口,指定輸出的檔案名。
=======
可以通過定義PageModelPipeline來選擇結果輸出方式。這裡new ConsolePageModelPipeline()是PageModelPipeline的一個實作,會将結果輸出到控制台。
>>>>>>> 3f4368cf780251a8d9fdd9da8cfff73e1190a637
-
分頁
處理單項資料分頁(例如單條新聞多個頁面)是爬蟲一個比較頭疼的問題。webmagic目前對于分頁的解決方案是:在注解模式下,Model通過實作**PagedModel**接口,并引入PagedPipeline作為第一個Pipeline來實作。具體可以參考webmagic-samples中抓取網易新聞的代碼:**us.codecraft.webmagic.model.samples.News163**。
關于分頁,這裡有一篇對于webmagic分頁實作的詳細說明的文章關于爬蟲實作分頁的一些思考。 <<<<<<< HEAD 目前分頁功能還沒有分布式實作,如果使用RedisScheduler進行分布式爬取,請不要使用分頁功能。
======= 目前分頁功能還沒有分布式實作,如果實作RedisScheduler進行分布式爬取,請不要使用分頁功能。
>>>>>>> 3f4368cf780251a8d9fdd9da8cfff73e1190a637
分布式
webmagic-extension中,通過redis來管理URL,達到分布式的效果。但是對于分布式爬蟲,僅僅程式能夠分布式運作,還滿足不了大規模抓取的需要,webmagic可能後期會加入一些任務管理和監控的功能,也歡迎各位使用者為webmagic送出代碼,做出貢獻。
<<<<<<< HEAD
更進一步
如果這篇文檔仍然滿足不了你的需要,你可以閱讀webmagic的Javadoc,或者直接閱讀源碼。
======= >>>>>>> 3f4368cf780251a8d9fdd9da8cfff73e1190a637