SPEC-007 — 抓取响应快照云端共享协议¶
状态:v0.10.121 客户端落地(draft);服务端未实施。 价值:用户共享反爬样本 → 服务端聚合 → 派生新 marker / 正则 → 推回客户端。
一、问题¶
当前 SPEC-004 Phase 1/2/3 已支持: - 抓 contact(写 ContactPool 共享) - 域名状态机(写 DomainStats) - 贡献度账本(ContributionLedger)
缺口:抓取失败时响应体内容丢失。
- fetch-too-small(< 1KB):是 SPA 壳?404 软文?反爬空页?无法事后判断
- antibot-body(CHALLENGE_MARKERS 命中):只记 marker 名,看不到上下文
- contact-keyword-but-empty:页面提 contact 但抓不到 — 正则漏了什么?
没有原始内容 → 无法迭代规则。
二、客户端设计(v0.10.121 已落地)¶
数据模型¶
interface FetchSnapshot {
id: number; // autoIncrement
urlHash: string; // sha256(normalizedUrl) — 跟 ContactPool 同款
normalizedUrl: string;
domain: string;
scrapedAt: number;
status: number; // HTTP status
contentType: string;
bodySize: number; // 原 body 大小
bodyPreview: string; // 前 N 字符(已脱敏 / 或原文)
sanitized: boolean; // 是否脱敏(默 true)
mstageReason: string; // 'fetch-too-small' | 'antibot-body' | ...
matchedMarker?: string; // 命中的反爬 marker
syncedAt: number; // 0 = 未上云
}
触发场景¶
| mstageReason | 何时记 | 值 |
|---|---|---|
fetch-too-small |
body < 1KB | SPA 壳 / 反爬空页 / 错误页 |
antibot-body |
CHALLENGE_MARKERS 命中 | 反爬模式变体 |
contact-keyword-but-empty |
页面提 contact 但抓不到 | 正则盲区 |
fetch-fail |
不记 | 没拿到 body |
success / dead@probe |
不记 | 价值低 |
存储策略¶
- jsstore
FetchSnapshots表(version 5) - 200 条上限,按 domain 唯一性环形淘汰(同 domain 只留最近 1 条)
- 写入时按
domain删旧 + 插新;超 200 时取最老 N 条删
脱敏(默开)¶
sanitizeBodyPreview(raw, maxChars=300):
1. emails → <REDACTED:email>
2. phones(含分隔符 7-15 位)→ <REDACTED:phone>
3. csrf_token / authenticity_token / api_key → <REDACTED:secret>
4. Bearer / Basic auth 头反射 → <REDACTED:secret>
5. 截到 maxChars,不在 < 中间断(防 markdown 显示破坏)
设置项¶
enableFetchSnapshot: boolean; // 默 true — 本地分析零成本
fetchSnapshotSanitize: boolean; // 默 true — 上云前必脱敏
fetchSnapshotMaxChars: number; // 默 300 — 100-2000
UI¶
- 日志 → 「响应快照」tab
- 列表:reason chip + 时间 + URL + status + 大小
- 点击行 → Dialog 看完整 preview
- 导出 Markdown(按 reason 分组)
三、服务端 API 契约(未实施)¶
仿 SPEC-004 Phase 3 风格。
Endpoint 1:上传快照(批量)¶
POST /api/v1/fetch-snapshots/batch
Authorization: Bearer <token>
Content-Type: application/json
Body:
{
"items": [
{
"urlHash": "<sha256>",
"domain": "example.com",
"scrapedAt": 1717000000000,
"status": 200,
"contentType": "text/html",
"bodySize": 512,
"bodyPreview": "<html><head>...REDACTED...</html>",
"sanitized": true,
"mstageReason": "fetch-too-small",
"matchedMarker": null
},
...
]
}
Response 200:
{
"ok": true,
"accepted": 50,
"rejected": [],
"rateLimit": { "remaining": 95, "resetAt": 1717100000000 }
}
Response 400 / 401 / 429 / 500:标准 SPEC-004 风格错误。
服务端责任:
- 拒绝 sanitized: false(强制脱敏才接受)
- 限流:每用户每小时 200 条
- 按 urlHash 去重(同用户同 URL 只留最新)
- 总存储:每用户每 domain 最多 1 条
Endpoint 2:拉派生规则¶
GET /api/v1/fetch-snapshot/rules?since=<timestamp>
Response 200:
{
"rules": {
"newMarkers": [
{ "marker": "captcha-challenge-v3", "minOccurrences": 200, "addedAt": ... },
...
],
"newAntiBotDomains": [
{ "domain": "walmart.com", "confidence": 0.95 },
...
],
"regexUpdates": [
{ "field": "phone", "version": "v3", "regex": "<base64>" }
]
},
"version": "2026-06-01",
"nextPullAt": 1717100000000
}
派生逻辑(服务端 ML/统计):
- 200 个用户都见过 "Cloudflare Just a moment" 字符串 → 自动加入 CHALLENGE_MARKERS
- 100 个用户在 walmart.com 都拿到含相同关键词的页面 → 标 antibot-hard
- 多位用户 contact-keyword-but-empty 含相同 email 模式(如 mailto: 后 base64)→ 加正则变体
Endpoint 3:撤回(GDPR 合规)¶
DELETE /api/v1/fetch-snapshots/all
Authorization: Bearer <token>
Response 200:
{ "ok": true, "deleted": 200 }
用户随时可撤回所有自己上传的快照。
四、客户端上云集成(未实施 — 留 stub)¶
// cloud-sync-orchestrator.ts 加
async function syncFetchSnapshots() {
if (!settings.enableCloudSync) return;
if (!settings.fetchSnapshotSanitize) return; // 不脱敏不传
const pending = await getPendingSnapshots(50);
if (pending.length === 0) return;
const r = await cloudClient.post('/fetch-snapshots/batch', { items: pending });
if (r.ok) await markUploaded(pending.map(s => s.id));
}
调用时机:跟现有 cloud-sync 周期合并(每 5 分钟一次)。
五、隐私 & 合规¶
- 必须脱敏才能上云(客户端 + 服务端双 enforce)
- opt-in:用户启用
enableCloudSync+fetchSnapshotSanitize才上传 - 撤回机制:DELETE endpoint
- 数据最小化:preview 上限 300(也可关闭快照功能整体)
- 匿名化:urlHash 不含 PII;domain 是公开信息
六、推进计划¶
- ✅ v0.10.121 客户端落地(schema + 写入 + 脱敏 + UI + Settings)
- 🔜 v0.10.125 服务端 API 实施(Endpoint 1)
- 🔜 v0.10.130 服务端聚合 + 派生规则
- 🔜 v0.10.140 客户端拉派生规则(Endpoint 2)
七、教训¶
- 数据缺失反过来推不出规则 — 没快照就只能猜
- 上云协议先写、再实施 — 客户端先生成数据躺好,服务端跟着上
- 脱敏是隐私底线,不是优化项