CRS 内部控制台

接口契约下载器侧 · crs.tongsou.com

CONTRACT.md 是唯一接口真相源,CONVENTION.md 是唯一命名真相源。两份同时冻结,变更规则相同。

两条铁律

CONTRACT.md §一

rule铁律why理由
所有对外请求都经过下载器 采样器与调度器都不直连目标站点,渲染、robots.txt、sitemap.xml、健康探针一律走本服务。域名限速需要单点保证 —— 一个新域名首次判定若走直连,是 7 次以上的集中探测,封禁往往就发生在这里
下载器拉,不被推 渲染慢(约 1 秒/页),lease 模式天然背压,横向扩容时调度器无需知道有几台机器

⚠️ 唯一豁免是导出给外部消费方的域名,因此必须满足两条约束:认领期间我方停抓、导出项携带限速与 robots 数据。外部消费方不是第四个服务,只能访问采样器的三个导出接口,使用独立 token,可单独吊销。

下载器提供的接口

CONTRACT.md §二

endpoint接口 caller调用方 mode模式 note说明
POST /v1/render采样器、下游同步 响应为完整网页对象。受域名限速约束,超限返回 429 + retry_after_seconds
POST /v1/probe调度器同步 返回精简结构。内部强制 should_store = false必须返回 has_reached_origin
GET /v1/objects/{object_id}下游同步 取网页对象

POST /v1/render 请求字段

field字段type类型note说明
url请求地址string
should_render是否渲染boolfalse 时只发 HTTP,不进渲染队列,但走同一条限速路径
wait_selectorstring采样器推导的正文容器选择器,等待策略里排第一
timeout_ms超时(毫秒)int单位必须在名字里
should_store是否入库bool入口 C 内部强制为 false

⚠️ should_render = false 不是一条抓取路径。它只服务于采样器取 robots / sitemap 与健康探针。判定为 ssrunknown 的域名由外部模块承担,本系统不另开纯 HTTP 抓取通道 —— 开了就同时顶翻 v1 的定位与契约的 ssr 分支。

下载器调用的接口

CONTRACT.md §三 · 调度器 API

endpoint接口 direction方向 note说明
GET scheduling-url/v1/lease下载器 → 调度器 查询参数 ?worker_id=w3&task_count=50&lease_ttl_seconds=120lease_ttl_seconds 到期未 report 的任务自动回收重投,这是防止下载器崩溃导致 URL 永久卡死的唯一机制
POST scheduling-url/v1/report下载器 → 调度器 必须重试到成功,否则该批 URL 要等 lease 超时才回收。幂等键是 lease_id
POST sampling-url/v1/api-endpoint-hints下载器 → 采样器 v2 才启用 接口逆向线索。必须走采样器,不能直接告诉调度器 —— 域名级配置只能有一个所有者

⚠️ 过期 lease 的迟到 report:接受对象落库,但忽略 in_flight_count、统计、health_signallink_list,返回 {"is_stale_lease": true}。该批 URL 可能已被重投并重新渲染,重复扣减会让计数变负,重复消费 link_list 会造成重复入队。

fetch_status 四值与字段可空性

CONTRACT.md §三 · 这张表决定调度器怎么处理每一条 report

fetch_status抓取结果状态 meaning含义 object_idstatistics统计字段 health_signallink_list
done渲染成功
empty渲染成功但正文为空
not_modified304,未渲染null全 nullnullnull
failed失败null全 nullnullnull

⚠️ health_signal 由下载器算好,不下发原始 network_logconsole_errors。那两个字段在网页对象里,不在 report payload 里。调度器若为拿它们去 GET /v1/objects/{object_id},单机每天数十万次额外读,且会把调度器与网页对象 schema 绑死。

⚠️ link_list 必须带 reldom_position。只传 URL 会让 nofollow 无人遵守,列表页优先也判断不出来。

health_signal 字段来源

CONTRACT.md §三 · report 与探针响应共用同一个结构

field字段computed from计算方式
xhr_total_countnetwork_logtype = xhr 的条数
xhr_error_count其中 http_status ≥ 500
xhr_throttled_count其中 http_status = 429
xhr_denied_count其中 http_status ∈ {401, 403}
has_console_errorconsole_errors 非空(布尔,不传内容)
redirect_target_hashfinal_urlurl_hash

⚠️ 不识别「取正文的那个 XHR」,只看比例。一个 CSR 页面几十个 XHR,「哪个是主接口」的判据定不下来;后端挂通常是一片挂,比例判据更稳。等 v2 接口逆向跑起来、api_endpoint 已知后再升级为精确匹配。

跨服务约定

CONTRACT.md §五

item事项rule约定
认证服务间固定 token,Authorization: Bearer {token}。外部消费方使用独立 token,可单独吊销
重试失败指数退避,上限 3 次。report 必须重试到成功
幂等reportlease_idrulesdomain + verified_atdomain-verdict-hintsclaim_id + domain三者都在 payload 里,不引用内部字段
存储隔离三服务各自独立数据,不共享表。keyspace 前缀 scheduler: / sampler: / downloader:
跨服务传递只能通过 CONTRACT.md 里已定义的边,没有边就是不能传。需要别的服务的数据走接口拿副本,副本表名带 _replica 并注明只读

⚠️ crs: 不得作为任何 key 前缀 —— 它与系统名撞义。crs 一词有两个含义:整个系统,以及下载器的域名;key 前缀用 downloader: 正是为了避开这个歧义。

契约变更规则

CONTRACT.md §七 · 本文与 CONVENTION.md 同时冻结

任何一方要改契约,三方确认。字段只增不删,废弃字段标 deprecated。接收方必须容忍未知字段,不得因此报错。冻结之后改名 = 新增字段 + 旧字段标废弃 + 三方确认 + 等一个大版本。

change变更trigger触发时机parties涉及
/v1/rules/v1/leaserender_strategy + api_endpoint下载器 v2 启动接口逆向时三方
采样器加 POST /v1/api-endpoint-hints同上下载器、采样器
fail_reason 取值集待补 当前全系统唯一没有取值表的「原因」字段下载器(取值定义),三方知会

⚠️ render_strategy 只描述「怎么取页」render | api),不承载租约、健康、认领等状态。一个字段两个语义轴是本系统反复在防的事 —— verdict 用布尔表三态已经付过一次学费。

⚠️ v2 的接口逆向会同时断掉链接图谱与 health_signal:直接打接口不渲染,没有 rendered_htmllink_listnetwork_log。所以 render_strategy = api 的域名仍需按低频渲染一次只为提取 link_list,且 health_signal 只填 redirect_target_hash。这两条必须在契约变更时一并约定。

M0:第一个联调点

CONTRACT.md §六 · 三个项目各做各的会在联调时炸,M0 是唯一对齐锚点

step步骤action动作
1手写规则:POST scheduling-url/v1/rules,payload 即 §三 那个形状
2投种子:POST scheduling-url/v1/seeds
3下载器 lease 一条 → 渲染 → report
4人工核对网页对象字段完整性

⚠️ M0 阶段采样器完全不参与 —— 规则手写。跑通这四步再接自动化。

命名规范要点

CONVENTION.md · 改本文不影响链路,改链路不影响本文

rule规则detail要点
全称,禁止缩写截断式缩写一律禁止(sched / stat / hist / conf / idx)。例外只有两类:镜像外部标准的字段名(rel / etag / lang),以及封闭的通用缩写白名单(URL / XHR / CSR / TiKV 等)
单位与方向进名字min_interval_ms 而非 intervalpriority_rank(小者优先)而非 priority
布尔用前缀is_ / should_ / has_,不用裸名词
Key 恒为三段{service}:{table_name}:{primary_key},单例表第三段写 current
一张表只干一件事任务与结果分家、配置与统计分家、副本与原本分家。选不出表名后缀,就说明这张表干了两件事
时间格式2026-08-15T07:47:52Z,秒级,末尾恒为 Z。禁止本地时区、偏移量写法、Unix 秒
哈希规范url_hash = 全局规范化后 URL 的 SHA-256 前 16 字节,小写 hex,32 字符。content_hash 同规格
枚举是默认全小写 snake_case,取值本身也禁止缩写,不得与字段名重名。禁止用布尔表三态以上的状态
json tag 逐字相同Go 结构体的 json tag 必须与 CONTRACT.md 里的字段名逐字相同,不做任何大小写或分隔符转换。有校验脚本,不一致构建失败

⚠️ 命名漂移不是「难看」的问题:status 在两个服务里是两套不同枚举,JSON 反序列化不会报错,只会静默走错分支