爬蟲技術淺析

Manning · 2014/11/17 12:53javascript

0x00 前言


網絡爬蟲(Web crawler),是一種「自動化瀏覽網絡」的程序,或者說是一種網絡機器人。它們被普遍用於互聯網搜索引擎或其餘相似網站,以獲取或更新這些網站的內容和檢索方式。它們能夠自動採集全部其可以訪問到的頁面內容,以便程序作下一步的處理。php

在WEB2.0時代,動態網頁盛行起來。那麼爬蟲就應該能在頁面內爬到這些有javascript生成的連接。固然動態解析頁面只是爬蟲的一個技術點。下面,我將按照以下順序分享下面的這些內容的一些我的經驗(編程語言爲Python)。html

1,爬蟲架構。html5

2,頁面下載與解析。java

3,URL去重方法。python

4,URL類似性算法。mysql

5,併發操做。linux

6,數據存儲git

7,動態爬蟲源碼分享。github

8,參考文章

0x01 爬蟲架構


談到爬蟲架構,不得不提的是Scrapy的爬蟲架構。Scrapy,是Python開發的一個快速,高層次的爬蟲框架,用於抓取web站點並從頁面中提取結構化的數據。Scrapy用途普遍,能夠用於數據挖掘、監測和自動化測試。Scrapy吸引人的地方在於它是一個框架,任何人均可以根據需求方便的修改。它也提供了多種類型爬蟲的基類,如BaseSpider、sitemap爬蟲等。

enter image description here

上圖是Scrapy的架構圖,綠線是數據流向,首先從初始URL 開始,Scheduler 會將其交給 Downloader 進行下載,下載以後會交給 Spider 進行分析,須要保存的數據則會被送到Item Pipeline,那是對數據進行後期處理。另外,在數據流動的通道里還能夠安裝各類中間件,進行必要的處理。 所以在開發爬蟲的時候,最好也先規劃好各類模塊。個人作法是單獨規劃下載模塊,爬行模塊,調度模塊,數據存儲模塊。

0x02 頁面下載與解析


頁面下載

頁面下載分爲靜態和動態兩種下載方式。

傳統爬蟲利用的是靜態下載方式,靜態下載的優點是下載過程快,可是頁面只是一個枯燥的html,所以頁面連接分析中獲取的只是< a >標籤的href屬性或者高手能夠本身分析js,form之類的標籤捕獲一些連接。在python中能夠利用urllib2模塊或requests模塊實現功能。 動態爬蟲在web2.0時代則有特殊的優點,因爲網頁會使用javascript處理,網頁內容經過Ajax異步獲取。因此,動態爬蟲須要分析通過javascript處理和ajax獲取內容後的頁面。目前簡單的解決方法是經過基於webkit的模塊直接處理。PYQT四、Splinter和Selenium這三個模塊均可以達到目的。對於爬蟲而言,瀏覽器界面是不須要的,所以使用一個headless browser是很是划算的,HtmlUnit和phantomjs都是可使用的headless browser。

enter image description here

以上這段代碼是訪問新浪網主站。經過對比靜態抓取頁面和動態抓取頁面的長度和對比靜態抓取頁面和動態抓取頁面內抓取的連接個數。

enter image description here

在靜態抓取中,頁面的長度是563838,頁面內抓取的連接數量只有166個。而在動態抓取中,頁面的長度增加到了695991,而連接數達到了1422,有了近10倍的提高。

抓連接表達式

正則:re.compile("href=\"([^\"]*)\"")

Xpath:xpath('//*[@href]')

頁面解析

頁面解析是實現抓取頁面內連接和抓取特定數據的模塊,頁面解析主要是對字符串的處理,而html是一種特殊的字符串,在Python中re、beautifulsoup、HTMLParser、lxml等模塊均可以解決問題。對於連接,主要抓取a標籤下的href屬性,還有其餘一些標籤的src屬性。

0x03 URL去重


URL去重是爬蟲運行中一項關鍵的步驟,因爲運行中的爬蟲主要阻塞在網絡交互中,所以避免重複的網絡交互相當重要。爬蟲通常會將待抓取的URL放在一個隊列中,從抓取後的網頁中提取到新的URL,在他們被放入隊列以前,首先要肯定這些新的URL沒有被抓取過,若是以前已經抓取過了,就再也不放入隊列了。

Hash表

利用hash表作去重操做通常是最容易想到的方法,由於hash表查詢的時間複雜度是O(1),並且在hash表足夠大的狀況下,hash衝突的機率就變得很小,所以URL是否重複的判斷準確性就很是高。利用hash表去重的這個作法是一個比較簡單的解決方法。可是普通hash表也有明顯的缺陷,在考慮內存的狀況下,使用一張大的hash表是不妥的。Python中可使用字典這一數據結構。

URL壓縮

若是hash表中,當每一個節點儲存的是一個str形式的具體URL,是很是佔用內存的,若是把這個URL進行壓縮成一個int型變量,內存佔用程度上便有了3倍以上的縮小。所以能夠利用Python的hashlib模塊來進行URL壓縮。 思路:把hash表的節點的數據結構設置爲集合,集合內儲存壓縮後的URL。

Bloom Filter

Bloom Filter是經過極少的錯誤換取了存儲空間的極大節省。Bloom Filter 是經過一組k 個定義在n 個輸入key 上的Hash Function,將上述n 個key 映射到m 位上的數據容器。

enter image description here

上圖很清楚的說明了Bloom Filter的優點,在可控的容器長度內,全部hash函數對同一個元素計算的hash值都爲1時,就判斷這個元素存在。 Python中hashlib,自帶多種hash函數,有MD5,sha1,sha224,sha256,sha384,sha512。代碼中還能夠進行加鹽處理,仍是很方便的。 Bloom Filter也會產生衝突的狀況,具體內容查看文章結尾的參考文章。

在Python編程過程當中,可使用jaybaird提供的BloomFilter接口,或者本身造輪子。

小細節

有個小細節,在創建hash表的時候選擇容器很重要。hash表佔用空間太大是個很不爽的問題,所以針對爬蟲去重,下列方法能夠解決一些問題。

enter image description here

上面這段代碼簡單驗證了生成容器的運行時間。

enter image description here

由上圖能夠看出,創建一個長度爲1億的容器時,選擇list容器程序的運行時間花費了7.2s,而選擇字符串做爲容器時,才花費了0.2s的運行時間。

接下來看看內存的佔用狀況。

enter image description here

若是創建1億的列表佔用了794660k內存。

enter image description here

而創建1億長度的字符串卻佔用了109720k內存,空間佔用大約減小了700000k。

0x04 URL類似性


初級算法

對於URL類似性,我只是實踐一個很是簡單的方法。

在保證不進行重複爬去的狀況下,還須要對相似的URL進行判斷。我採用的是sponge和ly5066113提供的思路。具體資料在參考文章裏。

下列是一組能夠判斷爲類似的URL組

http://auto.sohu.com/7/0903/70/column213117075.shtml

http://auto.sohu.com/7/0903/95/column212969565.shtml

http://auto.sohu.com/7/0903/96/column212969687.shtml

http://auto.sohu.com/7/1103/61/column216206148.shtml

http://auto.sohu.com/s2007/0155/s254359851/index1.shtml

http://auto.sohu.com/s2007/5730/s249066842/index2.shtml

http://auto.sohu.com/s2007/5730/s249067138/index3.shtml

http://auto.sohu.com/s2007/5730/s249067983/index4.shtml

按照預期,以上URL歸併後應該爲

http://auto.sohu.com/7/0903/70/column213117075.shtml

http://auto.sohu.com/s2007/0155/s254359851/index1.shtml

思路以下,須要提取以下特徵

1,host字符串

2,目錄深度(以’/’分割)

3,尾頁特徵

具體算法

enter image description here

算法自己很菜,各位一看就能懂。

實際效果:

enter image description here

上圖顯示了把8個不同的url,算出了2個值。經過實踐,在一張千萬級的hash表中,衝突的狀況是能夠接受的。

0x05 併發操做


Python中的併發操做主要涉及的模型有:多線程模型、多進程模型、協程模型。Elias專門寫了一篇文章,來比較經常使用的幾種模型併發方案的性能。對於爬蟲自己來講,限制爬蟲速度主要來自目標服務器的響應速度,所以選擇一個控制起來順手的模塊纔是對的。

多線程模型

多線程模型,是最容易上手的,Python中自帶的threading模塊能很好的實現併發需求,配合Queue模塊來實現共享數據。

多進程模型

多進程模型和多線程模型相似,multiprocessing模塊中也有相似的Queue模塊來實現數據共享。在linux中,用戶態的進程能夠利用多核心的優點,所以在多核背景下,能解決爬蟲的併發問題。

協程模型

協程模型,在Elias的文章中,基於greenlet實現的協程程序的性能僅次於Stackless Python,大體比Stackless Python慢一倍,比其餘方案快接近一個數量級。所以基於gevent(封裝了greenlet)的併發程序會有很好的性能優點。

具體說明下gevent(非阻塞異步IO)。,「Gevent是一種基於協程的Python網絡庫,它用到Greenlet提供的,封裝了libevent事件循環的高層同步API。」

從實際的編程效果來看,協程模型確實表現很是好,運行結果的可控性明顯強了很多, gevent庫的封裝易用性極強。

0x06 數據存儲


數據存儲自己設計的技術就很是多,做爲小菜不敢亂說,可是工做仍是有一些小經驗是能夠分享的。

前提:使用關係數據庫,測試中選擇的是mysql,其餘相似sqlite,SqlServer思路上沒有區別。

當咱們進行數據存儲時,目的就是減小與數據庫的交互操做,這樣能夠提升性能。一般狀況下,每當一個URL節點被讀取,就進行一次數據存儲,對於這樣的邏輯進行無限循環。其實這樣的性能體驗是很是差的,存儲速度很是慢。

進階作法,爲了減小與數據庫的交互次數,每次與數據庫交互從以前傳送1個節點變成傳送10個節點,到傳送100個節點內容,這樣效率變有了10倍至100倍的提高,在實際應用中,效果是很是好的。:D

0x07 動態爬蟲源碼分享


爬蟲模型

enter image description here

目前這個爬蟲模型如上圖,調度模塊是核心模塊。調度模塊分別與下載模塊,析取模塊,存儲模塊共享三個隊列,下載模塊與析取模塊共享一個隊列。數據傳遞方向如圖示。

爬蟲源碼

實現瞭如下功能:

動態下載

gevent處理

BloomFilter過濾

URL類似度過濾

關鍵字過濾

爬取深度

Github地址:https://github.com/manning23/MSpider

代碼整體來講難度不大,各位輕噴。

0x08 參考文章


感謝如下分享的文章與討論

http://security.tencent.com/index.php/blog/msg/34 http://www.pnigos.com/?p=217

http://security.tencent.com/index.php/blog/msg/12 http://wenku.baidu.com/view/7fa3ad6e58fafab069dc02b8.html

http://wenku.baidu.com/view/67fa6feaaeaad1f346933f28.html

http://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/

http://www.elias.cn/Python/PyConcurrency?from=Develop.PyConcurrency

http://blog.csdn.net/HanTangSongMing/article/details/24454453

http://blog.csdn.net/historyasamirror/article/details/6746217 http://www.spongeliu.com/399.html

http://xlambda.com/gevent-tutorial/ http://simple-is-better.com/news/334

http://blog.csdn.net/jiaomeng/article/details/1495500 http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=1337181

http://www.tuicool.com/articles/nieEVv http://www.zhihu.com/question/21652316 http://code.rootk.com/entry/crawler

相關文章
相關標籤/搜索