GraphQL 是一種查詢語言,用於從網站後端獲取深度嵌套的結構化數據,類似於 MongoDB 查詢。
請求通常是對某個通用 /graphql 端點的 POST,其正文如下:
但是,對於大型資料結構,這會變得低效- 您在POST 請求正文中發送大型查詢,這(幾乎總是)相同,並且僅在網站更新時發生變化;POST 請求無法被快取等。因此,開發了一個名為「持久性查詢」的擴充。這不是一個反抓取的秘密;而是一個秘密。您可以在此處閱讀有關它的公開文件。
TLDR:客戶端計算查詢文字的 sha256 雜湊值並僅發送該雜湊值。此外,您可以將所有這些內容放入 GET 請求的查詢字串中,使其易於快取。以下是來自 Zillow 的請求範例
如您所見,它只是有關 persistedQuery 擴充功能的一些元資料、查詢的雜湊值以及要嵌入查詢中的變數。
這是來自 expedia.com 的另一個請求,以 POST 形式發送,但擴展名相同:
這主要優化了網站效能,但它為網頁抓取帶來了一些挑戰:
因此,由於不同的原因,您可能會發現自己需要提取整個查詢文字。您可以深入研究網站 JavaScript,如果幸運的話,您可能會在那裡找到完整的查詢文本,但通常,它是以某種方式從多個片段等動態構建的。
因此,我們想出了一個更好的方法:我們根本不碰客戶端 JavaScript。相反,我們將嘗試模擬客戶端嘗試使用伺服器不知道的雜湊的情況。因此,我們需要攔截瀏覽器發送的(有效)請求,並將雜湊值修改為偽造的,然後再傳遞給伺服器。
對於這個用例,存在一個完美的工具:mitmproxy,一個開源 Python 庫,它可以攔截您自己的設備、網站或應用程式發出的請求,並允許您使用簡單的 Python 腳本對其進行修改。
下載 mitmproxy,並準備一個 Python 腳本,如下所示:
import json def request(flow): try: dat = json.loads(flow.request.text) dat[0]["extensions"]["persistedQuery"]["sha256Hash"] = "0d9e" # any bogus hex string here flow.request.text = json.dumps(dat) except: pass
這定義了 mitmproxy 將在每個請求上運行的鉤子:它嘗試載入請求的 JSON 主體,將雜湊修改為任意值,並將更新的 JSON 寫入作為請求的新主體。
我們還需要確保將瀏覽器請求重新路由到 mitmproxy。為此,我們將使用名為 FoxyProxy 的瀏覽器擴充功能。它在 Firefox 和 Chrome 中均可使用。
只需新增具有以下設定的路線:
現在我們可以使用以下腳本來執行 mitmproxy:mitmweb -s script.py
這將開啟一個瀏覽器選項卡,您可以在其中即時觀看所有攔截的請求。
如果您轉到特定路徑並查看請求部分中的查詢,您將看到一些垃圾值已替換了雜湊值。
現在,如果您訪問 Zillow 並打開我們嘗試用於擴展的特定路徑,然後轉到回應部分,客戶端會收到 PersistedQueryNotFound 錯誤。
Zillow 前端的反應是將整個查詢作為 POST 請求發送。
我們直接從此 POST 請求中提取查詢和哈希。為了確保 Zillow 伺服器不會忘記此哈希,我們定期使用完全相同的查詢和哈希運行此 POST 請求。這將確保即使伺服器的快取被清理或重置或網站發生更改,抓取工具也能繼續運作。
結論
持久性查詢是 GraphQL API 的強大最佳化工具,透過最小化負載大小和啟用 GET 請求快取來增強網站效能。然而,它們也為網路抓取帶來了重大挑戰,主要是由於對伺服器儲存的雜湊值的依賴以及這些雜湊值可能無效的可能性。
使用 mitmproxy 攔截和操作 GraphQL 請求提供了一種有效的方法來顯示完整的查詢文本,而無需深入研究複雜的客戶端 JavaScript。透過強制伺服器回應 PersistedQueryNotFound 錯誤,我們可以擷取完整的查詢負載並將其用於抓取目的。定期執行提取的查詢可確保抓取器保持功能,即使發生伺服器端快取重置或網站發展也是如此。
以上是逆向工程 GraphQL persistedQuery 擴展的詳細內容。更多資訊請關注PHP中文網其他相關文章!