現(xiàn)在的網(wǎng)頁代碼搞得越來越復(fù)雜,除了使用vue等前端框架讓開發(fā)變得容易外,主要就是為了防爬蟲,所以寫爬蟲下的功夫就越來越多。攻和防在互相廝殺中結(jié)下孽緣卻又相互提升著彼此。
本文就JS反爬蟲的策略展開討論,看看這中間都有著怎樣的方法破解。

一 、JS寫cookie
我們要寫爬蟲抓某個(gè)網(wǎng)頁里面的數(shù)據(jù),無非是打開網(wǎng)頁,看看源代碼,如果html里面有我們要的數(shù)據(jù),那就簡單了。用requests請(qǐng)求網(wǎng)址得到網(wǎng)頁源代碼然后解析提取。
等等!requests得到的網(wǎng)頁是一對(duì)JS,跟瀏覽器打開看到的網(wǎng)頁源碼完全不一樣!這種情況,往往是瀏覽器運(yùn)行這段JS生成一個(gè)(或多個(gè))cookie再帶著這個(gè)cookie做二次請(qǐng)求。服務(wù)器那邊收到這個(gè)cookie就認(rèn)為你的訪問是通過瀏覽器過來的合法訪問。
其實(shí),你在瀏覽器(chrome、Firefox都可以)里可以看到這一過程。首先把Chrome瀏覽器保存的該網(wǎng)站的cookie刪除,按F12到Network窗口,把“preserve log”選中(Firefox是“Persist logs”),刷新網(wǎng)頁,這樣我們就可以看到歷史的Network請(qǐng)求記錄。比如下圖:

第一次打開“index.html”頁面時(shí)返回的是521, 內(nèi)容是一段JS代碼;第二次請(qǐng)求這個(gè)頁面就得到了正常的HTML。查看兩次請(qǐng)求的cookies,可以發(fā)現(xiàn)第二次請(qǐng)求時(shí)帶上了一個(gè)cookie,而這個(gè)cookie并不是第一次請(qǐng)求時(shí)服務(wù)器發(fā)過來的。其實(shí)它就是JS生成的。
對(duì)策就是,研究那段JS,找到它生成cookie的算法,爬蟲就可以解決這個(gè)問題。
二、JS加密ajax請(qǐng)求參數(shù)
寫爬蟲抓某個(gè)網(wǎng)頁里面的數(shù)據(jù),發(fā)現(xiàn)網(wǎng)頁源代碼里面沒有我們要的數(shù)據(jù),那就有點(diǎn)麻煩了。那些數(shù)據(jù)往往是ajax請(qǐng)求得到的。但是也不用怕,按F12打開Network窗口,刷新網(wǎng)頁看看加載這個(gè)網(wǎng)頁都下載了哪些URL,我們要的數(shù)據(jù)就在某個(gè)URL請(qǐng)求的結(jié)果里面。這類URL在Chrome的Network里面的類型大多是XHR。通過觀察它們的“Response”就可以發(fā)現(xiàn)我們要的數(shù)據(jù)。
然而事情往往不是這么順利,這個(gè)URL包含很多參數(shù),某個(gè)參數(shù)是一串看上去無意義的字符串。這個(gè)字符串很可能是JS通過一個(gè)加密算法得到的,服務(wù)器也會(huì)通過同樣的算法進(jìn)行驗(yàn)證,驗(yàn)證通過了才認(rèn)為你這是從瀏覽器來的請(qǐng)求。我們可以把這個(gè)URL拷貝到地址欄,把那個(gè)參數(shù)隨便改個(gè)字母,訪問一下看看是不是能得到正確的結(jié)果,由此來驗(yàn)證它是否是很重要的加密參數(shù)。
對(duì)于這樣的加密參數(shù),對(duì)策是通過debug JS來找到對(duì)應(yīng)的JS加密算法。其中關(guān)鍵的是在Chrome里面設(shè)置“XHR/fetch Breakpoints”。

三、JS反調(diào)試(反debug)
前面我們都用到了Chrome 的F12去查看網(wǎng)頁加載的過程,或者是調(diào)試JS的運(yùn)行過程。這種方法用多了,網(wǎng)站就加了反調(diào)試的策略,只有我們打開F12,就會(huì)暫停在一個(gè)“debugger”代碼行,無論怎樣都跳不出去。它看起來像下面這樣:

不管我們點(diǎn)擊多少次繼續(xù)運(yùn)行,它一直在這個(gè)“debugger”這里,每次都會(huì)多出一個(gè)VMxx的標(biāo)簽,觀察“Call Stack”發(fā)現(xiàn)它好像陷入了一個(gè)函數(shù)的遞歸調(diào)用。這個(gè)“debugger”讓我們無法調(diào)試JS。但是關(guān)掉F12窗口,網(wǎng)頁就正常加載了。
解決這種JS反調(diào)試的方法我們稱之為“反-反調(diào)試”,其策略是:通過“Call Stack”找到把我們帶入死循環(huán)的函數(shù),重新定義它。
這樣的函數(shù)幾乎沒有任何其它功能只是給我們?cè)O(shè)置的陷阱。我們可以把這個(gè)函數(shù)在“Console”里面重新定義,比如把它重新定義為空函數(shù),這樣再運(yùn)行它時(shí)就什么都不做,也就不會(huì)把我們帶人陷阱。在這個(gè)函數(shù)調(diào)用的地方打個(gè)“Breakpoint”。因?yàn)槲覀円呀?jīng)在陷阱里面了,所以要刷新頁面,JS的運(yùn)行應(yīng)該停止在設(shè)置的斷點(diǎn)處,此時(shí)該函數(shù)尚未運(yùn)行,我們?cè)贑onsole里面重新定義它,繼續(xù)運(yùn)行就可以跳過該陷阱。
四、JS發(fā)送鼠標(biāo)點(diǎn)擊事件
還有些網(wǎng)站,它的反爬都不是上面的方式。你從瀏覽器可以打開正常的頁面,而在requests里面卻被要求輸入驗(yàn)證碼或重定向其它網(wǎng)頁。起初你可能一頭霧水,但不要怕,認(rèn)真看看“Network”或許能發(fā)現(xiàn)些線索。比如下面這個(gè)Network流里面的信息:

認(rèn)真觀察后發(fā)現(xiàn),每點(diǎn)擊頁面的的鏈接,它都會(huì)做一個(gè)“cl.gif”的請(qǐng)求,它看上去是下載一個(gè)gif圖片,然而并不是。它請(qǐng)求時(shí)發(fā)送的參數(shù)非常多,而且這些參數(shù)都是當(dāng)前頁面的信息。比如包含了被點(diǎn)擊的鏈接等等。
我們先來梳理一下它的邏輯。JS會(huì)響應(yīng)鏈接被點(diǎn)擊的事件,在打開鏈接前,先訪問cl.gif,把當(dāng)前的信息發(fā)送給服務(wù)器,然后再打開被點(diǎn)擊的鏈接。服務(wù)器收到被點(diǎn)擊鏈接的請(qǐng)求,會(huì)看看之前是不是已經(jīng)通過cl.gif把對(duì)應(yīng)信息發(fā)過來,如果發(fā)過來了就認(rèn)為是合法的瀏覽器訪問,給出正常的網(wǎng)頁內(nèi)容。
因?yàn)閞equests沒有鼠標(biāo)事件響應(yīng)就沒有訪問cl.gif的過程就直接訪問鏈接,服務(wù)器就拒絕服務(wù)。
明白了這個(gè)過程,我們不難拿出對(duì)策,幾乎不需要研究JS內(nèi)容(JS也有可能對(duì)被點(diǎn)擊鏈接進(jìn)行修改哦)就可以繞過這個(gè)反爬策略,無非是在訪問鏈接前先訪問一下cl.gif即可。關(guān)鍵是要研究cl.gif后的參數(shù),把這些參數(shù)都帶上就萬事大吉啦。
結(jié)尾
爬蟲和網(wǎng)站是一對(duì)冤家,相克相生。爬蟲知道了反爬策略就可以做成響應(yīng)的反-反爬策略;網(wǎng)站知道了爬蟲的反-反爬策略就可以做一個(gè)“反-反-反爬”策略……道高一尺魔高一丈,兩者的斗爭也不會(huì)結(jié)束。