Java爬蟲領域最強大的框架是JSoup:可直接解析具體的URL地址(即解析對應的HTML),提供了一套強大的API,包括可以通過DOM、CSS選擇器,即類似jQuery方式來取出和操作數據。主要功能有:
- 從給定的URL、文件、字符串中,獲得HTML代碼。
- 然后通過DOM、CSS選擇器(類jQuery方式)來查找、取出數據:先找到HTML元素,然后獲取其屬性、文本等。
API初步學習:
上面提到了三種方式,獲取HTML文檔(JSoup的Document對象的結構是:<html><head></head><body></body></html>):
1、通過字符串:String html="hello"; Document doc = Jsoup.parse(html);//(此時JSoup會把hello放在doc對象中的body中。如果字符串是一個完整的html文檔,那么doc對象將以字符串的html結構為准)。
2、獲取URL的HTML文檔(注意:這種方式下,只有當調用get或post方法時,才真正發送請求):
//通過URL獲得連接:Connection對象 Connection conn = Jsoup.connect("http://www.baidu.com"); //以下為主要方法,多數返回Connection conn.data("query", "Java"); // 請求參數 conn.userAgent("I ’ m jsoup"); // 設置 User-Agent conn.cookie("auth", "token"); // 設置 cookie conn.timeout(3000); // 設置連接超時時間 //發送請求,獲得HTML文檔:Document對象 Document doc = conn.get(); Document doc = conn.post();
獲取元素:
1、通過DOM方式(與純JavaScript方式相同):
//獲取文檔級信息,如: String title = doc.title(); //獲取單個HTML元素,如:<div id="content"></div> Element content = doc.getElementById("content"); //獲取多個元素,如:<a href="http://www.qunyh.cn"></a> <a href="http://cn.bing.com"></a> Elements links = doc.getElementsByTag("a");
2、類似於jQuery的方式,這里是將$換成了select方法:
//select的參數是類似於jQuery的選擇器selector Elements allP = doc.select("p"); Element firstP = allP.first(); Element oneP = allP.get(1);//從0開始 //操作元素: for (Element p : allP) { //操縱元素:這里就類似於jQuery String text = p.text(); }
當然JSoup只是模仿jQuery的方便性,並不具備jQuery的所有功能,例如jQuery的插件肯定是無法在JSoup中使用。因此如果對JS掌握很好,選擇Node.js+MongoDB來處理就比較有優勢(即相互之間的支持度比較大)。如果熟悉Java,那么就可以選擇JSoup+MySql+Quartz,也是非常好用的(全程java實現,省心方便),再配合Java調度器Quartz,就可實現一個完整的爬蟲了。
爬蟲框架 Gecco:
Gecco 是用 Java 實現的,輕量化,面向主題的爬蟲,與 Nutch 這種面向搜索引擎的通用爬蟲不同:
- 通用爬蟲通常關注三個問題:下載、排序、索引。
- 主題爬蟲則關注:下載、內容抽取、靈活的業務邏輯處理。
Gecco 的目標:提供一個完善的主題爬蟲框架:簡化下載和內容抽取的開發;利用管道過濾器模式,提供靈活的內容清洗和持久化處理模式。故而開發可以集中精力在業務主題方面的邏輯、內容處理。
學習一個框架,首先要了解到它的用途何在:
- 簡單易用,使用 jQuery 的 Selector 風格抽取元素。
- 支持頁面中的異步 Ajax 請求。
- 支持頁面中的 JavaScript 變量抽取。
- 利用 Redis 實現分布式抓取,可參考 Gecco-Redis。
- 支持下載時 UserAgent 隨機選取。
- 支持下載代理服務器隨機選取。
- 支持結合 Spring 開發業務邏輯,參考 Gecco-Spring。
- 支持 htmlUnit 擴展,參考 Gecco-htmlUnit。
- 支持插件擴展機制。
一分鍾你就可以寫一個簡單爬蟲:
先將一個基本的程序撂出來(內容以 json 格式在 console 輸出):
@Gecco(matchUrl = "https://github.com/{user}/{project}", pipelines = "consolePipeline") public class Tester implements HtmlBean { private static final long serialVersionUID = 4538118606557597719L; @RequestParameter("user") private String user; @RequestParameter("project") private String project; @Text @HtmlField(cssPath = ".repository-meta-content .mr-2") private String title; @Text @HtmlField(cssPath = ".pagehead-actions li:nth-child(2) .social-count") private int star; @Text @HtmlField(cssPath = ".pagehead-actions li:nth-child(3) .social-count") private int fork; @Html @HtmlField(cssPath = ".entry-content") private String readme; // 此處略去 getter and setter public static void main(String[] args) { GeccoEngine.create() // Geeco搜索的包路徑:即使用了@Geeco注解的類所在的包路徑 .classpath("com.jackie.json.controller") // 最開始抓取的頁面地址:簡單的就是一個,當然也有可變參數列表的重載(即接受多個url地址參數) .start("https://github.com/xtuhcy/gecco") // 開啟幾個爬蟲線程 .thread(1) // 對每個爬蟲而言,每次抓取完一個請求之后,再次請求的間隔時間 .interval(2000) // 與上面的start方法用途不同,用於啟動爬蟲線程:start方法非阻塞式運行爬蟲;run方法則是阻塞式 .start(); } }
代碼說明:
- 首先爬蟲類 implements 了一個 HtmlBean 接口:說明了該爬蟲是一個解析 html 頁面的爬蟲(還可以支持 json 格式的解析,即 JsonBean)。
- @Gecco 注解:告知爬蟲,匹配的 url 格式(matchUrl)和內容抽取后的 bean 處理類 pipelines(采用管道過濾器模式,可以指定多個 pipeline 處理類。例如指定為 consolePipeline,則輸出在 console 中)。
- @RequestParamter:注入請求過的 url 中匹配的請求參數,如@RequestParamer("user") 則匹配到 url 中的 {user}。
- @HtmlField:表示抽取 html 中的元素,其中 cssPath 采用類似於 jQuery 的 css selector 的方式選取元素。
- @Text:表示獲取 @HtmlField 抽取元素的 Text 內容。@Html:表示獲取對應的 Html 內容(默認 @Html)。注意的是:若抽取的元素中沒有 html 代碼,則不能指定為 @Html,否則會報錯;若有 html 代碼,則不能指定為 @Text,否則不能獲取任何內容,將會輸出為空字符串。
- GeccoEngine:爬蟲引擎類,通過 create() 初始化,通過 start() / run() 運行。還可以配置一些啟動參數,如上代碼所示,后面也會詳細介紹。
軟件總體架構(爬蟲引擎的架構):
如圖:
簡單介紹:
- GeccoEngine:爬蟲引擎,每個爬蟲引擎最好是一個獨立進程。在分布式爬蟲場景下,建議每台爬蟲服務器(物理機、虛擬機)只運行一個 GeccoEngine。爬蟲引擎主要包括Scheduler、Downloader、Spider、SpiderFactory、PipelineFactory 5個模塊。
- Scheduler:
GeccoEngine 的詳細介紹:
Gecco 如何運行,下面是最基本的啟動方法:
GeccoEngine.create() // classpath是必填項 .classpath("com.geccocrawler.gecco.demo") // 初始請求地址 .start("https://github.com/xtuhcy/gecco") .start();
GeccoEngine 的基本配置:
- loop(boolean):表示是否循環抓取,默認為 false。
- thread(int):表示開啟的爬蟲線程數量,默認是1。值得注意的是,線程數量要小於等於 start 方法中指定的 url 數量。
- interval(int):表示請求間隔時間,單位是毫秒 ms。假設設置的值是 x 秒,那么系統會把間隔時間隨機在 [ x - 1, x + 1] 這個區間內(1 即指 1 秒)。例如,設置為 2000 ms,那么最終的間隔時間將會在 1000 ms ~ 3000 ms 內隨機。
- mobile(bollean):表示使用移動端還是 pc 端的 UserAgent。默認為 false,使用 pc 端的 UserAgent。
- debug(boolean):是否開啟 debug 模式,即如果開啟 debug 模式,那么將會在控制台輸出 JSoup 元素抽取的日志 —— 如果出了問題才能在日志中找到原因。
- pipelineFactory(PipelineFactory):設置自定義的 pipelineFactory,通過實現 PipelineFactory 接口,自定義 pipelineFactory 類。
- scheduler(Scheduler):設置自定義的請求隊列管理器 Scheduler。
非阻塞式啟動和阻塞式啟動:
- start():非阻塞式啟動,即 GeccoEngine 會單獨啟動線程運行。推薦以該方式運行,線程模式如下:MainThread ——> GeccoEngineThread ——> SpiderThread。
- run():阻塞式啟動,GeccoEngine 在主線程中啟動並運行。非循環模式 GeccoEngine 需要等待其他爬蟲線程運行完畢后,run() 方法才會退出。線程模型如下:GeccoEngineThread(即在主線程中)——> SpiderThread。
Gecco 如何匹配 URL:
matchUrl 的作用:告知 Gecco,這種格式的 url 對應的網頁將會被渲染成當前指定的 SpiderBean(如包裝成 HtmlBean 的對象)。matchUrl 模糊匹配模式中的 {} 可以匹配任意非空字符串,除了 "/"。
如果不寫 matchUrl,則為任意匹配模式,那么任意的 url 都會被匹配,可用作通用爬蟲,例如官網給出的 CommonClawer。
說說下載:
下載引擎:爬蟲最基本的能力就是發起 http 請求,然后下載網頁。Gecco 默認采用 httpClientDownloader(httpclient4)作為下載引擎。通過實現 downloader 接口,可以自定義設置下載引擎。例如:
@Gecco(matchUrl="https://github.com/{user}/{project}", pipelines="consolePipeline", downloader="htmlUnitDownloder")
HttpRequest 和 HttpReponse:一個表示下載請求,一個表示下載響應。爬蟲會模擬瀏覽器,將下載請求包裝成 GET 或 POST 請求,分別對應 HttpGetRequest、HttpPostRequest 類。
Gecco 的請求可以支持:
- 模擬 userAgent,並支持 userAgent 的隨機輪詢。在 classpath 的根目錄下定義 userAgents 文件,每行代表一個 UserAgent。
- cookie 定義:request.addCookie(String name, String value),可模擬用戶登錄。
- 代理服務器的隨機輪詢。在 classpath 的根目錄下定義 proxys 文件,每行代表一個代理服務器的主機和端口,如 127.0.0.1:8888。
下載地址管理:
爬蟲通常需要一個請求隊列管理器 Scheduler,負責下載地址的管理:
Gecco 使用 StartScheduler 管理初始地址,StartScheduler 內部采用一個阻塞的 FIFO 隊列(即嚴格的 FIFO,一個線程抓取完一個地址之后,才會去抓另一個地址);初始地址通常會派生出很多其他待抓取的地址(例如列表頁是初始地址的集合頁面,詳情頁是派生的地址),並用 SpiderScheduler 管理派生的地址,SpiderScheduler 內部采用線程安全的非阻塞 FIFO 隊列(不嚴格的先進先出)。
這種設計使得 Gecco 對初始地址采用了深度遍歷(理解還不夠深刻)?的策略;對派生的地址采用了廣度遍歷的策略(如何派生參考下面的 “派生地址“)。此外,Gecco 的分布式抓取也是通過 Scheduler 完成,准確的說是通過實現不同的StartScheduler,來將初始地址分配到不同的服務器中來完成;Gecco 分布式抓取默認采用 redis 來實現,具體參考 gecco-redis 項目。
派生地址:
Gecco 派生地址的方式有兩種:
一是使用注解,如下:
@Href(click = true) @HtmlField(cssPath = "...") private String detailUrl;
派生地址如同其他內容一樣,需要使用 @HtmlField 注解,從初始地址的頁面中抽取。不同的是 @Href 注解,當參數 click 為 true 時,抽取出來的 url 會繼續抓取。
二是顯式的加入 SpiderScheduler 的方法,如:
DeriveSchedulerContext.into(request.subRequest("subUrl"));
上面的 HttpRequest 對象可以通過 @Request 注解獲得,然后通過該對象的 subRequest 方法生成對派生地址的請求,然后加入到 SpiderScheduler 隊列中。可以在相應的 pipeline 類中對請求的增加進行過濾。
初始地址的配置:
可以在 classpath 根目錄下,放置 starts.json 配置文件,可配置多個初始地址,如下:
[ { "charset": "GBK", "url": "http://item.jd.com/123.html" }, { "url": "https://github.com/xtuhcy/gecco" } ]
此外,如果要設置的初始地址不多,則可以如上面的程序,直接在 GeccoEngine.start(String ...) 方法中添加。
BeforeDownload 和 AfterDownload:
有一些特殊場景,可能會需要在下載的前后做一些處理,處理之后再傳遞給 pipeline 輸出。如下:
@GeccoClass(CruiseDetail.class) public class CruiseDetailBeforeDownload implements BeforeDownload { @Override public void process(HttpRequest request) { } }
上面這個類是針對 CruiseDetail 這個 bean 的下載之前的自定義預處理。同理,下載后如下:
@GeccoClass(CruiseRedirect.class) public class CruiseRedirectAfterDownload implements AfterDownload { @Override public void process(HttpRequest request, HttpResponse response) { } }
使用 HtmlUnit 作為下載引擎:
Gecco 通過擴展 downloader 實現了對 htmlUnit 的支持,如下(使用詳情參照 gecco-htmunit 項目):
<dependency> <groupId>com.geccocrawler</groupId> <artifactId>gecco-htmlunit</artifactId> <version>1.0.9</version> </dependency>
找資料的時候發現了新的、不錯的爬蟲框架(以后有時間嘗試):
Gecco Crawler:
Gecco是一款用java語言開發的輕量化的易用的網絡爬蟲,不同於Nutch這樣的面向搜索引擎的通用爬蟲,Gecco是面向主題的爬蟲。
https://github.com/xtuhcy/gecco
https://xtuhcy.gitbooks.io/geccocrawler/content/index.html
https://my.oschina.net/u/2336761/blog/688534
模板代碼生成器:J2ee template(由JSoup開發而成,可借鑒其如何開發的爬蟲):
https://www.oschina.net/p/jeetemp