胡學(xué)軍,李嘉誠
(上海理工大學(xué)機(jī)械工程系,上海 200082)
隨著互聯(lián)網(wǎng)技術(shù)的蓬勃發(fā)展,變化之一就是網(wǎng)絡(luò)數(shù)據(jù)的增長曲線就像指數(shù)函數(shù)圖像一般,而網(wǎng)絡(luò)爬蟲是獲取數(shù)據(jù)的手段之一。初期單機(jī)模式的網(wǎng)絡(luò)爬蟲技術(shù)將需要抓取的鏈接組成一個隊(duì)列,再循環(huán)逐個地爬取,直至結(jié)束。
分布式爬蟲技術(shù)就等同于同時使用多臺單機(jī)網(wǎng)絡(luò)爬蟲抓取和處理數(shù)據(jù),抓取相同頁面信息時,顯著地縮短了時間。隨著爬蟲手段的逐步優(yōu)化和一次次技術(shù)上的迭代,許多大型的互聯(lián)網(wǎng)公司(如百度、谷歌等)都研發(fā)出了自己的復(fù)雜的分布式網(wǎng)絡(luò)爬蟲,但是這一類資源是未開放的。目前公開可使用的爬蟲資源,如Scrapy,就是典型的單機(jī)模式,此外還有很多類似的爬蟲技術(shù)。當(dāng)然也存在一些爬蟲技術(shù)采用的是分布式模式,如Nutch、Igloo等,但是這類系統(tǒng)在實(shí)現(xiàn)的過程中是比較復(fù)雜的。
本文研究基于Scrapy-Redis框架,設(shè)計(jì)和實(shí)現(xiàn)一個針對當(dāng)當(dāng)網(wǎng)圖書信息抓取的分布式系統(tǒng)。
在網(wǎng)絡(luò)爬蟲開發(fā)中,Python是使用最廣泛的設(shè)計(jì)語言,Scrapy則是使用最為簡單和方便的一種。Scrapy框架是基于Twisted開發(fā)的,而Twisted是一款使用Python語言實(shí)現(xiàn)的基于事件驅(qū)動網(wǎng)絡(luò)引擎框架。相較于其他網(wǎng)絡(luò)爬蟲技術(shù),該框架的優(yōu)勢在于它是已經(jīng)封裝好的,同時能夠執(zhí)行多個任務(wù),且抓取網(wǎng)站內(nèi)容時速度很快,也能將抓取內(nèi)容下載到本地,方便使用者使用。
Scrapy框架主要的組件就是作為核心的爬蟲引擎(Scrapy Engine)、負(fù)責(zé)路由(URL)調(diào)度處理的調(diào)度器(Scheduler)、在互聯(lián)網(wǎng)上下載所需要的數(shù)據(jù)時使用的下載器(Downloader),以及處理需求定義、頁面解析等使用到的爬蟲(Spider)組件;項(xiàng)目管道(Item Pipeline)組件作為下載下來的數(shù)據(jù)的加工廠,可以對其進(jìn)行清除、存儲和驗(yàn)證等操作;兩個中間件(下載中間件和爬蟲中間件)是連接組件的紐帶。
Scrapy工作流程如圖1所示,URL和下載數(shù)據(jù)在各組件之間的流走如圖中的箭頭方向所示(圖中編號1—10為數(shù)據(jù)走向)。
圖1 Scrapy工作流程圖Fig.1 Workflow chart of Scrapy
Scrapy的工作原理可以概括如下:爬蟲先根據(jù)初始的需下載的網(wǎng)頁URL到互聯(lián)網(wǎng)上下載該網(wǎng)頁數(shù)據(jù);在整個下載數(shù)據(jù)過程中,新的要下載的網(wǎng)頁URL會持續(xù)加入Scheduler中的等待下載隊(duì)列里;爬蟲按照下載隊(duì)列的順序一次抓取網(wǎng)頁,直到下載隊(duì)列為空。
(1)Scrapy-Redis框架
Scrapy框架并不支持抓取URL在各個爬蟲端之間共享,也就不支持分布式操作。在Scrapy單機(jī)爬蟲系統(tǒng)中存在一個由deque模塊實(shí)現(xiàn)的本地爬取隊(duì)列Queue。這個隊(duì)列是依靠Scheduler組件負(fù)責(zé)維護(hù)的,是獨(dú)一無二的,并且它具有不被多個Scheduler共享的特性,造成了Scrapy框架不能實(shí)現(xiàn)分布式抓取數(shù)據(jù)。但是Scrapy框架具有易擴(kuò)展的特點(diǎn),通過Scrapy-Redis組件引入Redis數(shù)據(jù)庫代替Scrapy框架的單機(jī)內(nèi)存來存儲待抓取的URL,Redis數(shù)據(jù)庫自身又有能共享數(shù)據(jù)庫給多個爬蟲端的特性,這樣就把抓取隊(duì)列共享出去了。
Scrapy-Redis架構(gòu)圖如圖2所示,它是Scrapy框架和Redis數(shù)據(jù)庫的結(jié)合。
圖2 Scrapy-Redis架構(gòu)圖Fig.2 Scrapy-Redis architecture diagram
(2)Scrapy-Redis框架爬蟲的主流模式
分布式爬蟲系統(tǒng)架構(gòu)復(fù)雜多變,目前公認(rèn)的兩種主要分類形式:主從模式和對等模式。
對等模式字面上就可以理解,系統(tǒng)里每一臺計(jì)算機(jī)都是平等的,它們都要參與爬蟲工作,各自完成自己的任務(wù),互不影響。每臺計(jì)算機(jī)的任務(wù)通過哈希(Hash)函數(shù)計(jì)算分配。
主從模式是由一臺主機(jī)和若干從機(jī)組成的,如圖3所示,我們把主機(jī)叫作Master節(jié)點(diǎn),把從機(jī)叫作Slave節(jié)點(diǎn)。主從模式把Redis數(shù)據(jù)庫搭建在Master上,請求的判重、分配和數(shù)據(jù)的存儲都交給Master,不執(zhí)行抓取任務(wù);Slave負(fù)責(zé)運(yùn)行爬蟲程序,并把新的請求發(fā)送給Master。本文采用的是主從模式。
圖3 主從分布式策略Fig.3 Master-Slave distributed strategy
巴頓·布隆于1970 年提出了布隆過濾器算法,它可以描述為把所有元素存到一個集合里,然后去比較判斷某一元素是否存在,其實(shí)現(xiàn)是依靠Hash函數(shù)能夠把一個大的數(shù)據(jù)集映射到一個小的數(shù)據(jù)集上。它的優(yōu)勢就是讓大規(guī)模的數(shù)據(jù)得到壓縮,減少了存儲所消耗的空間,同時查詢時間也比其他算法短。
(1)算法描述
第一步是位數(shù)組的定義。布隆過濾器是將數(shù)據(jù)保存在位數(shù)組里,所以首先是位數(shù)組定義,然后設(shè)位數(shù)組長度為,各個位置初始值為0。
第二步是添加元素。假設(shè)獨(dú)立的Hash函數(shù)個數(shù)是,布隆過濾器算法把想要添加的元素用Hash函數(shù)計(jì)算次,得到對應(yīng)于位數(shù)組上的不同位置,將多個不相同的位置設(shè)為1,如圖4所示。
圖4 添加元素Fig.4 Adding elements
第三步是查詢元素。將要查詢的元素執(zhí)行和第二步一樣的操作,通過Hash函數(shù)進(jìn)行映射,也就是計(jì)算次,把得到的值對應(yīng)地放到位數(shù)組上。假設(shè)這些位置上出現(xiàn)一個0,則可以判斷該元素肯定不在我們要查詢的集合中;如果這些位置全為1,則不能判斷是否在集合中,因?yàn)椴悸∵^濾器存在誤判率。
(2)性能分析
布隆過濾器算法使用個相互獨(dú)立的Hash函數(shù),逐一地把原始數(shù)據(jù)集合中的元素映射到數(shù)組中。查閱相關(guān)文獻(xiàn)了解到,如果存在一個元素S,其被錯誤判斷為屬于該集合的概率如式(1)所示。
由式(1)可以看出,影響該值大小的三個因素:Hash函數(shù)個數(shù)、位數(shù)組長度和原始數(shù)據(jù)集合。當(dāng)原始數(shù)據(jù)不斷增加時,位數(shù)組長度也要和其同步線性增長,才能讓誤判率保持穩(wěn)定。而Hash函數(shù)個數(shù)的最優(yōu)解如式(2)所示。
布隆過濾器算法的代碼實(shí)現(xiàn)步驟如下所述。
第一步是初始化位數(shù)組,定義一個Init(self,size,HashCount)方法,其中size表示位數(shù)組長度,通過導(dǎo)入bitarray對象可以把二進(jìn)制串轉(zhuǎn)化為bitarray對象,然后其又能轉(zhuǎn)化為bytes,實(shí)現(xiàn)字符串和二進(jìn)制之間的轉(zhuǎn)化;HashCount表示Hash函數(shù)的個數(shù)。采用集合(set)形式來表示算法對位數(shù)組的初始化。
第二步是對查詢元素進(jìn)行哈希計(jì)算,采用的是不同的Hash函數(shù)。先導(dǎo)入Murmurhash3,然后把該元素計(jì)算次,得到對應(yīng)的哈希值。
第三步是驗(yàn)證第二步中要查詢的元素是否存在,利用集合查找方法在位數(shù)組中進(jìn)行查找操作。如果對應(yīng)的位置上出現(xiàn)0,則認(rèn)為要查詢的元素不在集合中,執(zhí)行第四步。
第四步是把URL添加進(jìn)集合,定義一個add(self,item)方法實(shí)現(xiàn)該目標(biāo)。
對當(dāng)當(dāng)網(wǎng)的網(wǎng)站結(jié)構(gòu)進(jìn)行分析,不難發(fā)現(xiàn)頁面之間存在“父子”關(guān)系。我們把網(wǎng)頁按不同層次進(jìn)行處理,把頁面內(nèi)所有的其他鏈接歸類成它的二級頁面鏈接。從首頁開始,逐級對剩下的頁面進(jìn)行搜索,把符合規(guī)則的鏈接加入待抓取隊(duì)列中。系統(tǒng)基本流程圖如圖5所示。
圖5 系統(tǒng)基本流程圖Fig.5 Basic flow chat of the system
(1)數(shù)據(jù)表設(shè)計(jì)
Scrapy爬蟲下載數(shù)據(jù)后,通過Item_Loader機(jī)制把解析的數(shù)據(jù)存放到爬蟲模型Item中,最后使用項(xiàng)目管道模塊存儲數(shù)據(jù)。如表1所示為數(shù)據(jù)字典設(shè)計(jì)。
表1 數(shù)據(jù)字典設(shè)計(jì)Tab.1 Data dictionary design
(2)數(shù)據(jù)存儲
設(shè)計(jì)完數(shù)據(jù)字典以后,采用MySQL數(shù)據(jù)庫來存儲爬取到的數(shù)據(jù),在Pipeline.py文件中編寫Scrapy框架和數(shù)據(jù)庫鏈接的代碼及操作數(shù)據(jù)庫的SQL語句。實(shí)現(xiàn)流程如下所述。
第一步:打開Scrapy和MySQL數(shù)據(jù)庫的鏈接,使用的是open_spider(self,spider)方法,再賦值存儲數(shù)據(jù)服務(wù)器的地址(host)、MySQL的端口號(port)、MySQL的用戶名(user)、MySQL密碼(password)、自定義數(shù)據(jù)庫的名稱(name)和字符集(utf8)。
第二步:鏈接Scrapy和MySQL數(shù)據(jù)庫,引入第三方模塊pymysql實(shí)現(xiàn)。
第三步:操作數(shù)據(jù)庫,定義一個名為process_item(self,item,spider)的方法,通過Insert into語句來實(shí)現(xiàn)數(shù)據(jù)的存入。
第四步:關(guān)閉鏈接。
Scrapy框架支持多種頁面解析工具,本文使用XPath對抓取頁面進(jìn)行解析,主要代碼如下所述。
在測試時,使用一臺主機(jī)做Master,兩臺從機(jī)做Slave。由Master主機(jī)負(fù)責(zé)URL的調(diào)度隊(duì)列和協(xié)調(diào)從機(jī)之間的抓取任務(wù),Slave從機(jī)負(fù)責(zé)執(zhí)行抓取程序,到互聯(lián)網(wǎng)上下載頁面數(shù)據(jù)并存入數(shù)據(jù)庫。以當(dāng)當(dāng)網(wǎng)圖書為例,運(yùn)行1 小時后,抓取圖書信息18,000余條,主要為圖書的名稱、作者、價格、封面和簡介信息,如圖6所示。
圖6 存儲在MySQL中的圖書相關(guān)信息Fig.6 Book information stored in MySQL
互聯(lián)網(wǎng)發(fā)展帶來網(wǎng)絡(luò)數(shù)據(jù)的大規(guī)模增長,網(wǎng)絡(luò)爬蟲也需要變得更加迅速、高效,才能達(dá)到使用要求,所以對分布式爬蟲的研究具有一定的實(shí)用價值。本文從介紹Scrapy框架的組件及其功能入手,講述了數(shù)據(jù)在組件之間流動的過程,分析了Scrapy-Redis組件實(shí)現(xiàn)分布式抓取數(shù)據(jù)的原理和主流模式。以抓取當(dāng)當(dāng)網(wǎng)圖書信息為例,研究了爬行策略、爬蟲設(shè)計(jì)、解析規(guī)則和布隆過濾器算法對抓取URL去重等的設(shè)計(jì)思路,構(gòu)建了針對抓取當(dāng)當(dāng)網(wǎng)圖書信息的主從式分布網(wǎng)絡(luò)爬蟲系統(tǒng)。
但是項(xiàng)目在實(shí)際運(yùn)行過程中出現(xiàn)了令人不滿意的問題:(1)Master主機(jī)對爬蟲任務(wù)的管理并不理想;(2)布隆過濾器算法相比其他一般算法,在空間利用上更充分,查詢時間也更短,但是誤判率也是其非常明顯的缺點(diǎn),后續(xù)針對布隆過濾器算法URL去重方法,還需要進(jìn)一步學(xué)習(xí)。