网络爬虫设计中需要注意的几个问题
我是通過看「靜覓」上的文章接觸爬蟲的。作者最近還寫了本書「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 codeif __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 ,歡迎關注。
總結
以上是生活随笔為你收集整理的网络爬虫设计中需要注意的几个问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python实现寻迹功能
- 下一篇: 对于全连接层的理解