王磊, 劉曉丹
(陜西廣播電視大學 信息與智能技術學院, 西安 710119)
網(wǎng)絡上的信息隨快速發(fā)展的互聯(lián)網(wǎng)表現(xiàn)出指數(shù)級的增長趨勢,增加了用戶快速獲取所需信息的難度。作為數(shù)據(jù)獲取工具的一種,搜索引擎中通常會應用到網(wǎng)絡爬蟲,但針對中小規(guī)模系統(tǒng)的網(wǎng)絡爬蟲往往面臨較多的問題,在抓取數(shù)據(jù)速度上單機的網(wǎng)絡爬蟲程序難以有效滿足需求,并且網(wǎng)絡爬蟲框架大都沒有實現(xiàn)分布化,單一的網(wǎng)絡爬蟲程序難以滿足多種類型的網(wǎng)頁結(jié)構,簡單穩(wěn)定的高性能分布式網(wǎng)絡爬蟲系統(tǒng)框架以滿足中小規(guī)模系統(tǒng)的需求具有較高的實際應用價值。
Scrapy能夠進行屏幕抓取,且具備web抓取框架、快速、高層次的優(yōu)勢,提供了多種類型爬蟲的基類(包括BaseSpider、sitemap等),用途廣泛,用于從web站點頁面中抓取并提取出結(jié)構化的數(shù)據(jù),可用于數(shù)據(jù)挖掘、監(jiān)測和自動化測試,同時用戶可以根據(jù)實際需求進行修改。
不同于傳統(tǒng)的通用網(wǎng)絡爬蟲,基于Scrapy的網(wǎng)絡爬蟲系統(tǒng)采集的網(wǎng)頁信息需與用戶需求相關,而無需對互聯(lián)網(wǎng)上所有的資源進行采集。針對不同網(wǎng)站所具有的網(wǎng)頁結(jié)構不僅相同,為降低資源耗費及開發(fā)成本,網(wǎng)絡爬蟲系統(tǒng)的設計需具有足夠的靈活性,從而能夠在不同環(huán)境下無需做較大改動即可應用;隨著網(wǎng)頁數(shù)量的激增,考慮到單機抓取速度的有限性,需在原有Scrapy框架基礎上進行優(yōu)化,完成部分組件的重寫以支持分布式抓取,以降低采集時間;分布式網(wǎng)絡爬蟲在中小規(guī)模系統(tǒng)中的關鍵在于負載均衡,需確保系統(tǒng)分配任務量時以各爬蟲節(jié)點的負荷狀態(tài)為依據(jù),使爬蟲的抓取效率得以有效提高;同一時刻需限制發(fā)送到遠程服務器的請求以及集群中爬蟲節(jié)點對網(wǎng)站的訪問頻率;提高系統(tǒng)錯誤處理的能力,能夠及時處理突發(fā)狀況;降低系統(tǒng)操作及配置的難度,爬蟲開始或停止操作簡單,對集群中各爬蟲速度進行實時監(jiān)控。
系統(tǒng)總體架構如圖1所示,(1)用戶交互層由爬蟲管理和配置兩個子頁面構成,完成同用戶的交互,用戶提交的請求(包括查詢爬蟲任務狀態(tài)、站點配置信息更新、查詢模塊狀態(tài)及爬蟲抓取4類請求)由配置子頁面接收,為便于后續(xù)消息預處理模塊解析每類請求各有其固定的格式;集群中爬蟲的啟動與停止以及對各節(jié)點上爬蟲實時的運行狀態(tài)的監(jiān)控由爬蟲管理子頁面實現(xiàn)。
(2)對配置子頁面?zhèn)鬟f請求消息的驗證、解析由處理層完成,為有效的處理大量活躍的流數(shù)據(jù)以滿足不斷增加的業(yè)務需求,系統(tǒng)的中間緩存器使用了Kafka,保證數(shù)據(jù)流轉(zhuǎn)的高性能及低延遲,其中配置子頁面請求的周期性獲取以及請求格式正確性的驗證由消息預處理模塊完成,單位時間內(nèi)該模塊請求成功/失敗數(shù)量的記錄則由狀態(tài)計數(shù)器完成,如果請求的驗證結(jié)果為爬蟲抓取請求即存入任務隊列,其余3類請求以相應格式存入到Redi中,Redis是一個開源的使用ANSI C語言編寫、支持網(wǎng)絡、可基于內(nèi)存亦可持久化的日志型、Key-Value數(shù)據(jù)庫,并提供多種語言的API。多個該模塊同時運行以以確保該模塊宕機對系統(tǒng)正常運行不會造成影響;Redis中傳入的驗證成功請求的周期性檢測由Redis監(jiān)控模塊完成,然后通過相應插件的調(diào)用完成該請求的處理解析,將結(jié)果傳送到kafka消息系統(tǒng)中,同樣使用多個Redis監(jiān)控模塊對請求進行檢測處理,從而提高系統(tǒng)的可靠性及請求響應速度[1]。
圖1 系統(tǒng)總體架構
(3)系統(tǒng)的關鍵在于業(yè)務邏輯層,其構成為主節(jié)點一個及從節(jié)點若干個,物理結(jié)構如圖2所示。
圖2 物理結(jié)構圖
各爬蟲節(jié)點的任務調(diào)度交通過此種結(jié)構由主節(jié)點負責統(tǒng)一執(zhí)行,降低了任務調(diào)度的難度,以爬蟲節(jié)點實時運行狀態(tài)為依據(jù)完成最優(yōu)調(diào)度決策的制定。主節(jié)點中的爬蟲任務隊列分為總爬蟲和節(jié)點兩種,爬蟲抓取請求(由配置子頁面中傳入)及新的爬蟲抓取任務(由后續(xù)爬蟲節(jié)點在網(wǎng)頁中抽取)存放于總爬蟲任務隊列中;各爬蟲節(jié)點待抓取的爬蟲任務存放于節(jié)點爬蟲任務隊列中,待抓取爬蟲任務的存放選用內(nèi)存型數(shù)據(jù)庫Redis,以確??焖佾@取與提交爬蟲任務。
該層中爬蟲任務由總爬蟲任務隊列到各節(jié)點隊列中的分發(fā)由任務調(diào)度器完成,以爬蟲節(jié)點實時狀態(tài)為依據(jù)任務調(diào)度器采用動態(tài)反饋任務調(diào)度策略分發(fā)爬蟲抓取任務,以確保集群中各爬蟲節(jié)點負載均衡;集群中爬蟲節(jié)點對某個網(wǎng)站的訪問頻率(在固定時間段內(nèi))的控制由限速器完成,避免被網(wǎng)站屏蔽以實現(xiàn)數(shù)據(jù)的抓取,降低遠程服務器的壓力;為使整體的抓取效率提高,需通過過濾器模塊避免重復抓取相同的URL,海量URL去重通過布隆過濾器算法實現(xiàn)[2]。
爬蟲節(jié)點啟動、采集及存儲數(shù)據(jù)的任務由從節(jié)點完成,其中爬蟲管理器分布放置在各個從節(jié)點中,從而有效降低管理器的負載,爬蟲的啟動或停止及對節(jié)點中各爬蟲運行狀態(tài)的監(jiān)控由其完成;數(shù)據(jù)采集則由Scrapy爬蟲負責完成,該模塊主要由5個子模塊構成:由調(diào)度器子模塊完成爬蟲抓取任務的周期性獲取,將任務以規(guī)定的格式進行處理后(即封裝成Request請求)交給中間件,按照既定格式將從數(shù)據(jù)采集子模塊接收到的URL完成新的爬蟲抓取任務的封裝并提交給總爬蟲任務隊列(主節(jié)點);為確保抓取過程中的穩(wěn)定性,Request請求中IP地址的動態(tài)改變由中間件子模塊完成,從而能夠應對網(wǎng)站的反爬蟲策略;向目標網(wǎng)頁發(fā)起訪問及對應網(wǎng)頁內(nèi)容的下載由下載器子模塊負責完成,然后將下載內(nèi)容傳輸至數(shù)據(jù)采集子模塊完成相應網(wǎng)頁數(shù)據(jù)及URL鏈接的提取(需以用戶自定義的抽取規(guī)則為依據(jù)),接下來數(shù)據(jù)管道子模塊會對網(wǎng)頁數(shù)據(jù)(主要包括網(wǎng)頁正文提取、編碼轉(zhuǎn)換、數(shù)據(jù)清理等操作)進行處理,調(diào)度器對需跟進的URL鏈接進行處理;存放最終抓取數(shù)據(jù)則通過MongoDB集群實現(xiàn),集群模式能夠滿足業(yè)務增長數(shù)據(jù)量突增的需求。
系統(tǒng)整體運行數(shù)據(jù)流圖如圖3所示。
圖3 系統(tǒng)運行數(shù)據(jù)流圖
(1)用戶先提交待抓取的URL及相關信息并啟動相應的爬蟲,后臺接收到爬蟲抓取請求后即時將其存入Kafka中,當新的抓取請求被消息預處理模塊檢測到后,在總爬蟲任務隊列中存入將符合系統(tǒng)定義格式的請求,將不符合的請求記錄到失敗日志中。
(2)任務調(diào)度器對總爬蟲任務隊列采用動態(tài)反饋任務調(diào)度策略(依據(jù)運行中各爬蟲節(jié)點負荷狀態(tài))分發(fā)爬蟲抓取任務,當有新的爬蟲抓取任務被爬蟲節(jié)點的調(diào)度器檢測到后,集群中的爬蟲節(jié)點的訪問頻率需先由限速器進行判斷,超過系統(tǒng)設定頻率則獲取其它爬蟲抓取任務,從隊列中取出未超過設定頻率的爬蟲抓取任務并將其封裝成Request請求(需按照Soapy規(guī)定的格式)后傳給中間件,中間件子模塊會在Request請求中裝載隨機取出的一條高匿IP后傳給下載器,下載器據(jù)此向目標網(wǎng)頁發(fā)起訪問并對網(wǎng)頁進行下載,數(shù)據(jù)采集子模塊完成所需網(wǎng)頁數(shù)據(jù)及URL鏈接(后續(xù)待跟進)的提取,數(shù)據(jù)采集子模塊將URL鏈接提交給調(diào)度器,調(diào)度器通過過濾器對當前URL鏈接進行判斷,丟棄己經(jīng)抓取過的,對判斷結(jié)果為未抓取過的URL鏈接進行封裝(需按照系統(tǒng)定義的格式),使其成為新的爬蟲抓取任務,然后提交給總爬蟲任務隊列。
(3)最后將采集到的網(wǎng)頁數(shù)據(jù)通過數(shù)據(jù)管道子模塊進行相應處理后(主要包括編碼轉(zhuǎn)換、正文提取及數(shù)據(jù)清理)存入MongoDB集群[3]。
爬蟲系統(tǒng)內(nèi)部使用了MongoDB(存儲爬蟲最終抓取的數(shù)據(jù))及Redis(存儲爬蟲抓取請求)兩類數(shù)據(jù)庫,整個系統(tǒng)的正常運行離不開MongoDB或Redis數(shù)據(jù)庫的性能及可靠性,因此本文使用集群完成數(shù)據(jù)庫的搭建。
為滿足數(shù)據(jù)量不斷增長的需求,通過分片技術的使用實現(xiàn)數(shù)據(jù)在多臺機器中的存放,MongoDB集群中各組件如圖4所示[4]。
圖4 MongoDB集群組件
Mongos(路由控制器)需對所有的數(shù)據(jù)庫集群的請求進行協(xié)調(diào),并完成數(shù)據(jù)請求到對應Shard分片服務器上的轉(zhuǎn)發(fā)。
Config Servers(配置服務器)對所有路由、分片的配置進行存儲,mongos則從該服務器中加載配置信息。
最終數(shù)據(jù)存儲由Shard (分片)完成,并提供方便的數(shù)據(jù)訪問接口,各分片可以是replica set(副本集),以避免數(shù)據(jù)丟失從而確保數(shù)據(jù)的安全。
本系統(tǒng)中的mongos和config server配置了3個,以確保存儲查詢能夠順利完成,MongoDB集群的結(jié)構圖5所示。
作為內(nèi)存型數(shù)據(jù)庫,Redis引入哈希槽概念,各節(jié)點負責一部分哈希槽(集群劃分為16 384個槽),提供多個程序集,有利于多Redis節(jié)點間數(shù)據(jù)的共享,進入的Redis的鍵值對key-value會通過散列分配到某個槽中,使數(shù)據(jù)庫占用的單臺機器內(nèi)存得以顯著降低,系統(tǒng)Redis集群結(jié)構如圖6所示。
圖5 MongoDB集群結(jié)構圖
圖6 Redis集群結(jié)構
集群提供主從復制機制,各主節(jié)點(負責客戶端的讀寫請求)對應從節(jié)點(對主節(jié)點中的數(shù)據(jù)進行備份)若干個,以保證集群在節(jié)點出現(xiàn)宕機時仍能夠正常運行,集群可通過在從節(jié)點中選取一個節(jié)點代替宕機的主節(jié)點作為新主節(jié)點繼續(xù)工作,以防單點故障問題,本系統(tǒng)包含Redis主節(jié)點三個,分配在3臺物理機上,哈希槽的具體分配為:1到5500號由節(jié)點A負責,5501到11000號由節(jié)點B負責,11001到16384號由節(jié)點C負責,各主節(jié)點都配備一個從節(jié)點[5]。
本文主要研究了基于Scrapy網(wǎng)絡爬蟲系統(tǒng)框架,以設計目標為依據(jù)對網(wǎng)絡爬蟲所需具備的功能特性進行了詳細分析,完成了系統(tǒng)總體框架的設計,該框架采用主從結(jié)構,并介紹了各層主要模塊的功能,詳細闡述了完整的抓取過程,為促進網(wǎng)絡爬蟲系統(tǒng)框架的實現(xiàn),綜合MongoDB及Redis完成了系統(tǒng)數(shù)據(jù)庫方案的設計,從而提高用戶獲取信息的速度和質(zhì)量。