做網(wǎng)絡(luò)爬蟲是件很有意義的事情揍愁。首先,它可以是一個(gè)專門的職業(yè)杀饵。從公司層面講莽囤,業(yè)務(wù)和戰(zhàn)略可能都需要很多數(shù)據(jù)進(jìn)行多維度分析,所以現(xiàn)在很多公司都有專門的爬蟲工程師負(fù)責(zé)設(shè)計(jì)數(shù)據(jù)采集系統(tǒng)切距;其次朽缎,很多公司以爬蟲為生,爬蟲就是他們用來賺取利潤(rùn)的最主要手段谜悟,比如說各大搜索引擎和最近比較流行的即刻 APP话肖;最后,爬蟲也可以成為程序員業(yè)余時(shí)間賺取外快的好玩具葡幸,很多社群找程序員兼職爬取目標(biāo)數(shù)據(jù)最筒;最不濟(jì),它還可以成為一個(gè)好玩具蔚叨,程序員可以抓取一些好玩的圖片和文章床蜘,做一個(gè)自己喜愛的 Side Project。
我是通過看「靜覓」上的文章接觸爬蟲的蔑水。作者最近還寫了本書「Python3網(wǎng)絡(luò)爬蟲開發(fā)實(shí)戰(zhàn) 」邢锯,算是現(xiàn)在市面上比較系統(tǒng)的爬蟲書籍了。我也寫點(diǎn)東西總結(jié)一下做爬蟲過程中遇到的主要問題搀别,希望對(duì)沒有接觸過的同學(xué)有參考意義丹擎,也希望老鳥們幫忙看看路子是否正確。本文主要是為了厘清爬蟲運(yùn)行的思路歇父,不會(huì)涉及太多的具體代碼蒂培。
「網(wǎng)絡(luò)爬蟲」又叫網(wǎng)絡(luò)蜘蛛再愈,實(shí)際上就是一種自動(dòng)化的網(wǎng)絡(luò)機(jī)器人,代替了人工來獲取網(wǎng)絡(luò)上的信息护戳。所以只要復(fù)原用戶獲取網(wǎng)絡(luò)信息的步驟践磅,就能夠厘清爬蟲運(yùn)行的整個(gè)脈絡(luò)。
網(wǎng)址管理
上網(wǎng)的時(shí)候灸异,我先是輸入一個(gè)網(wǎng)址:https://www.cnblogs.com/,服務(wù)器給我返回網(wǎng)頁的結(jié)果羔飞,我碰到我感興趣的文章就用鼠標(biāo)拖動(dòng)肺樟,瀏覽器會(huì)自動(dòng)給我新建一個(gè)標(biāo)簽,一分鐘以后逻淌,我就獲取到了首頁我需要的所有內(nèi)容么伯。我還有另外一種選擇,當(dāng)我碰到感興趣的文章我就點(diǎn)進(jìn)去卡儒,然后在文章里我又看到了更感興趣的田柔,我又點(diǎn)擊進(jìn)去,然后我再返回到首頁看第二篇我感興趣的文章骨望。
第一種策略稱為「廣度優(yōu)先」硬爆,第二種策略稱為「深度優(yōu)先」。實(shí)際使用過程中一般是采用廣度優(yōu)先的策略擎鸠。我們先從入口返回的數(shù)據(jù)中拿到我們感興趣的 URL缀磕,放到一個(gè)列表中,每爬取完一個(gè) URL劣光,就把它放到已完成的列表中袜蚕。對(duì)于異常的,另外作標(biāo)記后續(xù)處理绢涡。
實(shí)際上最簡(jiǎn)單的爬蟲只作一件事:訪問地址牲剃,獲取數(shù)據(jù)。 當(dāng)要訪問的地址變得很多時(shí)雄可,成立一個(gè) URL 管理器凿傅,對(duì)所有需要處理的 URL 作標(biāo)記。當(dāng)邏輯不復(fù)雜的時(shí)候可以使用數(shù)組等數(shù)據(jù)結(jié)構(gòu)滞项,邏輯復(fù)雜的時(shí)候使用數(shù)據(jù)庫進(jìn)行存儲(chǔ)狭归。數(shù)據(jù)庫記錄有個(gè)好處是當(dāng)程序意外掛掉以后,可以根據(jù)正在處理的 ID 號(hào)繼續(xù)進(jìn)行文判,而不需要重新開始过椎,把之前已經(jīng)處理過的 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()
什么語言可以做爬蟲
雖然我會(huì)的語言不多疚宇,但是我相信任何語言亡鼠,只要他具備訪問網(wǎng)絡(luò)的標(biāo)準(zhǔn)庫,都可以很輕易的做到這一點(diǎn)敷待。剛剛接觸爬蟲的時(shí)候间涵,我總是糾結(jié)于用 Python 來做爬蟲,現(xiàn)在想來大可不必榜揖,無論是 JAVA勾哩,PHP 還是其他更低級(jí)語言,都可以很方便的實(shí)現(xiàn)举哟,靜態(tài)語言可能更不容易出錯(cuò)思劳,低級(jí)語言運(yùn)行速度可能更快,Python 的優(yōu)勢(shì)在于庫更豐富妨猩,框架更加成熟潜叛,但是對(duì)于新手來說,熟悉庫和框架實(shí)際上也要花費(fèi)不少時(shí)間壶硅。
比如我接觸的 Scrapy威兜,配環(huán)境就配了兩天,對(duì)于里面復(fù)雜的結(jié)構(gòu)更是云里霧里庐椒,后來我果斷放棄了椒舵,任何爬蟲我都只使用幾個(gè)簡(jiǎn)單的庫來實(shí)現(xiàn),雖然耗費(fèi)了很多時(shí)間约谈,但是我對(duì)整個(gè) HTTP 流程有了更深的理解逮栅。我認(rèn)為:
在沒有搞清楚設(shè)計(jì)優(yōu)勢(shì)的時(shí)候盲目的學(xué)習(xí)框架是阻礙技術(shù)進(jìn)步的。
在我剛轉(zhuǎn)行學(xué)習(xí) Python 的那段時(shí)間窗宇,我每天都花很多時(shí)間在社區(qū)里去讀那種比較 Flask措伐,Django,Tornado 甚至是 Bottom军俊,Sanic 這樣的文章侥加。這些文章很多都寫得非常好,我也從中學(xué)到了很多知識(shí)粪躬,我知道了 Flask 勝在靈活担败,Django 更大更全面等等。
可是說真的镰官,這浪費(fèi)了我很多時(shí)間提前。新手總是有一種傾向,花費(fèi)巨大的精力去尋找那些一勞永逸的方法泳唠,語言和框架狈网,妄想只要學(xué)了這個(gè),以后長(zhǎng)時(shí)間就可以高枕無憂,面對(duì)各種挑戰(zhàn)拓哺。如果要我重來一次勇垛,我會(huì)選擇看一兩篇這種優(yōu)質(zhì)的比較文章,然后大膽的選用其中一種主流的框架士鸥,在不重要的學(xué)習(xí)項(xiàng)目中嘗試其他的框架闲孤,用了幾次自然而然就會(huì)發(fā)現(xiàn)他們的優(yōu)劣。
現(xiàn)在我還發(fā)現(xiàn)這種傾向不僅在新手中存在烤礁,老鳥也有很多患有這種技術(shù)焦慮癥讼积。他們看到媒體鼓吹 Go 語言和 Assembly,大家都在討論微服務(wù)和 React Native脚仔,也不知所以的加入币砂。但是有的人還是真心看懂了這些技術(shù)的優(yōu)勢(shì),他們?cè)诤线m的場(chǎng)景下進(jìn)行試探性的嘗試玻侥,然后步步為營,將這些新技術(shù)運(yùn)用到了主要業(yè)務(wù)中亿蒸,我真佩服這些人凑兰,他們不焦不燥熱的引領(lǐng)著新技術(shù),永遠(yuǎn)都不會(huì)被新技術(shù)推著走边锁。
解析數(shù)據(jù)
本來應(yīng)該叫解析網(wǎng)頁姑食,但是因?yàn)楝F(xiàn)在大多數(shù)數(shù)據(jù)都是在移動(dòng)端,所以叫解析數(shù)據(jù)應(yīng)該更合適茅坛。解析數(shù)據(jù)是說當(dāng)我訪問一個(gè)網(wǎng)址音半,服務(wù)器返回內(nèi)容給了我,我怎么把我需要的數(shù)據(jù)提取出來贡蓖。當(dāng)服務(wù)器返回給我的是 HTML 時(shí)曹鸠,我需要提取到具體哪個(gè) DIV 下面的內(nèi)容;當(dāng)服務(wù)器返回給我的是 XML 時(shí)斥铺,我也需要提取某個(gè)標(biāo)簽下面的內(nèi)容彻桃。
最原始的辦法是使用「正則表達(dá)式」,這是門通用的技術(shù)晾蜘,應(yīng)該大多數(shù)語言都有類似的庫吧邻眷,在 Python 中對(duì)應(yīng)的是 re 模塊,不過正則表達(dá)式非常難于理解剔交,不到萬不得已我真不想使用肆饶。Python 中的 BeautifulSoup 和 Requests-HTML 非常適合通過標(biāo)簽進(jìn)行內(nèi)容提取。
應(yīng)對(duì)反爬蟲策略
爬蟲對(duì)于服務(wù)器是一種巨大的資源負(fù)荷岖常,想象一下驯镊,你從云服務(wù)商那里買了個(gè) 30 塊錢一個(gè)月的虛擬云服務(wù)器,搭建了一個(gè)小型的博客用于分享自己的技術(shù)文章。你的文章非常優(yōu)質(zhì)阿宅,很多人慕名來訪問候衍,于是服務(wù)器的響應(yīng)速度變慢了。有些人開始做爬蟲來訪問你的博客洒放,為了做到實(shí)施更新蛉鹿,這些爬蟲每秒鐘都要瘋狂的訪問幾百次,這時(shí)候可能你的博客再也沒人能成功獲取到內(nèi)容了往湿。
這時(shí)候你就必須想辦法遏制爬蟲了妖异。服務(wù)器遏制爬蟲的策略有很多,每次 HTTP 請(qǐng)求都會(huì)帶很多參數(shù)领追,服務(wù)器可以根據(jù)參數(shù)來判斷這次請(qǐng)求是不是惡意爬蟲他膳。
比如說 Cookie 值不對(duì),Referer 和 User-Agent 不是服務(wù)器想要的值绒窑。這時(shí)候我們可以通過瀏覽器來實(shí)驗(yàn)棕孙,看哪些值是服務(wù)器能夠接受的,然后在代碼里修改請(qǐng)求頭的各項(xiàng)參數(shù)偽裝成正常的訪問些膨。
除了固定的請(qǐng)求頭參數(shù)蟀俊,服務(wù)器可能還會(huì)自定義一些參數(shù)驗(yàn)證訪問是否合法,這種做法在 app 端尤其常見订雾。服務(wù)器可能要求你利用時(shí)間戳等一系列參數(shù)生成一個(gè) key 發(fā)送給服務(wù)器肢预,服務(wù)器會(huì)校驗(yàn)這個(gè) key 是否合法。這種情況需要研究 key 的生成洼哎,如果不行干脆用模擬瀏覽器以及虛擬機(jī)來完全冒充用戶烫映。
服務(wù)器還會(huì)限制 IP,限制 IP 的訪問速度噩峦。比如我用 IP 為 45.46.87.89 的機(jī)器訪問服務(wù)器锭沟,服務(wù)器一旦自認(rèn)為我是爬蟲,會(huì)立刻加入黑名單识补,下一次起我的訪問就完全無效了冈钦。絕大多數(shù)的 IP 限制都不會(huì)有這么嚴(yán)格,但是限制訪問速度是很常見的李请,比如服務(wù)器規(guī)定 1 個(gè)小時(shí)以內(nèi)瞧筛,每個(gè) IP 只能訪問 40 次。
這要求爬蟲設(shè)計(jì)者要注意兩件事:
- 珍惜服務(wù)器資源导盅,不要太暴力的獲取服務(wù)器資源
- 時(shí)刻注意 IP 代理池的設(shè)計(jì)
設(shè)計(jì)太快的訪問速度是一種不道德的行為较幌,不應(yīng)該受到任何鼓勵(lì),服務(wù)器在受到爬蟲暴力訪問后可能會(huì)將迅速反應(yīng)白翻,將反爬蟲策略設(shè)計(jì)得更加嚴(yán)格乍炉,因此我從來不將爬蟲的速度設(shè)計(jì)得太快绢片,有時(shí)候會(huì)延時(shí) 1 分鐘再做下一次爬取,我始終認(rèn)為免費(fèi)獲取別人的內(nèi)容也應(yīng)該珍惜岛琼。
在設(shè)計(jì)爬蟲的時(shí)候不要忘記隱藏自己的真實(shí) IP 來保護(hù)自己底循。IP 代理池是每一次訪問都換不同的 IP,避免被服務(wù)器封掉槐瑞。網(wǎng)上有很多免費(fèi)的代理池熙涤,可以做個(gè)爬蟲爬取下來存儲(chǔ)備用。也有很多現(xiàn)成的庫比如 proxy_pool 就非常好用困檩,安裝完成以后訪問本地地址就可以獲取到可以用的 IP 列表祠挫。
爬蟲和反爬蟲會(huì)長(zhǎng)時(shí)間斗志斗勇,除了上述問題還會(huì)遇到其他問題悼沿,比如說驗(yàn)證碼設(shè)置等舔。不同的驗(yàn)證碼有不同的處理方式,常見的應(yīng)對(duì)策略有買付費(fèi)的驗(yàn)證服務(wù)糟趾,圖像識(shí)別等慌植。
其他具體的問題可以使用「抓包工具」去分析,比較常用的抓包工具有 charles 和 Fiddler义郑,使用也很簡(jiǎn)單蝶柿,搜教程看幾分鐘就會(huì)了。命令行我用過 mitmproxy魔慷,名字非常高大上,「中間人攻擊」著恩。我還嘗試了 Wireshark院尔,這個(gè)操作起來復(fù)雜得多,不過整個(gè)訪問流程都不放過喉誊,不愧是學(xué)習(xí) HTTP 的利器邀摆,有精力應(yīng)該看一下 『網(wǎng)絡(luò)是怎樣鏈接的』和『WireShark網(wǎng)絡(luò)分析就這么簡(jiǎn)單』這兩本書,對(duì)理解網(wǎng)絡(luò)訪問非常有幫助伍茄。
抓包工具非常有用栋盹,不僅可以用來做爬蟲分析,還可以用做網(wǎng)絡(luò)攻防練習(xí)敷矫。我曾經(jīng)用 Fiddler 發(fā)現(xiàn)了一個(gè)主流健身軟件的很多漏洞例获,不過很快被他們發(fā)現(xiàn)了,他們通知我通過他們官方的渠道提交漏洞會(huì)有獎(jiǎng)勵(lì)曹仗,我照做了榨汤,不過沒有得到他們的任何獎(jiǎng)勵(lì)和回復(fù)≡趺#可見收壕,大公司也并不都靠譜妓灌。
模擬器
設(shè)計(jì)爬蟲還需要注意一個(gè)非常殘酷的現(xiàn)狀:Web 端越來越 JS 化,手機(jī)端 key 值校驗(yàn)越來越復(fù)雜以致無法破解蜜宪。這時(shí)候只能選擇模擬器來完全假扮成用戶了虫埂。
網(wǎng)頁端常見的模擬瀏覽器工具有 Selenium,這是一個(gè)自動(dòng)化測(cè)試工具圃验,它可以控制瀏覽器作出點(diǎn)擊掉伏,拖拉等動(dòng)作,總之就是代替人來操作瀏覽器损谦,通常搭配 PhantomJS 來使用岖免。
PhantomJS 是一個(gè)基于WebKit的服務(wù)器端 JavaScript API,它基于 BSD開源協(xié)議發(fā)布照捡。PhantomJS 無需瀏覽器的支持即可實(shí)現(xiàn)對(duì) Web 的支持颅湘,且原生支持各種Web標(biāo)準(zhǔn),如DOM 處理栗精、JavaScript闯参、CSS選擇器、JSON悲立、Canvas 和可縮放矢量圖形SVG鹿寨。不過目前好像已經(jīng)停止維護(hù)啦。
不過沒關(guān)系薪夕,Selenium 同樣可以操作 FireFox 和 Chrome 等瀏覽器脚草。如果有需要再學(xué)不遲。
除了 web 端原献,手機(jī)端 APP 也可以使用模擬器技術(shù)來完全模擬人的動(dòng)作馏慨。我使用過 uiautomator,另外還有 Appium 非常強(qiáng)大姑隅,我暫時(shí)還沒用過写隶。
當(dāng)需要并發(fā)的時(shí)候,我們手頭上沒有足夠多的真機(jī)用來爬取讲仰,就要使用 genymotion 這樣的虛擬機(jī)慕趴,使用起來跟 linux 虛擬機(jī)是一樣的,下載安裝包配置就可以了鄙陡。
爬蟲的并發(fā)和分布式
Python 作并發(fā)爬蟲實(shí)際上毫無優(yōu)勢(shì)冕房,不過如之前所講,太高并發(fā)的爬蟲對(duì)別人的服務(wù)器影響太大了趁矾,聰明的人不可能不作限制毒费,所以高并發(fā)語言實(shí)際上優(yōu)勢(shì)也不大。Python 3.6 以后異步框架 Aiohttp 配合 async/await 語法也非常好用的愈魏,能在效率上提升不少觅玻。
至于分布式問題想际,我還沒有好好研究,我做的大多數(shù)爬蟲還達(dá)不到這個(gè)級(jí)別溪厘。我用過分布式存儲(chǔ)胡本,mongodb 配個(gè)集群不是很難。
總結(jié)
爬蟲說起來是件簡(jiǎn)單的事情畸悬。但是往往簡(jiǎn)單的事情要做到極致就需要克服重重困難侧甫。要做好一個(gè)爬蟲我能想到的主要事項(xiàng)有:
- URL 的管理和調(diào)度。聰明的設(shè)計(jì)往往容錯(cuò)性很高蹋宦,爬蟲掛掉以后造成的損失會(huì)很小披粟。
- 數(shù)據(jù)解析。多學(xué)點(diǎn)正則表達(dá)式總是好事情冷冗,心里不慌守屉。
- 限制反爬蟲策略。要求對(duì) HTTP 有一定的理解蒿辙,最好系統(tǒng)的學(xué)習(xí)一下拇泛。
- 模擬器。這樣做的效率有點(diǎn)低思灌,而且電腦不能做其他事情俺叭。
我非常喜歡設(shè)計(jì)爬蟲,以后我會(huì)嘗試設(shè)計(jì)個(gè)通用性質(zhì)的爬蟲泰偿。這篇文章沒有寫具體的代碼熄守,因?yàn)槲铱吹骄W(wǎng)上的源碼都非常好懂,我就不做重復(fù)的事情了耗跛。我學(xué)爬蟲的時(shí)候收集了幾個(gè)裕照,是 Python 的,如果你感興趣课兄,可以找我索要牍氛。
更多原創(chuàng)文章我會(huì)第一時(shí)間發(fā)布在公眾號(hào):wang_little_yong 晨继,歡迎關(guān)注烟阐。