跳转至

SPEC-004 — 网站采集多阶段优化 + 云端协同

背景

当前网站抓取(contact info / emails / phones)每条 URL 都开 chrome.tabs 实抓: - ~ 5-10 秒/站 - 大批量任务(如 22w 网站)需 ~ 30 天 - 浪费场景:DNS 死、404 死、anti-bot 拒、真无联系信息的站

用户提出多阶段筛选 + 云端协同 + 贡献度三层方案。

设计目标

  1. 多阶段筛选:分级判断(黑名单 → HEAD → GET → 解析 → tab 兜底)— 跳过明确无价值的站
  2. 域名记忆:每域名独立状态(dead / antibot / friendly / unknown),持久化 + 自动衰减
  3. 云端协同:先查云端 → 命中直接用 → 不命中本地抓 + 回写
  4. 贡献度激励:抓新数据 +X 分,查云端消耗 -Y 分(防搭便车)
  5. 可观测性:每条 URL 完整记录路径与结果,便于分析

整体架构(4 层)

┌─────────────────────────────────────────────────────────────┐
│                      用户输入:URL                            │
└──────────────────────────────┬──────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│  [Layer 1] 本地缓存查询  (0ms)                                 │
│   - domainStats: dead / antibot / friendly                  │
│   - urlContactCache: 本地已抓过的 url + contact 数据           │
└──┬──────────────────────────────────────────────────────────┘
   │ miss
┌─────────────────────────────────────────────────────────────┐
│  [Layer 2] 云端查询  (200-500ms)                              │
│   - 按 url hash 查云端 contact 表                              │
│   - 命中 + 未过期 → 拉数据 + 消耗贡献度 (-1)                    │
│   - 顺便拿到 domainState 加速本地判断                          │
└──┬──────────────────────────────────────────────────────────┘
   │ miss / expired
┌─────────────────────────────────────────────────────────────┐
│  [Layer 3] 本地多阶段抓取                                       │
│   ① HEAD probe (1s): dead/404/anti-bot 早杀                   │
│   ② GET fetch (5s): 静态 HTML 抓                              │
│   ③ Body 双层 anti-bot 判断                                    │
│   ④ Regex 解析 emails/phones/socials                          │
│   ⑤ 失败 → tab fallback (旧路径)                              │
└──┬──────────────────────────────────────────────────────────┘
   │ 抓到 contact
┌─────────────────────────────────────────────────────────────┐
│  [Layer 4] 回写云端 + 贡献度奖励                                │
│   - 上传 contact info(去重)+ 获得贡献度 (+5/+3/+1)            │
│   - 更新 domainState(供其他客户端用)                          │
└─────────────────────────────────────────────────────────────┘

Phase 1 — 客户端多阶段抓取(独立可上线)

Phase 1.1 决策树(已与用户对齐)

URL
[① 黑名单] (0ms)
 ├─ deadDomains → SKIP
 └─ antiBotDomains → tab fallback
[② HEAD probe] (1s timeout)
 ├─ DNS/TCP/SSL fail → mark dead, SKIP
 ├─ 404/410 → mark dead, SKIP
 ├─ 5xx → 24h retry queue
 ├─ cf-mitigated: challenge/block → mark antiBot, tab fallback
 ├─ 403/503 + cf-ray → mark antiBot, tab fallback
 ├─ 429 → retry queue
 └─ 200/204 + 其它 → 进入 ③
[③ GET fetch] (5s timeout, custom UA + Referer)
 ├─ network timeout/error → tab fallback
 ├─ Content-Type 非 HTML → SKIP
 ├─ Body 长度 < 1KB → tab fallback
 ├─ isChallengeBody(body) → mark antiBot, tab fallback
 └─ 正常 HTML → 进入 ④
[④ Regex 解析]
 ├─ ≥ 1 email/phone → SUCCESS
 ├─ HTML 含 contact 关键词但抽不到 → tab fallback(JS 渲染)
 └─ HTML 无 contact 关键词 → mark 'no contact', SKIP
[⑤ tab fallback] (旧路径)

Phase 1.2 关键代码模块

文件 职责 工程量
src/utils/website-probe.ts HEAD probe + 分类(dead/antibot/ok/try) 1.5h
src/utils/website-fetcher.ts GET fetch + body anti-bot 双层判断 2h
src/utils/contact-extractor.ts regex 提取(复用现有 isEmailLikelyBroken) 1.5h
src/utils/website-scrape-pipeline.ts 多阶段主入口 + 路由 2h
集成 scrape-executor.ts 替换旧入口 2h
单元测试 + dogfood - 2h

Phase 1 工程量 ≈ 11h(不含云端协同)

Phase 1.3 anti-bot 双层判断

HEAD response 信号(明确): - cf-mitigated: challenge/block → 直接 antiBot - 403/503 + cf-ray → 直接 antiBot - 429 → retry queue

GET body 关键词(HEAD 通过但 body 是 challenge page):

const CHALLENGE_MARKERS = [
  '/cdn-cgi/challenge-platform', 'cf-browser-verification', 'cf-im-under-attack',
  'cf-challenge-running', 'just a moment', 'checking your browser',
  'enable javascript and cookies', 'window._cf_chl_opt', '__cf_chl_jschl_tk__',
  'sucuri_cloudproxy_uuid', '__incapsula__', 'awswafcaptchacdk',
];

Server: cloudflare 单独不路由(70% CF 站点是纯 CDN,fetch OK)— 只作 domainStats 标签。


Phase 2 — 域名状态本地管理

Phase 2.1 状态机

详见 域名状态机 wiki。简版:

状态 含义 行为 TTL
dead DNS/SSL/404 永久失败 永远 SKIP 30d(之后重试)
antibot-hard cf-mitigated:block / 永久 永远 tab fallback 90d
antibot-soft challenge 偶发 fetch 试 + tab 兜底 7d 衰减
friendly fetch 成功率 > 70% 永远 fetch, 不 fallback 长期
cold fetch+tab 都拿不到 contact 永远 SKIP(除非用户强制) 90d
unknown 默认 / 首次访问 完整 pipeline 7d 后重评估

Phase 2.2 数据结构

interface DomainStat {
  domain: string;
  state: 'dead' | 'antibot-hard' | 'antibot-soft' | 'friendly' | 'cold' | 'unknown';
  // 最近 N 次(FIFO, max 20)
  recent: Array<{
    at: number;  // timestamp
    method: 'head' | 'fetch' | 'tab';
    outcome: 'ok-contact' | 'ok-empty' | 'http-error' | 'network-error' | 'antibot' | 'dead';
  }>;
  // 派生指标(cron 每天重算)
  fetchTotal: number;
  fetchOk: number;       // fetch 拿到 contact
  tabTotal: number;
  tabOk: number;
  // 时间戳
  firstSeen: number;
  lastSeen: number;
  stateExpiresAt: number;
}

存储:storage.local:domainStats:v1 (Record),max 5k 条 LRU 淘汰。

Phase 2.3 状态转换规则

unknown ─(fetch ok-contact × 3 连续)─→ friendly
unknown ─(fetch ok-empty × 5 连续)─→ cold
unknown ─(head dead)─→ dead
unknown ─(head antibot)─→ antibot-soft
antibot-soft ─(antibot × 3 连续)─→ antibot-hard
antibot-soft ─(fetch ok × 2)─→ unknown (恢复)
friendly ─(fetch fail × 5)─→ unknown (重新评估)
任何状态 ─(TTL 到期)─→ 重置 recent + state='unknown'

Phase 3 — 云端协同 + 贡献度

⚠️ 该阶段涉及产品/法律/商业决策,本 spec 仅给设计草案,需 PM/法务 review。

Phase 3.1 数据复用模型

云端表 domain_contact_pool(按 url hash 主键):

interface CloudContactRecord {
  urlHash: string;          // sha256(normalized url) primary key
  domain: string;           // 索引
  emails: string[];         // 去脏 + 去重
  phones: string[];
  socials: Record<string, string>;
  // 抓取来源(统计 + 反作弊)
  contributorCount: number; // N 个用户独立抓到(相同结果)
  firstContributedAt: number;
  lastVerifiedAt: number;   // 最后一次验证(任何人抓到相同)
  // 衰减信号
  verifyTotal: number;      // 总验证次数
  verifyMatch: number;      // 数据匹配的次数(用于一致性评分)
}

interface CloudDomainState {
  domain: string;           // 主键
  state: DomainStat['state'];
  consensus: number;        // 0-1,N 个客户端共识强度
  updatedAt: number;
  expiresAt: number;
}

Phase 3.2 客户端 → 云端协议

时机 动作
客户端抓到新 contact POST /api/scrape/contribute(url + emails + phones + extraction method)
客户端确认 dead/antibot POST /api/scrape/domain-state(domain + state + evidence)
客户端查询 url GET /api/scrape/lookup?hashes=... (batch)
客户端批量预热 POST /api/scrape/prefetch-states body 含 100-1000 domains

Phase 3.3 数据时效(TTL 设计)

数据类型 TTL 重验证策略
emails 90d 用户使用后报"无效"→ 立即标 stale
phones 180d (电话号变化少) 同上
domainState=dead 30d (域名可能被买回) TTL 到期后任一客户端重新探测
domainState=antibot-hard 90d 同上
domainState=friendly 长期(无 TTL) fetch 失败 3 次自动降级 unknown
domainState=cold 90d 周期性给小批量客户端 retry(10%)

Phase 3.4 贡献度机制(防作弊)

积分汇率

行为 贡献度 备注
上传 url contact(云端不存在) +5 真贡献
上传已存在 url contact,且数据一致 +1 验证贡献(共识增强)
上传已存在 url contact,数据不一致 0 不奖不罚(可能是脏数据 / 真有更新)
上传 dead/antibot state +0.5 状态贡献,少量奖励
查询云端拿到 contact -1 消耗
用户已是 VIP / Pro 用户 免费查 (已付费替代积分)
新用户注册赠 50 分 +50 让用户能立刻用

防作弊

风险 防御
用户随便造假数据上传刷分 "上传 url 前必须客户端真实抓过" — 服务端校验抓取路径(method 字段)+ 跟其他客户端共识强度比对,超过 2σ 直接拒收 + 标用户
拿到云端数据后批量倒卖 rate limit + 单日下载量 cap + 异常用量预警
同 IP / 同账号刷 bind device fingerprint + 每日 free 抓取额度
上传数据正确但用户没真用 不直接奖励上传,奖励"被其他用户验证一致" — 验证次数累积才结算

积分等级(VIP/付费用户绕开)

免费用户:50 起步,0-100 区间,需查询前先贡献
活跃用户(每日 50+ 贡献):自动累积可观分数
重度用户(每日 1000+ 贡献):考虑升 VIP(不限)
付费 VIP:完全免费查(积分不影响)

Phase 3.5 URL 归一化(去重 key)

function normalizeUrl(url: string): string {
  // 1. lowercase scheme + host
  // 2. 去掉 www. 前缀
  // 3. 去掉 trailing slash
  // 4. 去掉 query/hash(contact 信息通常不依赖 query)
  // 5. 去掉端口(如果是 80/443 默认)
  // 例:https://www.Example.com/contact/?utm=x → example.com/contact
}
function urlHash(url: string): string {
  return sha256(normalizeUrl(url)).slice(0, 16);
}

Phase 3.6 隐私 / 合规风险

风险 应对
GDPR / 个人信息保护法:email/phone 是 PII 1. 服务条款明确声明用户上传 = 同意共享公开 contact 数据;2. 不上传 user-input form data(只抓公网 HTML 提取);3. 提供"反向请求删除"接口
网站 robots.txt 禁止抓取 客户端 fetch 时 honor robots.txt(先 GET /robots.txt 看 Disallow)
数据所有权:抓的是网站公开数据,但聚合算我们的? 类比 Common Crawl,公开数据聚合本身合法。用户上传的是用户已抓的数据,所有权问题在用户 vs 网站,平台聚合只是路由
被反爬大规模封 IP 客户端分散抓取 → 单点封不杀全局,反而比单服务器抓取健康
垃圾数据攻击 共识机制 + 异常上传检测(见 3.4)

Phase 3.7 工程量

模块 工程量
客户端:lookup API client + cache + retry 4h
客户端:contribute API client + 反作弊签名 3h
客户端:积分 UI + 余额查询 + 历史记录 5h
服务端:DB schema (postgres + redis cache) 3h
服务端:lookup/contribute API + rate limit 8h
服务端:共识算法 + 异常用户检测 cron 8h
服务端:积分账户 + 流水 + 后台运营 12h
法务/产品:服务条款更新 + 隐私政策 (外部)

Phase 3 总计 ≈ 40h+ 服务端工作,且需法务 review。


日志可观测性(贯穿所有 Phase)

page-log 字段扩展:

interface PageLogEntry {
  // 现有
  type: 'maps' | 'website' | 'social';
  url: string;
  status?: number;
  emails?: number;
  phone?: string;
  socials?: string[];

  // SPEC-004 新增
  method?: 'head' | 'fetch' | 'tab' | 'cloud' | 'cache' | 'skip';
  outcome?: 'ok-contact' | 'ok-empty' | 'dead' | 'antibot' | 'cold-skip' | 'error';
  fetchMs?: number;          // 客户端耗时
  bodySize?: number;         // GET body 字节数
  cloudHit?: boolean;        // 是否走云端命中
  contribPoints?: number;    // 本次产生 +/- 积分
  domainState?: DomainStat['state']; // 决策时的 domain state
  errorClass?: 'dns' | 'ssl' | 'http' | 'timeout' | 'parse' | 'cf-challenge' | 'wider';
}

日志面板(log-view)加 filter:按 method / outcome / domainState 切分,做 funnel 分析。


分阶段路线图

Phase 内容 工程量 风险 收益
1.0 客户端 HEAD probe + dead/404 早杀 4h 5-15% URL 立即 SKIP(节约 5-10s/站)
1.5 客户端 GET fetch + body anti-bot + 解析 6h 50-60% URL 跳过 tab(3-5x 加速)
2.0 域名状态本地持久化 + LRU + 衰减 6h 长期使用累积智能(友好域名直接 fetch)
2.5 日志 method/outcome 字段 + funnel 分析 3h 可观测性
3.0 云端 lookup(只读,免费)+ 命中复用 8h 客户端 + 服务端 schema 与已有用户库重叠时收益大
3.5 云端 contribute(写) + 共识机制 12h 客户端 + 20h 服务端 (法务/隐私/反作弊) 用户量级越大越值
4.0 贡献度机制 + 积分账户 20h 服务端 + UI 商业模型变化,需 PM/产品决策

推荐执行顺序

  1. 立即上:Phase 1.0 + 1.5(v0.10.88+)— 客户端独立可见效
  2. 下个 sprint:Phase 2.0 + 2.5 — 智能记忆 + 日志
  3. 暂缓:Phase 3.x — 等 Phase 1+2 稳定后再评估
  4. 最远:Phase 4.0 — 积分商业模型重大变化

决策记录(2026-05-28 用户拍板)

Phase 状态 说明
Phase 1 approved 本地多阶段抓取,v0.10.88+ 起步
Phase 2 approved 域名状态机本地化,Phase 1 之后接力
Phase 3 ⏸️ parked 云端 + 贡献度暂不做。触发条件:本地 Phase 1+2 上线后稳定运行 ≥ 2 周,再评估
Phase 4 ⏸️ parked 同 Phase 3

暂缓 Phase 3 的理由

  • 本地优化(Phase 1+2)单独就能解决 80% 痛点:跳过死站 / 跳过 antibot / 复用域名状态
  • 云端涉及服务端工程(~30h+)+ 法务合规(数据共享 GDPR)+ 业务决策(贡献度商业模型),先用本地版本拿真实数据再评估投入产出
  • Phase 3 下面 7 个待决策项自然进入冷藏(不必现在拍板)

Phase 3+ 冷藏待决策项

待 Phase 1+2 稳定后再处理:

  • 🤔 客户端是否要 honor robots.txt?(合规 vs 效率)
  • 🤔 用户上传 contact 数据 = 默认同意共享?还是 opt-in?
  • 🤔 免费查 vs 积分查的免费额度(用户感受 vs 商业模型)
  • 🤔 数据时效(90d 是否合理?需 dogfood 校准)
  • 🤔 共识算法门槛(N 个用户独立抓到才信?N=2 还是 3)
  • 🤔 服务端栈(postgres vs ClickHouse;命中查询性能要求)
  • 🤔 老用户已有的本地数据,是否回填云端(一次性迁移)

相关

  • 域名状态机 — Phase 2 概念详解
  • docs/wiki/贡献度机制.md — Phase 3 详解(待 Phase 3 实施时写)
  • [[2026-05-28-cloud-sync-contribution-proposal|2026-05-28-网站采集云端协同-贡献度构想]] — 用户原始素材
  • 共享队列架构 — 现有任务调度,与本 spec 集成点