爬取帳戶資料
概述
用戶在 Playwright 瀏覽器中親手登入;你找出網站自身頁面所使用的資料來源,並在頁面內重放它(使用 session cookie 與真實標頭),將原始回應儲存到磁碟。從不處理憑證。
使用時機
- 「取得我的 <服務> 記錄」、「爬取我的訂單/行程/預訂」、「使用這個爬蟲套件」
- 舊的爬蟲套件(requests + 密碼/OTP 登入):僅在 GitHub 上閱讀它們以取得端點名稱和選擇器。絕對不要執行它們——機器人檢測(HTTP 202 空白主體、驗證碼)會使其失效,而且它們會要求密碼。
先嘗試官方匯出。 Google Takeout、「下載你的資料」、GDPR/DPDP 請求、公開 API、電子郵件收據。緩慢的匯出需要數天——請在開始時要求用戶發起一個匯出請求,然後立刻開始爬取,並在匯出資料到達時根據 ID 進行交叉比對。
工作流程
- 在寫入任何內容之前先設定 .gitignore 輸出目錄(
data/<service>/或專案的對應目錄);並用git check-ignore確認。原始資料包含地址、電話號碼、GPS 資訊。 - 導航,交出登入控制權。 開啟帳戶頁面;告知用戶在瀏覽器視窗中輸入電話/密碼/OTP,並在完成後回覆。切勿在聊天中要求代碼。
- 發現資料來源(
browser-snippets.md§1)。使用監聽器重新載入頁面並分類:- JSON XHR/fetch(最佳)——記錄列表端點、分頁形式(
next_url/遊標、page_number、偏移 = 最後一個 ID、endOfList)以及詳情端點(開啟一個項目)。 - GraphQL——一個
POST /graphql;記錄完整的請求主體(operationName、query 或 persisted-query hash、variables)。 - 伺服器端渲染 HTML——資料在
document回應中到達;在頁面內擷取列表和詳情頁面,並使用DOMParser解析;同時儲存原始 HTML 以便重新解析。 - 分割列表——如果列表被一個篩選條件(年份下拉選單、日期範圍、分頁如已歸檔/已取消/數位)分割,請讀取每個篩選值並分別處理每個分頁。
- JSON XHR/fetch(最佳)——記錄列表端點、分頁形式(
- 身份驗證。 先嘗試
fetch(url, {credentials: 'include'})。如果返回 401/403/空白,請從即時請求中複製非瀏覽器標頭(§2)——複製,切勿猜測值。僅記錄標頭名稱;絕不列印或儲存數值。 - 在頁面內背景分頁處理(§2):
window.__x = {running, pages, error},請求間隔 1.5 秒,包含重試機制,使用簡短的 evaluate 輪詢。每個回應:如果是登入/驗證碼頁面(URL 包含 signin/captcha,或預期 JSON 卻收到 HTML),則設定error並停止——這些通常以 200 狀態碼返回。 - 在導航前先儲存(§3)。任何
goto/重新載入都會清除window。使用帶有絕對filename路徑的browser_evaluate,返回一個物件(JSON.stringify會雙重編碼)。每個檔案約 5 MB 是可行的;超過此大小則按批次分割。 - 已簽名/不可重放的請求(請求簽章標頭、nonce 主體、拒絕編輯的持久化查詢):訪問每個詳情頁面並被動捕捉其回應(§4)。
- 建立 + 驗證(見下方)使用資料目錄外部的解析器腳本,然後建立一個洞察筆記。如果專案已有佈局(例如
tools/<service>/build_db.py、資料旁的筆記),請遵循;否則請提問。
僅讀取。 僅重放頁面本身為顯示資料而請求的內容。GraphQL:根據 operation(query/Get*/List*)判斷,絕不使用 mutation。絕不重新排序、取消、評分、歸檔或刪除。
驗證——請執行所有檢查
| 檢查項目 | 原因 |
|---|---|
| 分頁因遊標/列表耗盡而結束,而非因為錯誤或登入重新導向 | 防止靜默截斷 |
| 尋找列表尾端的「在應用程式中檢視較舊訂單」文字 | 網頁版會限制歷史記錄(Swiggy:約 11 個月) |
| 不要只相信網站的計數端點或標頭 | Blinkit 顯示 40 筆;但列表實際有 83 筆 |
| 各元件總和等於每列的總計(項目 + 費用 − 折扣 = 已付金額) | 抓取解析偏移;如果網站對舊記錄省略了明細行,請說明——不要強制 |
| 項目價格總和等於項目小計 | 避免單價與合計混淆 |
| 唯一的 ID;對完全重複的資料進行去重 | 匯出和收據會重複列出 |
| 按 ID 與另一個來源(匯出、收據)交叉比對 | 證明覆蓋範圍 |
| 每個列表/分頁/篩選條件都已涵蓋(已歸檔、已取消、其他年份、子服務) | 「所有訂單」通常涵蓋多個列表 |
解析筆記
- 伺服器驅動的 UI API(
snippets、widgets、widget_type、layout):依序遍歷 widget,收集每個 widget 的text欄位,並根據已知標籤進行映射。一個只有一個 text 欄位的 widget 通常是下一個 widget 的標籤——將標籤名稱列入白名單,否則每個欄位都會偏移。 - 金額可能是分/角單位;「FREE」= 0;開頭的「-」= 折扣;刪除貨幣符號和印度千位分組逗號。將貨幣單位分開存放。
- 時間戳記:購買記錄 → 用戶時區;行程/旅遊 → 記錄當地時間。
- 將原始檔案標記日期(
orders-raw-YYYY-MM-DD.json)存放在建立的 SQLite 旁。
Git
僅提交:.gitignore 行、解析器腳本、任何 README/index 行。原始檔案和 SQLite 保留在本機。明確指定路徑進行暫存(絕不在髒的工作目錄中使用 git add -A);僅在被要求時才提交。
常見錯誤
- 一個長時間的同步 evaluate 循環——會超時;請在背景執行並輪詢。
- 將大量資料返回聊天而非使用
filename。 - snippet 監聽器未卸載——使用
page.off將它們移除,否則重複執行會疊加處理器。 - 在 HTML 渲染的網站上只過濾 xhr/fetch,並得出「沒有資料來源」的結論。