優雅的使用WebMagic框架寫Java爬蟲

優雅的使用WebMagic框架,爬取唐詩別苑網的詩人詩歌數據html

同時在幾種動態加載技術(HtmlUnit、PhantomJS、Selenium、JavaScriptEngine)中對比做選擇jquery

WebMagic雖然差很少兩年沒有維護,但其自己是一個優秀的爬蟲框架的實現,源碼中有不少值得參考的地方,特別是對爬蟲多線程的控制。另外,因爲頁面爬取到的是非結構化數據,因此數據保存到MongoDB。

技術準備

  • IDE:IntelliJ IDEA 2018.3.5
  • JDK版本:1.8.0_181
  • 數據庫:MongoDB 4.0.10
  • 涉及技術:git

    • Webmagic輕量級爬蟲框架
    • HtmlUnit網頁分析工具包,模擬瀏覽器運行
    • PhantomJS
    • JavaScriptEngine
    • MongoDB ORM框架 Morphia
    • JUC:Java線程池、線程協做、線程安全類
    • 日誌log4j 1.7.25
    • Java反射
    • 單例模式、工廠模式、代理模式
pom.xml文件中的依賴很是簡單,並無使用到Spring系列的框架,因此有些地方本身編碼實現了Spring提供的功能

項目結構

  • biz包:包括頁面爬取邏輯的Processor類,爬蟲結果保存的Pipeline類
  • dao包:數據獲取層
  • entity包:實體類,映射保存在MongoDB的文檔(Document)
  • vo包:值對象,簡單的Java對象
  • util包:工具包,包括數據庫鏈接類、爬蟲輔助類
  • common包:項目相關通用類
  • Main類:程序入口

項目說明

根據需求將數據保存到MongoDB數據庫,所以在程序運行前必須設置好resources/mongodb.properties文件github

最好保證MongoDB的版本是4.0以上。另外MongoDB的用戶管理比較麻煩,過程大體以下:首先須要建立存儲數據的數據庫,如命名爲user_tangpoem,並存入隨便一條數據(集合)使數據庫有效化,而後建立一個
admin數據庫的root用戶,繼續建立一個能夠讀寫應用數據庫user_tangpoem的用戶,而後修改MongoDB配置文件使其以安全認證模式啓動。重啓數據庫,選擇admin數據庫(use admin)
用剛剛建立的用戶(非root用戶)使用db.auth()進行登陸,返回1說明驗證成功,選擇user_tangpoem數據庫(use user_tangpoem),輸入show collections,若是看到最初建立數據庫時的集合,則說明用戶建立成功。
詳細可參考 MongoDB4.0.0 遠程鏈接及用戶名密碼認證登錄配置——windows

爬蟲以多線程的方式運行,在resources/spider.properties文件中能夠設置線程數和線程睡眠時間,在設置好數據庫配置的基礎上,直接運行Main.main(),爬蟲就會開始爬取。web

線程睡眠,是WebMagic框架源碼中每線程爬取完一個url後必然經歷的過程,但做者文檔並無對此進行說明,請根據實際狀況調整

動態加載技術的選擇

1. PhantomJS和Selenium

WebMagic底層已經很好的使用了HttpClient加載靜態頁面,對於動態頁面,也有PhantomJSDownloaderSeleniumDownloader兩個經常使用的利用
瀏覽器內核模擬瀏覽器行爲的實現,其中,PhantomJS須要指定phantomjs.exe和進行爬取的JS文件,而seleniumDownloader須要指定chromedriver.exe,須要自行下載對應操做系統的版本,
使用起來並不難,本項目很少做討論。這裏關鍵說明HtmlUnitmongodb

2. HtmlUnit

一款開源的Java頁面分析工具,讀取頁面後,能夠有效的使用HtmlUnit分析頁面上的內容。使用純Java實現的模擬瀏覽器,不須要指定外部文件。chrome

雖然其對JS的支持並不徹底,但整體而言HtmlUnit的內存消耗、CPU消耗和效率都比PhantomJS和Selenium好,值得進行使用數據庫

本項目使用2.25版本的HtmlUnit並無出現JS加在不成功的問題,但使用2.3x的版本會沒法加載

3. JavaScriptEngine

  • 既然要加載JS,爲什麼不直接提取JS代碼,使用Java自帶的JS引擎處理呢?

由於JavaScriptEngine是有侷限性的,最明顯就是其不支持jquery的語法,由於jquery使用了瀏覽器內置的對象,而JS引擎自己是沒有瀏覽器對象的windows

  • 那還能使用JS引擎嗎?

固然能夠,只要分析過頁面的加載邏輯,若是不涉及瀏覽器對象的使用,或者將JS邏輯進行轉化,仍是可以使用JS引擎的,但犧牲了泛用性。本項目經分析後使用JS引擎加載瀏覽器

4. 橫向對比

通過測試,三者比較以下

  • 加載一個接口的效率:PhantomJS約13秒,HtmlUnit約10秒,JS引擎約6秒
  • 內存消耗、CPU消耗:PhantomJS > HtmlUnit > JS引擎
PhantomJS使用外置的程序,因此JVM沒法管理這部分的硬件資源,須要打開任務管理器

爬取過程

通過分析,爬取步驟分爲4步:

  1. 爬取全部的詩人id。調用一次接口便可得到全部的詩人id,返回JSON格式數據,接口地址爲:http://poem.studentsystem.org...
  2. 爬取全部的詩人信息。根據上一步的詩人id逐一爬取對應的詩人詳細信息,一共有 2529 條數據,則接口調用 2529 次,返回JSON格式數據,接口地址爲:http://poem.studentsystem.org...{id}
  3. 爬取全部的詩歌信息。根據上一步的詩人信息獲取全部的詩歌id,而後逐一調用接口獲取詩歌詳細信息,一共有約 48000 條數據,則接口調用 48000 次,返回html頁面,須要模擬瀏覽器動態執行JS,接口地址爲:http://poem.studentsystem.org...{id}
  4. 因爲動態執行JS可會能超時,所以最後要處理未成功加載完畢的詩歌信息,從數據庫中讀取這類數據,再次構成url調用接口爬取,直到全部數據都完整。這類數據約佔1%,則接口調用約 480 次
顯然,如上描述,採用的是寬度優先遍歷,因此當執行到第3步時,纔會有數據入庫

優化後使用Java8的nashorn JS引擎執行JS代碼,不須要動態加載JS,因此不會出現4的問題

耗時估計

根據爬取過程分析,忽略程序啓動時間和調用獲取詩人id接口的時間

在開啓8線程的併發模式下(使用HtmlUnit進行動態加載):

  • 調用獲取詩人信息的接口,每次須要5秒(5秒是線程內置的睡眠時間,可設置)
  • 調用獲取詩歌信息的接口,每次須要10秒(包含了上述的5秒)

一共須要: 2529 / 8 5 + 48000 (1 + 0.01)/ 8 * 10 ≈ 62596秒 ≈ 1043分鐘 ≈ 17.4小時

上述數據是在本地測試中獲得的,配置爲 win10 8G i5-4210M 4核

優化後,用JS引擎取代模擬瀏覽器動態加載JS,獲取詩歌信息的耗時明顯縮短,由10秒縮短到6秒左右,所以從新計算耗時以下:

2529 / 8 5 + 48000 / 8 6 ≈ 37185秒 ≈ 620分鐘 ≈ 10.3小時

性能評估

當使用模擬瀏覽器動態加載JS時,觀察JVM的使用狀況,發現爬取詩歌階段頻繁發生Minor GC(新生代GC),差很少10秒一次,以下圖所示,
後判明是多線程模擬瀏覽器加載頁面行爲很是的耗內存(參考同時打開8個瀏覽器加載網頁),對象頻繁建立,頻繁消耗,
建議運行時經過-Xms -Xmx把JVM內存設置得大些,至少1G,而後把新生代的比例設大,如-Xms2048M -Xmx2048M -XX:+UseParallelOldGC -XX:NewRatio=1

模擬瀏覽器動態加載JS內存評估


後來,用JS引擎取代模擬瀏覽器動態加載JS,不只速度獲得明顯提高,並且內存的消耗大幅度下降,Minor GC平均1分鐘發生一次,以下圖所示,

JS引擎執行代碼內存評估


最後附上GitHub項目地址:https://github.com/Kanarienvogels/spider-tangpoem

相關文章
相關標籤/搜索