最近看過不少講爬蟲的教程[1][2],基本都是一個模式:
開始先來拿正則、lxml、jquery/pyquery等等教大家從頁面上摳出一個一個的值來
然后深入一些在講講http 協(xié)議,講講怎么拿出 cookie 來模擬登錄之類的,講講基本的反爬蟲和反反爬蟲的方法
最后在上一個 簡單地 scrapy 教程,似乎就皆大歡喜了。
具體地采集一個一個的數(shù)據(jù)的確讓人產(chǎn)生成就感,然而這些教程卻都忽略了爬蟲最核心的邏輯抽象,也就是「爬蟲應(yīng)該采取什么樣的策略遍歷網(wǎng)頁」。其實(shí)也很簡單,只需要兩個隊列和一個集合,Scrapy 等框架拆開來看也是如此,本文參照 Scrapy 實(shí)現(xiàn)一個最基礎(chǔ)的通用爬蟲。
萬維網(wǎng)是由一個一個的頁面構(gòu)成的,而每個頁面和頁面之間是由鏈接來聯(lián)系的,并且這些鏈接都是具有方向性的。對應(yīng)到數(shù)據(jù)結(jié)構(gòu)的話,我們可以把每一個頁面都看作一個節(jié)點(diǎn),而每一個鏈接都是一個有向邊,也就是整個萬維網(wǎng)其實(shí)是一個巨大的「有向圖」[3]。說到這里,可能有的同學(xué)已經(jīng)明白了,可以用廣度優(yōu)先或者深度優(yōu)先的算法來遍歷這個圖。當(dāng)然,這個圖是在太巨大了,我們不可能遍歷整個圖,而是加一些限定條件,只去訪問其中很小一部分我們感興趣的節(jié)點(diǎn),比如某個域名下的網(wǎng)頁。
廣度優(yōu)先和深度優(yōu)先都可以使用遞歸或者輔助的隊列(queue/lifo_queue)來實(shí)現(xiàn)。然而如果你的爬蟲是用 python 寫的話,很遺憾不能使用遞歸來實(shí)現(xiàn)了,原因很簡單,我們要訪問的網(wǎng)頁可能成千上萬,如果采用遞歸來實(shí)現(xiàn),那么爬蟲每向前訪問一個節(jié)點(diǎn),系統(tǒng)的調(diào)用棧就會 +1,而 python 中至今沒有尾遞歸優(yōu)化,默認(rèn)的堆棧深度為1000,也就是很可能你訪問了1000個網(wǎng)頁之后就拋出異常了。所以我們這里使用隊列實(shí)現(xiàn)對網(wǎng)頁的遍歷訪問。
理論知識說了這么多,下面以一個例子來說明一下如何爬取數(shù)據(jù):爬取煎蛋網(wǎng)的妹子圖: http://jandan.net/ooxx
首先,我們打開對應(yīng)的網(wǎng)址,作為起始頁面,也就是把這個頁面放入待訪問的頁面的隊列。注意,這是我們需要的第一個隊列,存放我們的待訪問頁面。
class MiniSpider(object): def __init__(self): self._request_queue = queue.Queue() # 帶請求頁面的隊列 self._request_queue.put('http://jandan.net/ooxx') # 把第一個待訪問頁面入隊