爬蟲技術淺析

wyzsk發表於2020-08-19
作者: Manning · 2014/11/17 12:53

0x00 前言


網路爬蟲(Web crawler),是一種“自動化瀏覽網路”的程式,或者說是一種網路機器人。它們被廣泛用於網際網路搜尋引擎或其他類似網站,以獲取或更新這些網站的內容和檢索方式。它們可以自動採集所有其能夠訪問到的頁面內容,以便程式做下一步的處理。

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

1,爬蟲架構。

2,頁面下載與解析。

3,URL去重方法。

4,URL相似性演算法。

5,併發操作。

6,資料儲存

7,動態爬蟲原始碼分享。

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的模組直接處理。PYQT4、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

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章