做網路爬蟲是件很有意義的事情。首先,它可以是一個專門的職業。從公司層面講,業務和戰略可能都需要很多數據進行多維度分析,所以現在很多公司都有專門的爬蟲工程師負責設計數據採集系統;其次,很多公司以爬蟲為生,爬蟲就是他們用來賺取利潤的最主要手段,比如說各大搜索引擎和最近比較流行的即刻 APP;最後,爬蟲也 ...
做網路爬蟲是件很有意義的事情。首先,它可以是一個專門的職業。從公司層面講,業務和戰略可能都需要很多數據進行多維度分析,所以現在很多公司都有專門的爬蟲工程師負責設計數據採集系統;其次,很多公司以爬蟲為生,爬蟲就是他們用來賺取利潤的最主要手段,比如說各大搜索引擎和最近比較流行的即刻 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 ,歡迎關註。
-
本文主要記錄了SpringBoot中AOP註解式攔截與方法規則攔截的基本使用。 ...
-
本文內容: servlet的介紹 servlet的基礎使用介紹 HttpServlet ServletConfig ServletContext Cookie Session 數據域對象 servlet的介紹: Servlet是sun公司提供的一門用於開發動態web資源的技術。 servlet程式運... ...
-
工作中偶然發現Scala構造方法中的參數,無論是否有val/var修飾都可以順利編譯運行,如下: 那麼兩者的區別在哪裡呢?對於case class呢?其區別又在哪裡?其應用場景又在哪裡呢?下麵就辨析一下如下幾個類的區別 單純的從代碼中來看,發現不了什麼區別,只是簡單的多了一個val的修飾符。為了一探 ...
-
在配置項目組件的過程中, 瞭解Tomcat載入組件順序很有必要。 例如某些框架如Quartz的集群功能需要資料庫的支持, 資料庫的載入肯定要在框架組件載入之前。 經過查閱和Debug發現, web.xml組件載入順序為:context-param -> listener -> filter -> s ...
-
背景在很多互聯網產品應用中,有些場景需要加鎖處理,比如:秒殺,全局遞增ID,樓層生成等等。大部分的解決方案是基於DB實現的,Redis為單進程單線程模式,採用隊列模式將併發訪問變成串列訪問,且多客戶端對Redis的連接並不存在競爭關係。其次Redis提供一些命令SETNX,GETSET,可以方便實現 ...
-
這是本系列的第三篇文章,前兩篇我們講了qt的安裝和編譯,今天我們講一講程式的打包。 好像我們現在都沒怎麼講到qt的使用,因為想要放開手腳寫代碼,一些基礎是要打牢的。 不過請放心,下一篇文章開始我們就會真正進入正題了。 打包 首先我們做一些打包前的準備工作,沒錯,做事之前先做好準備是個好習慣:-p。 ...
-
1、While迴圈 2、do ... While迴圈 3、For迴圈 一、While /*while迴圈 語句格式: while(boolean表達式){ 語句塊; } 執行順序: 先判斷boolean表達式的值,如果是true。就執行語句塊。 再判斷boolean表達式的值,如果是true。就執行 ...
-
先做個自我介紹,我13年考上一所很爛專科民辦的學校,學的是生物專業,具體的學校名稱我就不說出來獻醜了。13年我就輟學了,我在那樣的學校,一年學費要1萬多,但是根本沒有人學習,我實在看不到希望,我就退學了。退學後我也迷茫,大專都沒有畢業,我真的不知道我能幹什麼,我在糾結著我能做什麼。所以輟學後我一段時 ...