曾经有个不存在的状态
DEV-sampler.md §8.1 · 这一节记录的是一个已被废除的设计
早期设计里 verdict = unknown 的终点是「转人工」。这是一个伪装成流程的永久拉黑 —— 没有表接它,没有字段记它,没有出口,而且比永久拉黑更糟:后者至少是明确记录,前者制造了「有人会处理」的错觉。
10 万域名规模下,判定失败率哪怕只有 3%,就是 3000 个域名卡死。对照系统里其他机制 —— lease 超时回收、判定任务超时重投、熔断探针自动恢复 —— 全都有自动出口。待定域名也必须有。
两条出口
DEV-sampler.md §8.2 · 互不依赖
| exit出口 | type性质 | mechanism机制 | current当前域名数 |
|---|---|---|---|
| 出口一 | 兜底 | 按 30 / 90 / 180 天退避重采,永不放弃 | 3,286 |
| 出口二 | 加速 | 外部消费方认领 → 用自己的爬虫判定 → 回报 verdict | 486 |
⚠️ 出口一是兜底,不是备选。没有任何外部消费方接入时,系统照常闭环。导出是加速器,不是依赖项 —— 这堵住了「接口写好了没人消费,3000 个域名躺在没人 GET 的 endpoint 后面」这个失败模式。
⚠️ 待定期间该域名不抓取。本系统只处理 CSR 页面,判不出是不是 CSR 就不抓 —— 调度器已按 verdict = unknown 丢弃其链接。传统爬虫能抓的那部分由外部模块承担,本系统内不另开一条纯 HTTP 通道。
⚠️ 开发顺序上出口一必须早于出口二。反过来的话,外部没接上就是黑洞。
undetermined_reason 九个取值
DEV-sampler.md §8.4 · 取值集与调度器的 suspend_reason 不得有交集
| undetermined_reason判不出来的原因 | meaning含义 | count域名数 | typical external handling外部模块的典型处置 |
|---|---|---|---|
| sample_all_failed | 样本页全部取不到 | 1,102 | 传统爬虫直接试 |
| judgement_timeout | 判定任务超时未完成 | 843 | 直接试 |
| requires_authentication | 全站需登录 | 561 | 人工确认 |
| domain_not_resolvable | DNS 长期无解析 | 448 | 一次 WHOIS 查询即可判定 |
| sample_all_not_found | 样本页全 404 | 372 | sitemap 可能过期,重新发现 |
| render_persistently_throttled | /v1/render 持续 429 | 211 | 换出口重试 |
| robots_disallowed | robots 禁止采样路径 | 148 | 合规留痕,不再尝试 |
| consumer_capability_exceeded | 外部也做不了(外部回报) | 74 | 回到退避重采 |
| legal_block | 451 | 13 | 人工与法务 |
⚠️ 取值集与调度器的 suspend_reason 不得有交集。suspend_reason 是「这个域名现在能不能连上」,undetermined_reason 是「这个域名判不出来是不是 CSR」。语义不同,不许互相赋值。部分重叠比完全不同更危险。
⚠️ consumer_capability_exceeded 这一行必须有。外部也做不了的域名不能退回「转人工」—— 那就绕回原来的黑洞了。它回到退避重采,永不放弃。
⚠️ legal_block 在两个服务里各有一个同名概念:本表是判定侧的 undetermined_reason,调度器 §9.6 有一个可用性侧的 suspend_reason。这是取值集交集的唯一一处,需在标定期确认是否要改名 —— 按规范两者不得重叠。
待定池
GET /v1/undetermined-domains · 只读观察,不产生认领,不改变任何状态
| domain域名 | undetermined_reason判不出来的原因 | judgement_attempt_count判定尝试次数 | first_submitted_at首次提交时间 | last_attempted_at最后一次判定尝试时间 | last_http_status最后一次 HTTP 状态 | last_raw_text_length最后一次渲染前文本长度 | backoff退避档位 | claim认领 |
|---|---|---|---|---|---|---|---|---|
| portal.example.mo | judgement_timeout | 3 | 2026-08-04T01:22:19Z | 2026-08-14T23:09:38Z | null | null | 30 天 | 未认领 |
| members.example.com | requires_authentication | 2 | 2026-07-11T16:44:53Z | 2026-08-12T07:54:58Z | 403 | 0 | 90 天 | 已认领 |
| gone.example.net | domain_not_resolvable | 5 | 2026-06-09T04:30:11Z | 2026-08-08T02:18:47Z | null | null | 180 天 | 未认领 |
| blocked.example.eu | robots_disallowed | 1 | 2026-07-29T13:02:40Z | 2026-08-10T11:25:37Z | 200 | 1,284 | 180 天 | 未认领 |
| flaky.example.tw | render_persistently_throttled | 4 | 2026-08-01T09:14:26Z | 2026-08-13T18:40:52Z | 429 | null | 30 天 | 已认领 |
⚠️ 纯 GET 查询接口不能替代认领。查询没有「谁在处理」的概念,两个外部模块会同时抓同一个域名,限速的单点保证就失效了。查询接口保留,但只用于观察。
⚠️ 查询参数:?undetermined_reason=judgement_timeout&min_verified_at=2026-08-01T00:00:00Z&domain_count=100。同一语义在查询参数与 body 里必须同名,不许一个叫 limit 一个叫 domain_count。
⚠️ last_raw_text_length 为 null 表示那次尝试压根没拿到响应,不是「文本长度为 0」。第二行的 0 才是真的取到了但没有内容。
导出必须携带的三个字段
DEV-sampler.md §8.6 · 强制项,不是可选
| field字段 | layer层面 | why mandatory为什么强制 |
|---|---|---|
| robots_rule_content | 合规 | 把域名交给外部却不给 robots 规则,等于转出义务而不转移依据。外部违规抓取,追责会追回导出方 |
| disallow_prefix_list | 合规 | 同上。外部需要知道哪些路径不能碰 |
| min_interval_ms最小请求间隔(毫秒) | 技术 | 不下发,外部只能用自己的默认值,限速的单点保证在这一环彻底失效 |
⚠️ 字段名是 robots_rule_content,不是 robots_snapshot。导出的内容来自 sampler:robots_rule(复核时会刷新的规则副本),不是网页对象里那份抓取时刻的同期证据。借用证据字段的名字会让合规论证本身塌掉。
⚠️ min_interval_ms 禁止外部再取一次 max,与调度器、下载器的约束完全一致。这条要写进消费方接入文档。