更新提示(2013-03-13):最新版本更新:
html
<!-- | name:目標名稱 --> <target name="travel" isForceUseXmlParser="1"> <!-- | 限制目標URL的來源爲網易旅遊子頻道,在spiderman裏面把頻道頁叫作"來源url" --> <sourceRules policy="and"> <rule type="regex" value="http://travel\.163\.com/special/cjgat(_\d+)?/"> <!-- | 定義如何在來源頁面上挖掘新的 URL --> <digUrls> <field name="source_url" isArray="1"> <parsers> <parser xpath="//div[@class='list-page']//a[@href]" attribute="href"/> </parsers> </field> <!-- | 在spiderman裏面把詳細文章頁叫作"目標url" --> <field name="target_url" isArray="1"> <parsers> <parser xpath="//div[@class='list-item clearfix']//div[@class='item-top']//h2//a[@href]" attribute="href"/> <parser exp="$this.replace('#p=891JUOOO17KK0006','')" /> </parsers> </field> </digUrls> </rule> </sourceRules>
<!-- | 目標URL的規則 --> <urlRules policy="and"> <rule type="regex" value="http://travel\.163\.com/\d{2}/\d{4}/\d{2}/\w[^_]+\.html"> <!-- | 遞歸抓取詳細頁的分頁,單篇文章的分頁會按順序抓取保證內容抓取的順序跟頁碼一致 --> <nextPage> <field name="next_url"> <parsers> <!-- | 正如field的name=next_url意思同樣,這裏的規則主要是來解析"當前"頁的下一頁url是什麼,咱們都知道分頁頁面裏面確定都有"下一頁"入口的,抓到這個,而後遞歸便可 --> <parser xpath="//div[@class='ep-pages']//a[@class='ep-pages-ctrl']" attribute="href" /> </parsers> </field> </nextPage> </rule> </urlRules> <!-- | 另外還須要在<model>下的<field>多添加一個參數 isAlsoParseInNextPage="1" 告訴爬蟲該字段須要在分頁裏繼續解析的,好比下面這個content字段,是須要在「下一頁」裏繼續解析的 --> <model name="travel-article"> <field name="content" isArray="1" isAlsoParseInNextPage="1">
<!-- | 告訴爬蟲僅抓取如下這些host的連接,多數是應對二級或多級域名的狀況 --> <validHosts> <validHost value="travel.163.com" /> <validHost value="wwww.163.com" /> </validHosts>
<!-- | 配置多個種子連接 | url:種子連接 --> <seeds> <seed url="" /> </seeds>
<!-- | isForceUseXmlParser 當解析的頁面是HTML時,除了XPath基本功能外不少XPath功能都不支持,例如XPath軸、其餘高級函數等,將此參數設置爲 1 便可讓其支持,可是會帶來某些不肯定的問題【暫時未發現】 --> <target name="travel" isForceUseXmlParser="1">
<!-- | skipStatusCode:設置哪些狀態碼須要忽略,多個用逗號隔開,一旦設置了,那麼爬蟲將會忽略掉一些錯誤的statusCode,而且繼續解析返回的內容 | userAgent:設置爬蟲標識 | includeHttps:是否抓取https頁 --> <site skipStatusCode="500,501" userAgent="Spiderman[https://gitcafe.com/laiweiwei/Spiderman]" includeHttps="0">
<target> <model> <field name="title"> <parsers> <parser xpath="//div[@class='QTitle']/h1/text()"/> </parsers> </field> </model> </target>
核心提示:本文介紹瞭如何使用垂直類網絡爬蟲#Spiderman#抓取目標網站 「感興趣」 的數據,這裏簡單地演示瞭如何抓取OSC【本站】的問答數據,引出後文對另一個複雜的團購網站內容的抓取,該網站的團購信息中,咱們須要在JS代碼裏抓取過時時間、須要過濾團購的一些描述信息【保留一些標籤,去掉一些標籤,去掉屬性等】、須要獲取好幾個地方的圖片、須要獲取團購的價格、購買人數等。關鍵的地方在於前面所述的這一切都將經過一個配置文件解決,無需編寫一句代碼。 java
所使用的爬蟲工具介紹: git
#Spiderman#,Java開源垂直類網絡爬蟲,使用XPath、正則、表達式引擎讓你輕鬆地抓取任何目標網站你「感興趣」的內容。基於多線程、微內核、插件式的架構。 github
Spiderman的正式版本尚未發佈,可是在github裏面有最新的代碼能夠取下來而且使用maven構建。 web
Spiderman依賴於EWeb4J的xml讀寫功能,所以還須要把最新的EWeb4J源碼從github拉下來構建。 正則表達式
下面介紹如何抓取OSC的問答數據: json
<?xml version="1.0" encoding="UTF-8"?> <beans> <site name="oschina" url="http://www.oschina.net/question" reqDelay="1s" enable="1" charset="utf-8" schedule="1h" thread="2" waitQueue="10s"> <queueRules policy="and"> <rule type="!regex" value="^.*\.(jpg|png|gif).*$" /> </queueRules> <targets> <target name="deal"> <urls policy="and"> <rule type="regex" value="http://www\.oschina\.net/question/\d+_\d+" /> </urls> <model> <field name="title"> <parser xpath="//div[@class='QTitle']/h1/text()"/> </field> <field name="content"> <parser xpath="//div[@class='Content']//div[@class='detail']" exp="$Tags.xml($output($this)).rm('div').Attrs().rm('style').ok()" /> </field> <field name="author"> <parser xpath="//div[@class='stat']//a[@target='_blank']/text()"/> </field> <field name="tags" isArray="1"> <parser xpath="//div[@class='Tags']//a/text()"/> </field> <field name="answers" isArray="1"> <parser xpath="//li[@class='Answer']//div[@class='detail']/text()" /> </field> </model> </target> </targets> <plugins> <plugin enable="1" name="spider_plugin" version="0.0.1" desc="這是一個官方實現的默認插件,實現了全部擴展點。"> <extensions> <extension point="task_poll"> <impl type="" value="spiderman.plugin.impl.TaskPollPointImpl" sort="0"/> </extension> <extension point="begin"> <impl type="" value="spiderman.plugin.impl.BeginPointImpl" sort="0"/> </extension> <extension point="fetch"> <impl type="" value="spiderman.plugin.impl.FetchPointImpl" sort="0"/> </extension> <extension point="dig"> <impl type="" value="spiderman.plugin.impl.DigPointImpl" sort="0"/> </extension> <extension point="dup_removal"> <impl type="" value="spiderman.plugin.impl.DupRemovalPointImpl" sort="0"/> </extension> <extension point="task_sort"> <impl type="" value="spiderman.plugin.impl.TaskSortPointImpl" sort="0"/> </extension> <extension point="task_push"> <impl type="" value="spiderman.plugin.impl.TaskPushPointImpl" sort="0"/> </extension> <extension point="target"> <impl type="" value="spiderman.plugin.impl.TargetPointImpl" sort="0"/> </extension> <extension point="parse"> <impl type="" value="spiderman.plugin.impl.ParsePointImpl" sort="0"/> </extension> <extension point="end"> <impl type="" value="spiderman.plugin.impl.EndPointImpl" sort="0"/> </extension> </extensions> <providers> <provider> <orgnization name="" website="" desc=""> <author name="weiwei" website="" email="l.weiwei@163.com" weibo="http://weibo.com/weiweimiss" desc="一個喜歡自由、音樂、繪畫的IT老男孩" /> </orgnization> </provider> </providers> </plugin> </plugins> </site> </beans>下面這個是加了註釋的版本,便於理解:)
<?xml version="1.0" encoding="UTF-8"?> <!-- | Spiderman Java開源垂直網絡爬蟲 | author: l.weiwei@163.com | blog: http://laiweiweihi.iteye.com | qq: 493781187 | time: 2013-01-08 16:12 --> <beans> <!-- | name:名稱 | url:種子連接 | reqDelay:{n}s|{n}m|{n}h|n每次請求以前延緩時間 | enable:0|1是否開啓本網站的抓取 | charset:網站字符集 | schedule:調度時間,每隔多長時間從新從種子連接抓取 | thread:分配給本網站爬蟲的線程數 | waitQueue:當任務隊列空的時候爬蟲等待多長時間再索取任務 --> <site name="oschina" url="http://www.oschina.net/question" reqDelay="1s" enable="1" charset="utf-8" schedule="1h" thread="2" waitQueue="10s"> <!-- | HTTP Header <headers> <header name="" value="" /> </headers>--> <!-- | HTTP Cookie <cookies> <cookie name="" value="" domain="" path="" /> </cookies>--> <!-- | 進入任務隊列的URL規則 | policy:多個rule的策略,暫時只實現了and,將來會有or --> <queueRules policy="and"> <!-- | 規則 | type:規則類型,包括 regex | equal | start | end | contains 全部規則能夠在前面添加 "!" 表示取反 | value:值 --> <rule type="!regex" value="^.*\.(jpg|png|gif).*$" /> </queueRules> <!-- | 抓取目標 --> <targets> <!-- | name:目標名稱 --> <target name="deal"> <!-- | 目標URL匹配規則 --> <urls policy="and"> <!-- | 同前面的隊列規則 --> <rule type="regex" value="http://www\.oschina\.net/question/\d+_\d+" /> </urls> <!-- | 目標網頁的數據模型 --> <model> <!-- | 屬性的配置 | name:屬性名稱 | parser:針對該屬性的解析規則 --> <field name="title"> <!-- | xpath: XPath規則,若是目標頁面是XML,則可使用2.0語法,不然HTML的話暫時只能1.0 | attribute:當使用XPath解析後的內容不是文本而是一個Node節點對象的時候,能夠給定一個屬性名獲取其屬性值例如<img src="" /> | regex:當使用XPath(包括attribute)規則獲取到的文本內容不知足需求時,能夠繼續設置regex正則表達式進行解析 | exp:當使用XPath獲取的文本(若是獲取的不是文本則會先執行exp而不是regex不然先執行regex)不知足需求時,能夠繼續這是exp表達式進行解析 | exp表達式有幾個內置對象和方法: | $output(Node): 這個是內置的output函數,做用是輸出某個XML節點的結構內容。參數是一個XML節點對象,能夠經過XPath得到 | $this: 當使用XPath獲取到的是Node節點時,這個表示節點對象,不然表示Java的字符串對象,能夠調用Java字符串API進行處理 | $Tags: 這個是內置的用於過濾標籤的工具類 | $Tags.xml($output($this)).rm('p').ok() | $Tags.xml($this).rm('p').empty().ok() | $Attrs: 這個是內置的用於過濾屬性的工具類 | $Attrs.xml($this).rm('style').ok() | $Attrs.xml($this).tag('img').rm('src').ok() | | $Tags和$Attrs能夠一塊兒使用: | $Tags.xml($this).rm('p').Attrs().rm('style').ok() | $Attrs.xml($this).rm('style').Tags().rm('p').ok() --> <parser xpath="//div[@class='QTitle']/h1/text()"/> </field> <field name="content"> <parser xpath="//div[@class='Content']//div[@class='detail']" exp="$Tags.xml($output($this)).rm('div').Attrs().rm('style').ok()" /> </field> <field name="author"> <parser xpath="//div[@class='stat']//a[@target='_blank']/text()"/> </field> <field name="tags" isArray="1"> <parser xpath="//div[@class='Tags']//a/text()"/> </field> <field name="answers" isArray="1"> <parser xpath="//li[@class='Answer']//div[@class='detail']/text()" /> </field> </model> </target> </targets> <!-- | 插件 --> <plugins> <!-- | enable:是否開啓 | name:插件名 | version:插件版本 | desc:插件描述 --> <plugin enable="1" name="spider_plugin" version="0.0.1" desc="這是一個官方實現的默認插件,實現了全部擴展點。"> <!-- | 每一個插件包含了對若干擴展點的實現 --> <extensions> <!-- | point:擴展點名它們包括 task_poll, begin, fetch, dig, dup_removal, task_sort, task_push, target, parse, pojo, end --> <extension point="task_poll"> <!-- | 擴展點實現類 | type: 如何獲取實現類 ,默認經過無參構造器實例化給定的類名,能夠設置爲ioc,這樣就會從EWeb4J的IOC容器裏獲取 | value: 當時type=ioc的時候填寫IOC的bean_id,不然填寫完整類名 | sort: 排序,同一個擴展點有多個實現類,這些實現類會以責任鏈的方式進行執行,所以它們的執行順序將變得很重要 --> <impl type="" value="spiderman.plugin.impl.TaskPollPointImpl" sort="0"/> </extension> <extension point="begin"> <impl type="" value="spiderman.plugin.impl.BeginPointImpl" sort="0"/> </extension> <extension point="fetch"> <impl type="" value="spiderman.plugin.impl.FetchPointImpl" sort="0"/> </extension> <extension point="dig"> <impl type="" value="spiderman.plugin.impl.DigPointImpl" sort="0"/> </extension> <extension point="dup_removal"> <impl type="" value="spiderman.plugin.impl.DupRemovalPointImpl" sort="0"/> </extension> <extension point="task_sort"> <impl type="" value="spiderman.plugin.impl.TaskSortPointImpl" sort="0"/> </extension> <extension point="task_push"> <impl type="" value="spiderman.plugin.impl.TaskPushPointImpl" sort="0"/> </extension> <extension point="target"> <impl type="" value="spiderman.plugin.impl.TargetPointImpl" sort="0"/> </extension> <extension point="parse"> <impl type="" value="spiderman.plugin.impl.ParsePointImpl" sort="0"/> </extension> <extension point="end"> <impl type="" value="spiderman.plugin.impl.EndPointImpl" sort="0"/> </extension> </extensions> <providers> <provider> <orgnization name="" website="" desc=""> <author name="weiwei" website="" email="l.weiwei@163.com" weibo="http://weibo.com/weiweimiss" desc="一個喜歡自由、音樂、繪畫的IT老男孩" /> </orgnization> </provider> </providers> </plugin> </plugins> </site> </beans>
import java.io.File; import java.util.List; import java.util.Map; import org.eweb4j.config.EWeb4JConfig; import org.eweb4j.spiderman.spider.SpiderListener; import org.eweb4j.spiderman.spider.SpiderListenerAdaptor; import org.eweb4j.spiderman.spider.Spiderman; import org.eweb4j.spiderman.task.Task; import org.eweb4j.util.CommonUtil; import org.eweb4j.util.FileUtil; import org.junit.Test; public class TestSpider { private final Object mutex = new Object(); @Test public void test() throws Exception { //啓動EWeb4J框架 String err = EWeb4JConfig.start(); if (err != null) throw new Exception(err); SpiderListener listener = new SpiderListenerAdaptor(){ public void onInfo(Thread thread, Task task, String info) { System.out.print("[SPIDERMAN] "+CommonUtil.getNowTime("HH:mm:ss")+" [INFO] ~ "); System.out.println(info); } public void onError(Thread thread, Task task, String err, Exception e) { e.printStackTrace(); } public void onParse(Thread thread, Task task, List<Map<String, Object>> models) { // System.out.print("[SPIDERMAN] "+CommonUtil.getNowTime("HH:mm:ss")+" [INFO] ~ "); // System.out.println(CommonUtil.toJson(models.get(0))); synchronized (mutex) { String content = CommonUtil.toJson(models.get(0)); try { File dir = new File("d:/jsons/"+task.site.getName()); if (!dir.exists()) dir.mkdirs(); File file = new File(dir+"/count_"+task.site.counter.getCount()+"_"+CommonUtil.getNowTime("yyyy_MM_dd_HH_mm_ss")+".json"); FileUtil.writeFile(file, content); System.out.print("[SPIDERMAN] "+CommonUtil.getNowTime("HH:mm:ss")+" [INFO] ~ "); System.out.println(file.getAbsolutePath() + " create finished..."); } catch (Exception e) { e.printStackTrace(); } } } }; //啓動爬蟲 Spiderman.me() .init(listener)//初始化 .startup()//啓動 .keep("15s");//存活時間,過了存活時間後立刻關閉 //------拿到引用後你還能夠這樣關閉------------------------- //spiderman.shutdown();//等待正在活動的線程都死掉再關閉爬蟲 //spiderman.shutdownNow();//立刻關閉爬蟲 } }原諒我寫的比較囉嗦的代碼 :)
//啓動EWeb4J框架 String err = EWeb4JConfig.start(); if (err != null) throw new Exception(err);而後,編寫一個爬蟲監聽器,這裏咱們使用了內置的監聽適配器選擇性的實現了其中三個方法,第一個是打印INFO的,第二個是打印異常的,第三個比較重要:
public void onParse(Thread thread, Task task, List<Map<String, Object>> models) { synchronized (mutex) { String content = CommonUtil.toJson(models.get(0)); try { File dir = new File("d:/jsons/"+task.site.getName()); if (!dir.exists()) dir.mkdirs(); File file = new File(dir+"/count_"+task.site.counter.getCount()+"_"+CommonUtil.getNowTime("yyyy_MM_dd_HH_mm_ss")+".json"); FileUtil.writeFile(file, content); } catch (Exception e) { e.printStackTrace(); } } }這個方法在爬蟲成功的抓取並解析了一個目標網頁內容以後被回調,從代碼能夠看到一個List<Map>對象被傳遞了進來,這個對象就是咱們想要的數據。所以咱們將它格式化爲JSON串後寫入到D盤的文件裏。
//啓動爬蟲 Spiderman.me() .init(listener)//初始化 .startup()//啓動 .keep("15s");//存活時間,過了存活時間後立刻關閉不知道各位客觀是否喜歡這種鏈式API,俺卻是挺喜歡的:)
//------拿到引用後你還能夠這樣關閉------------------------- spiderman.shutdown();//等待正在活動的線程都死掉再關閉爬蟲 spiderman.shutdownNow();//立刻關閉爬蟲
以上是「拋磚」之舉 :) (紅薯別介意哈,OSC一直都很優秀,絕沒有「磚」的意思),下面就是「引玉」之時了! cookie
忽然尿急,這個「引玉」看來還得放到後面來作......【待續 :)】 網絡