将 sif 提炼的抓取微服务范式作为开发建议入库,并标明本仓移交其他团队落实开发;不含凭据与实现代码。 Co-authored-by: Cursor <cursoragent@cursor.com>
16 KiB
数据抓取微服务构建通用范式(Playbook)
文档性质:开发建议(非强制规范、非已落地实现)
- 本文供接手
amazon-h10-api的开发团队参考,说明「同类抓取微服务」推荐怎么做。- 本仓当前未实现业务代码;落地开发、契约逆向、部署与运维由接手团队负责。
- 内容提炼自
amazon-sif-api的真实构建过程,属可复用方法论,可按 H10 目标站实际情况裁剪,不必逐字照搬。- 对标蓝本:
amazon-sif-api/amazon-keepa-api(轻量、默认无 PG、api + auth + worker + mq)。
适用:目标是某个有登录态的 SaaS/数据平台,要把它已聚合的数据,以自有 REST API 稳定、低成本地对外提供。
0. 一句话范式
用浏览器把"人能看到的数据"逆向成"机器能调的接口契约",再用一套无状态取数 + 集中式会话 + 队列派发的微服务把它工程化、可运维地交付。
核心信条:
- 浏览器只用来"探路"和"拿钥匙",不用来"搬货"。 登录/拿 token 用真浏览器;批量取数用 HTTP 直连(快、省、稳)。
- 先抓契约,再写代码。 没摸清请求/响应结构前不写业务逻辑。
- 会话集中、取数无状态、派发异步。 三者解耦,才能横向扩展又不互相拖垮。
- 每个域(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 方法
- 真人操作 + 拦截网络:用浏览器自动化打开目标页,注入网络拦截器(或开 DevTools/CDP),触发一次目标操作,捕获
XHR/fetch。 - 抓三类东西:
- 鉴权机制:token 在哪(localStorage/cookie/header)?格式(JWT?)?有无设备指纹/签名参数?过期时间?
- 公共参数:每个请求都带的 query/header(时间戳、站点、
_m指纹…)。 - 业务接口:路径、方法、请求体字段、响应外层与
data结构。
- 分域归档:把接口按数据域分类记进
*-contract.md,每个接口标注「对应哪个对外功能」「是否计费」「关键返回字段」。 - 标注噪音: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 的硬性规范
- 多副本 worker 必须经 pgbouncer,禁止每个 worker 进程各自开大池直连 PG。
- 连接池要小:worker 侧
pool_size建议 2–5;max_overflow0–3;宁可排队不要占连接。 - 部署前做预算表:写入
design.md,标明「副本数 × 池大小 = 峰值」,确认< PG max_connections × 30%(留余量给其他服务)。 - 优先异步单连接:轻量写入/状态表可用「单连接 + 队列」而非每请求开连接。
- 监控:接入后观察
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。