抓取账户数据
概述
用户用自己的双手在 Playwright 浏览器中登录;您找到网站自身页面使用的数据源,并在页面内部重放它(会话 Cookie + 真实请求头),将原始响应保存到磁盘。绝不处理凭据。
何时使用
- “获取我的<服务>历史记录”、“抓取我的订单/行程/预订”、“使用这个爬虫仓库”
- 旧的爬虫仓库(requests + 密码/OTP 登录):仅在 GitHub 上读取以获取端点名称和选择器。切勿运行它们——机器人检测(HTTP 202 空响应体、验证码)会破坏它们,且它们需要密码。
首先尝试官方导出。 Google 数据导出、“下载您的数据”、GDPR/DPDP 请求、公开 API、电子邮件收据。慢速导出需要几天时间——请让用户先请求一个,然后立即抓取,等导出到达后按 ID 交叉检查。
工作流程
- 在写入任何内容之前先配置 Git 忽略输出目录(
data/<服务>/或项目中的等效目录);用git check-ignore确认。原始数据包含地址、电话号码、GPS 信息。 - 导航,移交登录。 打开账户页面;让用户在浏览器窗口中输入手机号/密码/OTP,完成后回复。切勿在聊天中要求提供验证码。
- 发现数据源(
browser-snippets.md§1)。使用监听器重新加载页面并进行分类:- JSON XHR/fetch(最佳)——记下列表端点、分页格式(
next_url/游标、page_number、offset = 最后一条 ID、endOfList)以及详情端点(打开一个条目)。 - GraphQL——一个
POST /graphql;记录完整的请求体(operationName、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,或返回的是 HTML 而不是预期的 JSON),设置error并停止——这些通常返回 200 状态码。 - 在导航前保存(§3)。任何
goto/重新加载都会清除window。使用带绝对filename路径的browser_evaluate,返回一个对象(JSON.stringify会双重编码)。每个文件约 5 MB 即可;超过此大小则按批次拆分。 - 已签名/不可重放的请求(请求签名头、nonce 请求体、拒绝编辑的持久化查询):访问每个详情页面并被动捕获其响应(§4)。
- 构建 + 验证(见下文)——在数据目录外编写解析脚本,然后编写分析笔记。如果已有仓库布局(例如
tools/<服务>/build_db.py,notes 位于数据旁边),则遵循它;否则询问。
只读。 仅重放页面自身请求用于显示数据的请求。GraphQL:通过 operation(query/Get*/List*)判断,绝不使用 mutation。绝不重新排序、取消、评分、归档、删除。
验证——请全部执行
| 检查项 | 原因 |
|---|---|
| 分页因游标/列表耗尽而结束,而非错误或登录重定向 | 静默截断 |
| 在末尾查找“在应用中查看更早订单”文本 | Web 视图限制历史记录(Swiggy:约 11 个月) |
| 不要仅相信站点计数端点或请求头 | Blinkit 显示 40;实际列表有 83 条 |
| 各行组件之和等于总计(商品 + 费用 − 折扣 = 已付) | 捕获解析漂移;如果站点对旧记录省略了某些行,请说明——不要强行补足 |
| 商品价格之和等于商品总计 | 单价 vs 行总计混淆 |
| 唯一 ID;对完全重复条目去重 | 导出和收据会重复列出 |
| 通过 ID 与另一个来源交叉检查(导出、收据) | 证明覆盖率 |
| 每个列表/标签/筛选条件都已覆盖(已归档、已取消、其他年份、子服务) | “全部订单”通常跨越多个列表 |
解析说明
- 服务端驱动 UI 的 API(
snippets、widgets、widget_type、layout):按顺序遍历 widgets,收集每个 widget 的text字段,根据已知标签进行映射。单个文本的 widget 通常是下一个 widget 的标签——将标签名称列入白名单,否则每个字段都会偏移。 - 金额可能以分/分为单位;“免费”= 0;前导“-”= 折扣;去除货币符号和印度逗号分组。保持货币分开。
- 时间戳:购买→用户时区;行程/旅行→记录本身的本地时间。
- 将原始文件按日期命名(
orders-raw-YYYY-MM-DD.json),放在构建的 SQLite 旁边。
Git
仅提交:gitignore 行、解析脚本、任何 README/索引行。原始文件和 SQLite 保留在本地。显式暂存路径(绝不在脏状态下执行 git add -A);仅在要求时提交。
常见错误
- 一个长时间同步的 evaluate 循环——超时;改为后台运行并轮询。
- 将批量数据返回聊天而非写入
filename。 - snippet 监听器保持挂载——使用
page.off移除,否则重复运行会堆叠处理程序。 - 在仅 HTML 渲染的站点上仅过滤 xhr/fetch 发现方式,并得出“没有数据源”的结论。