跳转至

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)

七、教训

  • 数据缺失反过来推不出规则 — 没快照就只能猜
  • 上云协议先写、再实施 — 客户端先生成数据躺好,服务端跟着上
  • 脱敏是隐私底线,不是优化项