劉國璽,劉江,徐海峰,張雁,呂丹桔
(西南林業(yè)大學大數據與智能工程學院,昆明650224)
隨著大數據時代的到來,數據變得越來越重要[1]。在日常學習和科技研究中,往往需要大量數據支撐,然而數據獲取并不是一件容易的事情。網絡爬蟲是人們最常用的一種獲取網絡數據的方式,網絡爬蟲是一個經過設計專門從萬維網上下載網頁并對網頁進行分析的程序[2]。它將下載的網頁和收集到的信息存儲在本地用于研究和開發(fā)。網絡爬蟲的工作原理是從一個或若干初始網頁的鏈接出發(fā)進而得到下一個鏈接隊列的過程;伴隨著網頁的抓取又不斷從抓取到的網頁里抽取新的鏈接放入到鏈接隊列中,直到爬蟲程序滿足系統(tǒng)中某一條件時停止[3]。爬蟲腳本的實例多種多樣,但基本流程都是請求HTML 頁面、從頁面中分析并保存需要的文本信息、URL 管理和下載靜態(tài)資源。利用爬蟲的有規(guī)律的特點,本文擬設計一個基于Go 的多線程模塊化爬蟲框架,能夠提高網絡爬蟲的效率,可以在進行二次開發(fā)時,減少開發(fā)人員大量重復的代碼編寫,提高開發(fā)效率。
基于模塊化的思想,本文把爬蟲業(yè)務抽象為三個大模塊,即URL 管理、頁面分析和文件下載。每個模塊內部又抽象出一個或多個模塊,每個模塊只負責處理一項簡單的事務,由一個或多個協(xié)程(goroutine)來負責執(zhí)行,模塊與模塊之間采用專用數據通道(channel)傳輸數據[4]。此設計方案降低了模塊間的耦合性,使多個模塊可以方便地自由組合,協(xié)程也由原有的長途跋涉(處理一連串的事務)變?yōu)槎鄠€短跑運動(一個協(xié)程只負責反復執(zhí)行單個任務),原來繁瑣的線程變成了容易控制的協(xié)程,線程間通信也變得十分的輕松,并且可以通過設置不同步驟的協(xié)程數量來優(yōu)化整體的效率。
總體架構主要分為:URL 管理器、頁面分析器、文件下載器,如圖1 所示(圖中實線矩形表示一個操作,可設置獨立的一個或多個協(xié)程負責執(zhí)行,箭頭表示協(xié)程間通信的專用數據通道)。
在圖1 中,所有模塊之間都是用通道連接,這些單向通道可視作接口。每個模塊只負責處理傳入通道的數據而不在乎是誰傳入的。這樣每個模塊都有很強的封裝性,模塊間耦合性極大降低,模塊的復用性和可替代性也極大增強。
在爬蟲中如果遇到同一模板渲染,URL 雖有一定規(guī)律,但是有些部分無法通過腳本自動生成[5]。例如:http:www.xxx.com/xx?id=加一串隨機無規(guī)則字符串,很難一次性鎖定要爬取的所有頁面所對應的URL。此時常用的辦法就是建立匹配規(guī)則,定義一個或多個初始頁面,每爬取一個頁面都用匹配規(guī)則查找所有符合規(guī)則的鏈接,如此往復尋找就可以找到很多匹配的頁面。URL 管理器主要由HTML 鏈接提取器和URL 分配器組成[6]。HTML 分配器可以解析通過數據通道傳入的HTML 數據,提取并還原其中所有a 標簽的鏈接,然后將鏈接傳給URL 分配器。URL 分配器接收到URL 后,將URL 根據用戶定義的匹配規(guī)則(匹配規(guī)則與頁面分析器一一對應)分配給匹配的頁面分析器。
圖1 系統(tǒng)主體架構
在網頁爬取過程中請求和頁面分析是必不可少的步驟。頁面請求就是通過URL 獲取對應的數據,一般是一個HTML 文件。頁面分析就是提取頁面請求所得到的東西中的有效數據的一個過程[7]。用戶需求不同或者頁面的變化都會導致分析步驟的不同,此步驟不能統(tǒng)一封裝的,只能給開發(fā)者提供一個接口,通過執(zhí)行開發(fā)者所傳入的方法分析頁面,解析出需要的數據。另外在Go 語言中方法可以像變量一樣傳遞,開發(fā)者能快速上手。
圖2 頁面分析器結構示意圖
如圖2 所示頁面分析器主要由URL 檢查器、HTML 下載器、HTML 解析器組成。每個頁面分析器都由一個map 保存已經抓取過的URL,當有新的URL 通過URL 通道傳入,URL 檢查器都會檢查這個URL 是否已經存在于map 中,避免重復抓取相同數據。如果沒有重復,URL 檢查器會把這個URL 傳給HTML 下載器。HTML 下載器通過URL 下載相應的HTML 頁面,然后傳遞給HTML 解析器。HTML 解析器執(zhí)行用戶傳入的解析方法,解析HTML 數據并把頁面對應的URL添加到已爬取URL 列表中。HTML 解析器是整個分析器的核心,同時也是整個系統(tǒng)的控制中心[8]。在解析方法中用戶可以使用其他Go 語言的HTML 解析方式如goquery、正則匹配等解析HTML 獲取自己需要的數據,也可以自己定義解析方式。同時也可以執(zhí)行數據存儲操作如把數據存儲到數據庫或者文本。HTML 解析器是控制中心是因為:使用URL 管理器,只需要把HTML或者URL 傳給URL 解析器;下載文件,只需要把URL傳遞給初始化好的資源文件下載器。
頁面分析器針對當前大多數網站使用視圖模板,結構相似的網頁對應一個頁面分析器,根據本文設計方案不同頁面分析器的主要區(qū)別就是傳入的解析方法不同,用戶根據自己需要抓取的網頁和數據設計解析方法通過接口傳入頁面解析器執(zhí)行,最終實現(xiàn)頁面的解析。
網絡爬蟲的根本目的是獲取需要的數據。這些數據大致可分為文本數據和靜態(tài)資源數據兩大類[7]。文本數據是指嵌套在頁面中的數據,如某公司首頁的文字說明、公司名稱等。靜態(tài)資源數據主要是音頻文件、視頻文件、圖片文件等。文本數據的獲取可以通過頁面分析器實現(xiàn),資源數據和頁面一樣也有指向文件的URL 地址[8]。靜態(tài)資源文件的流程就是通過URL 解析下載資源,然后以某個文件名存儲在硬盤中的某位置。將此過程封裝為“一個下載器”,開發(fā)者只需提供存儲路徑、URL 等參數,根據硬件資源設置線程數就可以完成下載,不必考慮多線程等問題。
資源下載器的完成的任務就是:檢查傳入的URL,下載URL 對應文件并保存在用戶指定的位置。
前面提到本系統(tǒng)采用模塊化設計,一個模塊只負責處理單個簡單的邏輯事務,每個模塊可以由一個或多個獨立的協(xié)程負責運作,這樣一個協(xié)程也由原來的處理一連串的事務變?yōu)橹回撠煼磸蛨?zhí)行單個任務。在一連串的事務處理中常常會因為某一步驟而導致線程阻塞,優(yōu)化起來很難[9]。在本模型中只需優(yōu)化阻塞的任務模型,甚至可以給阻塞任務多開幾個協(xié)程,就可以使整個流程變得流暢。熟悉多線程編程的用戶一定會發(fā)現(xiàn)這種模型的弊端:切換線程是有高性能代價的,這也是本系統(tǒng)選擇用Go 語言實現(xiàn)的一個重要原因。在Go 中本文協(xié)程來取代線程,把協(xié)程看做線程的一種封裝,一個四線程的CPU 最多可以同時執(zhí)行四個線程操作[10]。在普通的多線程操作中需要頻繁切換線程,在Go 中默認所有goroutine(協(xié)程)會在一個原生線程里運行。在同一個原生線程里,默認如果當前協(xié)程不發(fā)生阻塞,該協(xié)程就不會讓出時間給其他同線程的協(xié)程。如果發(fā)生阻塞,線程會把CPU 時間讓給其他協(xié)程而不切換線程,而切換和創(chuàng)建協(xié)程的性能代價遠遠低于線程的切換和創(chuàng)建,大大降低了頻繁切換協(xié)程對性能的影響[11]。
文本有效實現(xiàn)了爬蟲的多線程退出方法,在Go 語言中,建立了一個Waitgroup 實例,每添加一個任務調用一次Waitgroup.Add 方法;每執(zhí)行完一次任務調用一次Waitgroup.Done 方法。當Waitgroup 實例中任務數量降到0 時就會結束所有協(xié)程。
文本實現(xiàn)的爬蟲軟件測試環(huán)境如下:
操作系統(tǒng):Win10 專業(yè)版;CPU:Intel i5 4700M;網絡帶寬:100MB。在此設計方案下進行頁面爬取實驗,實驗結果如下:
(1)普通多線程爬蟲
表1 普通多線程爬蟲
(2)基于Go 的模塊化爬蟲
表2 基于Go 的模塊化爬蟲
在相同運行環(huán)境下,執(zhí)行相同任務時,在爬取頁面效率上,基于Go 的模塊化爬蟲比普通多線程爬蟲有所提升。對于開發(fā)效率,基于Go 的多線程由于進行模塊化封裝比普通多線程在性能、開發(fā)和調試效率上都有很大提升,實驗中,如表1 和表2 所示,編程所需時間由120min 降低為25min,開發(fā)效率明顯提高。開發(fā)者在爬取其他網站時,只需要對頁面解析方法進行設計,通過解析器接口傳入解析器執(zhí)行?;诒疚脑O計的爬蟲框架下,進行二次開發(fā),不僅可以減少大量的代碼編寫,提高開發(fā)者效率;而且可以減少開發(fā)者在開發(fā)過程中的重復性工作,避免開發(fā)過程的反復。
模塊化爬蟲設計,可以把復雜邏輯抽象成多個簡單任務模塊,采用協(xié)程和任務綁定的編程思路,不僅可以用在爬蟲程序中,也可以用在其他一些結構化的事務處理中。本文設計了一個方案把爬蟲基本流程進行模塊化封裝,為開發(fā)者提供一個可控的并且能夠支持多線程的集頁面請求、資源下載、URL 重復檢查管理等基本功能的開發(fā)包,便于開發(fā)者使用以及避免開發(fā)過程中的反復造輪子。除此之外,基于Go 的模塊化爬蟲設計可以降低模塊間耦合性,增強模塊的復用性和可替代性。