java併發小說爬蟲,多站點搜尋下載,並實現Android客戶端開發
小說爬蟲真的很簡單,但要能優雅地使用卻很麻煩。下面讓我來訴說一下這幾天的肝路歷程。整個流程很完整,但不會很深入,主要是講思路,給想要寫類似功能的同學踩點坑,有什麼細節問題直接評論就好。
先奉上專案Github,裡面有實現程式碼以及jar和apk兩種軟體,幾天課餘時間肝出來的,有些想的不周到的地方還請見諒。
明確產品需求
- 最基本的要求,能夠搜尋小說,然後點選小說進行閱讀或者下載。
- 同時,我們想要能夠搜尋到各個站點的小說,並且速度不能太慢。
- Android客戶端實現書籍收藏(追更),並自動檢查是否有章節更新。
- 下載格式,我們不僅想要生成txt格式的書籍,還想要epub這種帶目錄圖片的格式,最好還要能夠支援mobi,然後直接匯入kindle。
- 下載速度不能像市面上普通的小說軟體一章一章的下,否則速度太慢會影響體驗,最好要能達到寬頻的最大速度。
- 該程式能有較好的可移植性,因為我們想要同時製作PC端和Android端的軟體。
那麼,開幹!
總流程
關於爬蟲框架
在爬網頁內容這部分,並沒有用什麼黑科技,只是普通的正則匹配爬蟲。我用了自己的工具類,後來有些網頁有些太複雜也引入了Jsoup負責解析html。這裡預設大家都明白怎麼解析html內容。
關於編碼方式的坑
大多數人一想到編碼方式,肯定是首選uft-8了。但是在小說網站裡,我們需要首選gbk,因為很多小說的某些字元是沒有包含在utf-8裡的,會變成??常見的小說網站編碼方式都是預設gbk。
因此在框架中需要保留一個方法設定編碼方式,並且預設應為gbk。
如何實現搜尋功能
這裡我想到了兩種方案。第一種是利用百度這些搜尋引擎搜尋小說,再對搜尋引擎得到的結果進行解析;還有一種就是直接利用小說網站的搜尋功能實現搜尋。很明顯,搜尋引擎的優勢是能夠得到很多小說網站的搜尋結果,但是這些網站是隨機的,在沒有足夠多的解析前拿到結果也無能為力。第二種方式是使用小說網站內部的搜尋功能,缺點是書籍不夠全面,但是隻要搜尋到就必定能夠解析,比起漫無目的的搜尋能提高更多效率。而書籍不全的缺點可以通過解析更多網站來彌補。於是最終確定使用小說站內的搜尋功能。
基本上每個小說網站都會提供搜尋功能的。抓一下包,會發現,無非就是post請求
搜尋結果有很多附帶屬性,為了能夠提供更好的使用體驗,記得把小說名字,小說的目標url,最新章節名,作者,字數,最近更新時間都解析下來,以便後面使用。有的網站有圖片連結,也可以解析下來,我個人覺得用處不是很大就沒有解析。
以上,就實現了單個站點的搜尋解析,按照同樣的方式,我解析了十多個站點的搜尋功能。在使用者點選搜尋時,使用併發的方式同時請求,把所有結果儲存到集合裡並展示給使用者。併發的好處是隻要網路狀況良好,就能用搜索一個網站的時間獲得十幾個網站的結果,這在網站多了之後是必須的。至於如何使用併發,用最簡單的執行緒池+CountdownLatch就好。
這裡有個小技巧,很多小說站內搜尋直接使用了百度站內,他們的解析方式是一樣的,可以封裝起來。只要是這樣的網站,搜尋程式碼一行就能解決。
如何解析小說
通過搜尋功能我們拿到了小說目錄的url,對這個url進行請求並解析出所有章節名和url即可得到這本小說的所有目錄。這裡單執行緒就行,多執行緒並不能提高多少效率。還是放一張某趣閣的截圖吧。
拿到url以後,就可以得到章節內容的html了,同樣解析出來,就拿到章節內容了。如下圖,解析content所在的div即可。
關於解析小說的第二種方式
昨天看了一下owllook的實現方案,使用了匹配class或者id的方法,實現自動解析。說明白點就是傳入目錄所在的div的class或者id,通過某個演算法自動解析章節名字和url,這樣做的好處是解析網站特別簡單。如上圖的某趣閣是沒問題的,因為這個網站還是比較規範的,div裡就是目錄或者小說內容。
但是一旦遇到比較流氓的,就不行了。例如下圖,這個網站的目錄div分成了三列,每一列是一個單獨的div。如果按照自然順序去解析,解析出來的內容自然就亂序了,而如果要靠第x章的順序去重新排序顯然不太現實。
因此,我並沒有採用自動解析的邏輯,而是每個網站全部需要自己實現解析。這樣雖然麻煩了點,但是能夠保證所有網站都能正常解析,並且還能根據不同的網站刪除廣告之類的內容。同時,可以將比較常用的解析方法封裝起來使用,也不會太繁瑣。例如使用Jsoup的textNodes的功能,能夠直接提取所有文字到一個集合裡。
如何實現章節併發解析,失敗無限重試
這個工具類還真是構思了很久,也寫了很多個版本。
粗略一想,直接用執行緒池,再配合CountdownLatch不就行了?這樣確實可以實現併發解析,但是如果某一章小說網路請求出錯怎麼辦?錯誤的章節直接就跳過了,導致小說不完整。所以需要一個機制來實現失敗重試。
我最開始的方案比較粗略,直接把錯誤的章節新增到一個併發集合裡,等待所有章節都解析完畢後再重新解析所有錯誤的章節。這樣在網路情況穩定下,基本沒什麼問題,但萬一重試的章節又失敗了呢?所以我想要一個能夠失敗無限重試直到成功的工具。
於是實現了一個下載佇列,這個佇列一邊把任務新增到執行緒池,一邊重新入隊失敗的任務,這樣就能無限重試了。使用了鎖實現這個佇列,並且實現了進度監聽。wait真香O(∩_∩)O。具體可以參考github上的engine->BookFu*ker類。
關於併發
- 網路請求的併發執行緒一定不要設定高了,不然會返回504錯誤。使用okhttp預設配置即可。
- 網頁解析的執行緒池可以設定任意數量,因為okhttp維護了一個佇列,只要使用同一個client,單個ip最多併發數量為5。增加解析的執行緒數量可以提高解析速度,是因為能夠讓html下載下來後更快被解析。玄學調參:電腦300,手機150。
- 小說伺服器是有限制的,比如某趣閣測試量大約2w章節,就被封ip了,只有等到第二天才能重新測試,所以千萬別用校花的貼身高手什麼的去測試=_=數量和速度應該都有影響吧,大家自行把握。我測試時2m/s的下載速度,斷斷續續大概30分鐘不到。
- 網路請求的超時設定。由於已經實現了失敗重試機制,所以可以適當減少超時,以免某一章節一直卡著浪費資源。如果沒有失敗重試,必須設定長一點。
小說內容怎麼儲存
最開始我直接將每一章小說的內容儲存為一個String型別,然後返回統一處理。這樣做如果只是針對txt格式是沒問題的,因為拿到所有小說章節後只需要簡單按順序合併起來就ok。
但是類似EPUB格式的小說,每一章其實是一個html文字,需要在每一行放在</p/>標籤裡,標題放在</h1/>裡,還要統一配置css等。
於是,我們解析的小說內容最好按照每一行儲存在一個集合裡,這樣能方便拓展小說儲存格式。
關於小說儲存格式
epub格式儲存需要將所有章節轉換成html,並生成章節目錄的html,和epub格式的配置檔案,最後壓縮打包,生成.epub字尾的檔案即可。
雖然知道原理,但是真的不想自己寫,於是直接抄襲別人的程式碼。
在我github程式碼裡的EpubWriter忘記從哪兒抄的了,這個工具類有點坑,不能在安卓平臺上使用,Android版本我依賴了epublib,親測可以使用。
至於mobi格式,目前我還沒有找到直接生成的方法,最佳方法應該是下載kindlegen(外網)軟體,用epub轉mobi,效果很好。但是這個軟體在不同作業系統需要下載不同版本,所以沒法整合到軟體中。
Android開發心得
java程式碼寫的飛起,各種介面各種抽象,複製到安卓工程裡才發現,有些介面使用還是不太方便,只能一遍一遍地重構了,現在只是把最基本的功能做出來了,以後慢慢說吧。
龐大集合的傳遞
很多小說一個網頁就幾百k的大小,解析後的目錄集合能有幾千的數量,千萬別把這個集合傳進intent裡跳轉activity。如果你的手機效能不錯可能不會有bug,其他人手機可能會在跳轉時閃退,而且沒有報錯!!!因此,對於目錄這樣的超大集合,可以用單例儲存。
解析真的太慢了
電腦上解析一個8k+章節的小說只要1s不到,但即便是驍龍835處理器手機也要3~4秒。因此最好將解析結果儲存起來,防止不小心退出後需要重新解析。
實現追更
其實實現非常的簡單,只需要在每次使用者開啟App的時候,將追更列表裡的書籍併發解析一遍目錄,對比資料庫裡的最新目錄即可。
自動更新以及bugly
這種才開始寫的app真的很需要bugly來檢視異常狀況,並且實現自動更新功能,便於修復bug後的推送。後續可能使用熱修復方案,畢竟小說網站解析可能會頻繁出錯。