網頁爬蟲及其用到的演算法和資料結構

edithfang發表於2014-09-30



網路爬蟲程式的優劣,很大程度上反映了一個搜尋引擎的好差。不信,你可以隨便拿一個網站去查詢一下各家搜尋對它的網頁收錄情況,爬蟲強大程度跟搜尋引擎好壞基本成正比。

1.世界上最簡單的爬蟲——三行情詩

我們先來看一個最簡單的最簡單的爬蟲,用python寫成,只需要三行。
import requests
url="http://www.cricode.com"
r=requests.get(url)
上面這三行爬蟲程式,就如下面這三行情詩一般,很乾脆利落。

是好男人,

就應該在和女友吵架時,

抱著必輸的心態。
2.一個正常的爬蟲程式

上面那個最簡單的爬蟲,是一個不完整的殘疾的爬蟲。因為爬蟲程式通常需要做的事情如下:
  • 1)給定的種子URLs,爬蟲程式將所有種子URL頁面爬取下來
  • 2)爬蟲程式解析爬取到的URL頁面中的連結,將這些連結放入待爬取URL集合中
  • 3)重複1、2步,直到達到指定條件才結束爬取
因此,一個完整的爬蟲大概是這樣子的:
import requests                       #用來爬取網頁
from bs4 import BeautifulSoup         #用來解析網頁
seds = ["http://www.hao123.com",      #我們的種子
              "http://www.csdn.net",
              "http://www.cricode.com"]
sum = 0                               #我們設定終止條件為:爬取到100000個頁面時,就不玩了
 
while sum < 10000 :
    if sum < len(seds):
         r = requests.get(seds[sum])
         sum = sum + 1
         do_save_action(r)
         soup = BeautifulSoup(r.content)               
         urls = soup.find_all("href",.....)                     //解析網頁
         for url in urls:
              seds.append(url)
 
    else:
         break
3.現在來找茬

上面那個完整的爬蟲,不足20行程式碼,相信你能找出20個茬來。因為它的缺點實在是太多。下面一一列舉它的N宗罪:
  • 1)我們的任務是爬取1萬個網頁,按上面這個程式,一個人在默默的爬取,假設爬起一個網頁3秒鐘,那麼,爬一萬個網頁需要3萬秒鐘。MGD,我們應當考慮開啟多個執行緒(池)去一起爬取,或者用分散式架構去併發的爬取網頁。
  • 2)種子URL和後續解析到的URL都放在一個列表裡,我們應該設計一個更合理的資料結構來存放這些待爬取的URL才是,比如佇列或者優先佇列。
  • 3)對各個網站的url,我們一視同仁,事實上,我們應當區別對待。大站好站優先原則應當予以考慮。
  • 4)每次發起請求,我們都是根據url發起請求,而這個過程中會牽涉到DNS解析,將url轉換成ip地址。一個網站通常由成千上萬的URL,因此,我們可以考慮將這些網站域名的IP地址進行快取,避免每次都發起DNS請求,費時費力。
  • 5)解析到網頁中的urls後,我們沒有做任何去重處理,全部放入待爬取的列表中。事實上,可能有很多連結是重複的,我們做了很多重複勞動。
  • 6)…..
4.找了這麼多茬後,很有成就感,真正的問題來了,學挖掘機到底哪家強?

現在我們就來一一討論上面找茬找出的若干問題的解決方案。

1)並行爬起問題

我們可以有多重方法去實現並行。

多執行緒或者執行緒池方式,一個爬蟲程式內部開啟多個執行緒。同一臺機器開啟多個爬蟲程式,如此,我們就有N多爬取執行緒在同時工作。能大大減少時間。

此外,當我們要爬取的任務特別多時,一臺機器、一個網點肯定是不夠的,我們必須考慮分散式爬蟲。常見的分散式架構有:主從(Master——Slave)架構、點對點(Peer to Peer)架構,混合架構等。

說道分散式架構,那我們需要考慮的問題就有很多,我們需要分派任務,各個爬蟲之間需要通訊合作,共同完成任務,不要重複爬取相同的網頁。分派任務我們要做到公平公正,就需要考慮如何進行負載均衡。負載均衡,我們第一個想到的就是Hash,比如根據網站域名進行hash。

負載均衡分派完任務之後,千萬不要以為萬事大吉了,萬一哪臺機器掛了呢?原先指派給掛掉的哪臺機器的任務指派給誰?又或者哪天要增加幾臺機器,任務有該如何進行重新分配呢?

一個比較好的解決方案是用一致性Hash演算法。

2)待爬取網頁佇列

如何對待待抓取佇列,跟作業系統如何排程程式是類似的場景。

不同網站,重要程度不同,因此,可以設計一個優先順序佇列來存放待爬起的網頁連結。如此一來,每次抓取時,我們都優先爬取重要的網頁。

當然,你也可以效仿作業系統的程式排程策略之多級反饋佇列排程演算法。

3)DNS快取

為了避免每次都發起DNS查詢,我們可以將DNS進行快取。DNS快取當然是設計一個hash表來儲存已有的域名及其IP。

4)網頁去重

說到網頁去重,第一個想到的是垃圾郵件過濾。垃圾郵件過濾一個經典的解決方案是Bloom Filter(布隆過濾器)。布隆過濾器原理簡單來說就是:建立一個大的位陣列,然後用多個Hash函式對同一個url進行hash得到多個數字,然後將位陣列中這些數字對應的位置為1。下次再來一個url時,同樣是用多個Hash函式進行hash,得到多個數字,我們只需要判斷位陣列中這些數字對應的為是全為1,如果全為1,那麼說明這個url已經出現過。如此,便完成了url去重的問題。當然,這種方法會有誤差,只要誤差在我們的容忍範圍之類,比如1萬個網頁,我只爬取到了9999個,剩下那一個網頁,who cares!

5)資料儲存的問題

資料儲存同樣是個很有技術含量的問題。用關聯式資料庫存取還是用NoSQL,抑或是自己設計特定的檔案格式進行儲存,都大有文章可做。

6)程式間通訊

分散式爬蟲,就必然離不開程式間的通訊。我們可以以規定的資料格式進行資料互動,完成程式間通訊。

7)……
 
廢話說了那麼多,真正的問題來了,問題不是學挖掘機到底哪家強?而是如何實現上面這些東西!:)

實現的過程中,你會發現,我們要考慮的問題遠遠不止上面這些。紙上得來終覺淺,覺知此事要躬行!
相關閱讀
評論(2)

相關文章