如何用 Hook 實時處理和保存 Ajax 數據


做爬蟲的時候我們經常會遇到這么一個問題:

網站的數據是通過 Ajax 加載的,但是 Ajax 的接口又是加密的,不費點功夫破解不出來。這時候如果我們想繞過破解抓取數據的話,比如就得用 Selenium 了,Selenium 能完成一些模擬點擊、翻頁等操作,但又不好獲取 Ajax 的數據了,通過渲染后的 HTML 提取數據又非常麻煩。

或許你會心想:要是我能用 Selenium 來驅動頁面,同時又能把 Ajax 請求的數據保存下來就好了。

辦法自然是有,比如可以加層代理,用 mitmdump 來實時處理就好了。

但如果不用代理,沒有好的辦法呢?

這里我們介紹一個工具,叫做 AjaxHook,利用它我們可以把 Ajax 請求的數據都攔截下來,只要發生了一個 Ajax 請求,它就能把請求和響應截獲下來,這樣我們就能實現 Ajax 數據的實時處理了。

Ajax Hook

Hook 大家估計不陌生了吧,這里我就不再展開講了,不太明白的可以自行搜索「Hook 技術」就能搜到一把資料。

那 Ajax Hook 顧名思義就是 Hook Ajax 請求了,Ajax 最重要的兩個部分?當然就是 Request、Response 了,有了 Hook,我們就能在發起 Request 前和得到 Response 后對二者進行處理了。

其基本作用點如圖所示:

那我們怎么來 Hook Ajax 請求呢?那自然就需要深入到 Ajax 的原生實現了。Ajax 其實就是利用 XMLHttpRequest 這個對象來實現的,要 Hook Ajax 的 Request 和 Response,那其實就是對它里面的一些屬性做一些處理,比如 send、onreadystatechange 等等。

聽起來似乎很麻煩的樣子,不用擔心,已經有人把這個寫好了,我們直接拿來用就好了,GitHub 地址為:https://github.com/wendux/Ajax-hook。

其實這個內部實現原理非常簡單,其實剛才就簡單提了一下,要想深入了解的話可以看下這篇文章:https://www.jianshu.com/p/7337ac624b8e。

OK,那這個怎么用呢?

Ajax-hook 的這個作者提供了兩個主要方法,一個是 proxy,一個是 hook,起作用都是來 Hook XMLHttpRequest 的。

這里借用一下官方介紹:

proxy 和 hook 方法都可以用於攔截全局XMLHttpRequest。它們的區別是:hook 的攔截粒度細,可以具體到 XMLHttpRequest 對象的某一方法、屬性、回調,但是使用起來比較麻煩,很多時候,不僅業務邏輯需要散落在各個回調當中,而且還容易出錯。而 proxy 抽象度高,並且構建了請求上下文,請求信息 config 在各個回調中都可以直接獲取,使用起來更簡單、高效。

大多數情況下,我們建議使用 proxy 方法,除非 proxy 方法不能滿足你的需求。

那我們就來看看 proxy 方法的用法吧,其用法如下:

proxy({
    //請求發起前進入
    onRequest: (config, handler) => {
        console.log(config.url)
        handler.next(config);
    },
    //請求發生錯誤時進入,比如超時;注意,不包括http狀態碼錯誤,如404仍然會認為請求成功
    onError: (err, handler) => {
        console.log(err.type)
        handler.next(err)
    },
    //請求成功后進入
    onResponse: (response, handler) => {
        console.log(response.response)
        handler.next(response)
    }
})

很清楚了,Ajax-hook 給我們提供了三個方法供復寫,onRequest、onResponse、onError 分別是在請求發起前的處理、請求成功后的處理、發生錯誤時的處理。

那我們如果要做數據爬取的話,其實就是為了截獲 Response 的結果,那其實實現 onResponse 方法就好了。

再仔細看看,這個 onResponse 方法接收兩個參數,為 response 對象和 handler 對象,這都是 Ajax-hook 為我們封裝好的,其實這里我們只需要用 response 里面的內容就好了,比如把 Response Body 打印出來,其實就是把 Ajax 得到的結果打印出來了。

行,那我們就來試試吧。

案例介紹

下面我們就拿一個我自己的案例來講吧,鏈接為:https://dynamic2.scrape.center/,界面如下:

這個網站是一個電影數據網站,其數據都是通過 Ajax 加載的,但是這些 Ajax 請求都帶着加密參數 token,如圖所示:

其實這個參數你要解的話倒不是很難,不過也得費點時間。

然后再看下 Ajax 的返回結果,如圖所示:

很純很清晰!所以我們如果能夠在得到 Ajax Response 的時候就把這些數據直接拿到,那就美滋滋了。

怎么辦?自然是用剛才所說的 Ajax-hook 了。

所以,我們這里就用上這個 Ajax-hook 來對這些數據進行實時處理吧。

實戰操作

首先,第一步那我們得能用上 Ajax-hook,怎么用呢?那肯定得需要引入一下這個 Ajax-hook 庫,瀏覽器里的這個頁面又怎么引入呢?

答案有很多,比如復寫 JavaScript、Tampermonkey、Selenium 等等。

這里我們就用最簡單的方法,Selenium 自動執行一下 Ajax-hook 的源代碼就好了。

那這時候我們就需要找到 Ajax-hook 的源碼了,去 GitHub 一找就有了,鏈接為:https://raw.githubusercontent.com/wendux/Ajax-hook/master/dist/ajaxhook.min.js,如圖所示:

看,代碼量真不多吧。

我們把這個代碼復制,粘貼到 https://dynamic2.scrape.center/ 這個網站的控制台里。

這時候我們會得到一個 ah 對象,代表 Ajax-hook,我們就能用它里面的 proxy 方法了。

怎么用呢?就直接實現 onResponse 方法,打印 Response 的結果就好了,實現如下:

ah.proxy({
  //請求成功后進入
  onResponse: (response, handler) => {
    if (response.config.url.startsWith('/api/movie')) {
      console.log(response.response)
      handler.next(response)
    }
  }
})

把這段代碼也放在控制台運行下,這時候我們就實現了 Ajax Response 的 Hook 了,只要有 Ajax 請求,Response 的結果就會被輸出出來。

這時候如果我們點擊翻頁,觸發一個新的 Ajax 請求,就可以看到控制台輸出了 Response 的結果,如圖所示:

 

嗯,這下我們就能獲取到 Ajax 的數據了。

數據轉發

那現在數據在瀏覽器里面啊,我們怎么存下來呢?

存還不簡單,最簡單的,把這個數據轉發給自己的一個接口保存下來就好了。

那我們就用 Flask 簡單弄一個接口吧,記得解除跨域限制,實現如下:

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)

這里我就簡單寫了個示例,寫了一個能接收 POST 請求的 API,地址為 /receiver/movie,然后把 POST 的數據打印出來再返回一個響應。

當然這里你可以做很多操作了,比如把數據切割,存儲到數據庫等都是可以的。

好的,那現在服務器有了,我們就在 Ajax-hook 這邊把數據發過來吧。

這里我們借助於 axios 這個庫,其庫地址為 https://unpkg.com/axios@0.19.2/dist/axios.min.js,也是放在瀏覽器執行就能用。

引入 axios 之后,我們把之前的 proxy 方法修改為如下內容:

ah.proxy({
  //請求成功后進入
  onResponse: (response, handler) => {
    if (response.config.url.startsWith('/api/movie')) {
      axios.post('http://localhost/receiver/movie', {
        url: window.location.href,
        data: response.response
      })
      console.log(response.response)
      handler.next(response)
    }
  }
})

其實這里就是調用了 axios 的 post 方法,然后把當前 url 和 Response 的數據發給了 Server。

到現在為止,每次 Ajax 請求的 Response 結果都會被發給這個 Flask Server,Flask Server 對其進行存儲和處理就好了。

自動化

OK,那現在我們已經可以實現 Ajax 攔截和數據轉發了,最后一步自然就是把爬取自動化了。

自動化就分為三部分:

•打開網站。•注入 Ajax-hook、axios、proxy 的代碼。•自動點擊下一頁翻頁。

最關鍵的就是第二步了,我們把剛才 Ajax-hook、axios、proxy 的代碼都放在一個 hook.js 文件里面,用 Selenium 的 execute_script 來執行就好了。

其他的幾步很簡單,最后實現如下:

from selenium import webdriver
import time

browser = webdriver.Chrome()
browser.get('https://dynamic2.scrape.center/')
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)

最后,運行一下。

可以發現瀏覽器先打開了頁面,然后模擬點擊了下一頁,再回過頭來觀察下 Flask Server 這邊,可以看到 Ajax 的數據就接收到了,如圖所示:

OK,到此為止。

總結

至此,我們就完成了:

•Ajax Response Hook•數據轉發與接收•瀏覽器自動化

以后我們再遇到類似的情形,也可以用同樣的思路來處理了。

本節代碼:https://github.com/Python3WebSpider/AjaxHookSpider。

 

作者:華為雲享專家 崔慶才靜覓


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM