做爬蟲的時(shí)候我們經(jīng)常會遇到這么一個(gè)問題: 網(wǎng)站的數(shù)據(jù)是通過 Ajax 加載的,但是 Ajax 的接口又是加密的,不費(fèi)點(diǎn)功夫破解不出來。這時(shí)候如果我們想繞過破解抓取數(shù)據(jù)的話,比如就得用 Selenium 了,Selenium 能完成一些模擬點(diǎn)擊、翻頁等操作,但又不好獲取 Ajax 的數(shù)據(jù)了,通過渲染后的 HTML 提取數(shù)據(jù)又非常麻煩。 或許你會心想:要是我能用 Selenium 來驅(qū)動頁面,同時(shí)又能把 Ajax 請求的數(shù)據(jù)保存下來就好了。 辦法自然是有,比如可以加層代理,用 mitmdump 來實(shí)時(shí)處理就好了。 但如果不用代理,沒有好的辦法呢? 這里我們介紹一個(gè)工具,叫做 AjaxHook,利用它我們可以把 Ajax 請求的數(shù)據(jù)都攔截下來,只要發(fā)生了一個(gè) Ajax 請求,它就能把請求和響應(yīng)截獲下來,這樣我們就能實(shí)現(xiàn) Ajax 數(shù)據(jù)的實(shí)時(shí)處理了。 Ajax HookHook 大家估計(jì)不陌生了吧,這里我就不再展開講了,不太明白的可以自行搜索「Hook 技術(shù)」就能搜到一把資料。 那 Ajax Hook 顧名思義就是 Hook Ajax 請求了,Ajax 最重要的兩個(gè)部分?當(dāng)然就是 Request、Response 了,有了 Hook,我們就能在發(fā)起 Request 前和得到 Response 后對二者進(jìn)行處理了。 其基本作用點(diǎn)如圖所示: 那我們怎么來 Hook Ajax 請求呢?那自然就需要深入到 Ajax 的原生實(shí)現(xiàn)了。Ajax 其實(shí)就是利用 XMLHttpRequest 這個(gè)對象來實(shí)現(xiàn)的,要 Hook Ajax 的 Request 和 Response,那其實(shí)就是對它里面的一些屬性做一些處理,比如 send、onreadystatechange 等等。 聽起來似乎很麻煩的樣子,不用擔(dān)心,已經(jīng)有人把這個(gè)寫好了,我們直接拿來用就好了,GitHub 地址為:https://github.com/wendux/Ajax-hook。 其實(shí)這個(gè)內(nèi)部實(shí)現(xiàn)原理非常簡單,其實(shí)剛才就簡單提了一下,要想深入了解的話可以看下這篇文章:https://www.jianshu.com/p/7337ac624b8e。 OK,那這個(gè)怎么用呢? Ajax-hook 的這個(gè)作者提供了兩個(gè)主要方法,一個(gè)是 proxy,一個(gè)是 hook,起作用都是來 Hook XMLHttpRequest 的。 這里借用一下官方介紹:
那我們就來看看 proxy 方法的用法吧,其用法如下: proxy({ //請求發(fā)起前進(jìn)入 onRequest: (config, handler) => { console.log(config.url) handler.next(config); }, //請求發(fā)生錯(cuò)誤時(shí)進(jìn)入,比如超時(shí);注意,不包括http狀態(tài)碼錯(cuò)誤,如404仍然會認(rèn)為請求成功 onError: (err, handler) => { console.log(err.type) handler.next(err) }, //請求成功后進(jìn)入 onResponse: (response, handler) => { console.log(response.response) handler.next(response) } }) 很清楚了,Ajax-hook 給我們提供了三個(gè)方法供復(fù)寫,onRequest、onResponse、onError 分別是在請求發(fā)起前的處理、請求成功后的處理、發(fā)生錯(cuò)誤時(shí)的處理。 那我們?nèi)绻鰯?shù)據(jù)爬取的話,其實(shí)就是為了截獲 Response 的結(jié)果,那其實(shí)實(shí)現(xiàn) onResponse 方法就好了。 再仔細(xì)看看,這個(gè) onResponse 方法接收兩個(gè)參數(shù),為 response 對象和 handler 對象,這都是 Ajax-hook 為我們封裝好的,其實(shí)這里我們只需要用 response 里面的內(nèi)容就好了,比如把 Response Body 打印出來,其實(shí)就是把 Ajax 得到的結(jié)果打印出來了。 行,那我們就來試試吧。 案例介紹下面我們就拿一個(gè)我自己的案例來講吧,鏈接為:https://dynamic2./,界面如下: 這個(gè)網(wǎng)站是一個(gè)電影數(shù)據(jù)網(wǎng)站,其數(shù)據(jù)都是通過 Ajax 加載的,但是這些 Ajax 請求都帶著加密參數(shù) token,如圖所示: 其實(shí)這個(gè)參數(shù)你要解的話倒不是很難,不過也得費(fèi)點(diǎn)時(shí)間。 然后再看下 Ajax 的返回結(jié)果,如圖所示: 很純很清晰!所以我們?nèi)绻軌蛟诘玫?Ajax Response 的時(shí)候就把這些數(shù)據(jù)直接拿到,那就美滋滋了。 怎么辦?自然是用剛才所說的 Ajax-hook 了。 所以,我們這里就用上這個(gè) Ajax-hook 來對這些數(shù)據(jù)進(jìn)行實(shí)時(shí)處理吧。 實(shí)戰(zhàn)操作首先,第一步那我們得能用上 Ajax-hook,怎么用呢?那肯定得需要引入一下這個(gè) Ajax-hook 庫,瀏覽器里的這個(gè)頁面又怎么引入呢? 答案有很多,比如復(fù)寫 JavaScript、Tampermonkey、Selenium 等等。 這里我們就用最簡單的方法,Selenium 自動執(zhí)行一下 Ajax-hook 的源代碼就好了。 那這時(shí)候我們就需要找到 Ajax-hook 的源碼了,去 GitHub 一找就有了,鏈接為:https://raw./wendux/Ajax-hook/master/dist/ajaxhook.min.js,如圖所示: 看,代碼量真不多吧。 我們把這個(gè)代碼復(fù)制,粘貼到 https://dynamic2./ 這個(gè)網(wǎng)站的控制臺里。 這時(shí)候我們會得到一個(gè) ah 對象,代表 Ajax-hook,我們就能用它里面的 proxy 方法了。 怎么用呢?就直接實(shí)現(xiàn) onResponse 方法,打印 Response 的結(jié)果就好了,實(shí)現(xiàn)如下:
把這段代碼也放在控制臺運(yùn)行下,這時(shí)候我們就實(shí)現(xiàn)了 Ajax Response 的 Hook 了,只要有 Ajax 請求,Response 的結(jié)果就會被輸出出來。 這時(shí)候如果我們點(diǎn)擊翻頁,觸發(fā)一個(gè)新的 Ajax 請求,就可以看到控制臺輸出了 Response 的結(jié)果,如圖所示: 嗯,這下我們就能獲取到 Ajax 的數(shù)據(jù)了。 數(shù)據(jù)轉(zhuǎn)發(fā)那現(xiàn)在數(shù)據(jù)在瀏覽器里面啊,我們怎么存下來呢? 存還不簡單,最簡單的,把這個(gè)數(shù)據(jù)轉(zhuǎn)發(fā)給自己的一個(gè)接口保存下來就好了。 那我們就用 Flask 簡單弄一個(gè)接口吧,記得解除跨域限制,實(shí)現(xiàn)如下: import json from flask import Flask, request, jsonify from flask_cors import CORS
app = Flask(__name__) CORS(app)
@app.route('/receiver/movie', methods=['POST']) def receive(): content = json.loads(request.data) print(content) # to something return jsonify({'status': True})
if __name__ == '__main__': app.run(host='0.0.0.0', port=80, debug=True) 這里我就簡單寫了個(gè)示例,寫了一個(gè)能接收 POST 請求的 API,地址為 /receiver/movie,然后把 POST 的數(shù)據(jù)打印出來再返回一個(gè)響應(yīng)。 當(dāng)然這里你可以做很多操作了,比如把數(shù)據(jù)切割,存儲到數(shù)據(jù)庫等都是可以的。 好的,那現(xiàn)在服務(wù)器有了,我們就在 Ajax-hook 這邊把數(shù)據(jù)發(fā)過來吧。 這里我們借助于 axios 這個(gè)庫,其庫地址為 https:///axios@0.19.2/dist/axios.min.js,也是放在瀏覽器執(zhí)行就能用。 引入 axios 之后,我們把之前的 proxy 方法修改為如下內(nèi)容:
其實(shí)這里就是調(diào)用了 axios 的 post 方法,然后把當(dāng)前 url 和 Response 的數(shù)據(jù)發(fā)給了 Server。 到現(xiàn)在為止,每次 Ajax 請求的 Response 結(jié)果都會被發(fā)給這個(gè) Flask Server,F(xiàn)lask Server 對其進(jìn)行存儲和處理就好了。 自動化OK,那現(xiàn)在我們已經(jīng)可以實(shí)現(xiàn) Ajax 攔截和數(shù)據(jù)轉(zhuǎn)發(fā)了,最后一步自然就是把爬取自動化了。 自動化就分為三部分: ·打開網(wǎng)站?!ぷ⑷?Ajax-hook、axios、proxy 的代碼。·自動點(diǎn)擊下一頁翻頁。 最關(guān)鍵的就是第二步了,我們把剛才 Ajax-hook、axios、proxy 的代碼都放在一個(gè) hook.js 文件里面,用 Selenium 的 execute_script 來執(zhí)行就好了。 其他的幾步很簡單,最后實(shí)現(xiàn)如下: from selenium import webdriver import time
browser = webdriver.Chrome() browser.get('https://dynamic2./') browser.execute_script(open('hook.js').read()) time.sleep(2)
for index in range(10): print('current page', index) btn_next = browser.find_element_by_css_selector('.btn-next') btn_next.click() time.sleep(2) 最后,運(yùn)行一下。 可以發(fā)現(xiàn)瀏覽器先打開了頁面,然后模擬點(diǎn)擊了下一頁,再回過頭來觀察下 Flask Server 這邊,可以看到 Ajax 的數(shù)據(jù)就接收到了,如圖所示: OK,到此為止。 總結(jié)至此,我們就完成了: ·Ajax Response Hook·數(shù)據(jù)轉(zhuǎn)發(fā)與接收·瀏覽器自動化 以后我們再遇到類似的情形,也可以用同樣的思路來處理了。 本節(jié)代碼:https://github.com/Python3WebSpider/AjaxHookSpider。 |
|