amazon-h10-api/docs/scraper-service-playbook.md
chii a1c8770e1e docs: 移交 playbook 与 H10 需求草稿
将 sif 提炼的抓取微服务范式作为开发建议入库,并标明本仓移交其他团队落实开发;不含凭据与实现代码。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 11:52:55 +08:00

16 KiB
Raw Blame History

数据抓取微服务构建通用范式(Playbook)

文档性质:开发建议(非强制规范、非已落地实现)

  • 本文供接手 amazon-h10-api 的开发团队参考,说明「同类抓取微服务」推荐怎么做。
  • 本仓当前未实现业务代码;落地开发、契约逆向、部署与运维由接手团队负责。
  • 内容提炼自 amazon-sif-api 的真实构建过程,属可复用方法论,可按 H10 目标站实际情况裁剪,不必逐字照搬。
  • 对标蓝本:amazon-sif-api / amazon-keepa-api(轻量、默认无 PG、api + auth + worker + mq)。

适用:目标是某个有登录态的 SaaS/数据平台,要把它已聚合的数据,以自有 REST API 稳定、低成本地对外提供。


0. 一句话范式

用浏览器把"人能看到的数据"逆向成"机器能调的接口契约",再用一套无状态取数 + 集中式会话 + 队列派发的微服务把它工程化、可运维地交付。

核心信条:

  1. 浏览器只用来"探路"和"拿钥匙",不用来"搬货"。 登录/拿 token 用真浏览器;批量取数用 HTTP 直连(快、省、稳)。
  2. 先抓契约,再写代码。 没摸清请求/响应结构前不写业务逻辑。
  3. 会话集中、取数无状态、派发异步。 三者解耦,才能横向扩展又不互相拖垮。
  4. 每个域(domain)闭环交付:探索→实现→冒烟→真实样本→文档。 不堆半成品。

1. 阶段总览

阶段 目标 关键产出
P1 需求澄清 定边界、定形态、定蓝本 需求共识、对标项目、首个交付域
P2 浏览器逆向 把页面行为还原成 HTTP 接口契约 *-contract.md(登录+各数据接口)
P3 架构设计 定服务拆分、会话/鉴权/扩展策略 design.md、里程碑、风险清单
P4 编码实现 按域实现取数+归一化+对外路由 三服务代码
P5 部署 按目标环境规范上线 部署脚本、stack/compose
P6 冒烟+取样 真实数据验证每个端点 samples/*.json
P7 文档交付 给接入方可直接用的文档 api.md(脱敏)

后续每新增一个数据域,只重复 P2(局部) → P4 → P6 → P7,架构不动。


2. P1 · 需求澄清

目标:把"做个 XX 数据 API"收敛成可执行的最小闭环。

必须问清的 4 件事(缺一不可):

  • 数据来源形态:登录后聚合页?官方 API?纯爬 HTML?→ 决定逆向难度与合规风险。
  • 对外形态:REST / MCP / GraphQL?响应结构对齐谁?→ 本项目选「REST 为主,结构对齐目标平台的 MCP 工具语义」,让下游迁移成本最低。
  • 首个交付域:别想一次做全。挑一个高价值 + 可独立验证的域先打通(本项目=反查关键词)。
  • 架构蓝本与体量:找一个同类已上线项目对标(本项目=amazon-keepa-api),明确"要多轻"。避免过度设计。

决策原则:能用「合理默认 + 事后说明」就别反复追问;只有范围/破坏性/不可逆的点才必须确认。


3. P2 · 浏览器逆向(范式的灵魂)

目标:把"人在页面上点一下看到的数据",还原成一条可复现的 HTTP 调用(URL + headers + body + 响应结构)。

3.1 方法

  1. 真人操作 + 拦截网络:用浏览器自动化打开目标页,注入网络拦截器(或开 DevTools/CDP),触发一次目标操作,捕获 XHR/fetch。
  2. 抓三类东西:
    • 鉴权机制:token 在哪(localStorage/cookie/header)?格式(JWT?)?有无设备指纹/签名参数?过期时间?
    • 公共参数:每个请求都带的 query/header(时间戳、站点、_m 指纹…)。
    • 业务接口:路径、方法、请求体字段、响应外层与 data 结构。
  3. 分域归档:把接口按数据域分类记进 *-contract.md,每个接口标注「对应哪个对外功能」「是否计费」「关键返回字段」。
  4. 标注噪音:UI 辅助接口(弹窗、埋点、文案)明确标记"无需复刻",避免后人误抓。

3.2 本项目实证

  • 发现鉴权 = localStorage.token(JWT, exp≈7天) 放进 authorization 头(无 Bearer 前缀)。
  • 发现每个请求带设备指纹 _m(来自 localStorage._murmur)——跨进程复用 token 必须连指纹一起带,否则风控。
  • 找到一个不计费的探针接口(/api/user/sys/info)用于校验会话有效性。
  • 把"反查/流量/概览/广告"四域接口结构全部记入契约文档。

3.3 坑

  • 入口不明 / 计费不清的功能先别碰:本项目"市场需求/竞争"域因入口是耗积分的增值功能且契约未实测,主动顺延而非硬抓——避免在不确定处耗时跑偏。
  • 逆向到的接口要立刻人工核对一次:字段名、计费字段(consumeIntegral)、分页规则。

3.4 产出

docs/*-contract.md:逆向契约(只记结构,不记真实凭据)。


4. P3 · 架构设计(四服务范式)

目标:一套能横向扩展、会话自愈、低成本的拆分。

        ┌───────────┐   REST(+API Key)   ┌──────────────┐
 调用方 →│   api      │───────────────────→│  account 池   │
        │ 网关/缓存   │                     └──────────────┘
        └─────┬─────┘
              │ 任务(MQ RPC)
        ┌─────▼─────┐   按需要会话   ┌───────────┐  无头登录  ┌────────┐
        │  worker    │──────────────→│   auth     │──────────→│ 目标站  │
        │ 取数+归一化 │   (HTTP直连取数)└───────────┘  探针/重登 └────────┘
        └───────────┘
服务 职责 为什么独立
api(网关) REST 路由、API Key 鉴权、账号池轮询、任务派发、TTL 缓存、运维 UI 对外唯一入口,无业务取数逻辑,便于加缓存/限流/鉴权
auth(会话) 无头浏览器登录、token/指纹/cookie 落盘、有效性探针、巡检、单飞重登 登录是最重、最易触发风控的环节,集中管理 + 串行化
worker(取数) 消费任务 → HTTP 直连取数 → 归一化;遇鉴权失败标记失效并重试一次 无状态,可任意多副本横向扩展吞吐
mq(队列) api↔worker 的异步 RPC 解耦派发与执行,削峰、天然负载均衡

设计要点(通用)

  • 会话三道校验:①取数前本地预检(零成本)②后台巡检(探针接口,不计费)③取数遇鉴权失败被动兜底 → 重登重试。
  • 单飞重登:同账号重登加锁,并发请求只触发一次登录,其余复用新 token——防惊群触发风控。
  • 设备指纹随 token 走:复用 token 时必须带登录时的指纹。
  • 轻量优先:文件会话 + 进程内 TTL 缓存,默认无数据库(对标蓝本体量,见 §4.1)。
  • 凭据外置:账号/密码走 config.json(不入库),对外 key 走环境变量/secret。
  • 产出:design.md(含里程碑 + 风险清单:token 并发、指纹绑定、计费、风控、schema 漂移、合规、PG 连接预算)。

4.1 存储选型与 PostgreSQL 连接数(prod 实证)

结论先行:与 sif / keepa / listing-scraper 同类的「轻量数据获取微服务」默认不需要 PostgreSQL,也不需要 pgbouncer。只有业务必须落库、或要做跨请求持久化查询时,才引入 PG,且必须做连接预算。

prod 同类服务调查(swarm-mgr3,排除 lsvdaas)

服务 用 PG? 副本 pgbouncer? 连接风险
sif 否 api 1 + worker 3 + auth 1 — 无
keepa 否 api 1 + worker 3 + auth 1 — 无
listing-scraper 否 api 1 + worker 3 — 无
jiimore 否 api 1 + worker 3 + auth 1 — 无
voc-analysis 是 api 1 + worker 1 否(直连) 低(副本少,采样时连接≈0)
onesvm-asinwarehouse 是 backend 1 + proxy 1 + worker 10 有 stack 但未接线 中高(理论峰值≈72,当前≈20/200)

共享 PG(postgres_db)max_connections=200,采样时总连接约 55,未见 too many connections。

何时不用 PG(推荐默认)

满足以下多数条件时,坚持「无 DB」范式(本项目的 sif / keepa 已验证):

  • 数据可实时从源站聚合取回,无需本地持久化查询。
  • 会话/配置用文件即可(config.json + sessions/*.json)。
  • 去重用进程内 TTL 缓存或 MQ 天然串行即可。
  • 不需要复杂报表、历史回溯、跨 ASIN 关联查询。

收益:零 PG 连接占用、部署简单、worker 随意扩副本无连接压力。

何时需要 PG + 怎么做才不爆连接

引入 PG 前必须算清连接预算:

集群峰值连接 ≈ Σ (每服务副本数 × 每副本最大连接数)
每副本最大连接数 = pool_size + max_overflow   # SQLAlchemy
                  或 max_size                    # asyncpg

ASW 反例(prod 实测):

组件 副本 每副本池上限 集群峰值
backend 1 30 (10+20) 30
proxy_manager 1 8 8
listing worker 4 4 16
reviews worker 6 3 18
合计 12 ≈72

平台虽有 asw-pgbouncer(:6432),但 ASW 服务全部直连 postgres_db:5432,pgbouncer 未接线——这是典型「建了没用」的教训。

引入 PG 的硬性规范

  1. 多副本 worker 必须经 pgbouncer,禁止每个 worker 进程各自开大池直连 PG。
  2. 连接池要小:worker 侧 pool_size 建议 2–5;max_overflow 0–3;宁可排队不要占连接。
  3. 部署前做预算表:写入 design.md,标明「副本数 × 池大小 = 峰值」,确认 < PG max_connections × 30%(留余量给其他服务)。
  4. 优先异步单连接:轻量写入/状态表可用「单连接 + 队列」而非每请求开连接。
  5. 监控:接入后观察 pg_stat_activity 按库/按应用计数;日志搜 too many connections。

决策树(简版)

需要跨请求持久化查询 / 历史落库?
├─ 否 → 文件会话 + MQ + 进程缓存(sif/keepa 范式)✅
└─ 是 → 算连接预算
         ├─ 峰值 < 20 且单副本 → 可直连,小池(pool≤5)
         └─ 多副本 worker / 峰值 > 20 → 必须 pgbouncer + 小池

5. P4 · 编码实现(按域可扩展)

目标:让"加一个数据域"成为填空题,而不是改架构。

落地模式(本项目验证有效):

  • worker 侧:一个通用 _request()(统一拼公共参数、塞鉴权头、判 code、识别鉴权错误抛 AuthError)+ 每个域一个 fetch_xxx() + 一个 normalize_xxx();main.py 用 OPS = {op: (fetcher, normalizer)} 表分发。
  • api 侧:一个共享 _dispatch(op, params, cache_key, ...)(查缓存→选账号→MQ RPC→包 meta)+ 每个域一个瘦路由。
  • 归一化原则:拿不准全部字段时,先"透传 data + 轻量字段映射",保证冒烟能跑、文档有内容,再按真实返回逐步精修字段(本项目 traffic-trend 就是先透传、看到真实键名后再对齐 totalScore/nfScore…)。
  • 错误语义化:鉴权类错误 → 触发重登重试;其余 → 明确 502/504/503。

扩展一个域的改动量 = 2 个函数(fetch+normalize)+ 1 条 OPS + 1 个路由。架构零改动。


6. P5 · 部署(按目标环境规范)

目标:一条命令可复现地上线到目标环境。

  • 遵守目标环境规范:本项目按 onesvm-ops——compose/stack、amd64、指定 registry、placement.constraints 固定节点、资源限制、docker secret 装敏感信息、--resolve-image never。
  • 部署脚本要点(踩坑沉淀):
    • 用用户可写目录同步源码(别假设 /opt 有权限)。
    • scp 前先 mkdir -p 远端目录。
    • 端口冲突先探测、可配置(本项目 8080→8088)。
    • docker service update 会换容器,之后要重取容器 ID再 exec。
    • 同名 tag 复用时,worker 需 --force 才会真正滚动到新镜像(否则跑旧代码——本项目实测踩到)。
  • 产出:scripts/deploy-xx.sh + deploy/stack.yml / docker-compose.yml。

7. P6 · 冒烟 + 取真实样本

目标:每个对外端点用真实请求验证,并把返回存成示例数据。

  • 逐端点单独冒烟,打印关键字段 + meta(延迟、计费、是否命中缓存)。
  • 省成本:注意计费额度,能一次验证就别重复;相同参数靠缓存。
  • 把真实返回落盘 docs/samples/*.json——它既是回归基线,又是文档示例的事实来源(文档示例必须来自真实数据,不杜撰)。
  • 冒烟暴露的字段不匹配 → 回 P4 精修归一化 → 重新取样。

8. P7 · 文档交付(给接入方)

目标:同事拿到就能调。

api.md 必含:

  • 接入信息(base URL、鉴权方式、健康检查、在线 /docs)。
  • 通用约定(公共参数、meta 结构、缓存、错误码)。
  • 每个端点:参数表 + 真实 curl + 真实裁剪响应 + 字段要点。
  • 开放边界:明确哪些开放(如"会员权益内、不计费、可无限查")、哪些关闭及原因(如"耗积分功能一律关闭")——让接入方不踩计费雷。

安全红线(务必)

  • 真实 key / 账号 / 手机号绝不进仓库。文档用占位符($API_KEY / "向维护方索取")。
  • 若敏感信息已误提交:移除 + 改写历史(reset --soft 重做提交) + force-with-lease 覆盖远程 + 本地 reflog expire && gc,并 git log -S '<secret>' 验证为空。
  • 误暴露过的凭据,最稳妥是轮换而非仅删除。

9. 复用检查清单(开新项目照着走)

[ ] P1 需求:来源形态 / 对外形态 / 首个域 / 对标蓝本+体量 都已确认
[ ] P2 逆向:鉴权机制 / 公共参数 / 指纹 / 不计费探针 / 各域契约 已记入 *-contract.md
[ ] P2 逆向:噪音接口已标注;入口不明或计费不清的域已显式顺延
[ ] P3 架构:api/auth/worker/mq 四服务;会话三道校验 + 单飞重登;凭据外置;默认无DB(见 §4.1)
[ ] P3 架构:若引入 PG → 连接预算表 + pgbouncer(多副本必走)+ 小池(pool≤5)
[ ] P3 文档:design.md(里程碑 + 风险清单)
[ ] P4 代码:通用 _request + 按域 fetch/normalize + OPS 分发 + 瘦路由;归一化先透传后精修
[ ] P5 部署:遵守目标环境规范;脚本处理权限/端口/容器ID/同tag强制刷新
[ ] P6 冒烟:逐端点真实验证 + meta;样本落盘 docs/samples
[ ] P7 文档:api.md(真实示例 + 开放边界);凭据脱敏;误提交则改写历史并轮换

10. 一页纸心法

  • 浏览器探路,HTTP 搬货。
  • 先契约,后代码。
  • 会话集中、取数无状态、派发异步。
  • 按域闭环,架构不动。
  • 样本即文档,文档即事实。
  • 凭据永不入库,泄露即轮换。
  • 轻量抓取默认无 PG;要落库先算连接数,多副本必走 pgbouncer。