分佈式爬蟲框架 Demo
記錄用Java開發一個簡單的分佈式爬蟲框架,從最開始的十幾行代碼到開發一個支持集羣、分佈式的爬蟲框架。
起因
在訓練智能問答機器人的模型時,缺少模型數據,決定使用爬蟲進行。
當前每天的生活如下:
- 寫一個爬蟲抓數據
- 訓練模型
- 查看訓練結果
弊端
- 單線程爬蟲太慢了,沒法充分利用計算資源,亟需性能提升。
- 目標網站經常變更,勢必經常添加、修改爬取網站。需要一個框架(其實已有很多很好地框架,這裏僅爲了學習)。
- 手動觸發爬蟲和訓練太蠢了,希望自動觸發,夜裏完成
由於“懶”,第一步第二步佔用了90%的時間,決定將其自動化。
目標
- 每天凌晨2點定時增量抓取數據並保存
- 每天凌晨3點訓練模型
- 每天白天到實驗室增加爬蟲數據源,看結果,調整模型
用最簡單的代碼實現一個爬蟲
用十幾行代碼爬取全站新聞 代碼地址
建立爬蟲模型(接口)
用面向對象的思想將爬取數據抽象爲
- 任務(主要包含目標網站 url)
- 爬蟲(執行任務(發起HTTP請求),返回目標 url 的數據)
- 結果處理(將爬蟲返回的數據處理,主要包含解析、保存)
模型完善
上面的基本模型有了,但是還不夠完善,補充一些東西。
- 爲了充分利用計算資源,爬蟲和結果都使用多線程執行,線程還要複用,這裏爲他們分爲兩個不同的線程池(IO和計算)。
- 爲了消除任務創建和爬蟲的耦合,在任務和爬蟲間增加一個隊列(因爲在處理頁面結果時,可能再發出新的爬蟲HTTP請求,或者說創建爬蟲任務)。
初步方案如圖:
模型解讀
- 由爬蟲啓動器(SpiderStarter)創建爬蟲任務(SpiderTask)加入到任務隊列(SpiderTaskQueue)
- 爬蟲管理器(SpiderManager)間歇性的從爬蟲任務隊列中嘗試獲取全部任務,並分配給爬蟲(Spider)使用新線程去爬取
- 爬蟲(Spider)向任務(SpiderTask)中描述的url發起Http請求並反回結果,交給結果處理器接頭人(ResultHanderManager)然後等等爬蟲管理器(SpiderManager)再次分配任務。
- 爬蟲結果處理器接頭人(ResultHanderManager)收到結果後開啓處理線程,交給對應的結果處理器(Handler)去處理
- 爬蟲結果處理器(Handler)來處理返回的數據(匹配想要的內容,保存到希望的文件),若希望爬取深一層的url,也可以在處理過程中創建新的爬蟲任務,扔進爬蟲隊列(SpiderTaskQueue)
觸發
- 項目啓動時
- 每天凌晨2點
調用啓動器(SpiderStarter)的start方法即可。
代碼實現
單機版本
使用
想新增加一個網站的爬取處理時,只需要新增一個爬蟲類型:
- 新建 xxxSpiderTask 類來保存 url 和任務類型
- 新建 xxxHandler 類來處理新的爬取來的數據
集羣,異構
實驗室裏有多臺電腦,想充分利用這些電腦,就得讓自己的爬蟲支持集羣,由於爬蟲本身並不關心是否支持集羣,那就從剛纔搭好的框架做,框架實現,所有爬蟲便全都支持。
需要改動的點
由於我們已經使用面向對象的思想建立好了模型,因此想支持集羣只需要多個實例共享 Task 的狀態即可。故只需要將 SpiderTaskQueue 切換爲消息中間件,並實現Task的序列化即可。
其中消息中間件開源的有如 RabbitMq、KafKa 等,序列化直接用 Json 就好了。
代碼參見 Gitee
分佈式
集羣后其實一個實例既能爬取,又能處理,但爬蟲和處理消耗的資源不對等,因此決定將它拆開,分開運行,同時實驗室有專門的同學也可以用python寫爬蟲,也有會Java的同學專門做數據處理,充分發揮各個語言的優勢。
.#### 需要改動的點
爲了解決這個問題很簡單,我們只需要將爬蟲執行器和處理器分開即可,只需要添加一個爬蟲結果隊列,爬蟲執行完後,將結果放置在隊列中,爬蟲結果處理器從這個隊列裏取就好了。
這樣可以更加合理的利用計算機資源。
- 當爬蟲任務大量增加時,便可以只部署爬蟲執行實例,快速爬取。
- 當爬蟲結果多但任務少時,可以減少爬蟲執行器的實例,增加結果處理實例加速解析。
後語
本框架僅爲思想啓發,如何將一個問題更好地解決,未關注性能問題,實現中有很多可以優化的點~。