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

267 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 数据抓取微服务构建通用范式(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。**