網絡爬蟲設計中需要注意的幾個問題

做網絡爬蟲是件很有意義的事情。首先,它可以是一個專門的職業。從公司層面講,業務和戰略可能都需要很多數據進行多維度分析,所以現在很多公司都有專門的爬蟲工程師負責設計數據採集系統;其次,很多公司以爬蟲爲生,爬蟲就是他們用來賺取利潤的最主要手段,比如說各大搜索引擎和最近比較流行的即刻 APP;最後,爬蟲也可以成爲程序員業餘時間賺取外快的好玩具,很多社羣找程序員兼職爬取目標數據;最不濟,它還可以成爲一個好玩具,程序員可以抓取一些好玩的圖片和文章,做一個自己喜愛的 Side Project。

我是通過看「靜覓」上的文章接觸爬蟲的。作者最近還寫了本書「Python3網絡爬蟲開發實戰 」,算是現在市面上比較系統的爬蟲書籍了。我也寫點東西總結一下做爬蟲過程中遇到的主要問題,希望對沒有接觸過的同學有參考意義,也希望老鳥們幫忙看看路子是否正確。本文主要是爲了釐清爬蟲運行的思路,不會涉及太多的具體代碼。

「網絡爬蟲」又叫網絡蜘蛛,實際上就是一種自動化的網絡機器人,代替了人工來獲取網絡上的信息。所以只要復原用戶獲取網絡信息的步驟,就能夠釐清爬蟲運行的整個脈絡。

網址管理

上網的時候,我先是輸入一個網址,服務器給我返回網頁的結果,我碰到我感興趣的文章就用鼠標拖動,瀏覽器會自動給我新建一個標籤,一分鐘以後,我就獲取到了首頁我需要的所有內容。我還有另外一種選擇,當我碰到感興趣的文章我就點進去,然後在文章裏我又看到了更感興趣的,我又點擊進去,然後我再返回到首頁看第二篇我感興趣的文章。

第一種策略稱爲「廣度優先」,第二種策略稱爲「深度優先」。實際使用過程中一般是採用廣度優先的策略。我們先從入口返回的數據中拿到我們感興趣的 URL,放到一個列表中,每爬取完一個 URL,就把它放到已完成的列表中。對於異常的,另外作標記後續處理。

實際上最簡單的爬蟲只作一件事:訪問地址,獲取數據。 當要訪問的地址變得很多時,成立一個 URL 管理器,對所有需要處理的 URL 作標記。當邏輯不復雜的時候可以使用數組等數據結構,邏輯複雜的時候使用數據庫進行存儲。數據庫記錄有個好處是當程序意外掛掉以後,可以根據正在處理的 ID 號繼續進行,而不需要重新開始,把之前已經處理過的 URL 再爬取一遍。以 Python3 爲例,編寫以下僞代碼:


def main():
    root_url = 'https://www.cnblogs.com'
    res = get_content(root_url)
    first_floor_urls = get_wanted_urls(res)

    for url in first_floor_urls:
        res_url = get_content(url)
        if sth_wrong(res_url):
            put_to_error_list(url)
        else:
            sencond_floor_urls = get_wanted_urls(res_url)
    # rest of the code
            
if __name__ == '__main__':
    main()

什麼語言可以做爬蟲

雖然我會的語言不多,但是我相信任何語言,只要他具備訪問網絡的標準庫,都可以很輕易的做到這一點。剛剛接觸爬蟲的時候,我總是糾結於用 Python 來做爬蟲,現在想來大可不必,無論是 JAVA,PHP 還是其他更低級語言,都可以很方便的實現,靜態語言可能更不容易出錯,低級語言運行速度可能更快,Python 的優勢在於庫更豐富,框架更加成熟,但是對於新手來說,熟悉庫和框架實際上也要花費不少時間。

比如我接觸的 Scrapy,配環境就配了兩天,對於裏面複雜的結構更是雲裏霧裏,後來我果斷放棄了,任何爬蟲我都只使用幾個簡單的庫來實現,雖然耗費了很多時間,但是我對整個 HTTP 流程有了更深的理解。我認爲:

在沒有搞清楚設計優勢的時候盲目的學習框架是阻礙技術進步的。

在我剛轉行學習 Python 的那段時間,我每天都花很多時間在社區裏去讀那種比較 Flask,Django,Tornado 甚至是 Bottom,Sanic 這樣的文章。這些文章很多都寫得非常好,我也從中學到了很多知識,我知道了 Flask 勝在靈活,Django 更大更全面等等。

可是說真的,這浪費了我很多時間。新手總是有一種傾向,花費巨大的精力去尋找那些一勞永逸的方法,語言和框架,妄想只要學了這個,以後長時間就可以高枕無憂,面對各種挑戰。如果要我重來一次,我會選擇看一兩篇這種優質的比較文章,然後大膽的選用其中一種主流的框架,在不重要的學習項目中嘗試其他的框架,用了幾次自然而然就會發現他們的優劣。

現在我還發現這種傾向不僅在新手中存在,老鳥也有很多患有這種技術焦慮症。他們看到媒體鼓吹 Go 語言和 Assembly,大家都在討論微服務和 React Native,也不知所以的加入。但是有的人還是真心看懂了這些技術的優勢,他們在合適的場景下進行試探性的嘗試,然後步步爲營,將這些新技術運用到了主要業務中,我真佩服這些人,他們不焦不燥熱的引領着新技術,永遠都不會被新技術推着走。

解析數據

本來應該叫解析網頁,但是因爲現在大多數數據都是在移動端,所以叫解析數據應該更合適。解析數據是說當我訪問一個網址,服務器返回內容給了我,我怎麼把我需要的數據提取出來。當服務器返回給我的是 HTML 時,我需要提取到具體哪個 DIV 下面的內容;當服務器返回給我的是 XML 時,我也需要提取某個標籤下面的內容。

最原始的辦法是使用「正則表達式」,這是門通用的技術,應該大多數語言都有類似的庫吧,在 Python 中對應的是 re 模塊,不過正則表達式非常難於理解,不到萬不得已我真不想使用。Python 中的 BeautifulSoup 和 Requests-HTML 非常適合通過標籤進行內容提取。

應對反爬蟲策略

爬蟲對於服務器是一種巨大的資源負荷,想象一下,你從雲服務商那裏買了個 30 塊錢一個月的虛擬雲服務器,搭建了一個小型的博客用於分享自己的技術文章。你的文章非常優質,很多人慕名來訪問,於是服務器的響應速度變慢了。有些人開始做爬蟲來訪問你的博客,爲了做到實施更新,這些爬蟲每秒鐘都要瘋狂的訪問幾百次,這時候可能你的博客再也沒人能成功獲取到內容了。

這時候你就必須想辦法遏制爬蟲了。服務器遏制爬蟲的策略有很多,每次 HTTP 請求都會帶很多參數,服務器可以根據參數來判斷這次請求是不是惡意爬蟲。

比如說 Cookie 值不對,Referer 和 User-Agent 不是服務器想要的值。這時候我們可以通過瀏覽器來實驗,看哪些值是服務器能夠接受的,然後在代碼裏修改請求頭的各項參數僞裝成正常的訪問。

除了固定的請求頭參數,服務器可能還會自定義一些參數驗證訪問是否合法,這種做法在 app 端尤其常見。服務器可能要求你利用時間戳等一系列參數生成一個 key 發送給服務器,服務器會校驗這個 key 是否合法。這種情況需要研究 key 的生成,如果不行乾脆用模擬瀏覽器以及虛擬機來完全冒充用戶。

服務器還會限制 IP,限制 IP 的訪問速度。比如我用 IP 爲 45.46.87.89 的機器訪問服務器,服務器一旦自認爲我是爬蟲,會立刻加入黑名單,下一次起我的訪問就完全無效了。絕大多數的 IP 限制都不會有這麼嚴格,但是限制訪問速度是很常見的,比如服務器規定 1 個小時以內,每個 IP 只能訪問 40 次。

這要求爬蟲設計者要注意兩件事:

  • 珍惜服務器資源,不要太暴力的獲取服務器資源

  • 時刻注意 IP 代理池的設計

設計太快的訪問速度是一種不道德的行爲,不應該受到任何鼓勵,服務器在受到爬蟲暴力訪問後可能會將迅速反應,將反爬蟲策略設計得更加嚴格,因此我從來不將爬蟲的速度設計得太快,有時候會延時 1 分鐘再做下一次爬取,我始終認爲免費獲取別人的內容也應該珍惜。

在設計爬蟲的時候不要忘記隱藏自己的真實 IP 來保護自己。IP 代理池是每一次訪問都換不同的 IP,避免被服務器封掉。網上有很多免費的代理池,可以做個爬蟲爬取下來存儲備用。也有很多現成的庫比如 proxy_pool 就非常好用,安裝完成以後訪問本地地址就可以獲取到可以用的 IP 列表。

爬蟲和反爬蟲會長時間鬥志鬥勇,除了上述問題還會遇到其他問題,比如說驗證碼設置。不同的驗證碼有不同的處理方式,常見的應對策略有買付費的驗證服務,圖像識別等。

其他具體的問題可以使用「抓包工具」去分析,比較常用的抓包工具有 charles 和 Fiddler,使用也很簡單,搜教程看幾分鐘就會了。命令行我用過 mitmproxy,名字非常高大上,「中間人攻擊」。我還嘗試了 Wireshark,這個操作起來複雜得多,不過整個訪問流程都不放過,不愧是學習 HTTP 的利器,有精力應該看一下 『網絡是怎樣鏈接的』和『WireShark網絡分析就這麼簡單』這兩本書,對理解網絡訪問非常有幫助。

抓包工具非常有用,不僅可以用來做爬蟲分析,還可以用做網絡攻防練習。我曾經用 Fiddler 發現了一個主流健身軟件的很多漏洞,不過很快被他們發現了,他們通知我通過他們官方的渠道提交漏洞會有獎勵,我照做了,不過沒有得到他們的任何獎勵和回覆。可見,大公司也並不都靠譜。

模擬器

設計爬蟲還需要注意一個非常殘酷的現狀:Web 端越來越 JS 化,手機端 key 值校驗越來越複雜以致無法破解。這時候只能選擇模擬器來完全假扮成用戶了。

網頁端常見的模擬瀏覽器工具有 Selenium,這是一個自動化測試工具,它可以控制瀏覽器作出點擊,拖拉等動作,總之就是代替人來操作瀏覽器,通常搭配 PhantomJS 來使用。

PhantomJS 是一個基於WebKit的服務器端 JavaScript API,它基於 BSD開源協議發佈。PhantomJS 無需瀏覽器的支持即可實現對 Web 的支持,且原生支持各種Web標準,如DOM 處理、JavaScript、CSS選擇器、JSON、Canvas 和可縮放矢量圖形SVG。不過目前好像已經停止維護啦。

不過沒關係,Selenium 同樣可以操作 FireFox 和 Chrome 等瀏覽器。如果有需要再學不遲。

除了 web 端,手機端 APP 也可以使用模擬器技術來完全模擬人的動作。我使用過 uiautomator,另外還有 Appium 非常強大,我暫時還沒用過。

當需要併發的時候,我們手頭上沒有足夠多的真機用來爬取,就要使用 genymotion 這樣的虛擬機,使用起來跟 linux 虛擬機是一樣的,下載安裝包配置就可以了。

爬蟲的併發和分佈式

Python 作併發爬蟲實際上毫無優勢,不過如之前所講,太高併發的爬蟲對別人的服務器影響太大了,聰明的人不可能不作限制,所以高併發語言實際上優勢也不大。Python 3.6 以後異步框架 Aiohttp 配合 async/await 語法也非常好用的,能在效率上提升不少。

至於分佈式問題,我還沒有好好研究,我做的大多數爬蟲還達不到這個級別。我用過分佈式存儲,mongodb 配個集羣不是很難。

總結

爬蟲說起來是件簡單的事情。但是往往簡單的事情要做到極致就需要克服重重困難。要做好一個爬蟲我能想到的主要事項有:

  • URL 的管理和調度。聰明的設計往往容錯性很高,爬蟲掛掉以後造成的損失會很小。

  • 數據解析。多學點正則表達式總是好事情,心裏不慌。

  • 限制反爬蟲策略。要求對 HTTP 有一定的理解,最好系統的學習一下。

  • 模擬器。這樣做的效率有點低,而且電腦不能做其他事情。

我非常喜歡設計爬蟲,以後我會嘗試設計個通用性質的爬蟲。這篇文章沒有寫具體的代碼,因爲我看到網上的源碼都非常好懂,我就不做重複的事情了。我學爬蟲的時候收集了幾個,是 Python 的,如果你感興趣,可以找我索要。

更多原創文章我會第一時間發佈在公衆號:wang_little_yong ,歡迎關注

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章