朱延剛
(南京信息工程大學(xué),江蘇 南京 210044))
隨著互聯(lián)網(wǎng)技術(shù)的迅速發(fā)展,各類互聯(lián)網(wǎng)平臺大量涌現(xiàn)。 但這些平臺在為網(wǎng)絡(luò)用戶提供豐富的媒體資源的同時,也增加了用戶直接高效獲取有用信息的難度,特別是具有行業(yè)性、專業(yè)性的信息,很容易淹沒在信息的海洋中。 僅僅通過傳統(tǒng)的搜索引擎獲取的檢索信息,已經(jīng)無法滿足更為專業(yè)的信息需求。 因此,如何對海量的信息進行歸納和提取變得十分重要。 目前,有很多針對特定場景設(shè)計的Java 爬蟲系統(tǒng),可以滿足如圖片下載[1]、特定技術(shù)主題或新聞媒體咨詢整合等多種特定需求[2-4]。 這些爬蟲系統(tǒng)的提出,都能針對特定的業(yè)務(wù)場景設(shè)計出針對性比較強的解決方案。 設(shè)計一種適用性和可擴展性更好的爬蟲信息采集系統(tǒng),不僅可以降低用戶構(gòu)建特定場景的爬蟲系統(tǒng)設(shè)計難度,還能提高設(shè)計者的開發(fā)效率。 本文基于WebMagic 框架,提出了一種適用性和可擴展性更好的開發(fā)框架。
HttpClient 是Apache HttpComponentsTM項目負(fù)責(zé)創(chuàng)建和維護的一個基于HTTP 協(xié)議的Java 組件開發(fā)包。 HttpClient 與一般的瀏覽器不同,它不提供UI 界面,但是可以完成和瀏覽器相同的功能。 此外,還可以用來發(fā)送請求,接收服務(wù)器響應(yīng)數(shù)據(jù)。 因此,HttpClient 通常被開發(fā)人員作為API 來調(diào)用。 Jsoup是一個用于處理HTML 的Java 庫,可以提供一個非常便捷的API。 在網(wǎng)頁爬蟲中,Jsoup 主要用來從URL、文件或字符串中抓取和解析HTML,使用DOM 遍歷或CSS 選擇器查找和提取數(shù)據(jù),操作 HTML 元素、屬性和文本。 除此之外,它還能根據(jù)安全列表清理用戶提交的內(nèi)容,以防止 XSS 攻擊。
SpringData 是被廣泛使用的Web 開發(fā)框架Spring的重要組成部分,用于簡化數(shù)據(jù)庫的訪問。 本文之所以介紹SpringData,是因為它具有強大的數(shù)據(jù)持久層的支持開發(fā)能力。 不僅支持關(guān)系型數(shù)據(jù)庫的數(shù)據(jù)存儲,比如JDBC,JPA 等,也支持非關(guān)系型的數(shù)據(jù)存儲,比如Redis[5],ElasticSearch[6],Neo4j[7]等。 SpringData框架很好地提升了數(shù)據(jù)存儲層面的適用性和可擴展性。
目前,主流的Java 爬蟲框架主要有Nutch,Crawler4j,WebMagic,WebCollector 等。 這些爬蟲框架都有各自的優(yōu)缺點。 本文選擇WebMagic 作為基本的爬蟲框架,主要基于以下3 點:(1)WebMagic 的設(shè)計參考了業(yè)界比較成熟的爬蟲框架 Scrapy;(2)WebMagic 支持多線程任務(wù),能充分利用硬件資源,提高數(shù)據(jù)的處理速率;(3)WebMagic 的4 大組件Downloader,PageProcessor,Scheduler,Pipeline 對應(yīng)了爬蟲生命周期中的下載、處理、管理和持久化4 個過程。 對系統(tǒng)開發(fā)者來說,主要的業(yè)務(wù)代碼通常在PageProcessor 完 成 即 可。 其 他 的3 大 組 件, 即Downloader,Scheduler,Pipeline,也具有定制性。 系統(tǒng)設(shè)計者可以根據(jù)自己的需求,來修改完善組件的代碼。 因此,選擇WebMagic 框架作為基礎(chǔ)架構(gòu)能夠使整個系統(tǒng)具有更好的適應(yīng)性和可用性。
本文提出的基于WebMagic 的爬蟲框架,主要包含了3 個業(yè)務(wù)處理流程:頁面數(shù)據(jù)獲取、數(shù)據(jù)分析處理和數(shù)據(jù)存儲持久化。 每個業(yè)務(wù)流程也會提交或者產(chǎn)生不同階段的數(shù)據(jù)。 通過分析這些數(shù)據(jù)流,能促進系統(tǒng)設(shè)計者更好地理解系統(tǒng)結(jié)構(gòu),了解需要參與的實體,以及每個業(yè)務(wù)流程在每個階段需要完成的明確任務(wù)和目標(biāo)。 具體的結(jié)構(gòu)如圖1 所示。 本文將具體闡述每個框架結(jié)構(gòu)部分,具體的設(shè)計思路。
對于頁面數(shù)據(jù)獲取而言,系統(tǒng)設(shè)計者在設(shè)計任何場景的網(wǎng)頁爬蟲系統(tǒng)時,都需要確定目標(biāo)信息定位,明確將哪些站點平臺作為信息來源,其選取的信息源的質(zhì)量越高,得到的信息就越有價值。 因此系統(tǒng)設(shè)計者是否能選取合適的站點,對于獲取數(shù)據(jù)頁面信息是至關(guān)重要的。 一般網(wǎng)頁獲取分為開放式和注冊登錄式。 前者的瀏覽權(quán)限比較低,使用者通過訪客模式就可以正常瀏覽站點信息,這種類型的頁面數(shù)據(jù)獲取比較容易。 相比之下,登錄注冊式的站點更加復(fù)雜,信息獲取的流程更為煩瑣。 本文針對登錄注冊式站點的頁面數(shù)據(jù)獲取流程做了簡要闡述。
對于登錄注冊式的頁面,用戶可以進行常規(guī)的注冊,然后獲取用戶名和密碼,再通過框架的API 接口,模擬瀏覽器登錄。 對于帶有圖片驗證的模擬登錄,其登錄過程也是目前的一個難點。 對于站點的分類分頁頁面數(shù)據(jù),系統(tǒng)設(shè)計者還要通過瀏覽器的開發(fā)者工具,分析HTTP 報文的請求頭和響應(yīng)頭以及響應(yīng)參數(shù)的特點。 設(shè)計者對請求頭的請求參數(shù)進行分析可以幫助其確定需要提交哪些請求參數(shù)。 設(shè)計者通過點擊分頁發(fā)起請求時,對應(yīng)的請求參數(shù)需要攜帶Cookie中的JSESSIONID,而當(dāng)設(shè)計者對這一請求所產(chǎn)生的響應(yīng)數(shù)據(jù)進行分析時,有時候可以得到一些JSON 字符串,也就是設(shè)計者需要提取的信息文本。
系統(tǒng)設(shè)計者獲取了頁面的數(shù)據(jù)之后,需要進一步地對頁面數(shù)據(jù)進行分析處理。 這里的頁面數(shù)據(jù)的主要形式就是服務(wù)器響應(yīng)給請求端的HTML 標(biāo)簽,CSS樣式表等字符源碼。 為了更好地定位到需要的頁面標(biāo)簽數(shù)據(jù),系統(tǒng)設(shè)計者同樣可以借助瀏覽器的開發(fā)者工具,通過點擊元素檢查按鈕,迅速定位標(biāo)簽,提升數(shù)據(jù)分析效率。 在確定了具體的元素標(biāo)簽之后,系統(tǒng)設(shè)計者就可以借助WebMagic 3 種數(shù)據(jù)抽取技術(shù)(即XPath、CSS 選擇器和正則表達式)對需要的頁面數(shù)據(jù)進行分割提取,或者選擇相對獨立的Jsoup 開發(fā)庫作為數(shù)據(jù)抽取工具。
基于WebMagic 的爬蟲框架有一個比較大的優(yōu)點,就是支持多線程任務(wù)。 系統(tǒng)設(shè)計者可以充分利用開發(fā)平臺的硬件性能,減少數(shù)據(jù)抓取處理的時間,但是需要注意線程的安全性問題。 WebMagic 會自動提取頁面出現(xiàn)的HTTP 的請求連接,然后把它們放到請求的隊列中去。 如果有多個分類,每個分類還有多個頁面,而且每個分類頁面數(shù)據(jù)返回的時間也具有不確定性。 如果系統(tǒng)設(shè)計者在同一個PageProcessor 里處理業(yè)務(wù)邏輯,就要考慮多線程條件下的線程安全問題,否則將無法保證結(jié)果數(shù)據(jù)的準(zhǔn)確可靠。
系統(tǒng)設(shè)計者在獲得了目標(biāo)數(shù)據(jù)之后,為了便于后期進一步對數(shù)據(jù)進行分析提取,就要解決數(shù)據(jù)的存儲問題。 目前,數(shù)據(jù)存儲主要有兩種方案:關(guān)系型數(shù)據(jù)持久方案和非關(guān)系型數(shù)據(jù)持久方案。 系統(tǒng)設(shè)計者最終選取何種數(shù)據(jù)存儲方案更合適,需要根據(jù)自身的業(yè)務(wù)需求和特點并結(jié)合下一步對數(shù)據(jù)處理需要采取的具體策略來確定。 關(guān)系型數(shù)據(jù)持久方案的優(yōu)點是易于維護,支持通用的SQL 查詢語句,且支持事務(wù)處理。但它也有缺點,即讀寫較慢,不適合對I/O 要求高的操作。 非關(guān)系型數(shù)據(jù)持久方案存儲數(shù)據(jù)的結(jié)構(gòu)靈活,還支持內(nèi)存存儲,所以讀寫性能較好,查詢速度快,但它的缺點是不支持通用的SQL 查詢,基本也不支持事務(wù)處理,且對于初學(xué)者來說,其學(xué)習(xí)成本也更高。
基于WebMagic 的爬蟲框架使用了Pipeline 數(shù)據(jù)持久化組件,并提供了3 個實現(xiàn)類:用于向控制臺輸出的ConsolePipeline 類,用于向磁盤輸出文件的FilePipeline 類,還有用于保存Json 格式文件的JsonFilePipeline 類。 系統(tǒng)設(shè)計者可以根據(jù)需要定制合適的Pipeline,以實現(xiàn)對數(shù)據(jù)的持久化存儲。 系統(tǒng)設(shè)計者可以把Pipeline 作為一個可選的組件,因為WebMagic 作為一種Java 開發(fā)平臺本身就擁有很好的靈活性,設(shè)計者可以使用個性化的數(shù)據(jù)持久化組件,例如,設(shè)計者可以把本文技術(shù)介紹部分提到的SpringData 框架作為組件,從而發(fā)揮該框架支持的多種數(shù)據(jù)類型的持久化功能。 系統(tǒng)設(shè)計者可以進一步提升系統(tǒng)的適用性和可擴展性以及對抓取數(shù)據(jù)后期的可用性。 這些系統(tǒng)提升有利于對數(shù)據(jù)進行更深層次的處理。
基于WebMagic 爬蟲框架的系統(tǒng)設(shè)計中,一般不可缺少的組件是PageProcessor。 通過這個組件,系統(tǒng)設(shè)計者可以對頁面數(shù)據(jù)分析處理邏輯進行設(shè)計,而具體的邏輯設(shè)計需要根據(jù)具體的業(yè)務(wù)需求來確定。PageProcessor 組件也定義實現(xiàn)了一些具有HttpClient功能的對象,比如Site 對象。 站點本身的一些配置信息,例如編碼、HTTP 頭、超時時間、重試策略和代理等,都可以通過設(shè)置Site 對象來進行配置,增強框架的功能。 下面展示了一個帶分頁的爬蟲數(shù)據(jù)分析提取的例子,部分核心代碼如下:
class DemoProcessor implements PageProcessor {
/ /站點對象參數(shù)設(shè)置
private Site site = Site.me()
.setCharset("UTF-8")/ /設(shè)置編碼
.setTimeOut(10000)/ /超時時間
.setRetrySleepTime(3000)/ /重試時間
.setSleepTime(10);/ /重試次數(shù)
public Site getSite() {return site;}
public void process(Page page) {
/ /編寫頁面數(shù)據(jù)分析處理代碼
/ /獲取頁面上的HTTP 鏈接
String pageUrl = page.getUrl().toString();
/ /將頁面的HTML 標(biāo)簽通過Jsoup 生成一個可解析的文檔
Document doc = Jsoup. parse(page. getHtml().toString());
/ /借助開發(fā)者工具定位標(biāo)簽元素解析數(shù)據(jù)
String div = doc.select("div[module-name=icbupc-productListPc]").attr("module-data");
/ /對數(shù)據(jù)進行URLDecode 解碼
String decode = URLDecoder.decode(div);
/ /將字符串轉(zhuǎn)化成JSON 對象進行解析
JSONObject jsonObject =JSON. parseObject(decode);
/ /獲得當(dāng)前分類是數(shù)據(jù)總數(shù)目
Integer totalLines = jsonObject. getJSONObject("mds")
. getJSONObject(" pageNavView"). getInteger("totalLines");
/ /獲取當(dāng)前分頁的頁碼
Integer pageLines = jsonObject. getJSONObject("mds")
. getJSONObject(" pageNavView"). getInteger("pageLines");
/ /獲取當(dāng)前分類的總頁數(shù)
int totalPage = (totalLines + pageLines - 1) /pageLines;
}}
Spider 對象是爬蟲啟動的入口對象。 在這個入口對象create 方法的內(nèi)部,系統(tǒng)設(shè)計者可以傳入完成的頁面數(shù)據(jù)爬取業(yè)務(wù)實現(xiàn)類DemoProcessor,然后再調(diào)用run 方法進行啟動。 此外,另一個在Spider 對象上比較常用的方法是addUrl,系統(tǒng)設(shè)計者可以用這個方法來添加初始的URL 地址參數(shù)。 該地址參數(shù)一般就是站點的入口地址。 Spider 對象的部分核心代碼如下:
Spider. create (newDemoProcessor ( ))/ /傳入PageProcessor 參數(shù)
.addUrl(URL)/ /初始的URL
.thread(10)/ /開啟的線程數(shù)
.run();
Spider 對象還提供一個addPipeline 方法,顧名思義,這個方法主要是用于傳入設(shè)計人員自定義的Pipeline 組件對象的。
關(guān)于如何把獲取的提取數(shù)據(jù)進行持久化的問題,本文在框架結(jié)構(gòu)設(shè)計部分已經(jīng)做了闡述。 如果系統(tǒng)設(shè)計者將得到的目標(biāo)數(shù)據(jù)保存成文件的形式,則不利于大數(shù)據(jù)量的管理和查詢。 對此,本文認(rèn)為,系統(tǒng)設(shè)計者需要對數(shù)據(jù)進行數(shù)據(jù)庫的持久化設(shè)計。
3.3.1 通過定制Pipeline 的方式
通過Pipeline 有兩種導(dǎo)入數(shù)據(jù)庫的方式:注解方式和常規(guī)的代碼方式。 系統(tǒng)設(shè)計者可以定義一個名為AnnotationObjectDaoPipeline 的注解類,具體代碼如下:
@Component("AnnotationObjectDaoPipeline")
public class AnnotationObjectDaoPipeline implements PageModelPipeline
注 解 的 方 式 需 要 實 現(xiàn) WebMagic 的PageModelPipeline 接口。 系統(tǒng)設(shè)計者通過泛型傳入定義好的數(shù)據(jù)模型,最后在內(nèi)部實現(xiàn)process 方法即可。
另一種是常規(guī)代碼的實現(xiàn)方式。 系統(tǒng)設(shè)計者需要定義一個實現(xiàn)類CommonCodePipeline,具體代碼如下:
public class CommonCodePipelineimplements Pipeline {}
這種方式也需要系統(tǒng)設(shè)計者重寫內(nèi)部的process方法。 兩種方式可以根據(jù)設(shè)計者自身的需求,靈活應(yīng)用。 Pipeline 其實就是將PageProcessor 的抽取結(jié)果,進行獨立的處理。 之所以設(shè)計者可以考慮使用Pipeline 組件,主要是因為其具有兩大優(yōu)點:(1)這種方式實現(xiàn)了模塊分離,代碼結(jié)構(gòu)比較清晰,而且解析抽取和數(shù)據(jù)保存各自占用獨立的線程,互不干擾;(2)Pipeline 的功能比較固定,更容易做成通用組件。
3.3.2 自定義數(shù)據(jù)持久化接口
如果系統(tǒng)設(shè)計者認(rèn)為使用Pipeline 組件比較煩瑣,學(xué)習(xí)成本較高,也可以設(shè)計定義常規(guī)的數(shù)據(jù)接口層。 具體而言,設(shè)計者可以根據(jù)業(yè)務(wù)需求設(shè)計Service層和Dao 層,然后直接在PageProcessor 組件內(nèi)部使用process 方法,增加數(shù)據(jù)保存的業(yè)務(wù)代碼。 這種方式的優(yōu)勢是降低了學(xué)習(xí)成本,提高了系統(tǒng)設(shè)計開發(fā)的效率,但其缺點是增加了系統(tǒng)模塊的耦合性,不利于后期的代碼維護。
針對一般網(wǎng)頁爬蟲系統(tǒng)設(shè)計具有的特殊性和場景單一性,本文提出了一種基于WebMagic 框架的具有可適用性和可擴展性的系統(tǒng)設(shè)計。 系統(tǒng)設(shè)計人員若采用這一系統(tǒng)的設(shè)計方案,可以更好地搭建自己的爬蟲項目,構(gòu)建可用性和健壯性更好的數(shù)據(jù)系統(tǒng)。 本文提出的系統(tǒng)仍存在較大的可完善空間,比如設(shè)計者可以繼續(xù)優(yōu)化增量爬蟲數(shù)據(jù)的備份以及解決去重問題等,以進一步提升系統(tǒng)功能的完善性。