Pārlūkot izejas kodu

docs: archive legacy data interface docs

SamLee 2 nedēļas atpakaļ
vecāks
revīzija
6475aa400d
34 mainītis faili ar 8 papildinājumiem un 6179 dzēšanām
  1. 4 4
      core/embedding.py
  2. 4 4
      开发文档/正式版施工文档.md
  3. 0 793
      数据接口与来源/00_数据接口总览.md
  4. 0 484
      数据接口与来源/01_DemandAgent输入合同.md
  5. 0 13
      数据接口与来源/captures/README.md
  6. 0 994
      数据接口与来源/crawler_endpoints.registry.json
  7. 0 42
      数据接口与来源/embedding接口.md
  8. 0 630
      数据接口与来源/external_data_sources_registry.json
  9. 0 113
      数据接口与来源/platform_profiles/bilibili.json
  10. 0 52
      数据接口与来源/platform_profiles/douyin.json
  11. 0 30
      数据接口与来源/platform_profiles/github.json
  12. 0 127
      数据接口与来源/platform_profiles/kuaishou.json
  13. 0 47
      数据接口与来源/platform_profiles/shipinhao.json
  14. 0 12
      数据接口与来源/platform_profiles/toutiao.json
  15. 0 93
      数据接口与来源/platform_profiles/weixin.json
  16. 0 113
      数据接口与来源/platform_profiles/xiaohongshu.json
  17. 0 94
      数据接口与来源/platform_profiles/youtube.json
  18. 0 12
      数据接口与来源/platform_profiles/zhihu.json
  19. 0 150
      数据接口与来源/walk_graph.json
  20. 0 55
      数据接口与来源/walk_policy.json
  21. 0 128
      数据接口与来源/快手视频号_URL解包与OSS验证报告_20260617.md
  22. 0 206
      数据接口与来源/接口台账/B站.md
  23. 0 93
      数据接口与来源/接口台账/github.md
  24. 0 167
      数据接口与来源/接口台账/youtube.md
  25. 0 189
      数据接口与来源/接口台账/公众号.md
  26. 0 76
      数据接口与来源/接口台账/头条.md
  27. 0 185
      数据接口与来源/接口台账/小红书.md
  28. 0 279
      数据接口与来源/接口台账/快手.md
  29. 0 344
      数据接口与来源/接口台账/抖音.md
  30. 0 63
      数据接口与来源/接口台账/知乎.md
  31. 0 252
      数据接口与来源/接口台账/视频号.md
  32. 0 59
      数据接口与来源/视频音频取数实测.md
  33. 0 235
      数据接口与来源/跨平台字段映射.json
  34. 0 41
      数据接口与来源/跨平台字段映射.md

+ 4 - 4
core/embedding.py

@@ -1,12 +1,12 @@
 """火山方舟 Doubao-embedding-vision 文本向量化客户端(scope-link 用)。
 
-契约见《数据接口与来源/embedding接口.md》
-  - Endpoint: POST .../api/v3/embeddings/multimodal(vision 版必须走 multimodal)
-  - model = 预置推理接入点 ep-id
+接口由 .env 配置
+  - ARK_EMBEDDING_URL: 默认 POST .../api/v3/embeddings/multimodal
+  - ARK_EMBEDDING_EP: 预置推理接入点 ep-id,作为 model 字段传入
   - 输入 input=[{"type":"text","text":...}],输出 data.embedding = 2048 维
   - 单条调用(一次请求一个向量),批量靠并发多次请求。
 
-密钥/ep 全部从 .env 读,不硬编码。
+密钥、endpoint、URL 全部从 .env 读,不硬编码。
 """
 from __future__ import annotations
 

+ 4 - 4
开发文档/正式版施工文档.md

@@ -61,7 +61,7 @@
 - `scripts/import_to_db.py`
 - `scripts/run_search.py`
 - `.claude/launch.json`
-- `数据接口与来源/captures/*`
+- 原始抓包证据已归档到 `archive/2026-06-30-step1/data_interface_captures/`
 
 本轮执行路径:已先迁到 `archive/2026-06-30-step1/`,不是直接物理删除;真实 `.env` 从 git tracking 移除,本地文件保留,提交 `.env.example` 作为模板。
 
@@ -72,7 +72,7 @@
 - `scripts/rebuild_payloads.py` 与 `scripts/decompose.py` 里 payload 组装重复,后续统一到 `decode_content/payloads.py`。
 - `scripts/import_to_db.py` 是本地 SQLite 迁移工具,正式版不再需要。
 - `scripts/run_search.py` 是旧搜索和本地媒体链路,正式链路由 `run_acquisition.py` 替代。
-- `数据接口与来源/captures/*` 是原始抓包证据,建议先外部归档,再从主干移除
+- 原始抓包证据已归档到 `archive/2026-06-30-step1/data_interface_captures/`,不再放主干活跃区
 
 ### 1.2.1 删除、归档、保留清单
 
@@ -90,7 +90,7 @@
 | 立即归档后从主干移除 | `scripts/run_search.py` | 移到 `archive/2026-06-30-step1/legacy_scripts/` | 旧搜索入口,正式入口改为 `scripts/run_acquisition.py` |
 | 立即归档后从主干移除 | `tests/test_legacy_paths.py` | 移到 `archive/2026-06-30-step1/legacy_tests/` | 测的是旧 demo 路径,不再代表正式验收 |
 | 立即归档后从主干移除 | `.claude/launch.json` | 移到 `archive/2026-06-30-step1/local_agent/` | 个人本地 agent 配置,不进入正式工程 |
-| 立即归档后从主干移除 | `数据接口与来源/captures/*` | 移到 `archive/2026-06-30-step1/data_interface_captures/` | 原始抓包证据保留为归档,不放主干活跃区 |
+| 已归档后从主干移除 | 原始抓包证据 | 已归档到 `archive/2026-06-30-step1/data_interface_captures/` | 原始抓包证据保留为归档,不放主干活跃区 |
 | 从 git 移除但本地保留 | `.env` | `git rm --cached .env`,新增 `.env.example` | 真实密钥不进入仓库 |
 | Step6 已归档 | `acquisition/store.py` | 移到 `archive/2026-06-30-step6/legacy_sqlite/` | SQLite adapter 只供 local-demo/历史参考,正式代码不再依赖 |
 | Step6 已归档 | `scripts/run_creation_search.py` | 移到 `archive/2026-06-30-step6/legacy_scripts/` | 旧 SQLite 采集 runner,正式入口改为 `scripts/run_acquisition.py` |
@@ -793,7 +793,7 @@ query batch -> acquisition -> coarse classify -> decode candidates -> payload dr
 
 `dedupe.py` 负责:
 
-- 同平台同 source_id 去重
+- 候选内容优先按 `unique_key` 去重,兼容缺失时再退回平台原始 ID 或 URL
 - 跨 query 重复 URL 去重。
 - 同 item 不重复 decode。
 - 已 ingest payload 不重复写入。

+ 0 - 793
数据接口与来源/00_数据接口总览.md

@@ -1,793 +0,0 @@
-# ContentFindAgent 数据接口与来源总览
-
-更新时间:2026-06-08
-
-## 0. 证据口径
-
-本文只记录旧版代码、旧版接口审计、DB-first schema inventory、ContentFindAgent 数据工程 run 和 subagent 交叉验证过的数据接口。当前数据接口与来源的目录入口以本文为准,字段合同以 [数据库字段总览](../数据库字段总览/) 为准。
-
-机器可读索引见 [external_data_sources_registry.json](external_data_sources_registry.json)。本文负责给人解释接口来源、用途和边界;JSON 负责让程序、脚本或后续检查工具按 `source_id` 查询外部数据源、环境变量、接入状态、输出落点和缺口。
-
-| 状态 | 含义 |
-|---|---|
-| 已验证 | 已有 DB / API 只读验证或旧版实测结果 |
-| 源码定位 | 旧版代码能定位到接口或写入逻辑,但本次未实连 |
-| blocked | 因 token、只读条件或副作用限制未实连 |
-| pending / 待接入 | DB 字段存在,产品应接入,但旧版 ContentFindAgent 未作为主链路实现 |
-| source-only | 已有配置来源或源码来源,但本轮未作为主链路实连验证 |
-| missing | 变量、key、表或 API 未找到可用真实值 |
-| 缺口 | 没有真实表、真实接口或稳定字段,不能写成已接入 |
-
-不把 `show` 静态样例、小红书搜索、共创作者、相似作者、发现内容列表、淘汰原因表写成已验证接口。
-
-### 0.1 阅读方式
-
-每个阶段都按“短索引 + 详情块”组织:
-
-- 短索引只看接口、用途和状态,方便快速扫读。
-- 详情块再看输入字段、输出字段和边界,避免长表在窄屏下挤成一行。
-- 缺口统一放在最后,避免和已验证接口混在一起。
-
-### 0.2 环境配置和只读验证摘要
-
-本节吸收原环境缺口报告的确认结果,只记录变量名、能力和验证状态,不记录密码、token、AK/SK 或完整 DSN。
-
-#### 已验证配置
-
-- `.env` 结构:pass
-  - 对象:根目录 `.env`
-  - 注释:共 `139` 个变量,无重复 key;本地私有文件,不进入代码仓库。
-- 旧版 env 覆盖:pass
-  - 对象:旧仓 Python `os.getenv(...)`
-  - 注释:旧代码中发现的 `52` 个 getenv 变量都已在 `.env` 中覆盖。
-- 业务库:pass
-  - 变量:`DB_*`, `CONTENT_SUPPLY_DB_*`, `CONTENTFIND_DB_*`
-  - 注释:`demand_content`, `workflow_decode_task_result`, `demand_find_author`, `demand_find_content_result`, `demand_find_task`, `demand_task_oprate` 均只读通过。
-- Pattern 库:pass
-  - 变量:`OPEN_AIGC_PATTERN_DB_*`, `PATTERN_GLOBAL_DB_*`, `CONTENTFIND_PATTERN_DB_*`
-  - 注释:旧 MySQL `topic_pattern_element`, `topic_pattern_itemset`, `topic_pattern_category` 均只读通过,但新版 V1 不再把它们作为 `evidence_pack` 来源;只保留为历史迁移对照。
-- Crawapi 抖音接口:pass
-  - 变量:`CONTENTFIND_API_CRAWAPI_BASE_URL`, `CONTENTFIND_DOUYIN_*`, `CONTENTFIND_HOT_TOPIC_PATH`
-  - 注释:抖音关键词、作者作品、内容画像、账号画像、今日热榜均只读通过。
-  - 最新 smoke:2026-06-08 使用当前 `.env` 复测抖音关键词搜索,已不再返回强制登录异常;`POST /crawler/dou_yin/keyword` 可返回内容、`has_more` 和 `next_cursor`。同一内容调用 `POST /crawler/dou_yin/re_dian_bao/video_like_portrait` 返回 `code=0 / msg=null`,但样本画像为空,需按 `portrait_available=false / age_50_plus_level=missing` 处理。
-- AIGC plan detail:pass
-  - 变量:`AIGC_TOKEN`, `CONTENTFIND_API_AIGC_TOKEN`, `CONTENTFIND_API_READONLY_TOKEN`, `CONTENTFIND_AIGC_READONLY_PRODUCE_PLAN_ID`
-  - 注释:只查 produce plan detail,未创建或保存计划。
-- OpenRouter:pass
-  - 变量:`OPENROUTER_API_KEY`, `OPENROUTER_BASE_URL`
-  - 注释:两个 key 别名已同步;只读 key metadata probe HTTP 200;未调用 chat completion 或 embeddings。
-- 上游 open_aigc MySQL:pass
-  - 变量:`OPEN_AIGC_MYSQL_*`
-  - 注释:本轮只读 `SELECT 1` 通过,并完成 5 张上游表 `LIMIT 1` 探测。
-- 上游 open_aigc PG:pass
-  - 变量:`OPEN_AIGC_PG_*`, `PGVECTOR_DSN`, `DATA_ENGINEERING_OPEN_AIGC_PG_DSN`
-  - 注释:`DATA_ENGINEERING_OPEN_AIGC_PG_DSN` 和同步后的 `OPEN_AIGC_PG_*` 均只读 `SELECT 1` 通过;Pattern 相关表 `LIMIT 1` 通过。
-- ODPS 表现数据:pass
-  - 变量:`ODPS_ACCESS_ID`, `ODPS_ACCESS_KEY`, `ODPS_ENDPOINT`, `ODPS_PROJECT`
-  - 注释:本轮 `SELECT 1` 通过,`video_dimension_detail_add_column`, `dwd_multi_demand_pool_di`, `feature_point_data` schema 可读。
-
-#### 待补或仅有来源配置
-
-- TikHub fallback:missing
-  - 变量:`TIKHUB_API_KEY`, `TIKHUB_BASE_URL`, `TIKHUB_DOUYIN_SEARCH_PATH`
-  - 注释:URL 和 path 已有,旧仓未找到真实 key;只能作为待补备用通道。
-- OSS:missing
-  - 变量:`ALIYUN_OSS_*`, `OSS_ACCESS_KEY_ID`, `OSS_ACCESS_KEY_SECRET`
-  - 注释:变量名已定位,真实 AK/SK、bucket、region、prefix、public base URL 缺失。
-- Feishu:source-only
-  - 变量:`FEISHU_APP_ID`, `FEISHU_APP_SECRET`
-  - 注释:旧版有来源,但不是 v2 主链路;本轮未调用。
-- 前端联调 API:missing
-  - 变量:`VITE_API_BASE_URL`, `VITE_CONTENTFIND_API_BASE_URL`
-  - 注释:show 当前仍是静态沙盘,尚未接真实后端。
-
-### 0.3 CFA 新数据合同
-
-旧接口审计记录的是老系统和上游能力;新版 CFA 的生产事实层使用同一个云库里的 `content_agent_*` 表。
-
-核心身份:
-
-| 字段 | 含义 |
-|---|---|
-| `run_id` | 一次 CFA 运行 ID,由系统生成,同编号只能跑一次 |
-| `policy_run_id` | 一次运行里的策略执行 ID。V1 一个 run 默认一套策略;V1.1 支持同一 run 多套策略实验 |
-| `schema_version` | DB 表或 JSON 文件结构版本 |
-| `record_schema_version` | JSONL 单行结构版本 |
-| `policy_bundle_id` | 策略组合包 ID |
-| `strategy_version` | 策略组合版本 |
-| `rule_pack_id` / `rule_pack_version` | 规则包 ID 和版本 |
-
-P0 新表:
-
-```text
-content_agent_runs
-content_agent_source_contexts
-content_agent_pattern_seed_packs
-content_agent_queries
-content_agent_discovered_content_items
-content_agent_content_media_records
-content_agent_rule_decisions
-content_agent_source_path_records
-content_agent_search_clues
-content_agent_run_events
-content_agent_final_outputs
-content_agent_publish_jobs
-content_agent_pattern_recall_evidence
-content_agent_strategy_reviews
-content_agent_policy_runs
-```
-
-字段边界:
-
-- 关联、去重、状态筛选、审计和多策略比较字段必须做 DB 列。
-- 原始响应、上游证据包、规则命中明细、运行事件详情和最终输出快照先放 JSON。
-- JSON 字段要晋升成列,必须先经过命名登记、DDL、回填、测试和文档同步。
-
-`content_agent_source_contexts` 是 run 级输入事实,不写 `policy_run_id`。从 `content_agent_pattern_seed_packs` 开始的策略相关表都必须写 `policy_run_id`。
-
-## 1. 数据源
-
-数据源阶段回答:从哪里拿原始素材、原始种子和可搜索线索。
-
-### 1.1 索引
-
-| 编号 | 接口/表 | 用途 | 状态 |
-|---|---|---|---|
-| S1 | `content-deconstruction-supply.demand_content` | 需求输入 | 已验证 |
-| S2 | `open_aigc.public.pattern_itemset + pattern_itemset_post` | PG Pattern V2 到支撑素材 | 已验证 / V1 使用 |
-| S3 | `content-deconstruction-supply.workflow_decode_task_result` | 直接 Case 原始素材和解构点 | 已验证 |
-| S4 | 旧 MySQL `topic_pattern_itemset -> workflow_decode_task_result` | 历史 Pattern Item Set 派生多个 Case | 已废弃 / 迁移对照 |
-| S5 | `open_aigc.public.pattern_mining_category` | PG Pattern V2 类目上下文 | 已验证 / V1 使用 |
-| S6 | `content-deconstruction-supply.demand_find_author` | 历史沉淀账号 | 已验证 |
-| S7 | 今日热榜 `/crawler/jin_ri_re_bang/content_rank` | 热点入口 | 已验证 |
-| S8 | `open_aigc.post` | 上游历史内容素材 | 已验证 |
-| S9 | `open_aigc.post_decode_topic_point_element` | 上游选题点元素 | 已验证 |
-| S10 | `open_aigc.post_script_paragraph_field_element` | 上游脚本段落元素 | 已验证 |
-
-### 1.2 详情
-
-#### S1. 数据库:`content-deconstruction-supply`,表:`demand_content`
-
-- 当前输入字段:`id`, `name`, `suggestion`, `score`, `merge_leve2`, `dt`, `ext_data`
-- 新版必解析字段:`ext_data.evidence_pack`
-- 输出字段:需求词、需求解释、品类、分数、日期。
-- 边界:这是需求源,不是 Case 素材源;旧版只取基础字段,新版不能丢掉证据包。
-
-`ext_data.evidence_pack` 需要包含并向下游传递:
-
-| 字段 | 用途 |
-|---|---|
-| `source_kind` | 区分 Pattern、Case、聚类、历史搜索等来源 |
-| `pattern_source_system` | 新版唯一合法值为 `pg_pattern_v2` |
-| `case_id_type` | 说明 Case ID / post ID / channel content ID 的口径 |
-| `source_post_id` | 绑定原始素材 ID |
-| `pattern_execution_id` | 回查 Pattern 执行 |
-| `mining_config_id` | 回查 Pattern 挖掘配置 |
-| `itemset_ids` | 回查频繁项集 |
-| `itemset_items[]` | 回查 itemset 内的分类、元素和维度 |
-| `category_bindings` | 绑定分类树父节点 |
-| `element_bindings` | 绑定分类树元素节点 |
-| `support` / `absolute_support` | Pattern 支持度 |
-| `matched_post_ids` | 记录 Pattern 支撑素材 |
-| `video_ids` / `case_ids` | Pattern 来源下均为 `post_id` 语义 |
-| `decode_case_ids` | PG-only 当前可为空数组,不参与 exact 闭合 |
-| `seed_terms` | 给 Query 使用的策略种子 |
-| `run_id` | 串起上游和下游 run_context |
-| `demand_task_id` / `demand_content_id` | 绑定 DemandAgent 任务和需求记录 |
-| `source_certainty` | 标记来源确定性 |
-| `validation_status` | 标记已验证、待接入或待复看反查 |
-
-这些字段要进入数据源、Query、判断、游走、资产清洗沉淀和策略学习。目标是从某个 `case_id` 或 `post_id` 能反查到 Pattern、itemset、分类节点或元素节点。
-
-#### S2. 数据库:`open_aigc PG`,表:`public.pattern_itemset` + `public.pattern_itemset_post`
-
-- 输入字段:`execution_id`, `itemset_id`, `mining_config_id`, `scope`
-- 输出字段:`support`, `absolute_support`, `matched_post_ids`, `itemset_items`
-- 边界:这是新版 DemandAgent `evidence_pack` 的正式 Pattern 主事实来源;旧 MySQL 只保留迁移对照。
-
-#### S3. 数据库:`content-deconstruction-supply`,表:`workflow_decode_task_result`
-
-- 输入字段:`channel_content_id`
-- 输出字段:`title`, `body_text`, `images`, `video_url`, `channel`, `channel_account_id`, `channel_account_name`
-- 解构字段:`purpose_points`, `key_points`, `inspiration_points`, `topic_fusion_result`, `merge_leve2`
-- 边界:Case 的最终落点;可由直接 Case 或 Pattern 派生 Case 回查。
-
-#### S4. 历史接口:旧 MySQL `topic_pattern_itemset` -> 数据库:`content-deconstruction-supply`,表:`workflow_decode_task_result`
-
-- 输入字段:`execution_id`, `combination_type`, `item_count`, `support`, `absolute_support`, `matched_post_ids`
-- 输出字段:用 `matched_post_ids` 回查 `workflow_decode_task_result.channel_content_id`
-- 边界:旧字段仅作历史迁移对照;新版 `evidence_pack` 不能从这里生成或兜底。
-
-#### S5. 数据库:`open_aigc PG`,表:`public.pattern_mining_category`
-
-- 输入字段:`execution_id`, `id`, `path`, `source_type`
-- 输出字段:`name`, `description`, `category_nature`, `level`, `parent_id`, `element_count`
-- 边界:辅助解释 PG Pattern V2 的分类路径,不直接召回内容。
-
-#### S6. 数据库:`content-deconstruction-supply`,表:`demand_find_author`
-
-- 输入字段:`content_tags`, `channel`, `author_id`, `author_name`
-- 输出字段:`run_id`, `author_name`, `author_link`, `author_id`, `elderly_ratio`, `elderly_tgi`, `is_good`, `remark`, `content_tags`, `channel`
-- 边界:旧版可按 `content_tags LIKE query` 找历史作者。
-
-#### S7. 接口:今日热榜 `/crawler/jin_ri_re_bang/content_rank`
-
-- 输入字段:`sort_type`, `cursor`
-- 输出字段:`source`, `jump_url`, `type`, `rankList[].title`, `rankList[].heat`, `has_more`, `next_cursor`
-- 边界:热点只做修饰和探索入口,不等于最终内容源。
-
-#### S8. 数据库:`open_aigc`,表:`post`
-
-- 输入字段:`post_id`, `platform`, `merge_leve1`, `merge_leve2`, `import_date`
-- 输出字段:`title`, `body_text`, `platform_account_id`, `platform_account_name`, `publish_timestamp`, `like_count`, `comment_count`, `collect_count`, `images`
-- 边界:数据工程上游素材表,不是旧版 ContentFindAgent 主读表。
-
-#### S9. 数据库:`open_aigc`,表:`post_decode_topic_point_element`
-
-- 输入字段:`post_id`, `topic_point_id`, `element_type`, `element_sub_type`
-- 输出字段:`element_name`, `element_description`, `element_source`, `element_reason`
-- 边界:用于理解上游元素,不替代 `workflow_decode_task_result`。
-
-#### S10. 数据库:`open_aigc`,表:`post_script_paragraph_field_element`
-
-- 输入字段:`post_id`, `paragraph_id`, `field_type`, `element_type`, `element_sub_type`
-- 输出字段:`element_name`, `element_description`
-- 边界:用于策略学习和素材理解。
-
-## 2. Query
-
-Query 阶段回答:哪些输入会被转成平台可执行搜索词或采集动作。
-
-### 2.1 索引
-
-| 编号 | 接口/表 | 用途 | 状态 |
-|---|---|---|---|
-| Q1 | `demand_content` | 需求转 Query 输入 | 已验证 |
-| Q2 | `workflow_decode_task_result` | Case 解构点转 Query 输入 | 已验证 |
-| Q3 | `ext_data.evidence_pack.seed_terms/itemset_items/category_bindings` | PG Pattern V2 证据转 Query 输入 | 已验证 / V1 使用 |
-| Q4 | 旧 MySQL `topic_pattern_element/topic_pattern_itemset` | 历史 Pattern 词或组合转 Query 输入 | 已废弃 / 迁移对照 |
-| Q5 | OpenRouter | LLM Query Builder / 判断辅助 | 只读鉴权通过 |
-| Q6 | `demand_find_task` | Query 执行 run_context 台账 | 已验证 |
-
-### 2.2 详情
-
-#### Q1. 数据库:`content-deconstruction-supply`,表:`demand_content`
-
-- 输入字段:`name`, `suggestion`, `merge_leve2`, `score`, `dt`, `ext_data.evidence_pack.seed_terms`, `ext_data.evidence_pack.source_kind`
-- 输出字段:Query 原始需求、品类、解释、策略种子。
-- 边界:旧版从需求池出发;新版有证据包时优先使用 `seed_terms`,不能只把宽泛 `name` 当 Query 输入。
-
-#### Q2. 数据库:`content-deconstruction-supply`,表:`workflow_decode_task_result`
-
-- 输入字段:`purpose_points`, `key_points`, `inspiration_points`, `topic_fusion_result`, `title`, `body_text`, `merge_leve2`
-- 输出字段:Case 策略种子、筛选点、回扣依据。
-- 边界:v2 以解构出的策略种子为主,不直接用账号名做 Query。
-
-#### Q3. 输入对象:`demand_content.ext_data.evidence_pack`
-
-- 输入字段:`seed_terms`, `itemset_items`, `category_bindings`, `element_bindings`, `pattern_source_system`
-- 输出字段:Pattern 词、维度、类目路径和 Query seed。
-- 边界:`pattern_source_system` 必须为 `pg_pattern_v2`,不再从旧 MySQL 表临时补证据。
-
-#### Q4. 历史接口:旧 MySQL `topic_pattern_element/topic_pattern_itemset`
-
-- 输入字段:`combination_type`, `item_count`, `support`, `absolute_support`, `dimensions`, `matched_post_ids`
-- 输出字段:Pattern 组合、支持度、多 Case 命中集合。
-- 边界:字段曾验证存在,但不再作为新版 Query 主链路或 fallback。
-
-#### Q5. 能力:OpenRouter
-
-- 输入字段:`model`, `messages`, `tools`, `system`, `max_tokens`, `temperature`
-- 输出字段:`content`, `tool_calls`, `prompt_tokens`, `completion_tokens`, `finish_reason`, `cost`, `usage`
-- 边界:本轮只做 key metadata 鉴权探测,未调用 chat completion 或 embeddings;真实生成仍需显式预算开关。
-
-#### Q6. 数据库:`content-deconstruction-supply`,表:`demand_find_task`
-
-- 输入字段:`run_id`, `demand_content_id`, `status`, `created_at`, `token_coast`
-- 输出字段:任务状态、成本、需求关联。
-- 边界:不是 Query 内容表,但承载执行追踪。
-
-## 3. Platform
-
-Platform 阶段回答:同一个 Query 在平台上用什么动作执行,返回什么字段。
-
-### 3.1 索引
-
-| 编号 | 接口/表 | 用途 | 状态 |
-|---|---|---|---|
-| P1 | 抖音关键词搜索 `/crawler/dou_yin/keyword` | Query -> 发现视频 | 已验证 |
-| P2 | TikHub 搜索 fallback | 抖音搜索备用 | blocked |
-| P3 | 抖音账号作品 `/crawler/dou_yin/blogger` | 作者 -> 作者作品 | 已验证 |
-| P4 | 热点宝内容画像 | 视频画像 | 已验证 |
-| P5 | 热点宝账号画像 | 作者画像 | 已验证 |
-| P6 | `demand_find_content_result` | 平台结果沉淀字段 | 已验证 |
-| P7 | `demand_find_author` | 作者平台身份 | 已验证 |
-
-### 3.2 详情
-
-#### P1. 接口:抖音关键词搜索 `/crawler/dou_yin/keyword`
-
-- 输入字段:`keyword`, `content_type`, `sort_type`, `publish_time`, `cursor`, `account_id`
-- 输出字段:`platform_content_id`, `desc`, `author.nickname`, `author.platform_author_id`, `statistics.*`, `has_more`, `next_cursor`
-- 边界:2026-06-08 真实 smoke 已确认 `content_type=视频` 可返回第一页、`has_more` 和 `next_cursor`;`综合` 口径不能直接照搬。
-
-#### P2. 接口:TikHub 搜索 fallback
-
-- 输入字段:`keyword`, `cursor`, `sort_type`, `publish_time`, `filter_duration`, `content_type`, `search_id`, `backtrace`
-- 输出字段:`business_data[].data.aweme_info`, `next_page`, `backtrace`
-- 边界:缺 `TIKHUB_API_KEY`,不能写成已接入。
-
-#### P3. 接口:抖音账号作品 `/crawler/dou_yin/blogger`
-
-- 输入字段:`account_id`, `sort_type`, `cursor`
-- 输出字段:`platform_content_id`, `desc`, `author.*`, `statistics.*`, `has_more`, `next_cursor`
-- 边界:已验证抖音作者作品;小红书作者作品仍待验证。
-
-#### P4. 接口:热点宝内容画像
-
-- 输入字段:`content_id`, `need_age`, `need_gender`, `need_province`
-- 输出字段:年龄、性别、省份画像,`percentage`, `preference`
-- 边界:用于判断,不是召回接口;2026-06-08 真实 smoke 确认接口可返回 `code=0`,但单条内容可能 `data.data=null`,运行层必须把它当作画像缺失而不是接口登录失败。
-
-#### P5. 接口:热点宝账号画像
-
-- 输入字段:`account_id`, `need_age`, `need_gender`, `need_province`
-- 输出字段:年龄、性别、省份画像,`percentage`, `preference`
-- 边界:可作为作者是否值得扩展的判断信号。
-
-#### P6. 数据库:`content-deconstruction-supply`,表:`demand_find_content_result`
-
-- 输入字段:`channel`, `platform_content_id`, `video_url`, `author_id`, `author_name`, `author_link`
-- 输出字段:平台、内容 ID、作者 ID、链接。
-- 边界:这是结果表字段,不是平台 API。
-
-#### P7. 数据库:`content-deconstruction-supply`,表:`demand_find_author`
-
-- 输入字段:`channel`, `author_id`, `author_name`, `author_link`, `content_tags`
-- 输出字段:作者资产、内容标签、平台身份。
-- 边界:可作为后续作者起点。
-
-## 4. 判断
-
-判断阶段回答:内容、作者、Pattern 派生 Case 是否值得留下或继续扩展。
-
-### 4.1 索引
-
-| 编号 | 接口/表 | 用途 | 状态 |
-|---|---|---|---|
-| J1 | 热点宝内容画像 | 50+ 内容适配 | 已验证 |
-| J2 | 热点宝账号画像 | 50+ 作者适配 | 已验证 |
-| J3 | `workflow_decode_task_result` | Case 回扣判断 | 已验证 |
-| J4 | 解构接口 + 分类树 `match-paths` | 新发现内容 Pattern 回扣判断 | 待接入 |
-| J5 | PG `pattern_itemset` + `pattern_itemset_post` | Pattern 来源 Case 支持度判断 | 已验证 / V1 使用 |
-| J6 | `demand_find_content_result` | 判断结果沉淀 | 已验证 |
-| J7 | `dwd_multi_demand_pool_di` | 下游需求池表现参考 | 已验证 |
-| J8 | `feature_point_data` | 特征表现参考 | 已验证 |
-
-### 4.2 详情
-
-#### J1. 接口:热点宝内容画像
-
-- 输入字段:`content_id`, `need_age`, `need_gender`, `need_province`
-- 输出字段:年龄、性别、省份画像,`percentage`, `preference`
-- 边界:判断信号,不直接代表内容质量。
-
-#### J2. 接口:热点宝账号画像
-
-- 输入字段:`account_id`, `need_age`, `need_gender`, `need_province`
-- 输出字段:年龄、性别、省份画像,`percentage`, `preference`
-- 边界:作者强不代表作品必然强。
-
-#### J3. 数据库:`content-deconstruction-supply`,表:`workflow_decode_task_result`
-
-- 输入字段:`purpose_points`, `key_points`, `inspiration_points`, `topic_fusion_result`
-- 输出字段:目的点、关键点、灵感点、选题描述。
-- 边界:直接 Case 和 Pattern 派生 Case 都回到这张表判断。
-
-#### J4. 接口:解构接口 + 分类树 `match-paths`
-
-- 输入字段:发现视频的解构元素、`source_type`, `top_k`, `min_score`
-- 输出字段:分类树备选路径、分数和匹配依据。
-- 当前 endpoint:`POST https://library.aiddit.com/api/search/categories/match-paths/v2`。
-- 最新 smoke:2026-06-08 提交元素词 `毛主席`,返回 `success=true`,首个候选路径为 `/表象/视觉/实体/人物/个体/现实人物`,score 为 `1.0`。
-- 边界:只有解构强证据词和本次 PG `evidence_pack` 能解释相连,才允许把 `pattern_recall` 升级为 `matched`。`strong / medium / weak` 只用于画像、置信度或强弱等级,不表示 Pattern 回扣通过。
-
-#### J5. 数据库:`open_aigc PG`,表:`public.pattern_itemset` + `public.pattern_itemset_post`
-
-- 输入字段:`support`, `absolute_support`, `item_count`, `matched_post_ids`, `combination_type`
-- 输出字段:Pattern 支持度、支撑素材和 itemset 规模。
-- 边界:只用于验证上游 `evidence_pack`,不替新发现内容做自动回扣。
-- 输出字段:支持度、帖子集合、组合类型。
-- 边界:用于 Pattern -> 多 Case 判断;旧版未主链路使用。
-
-#### J6. 数据库:`content-deconstruction-supply`,表:`demand_find_content_result`
-
-- 输入字段:`digg_count`, `comment_count`, `share_count`, `portrait_source`, `elderly_ratio`, `elderly_tgi`, `recommendation_reason`, `process_trace`
-- 输出字段:互动、画像来源、50+ 指标、推荐理由、过程 run_context。
-- 边界:旧版只沉淀最终结果,没有全量发现内容判断表。
-
-#### J7. 数据库:`ODPS`,表:`dwd_multi_demand_pool_di`
-
-- 输入字段:`strategy`, `demand_id`, `demand_name`, `weight`, `type`, `video_count`, `video_list`, `extend`, `dt`
-- 输出字段:需求池权重和视频列表。
-- 边界:用于策略学习/复盘,不是旧版判断实时输入。
-
-#### J8. 数据库:`ODPS`,表:`feature_point_data`
-
-- 输入字段:`特征点`, `总分发曝光pv`, `bn_总回流`, `质bn_rovn`, `分发视频量`, `总日回流uv`, `vid_list`, `dt`
-- 输出字段:特征点表现、回流、视频列表。
-- 边界:用于后续策略学习,不是旧版实时评分表。
-
-## 5. 游走
-
-游走阶段回答:从视频、作者、Pattern、Case 还能走到哪里。
-
-### 5.1 索引
-
-| 编号 | 接口/表 | 用途 | 状态 |
-|---|---|---|---|
-| W1 | 抖音关键词搜索返回作者 | 视频 -> 作者 | 已验证 |
-| W2 | 抖音账号作品 | 作者 -> 作者作品 | 已验证 |
-| W3 | `demand_find_author` | 历史作者 -> 作者作品 | 已验证 |
-| W4 | PG `pattern_itemset_post` | Pattern -> 支撑素材 | 已验证 / V1 使用 |
-| W5 | 旧 MySQL `topic_pattern_* -> workflow_decode_task_result` | 历史特征词或 itemset -> Case | 已废弃 / 迁移对照 |
-| W6 | 相关搜索 / 标签 / 共创 / 相似作者 | 内容继续扩展 | 缺口 |
-| W7 | 小红书笔记/作者/话题游走 | 小红书扩展 | 缺口 |
-
-### 5.2 详情
-
-#### W1. 接口:抖音关键词搜索返回作者
-
-- 输入字段:`platform_content_id`, `desc`, `author.nickname`, `author.platform_author_id`, `statistics.*`
-- 输出字段:作者昵称、`platform_author_id`、互动指标。
-- 边界:只是进入作者判断,不代表作者自动入库。
-
-#### W2. 接口:抖音账号作品
-
-- 输入字段:`account_id` / `author.platform_author_id`, `sort_type`, `cursor`
-- 输出字段:`platform_content_id`, `desc`, `author.*`, `statistics.*`, `has_more`, `next_cursor`
-- 边界:作品需要重新过判断。
-
-#### W3. 数据库:`content-deconstruction-supply`,表:`demand_find_author`
-
-- 输入字段:`author_id`, `author_link`, `content_tags`, `channel`
-- 输出字段:可复用作者身份、标签、画像指标。
-- 边界:旧版按作者资产复用;作品仍走平台接口。
-
-#### W4. 数据库:`open_aigc PG`,表:`public.pattern_itemset_post`
-
-- 输入字段:`execution_id`, `itemset_id`
-- 输出字段:`post_id` 列表,进入 `matched_post_ids`、`video_ids`、`case_ids`。
-- 边界:这是新版 PG-only `evidence_pack` 的支撑素材来源。
-
-#### W5. 历史接口:旧 MySQL `topic_pattern_*` -> 数据库:`content-deconstruction-supply`,表:`workflow_decode_task_result`
-
-- 输入字段:`matched_post_ids`, `support`, `absolute_support`
-- 输出字段:历史 Case 解构点。
-- 边界:仅保留迁移说明,新版不再用它补齐或兜底 `evidence_pack`。
-
-#### W6. 缺口:相关搜索 / 标签 / 共创 / 相似作者
-
-- 输入字段:暂无稳定字段。
-- 输出字段:暂无稳定返回。
-- 边界:不写成已验证接口。
-
-#### W7. 缺口:小红书笔记/作者/话题游走
-
-- 输入字段:暂无旧版实测字段。
-- 输出字段:暂无稳定返回。
-- 边界:目前只有静态 Case 字段,不等于平台接口接入。
-
-## 6. 资产清洗沉淀
-
-资产清洗沉淀阶段回答:召回和判断后的内容、作者、后处理链接沉到哪里。
-
-### 6.1 索引
-
-| 编号 | 接口/表 | 用途 | 状态 |
-|---|---|---|---|
-| A1 | `demand_find_content_result` | 内容资产入库 | 已验证 |
-| A2 | `demand_find_content_result` | 内容质量和画像沉淀 | 已验证 |
-| A3 | `demand_find_author` | 作者资产入库 | 已验证 |
-| A4 | AIGC plan | 后处理计划绑定 | 源码定位 / blocked |
-| A5 | OSS log HTML | 过程可视化链接 | 源码定位 / blocked |
-| A6 | 关系资产 | 来源、query、作者、标签、游走路径关系 | 缺口 |
-| A7 | 搜索线索 | 有效 query、失败 query、标签、话题 | 缺口 |
-
-### 6.2 详情
-
-#### A1. 数据库:`content-deconstruction-supply`,表:`demand_find_content_result`(内容资产入库)
-
-- 输入字段:`run_id`, `query`, `rank_no`, `video_url`, `title`, `author_name`, `author_link`, `author_id`, `platform_content_id`, `demand_content_id`, `channel`, `dt`
-- 输出字段:内容资产、排序、来源需求、平台身份。
-- 新版必须补:结构化 `source_evidence`,或 sidecar/source edge artifact。
-- 边界:旧版沉淀最终内容,不沉淀全量发现内容列表;只靠 `platform_content_id + demand_content_id + process_trace` 不能 exact 回溯到 Pattern 和分类树节点。
-
-#### A2. 数据库:`content-deconstruction-supply`,表:`demand_find_content_result`(内容质量和画像沉淀)
-
-- 输入字段:`digg_count`, `comment_count`, `share_count`, `portrait_source`, `elderly_ratio`, `elderly_tgi`, `recommendation_reason`, `process_trace`
-- 输出字段:互动指标、画像、推荐理由、过程记录。
-- 边界:`process_trace` 可复盘,但不是结构化全量 run_context 表;不能替代 `source_evidence`。
-
-#### A3. 数据库:`content-deconstruction-supply`,表:`demand_find_author`
-
-- 输入字段:`run_id`, `author_name`, `author_link`, `author_id`, `elderly_ratio`, `elderly_tgi`, `is_good`, `remark`, `content_tags`, `channel`
-- 输出字段:作者资产、画像、标签、入选理由。
-- 边界:作者身份合并逻辑需新版明确。
-
-#### A4. 能力:AIGC plan
-
-- 输入字段:`platform_content_id`, `merge_leve2`, `produce_plan_id`, `account_id`, `sort_type`
-- 输出字段:`crawler_plan_id`, `produce_plan_id`, `produce_plan_name`, `publish_plan_id`
-- 边界:写侧副作用,开发测试不能默认触发。
-
-#### A5. 能力:OSS log HTML
-
-- 输入字段:`html_path`, `object_key`, `task_id`
-- 输出字段:`web_html_url`
-- 边界:上传有副作用;旧版只保留能力边界。
-
-#### A6. 缺口:关系资产
-
-- 输入字段:暂无真实表。
-- 输出字段:暂无真实字段。
-- 边界:内容与数据源、query、作者、标签、游走路径关系都需要承载,但当前不伪造 schema。
-
-#### A7. 缺口:搜索线索
-
-- 输入字段:暂无真实表。
-- 输出字段:暂无真实字段。
-- 边界:有效 query、失败 query、标签、话题、相关搜索线索目前只在结果和 run_context 中间接保留。
-
-## 7. 策略学习
-
-策略学习阶段回答:下一轮如何根据 run_context、表现和概念层事实调整策略。
-
-### 7.1 索引
-
-| 编号 | 接口/表 | 用途 | 状态 |
-|---|---|---|---|
-| L1 | `demand_find_task` | 任务级 run_context 和成本 | 已验证 |
-| L2 | `demand_task_oprate` | 调度和预算开关 | 源码定位 |
-| L3 | `demand_find_content_result` | 内容结果复盘 | 已验证 |
-| L4 | `demand_find_author` | 作者资产复盘 | 已验证 |
-| L5 | `open_aigc.global_category` | 长期概念分类 | 已验证 |
-| L6 | `open_aigc.global_element` | 长期概念元素 | 已验证 |
-| L7 | `open_aigc.element_classification_mapping` | 元素归类映射 | 已验证 |
-| L8 | `open_aigc.public.pattern_mining_execution` | Pattern V2 执行快照 | 已验证 |
-| L9 | `open_aigc.public.pattern_mining_element` | Pattern V2 元素快照 | 已验证 |
-| L10 | `open_aigc.public.pattern_itemset` | Pattern V2 itemset | 已验证 |
-| L11 | `open_aigc.public.pattern_itemset_item` | Pattern V2 itemset 成员 | 已验证 |
-| L12 | `loghubods.video_dimension_detail_add_column` | 视频表现回看 | 已验证 |
-
-### 7.2 详情
-
-#### L1. 数据库:`content-deconstruction-supply`,表:`demand_find_task`
-
-- 输入字段:`run_id`, `demand_content_id`, `status`, `created_at`, `token_coast`
-- 输出字段:执行状态、成本、需求关联。
-- 边界:只能看任务级结果,不能还原全量发现内容过程。
-
-#### L2. 数据库:`content-deconstruction-supply`,表:`demand_task_oprate`
-
-- 输入字段:`is_open`, `day_limit_coast`, `update_time`
-- 输出字段:是否开启、日成本限制。
-- 边界:策略控制数据,不是学习结论。
-
-#### L3. 数据库:`content-deconstruction-supply`,表:`demand_find_content_result`
-
-- 输入字段:`run_id`, `query`, `rank_no`, `channel`, `platform_content_id`, `process_trace`, `crawler_plan_id`, `produce_plan_id`, `publish_plan_id`, `web_html_url`, `source_evidence`
-- 输出字段:成功路径、平台、计划绑定、过程记录、来源证据。
-- 边界:没有失败待复看和淘汰原因结构化表;如果没有 `source_evidence` 或 source edge artifact,策略学习只能看到结果,不能 exact 回溯到 Pattern、Case 和分类树节点。
-
-#### L4. 数据库:`content-deconstruction-supply`,表:`demand_find_author`
-
-- 输入字段:`run_id`, `author_id`, `author_name`, `content_tags`, `elderly_ratio`, `elderly_tgi`, `is_good`, `channel`
-- 输出字段:作者表现和可复用标签。
-- 边界:后续需要与作品表现关联。
-
-#### L5. 数据库:`open_aigc`,表:`global_category`
-
-- 输入字段:`stable_id`, `name`, `source_type`, `path`, `parent_stable_id`, `category_nature`, `level`
-- 输出字段:分类树和稳定分类 ID。
-- 边界:上游概念层,非旧版实时输入。
-
-#### L6. 数据库:`open_aigc`,表:`global_element`
-
-- 输入字段:`id`, `name`, `source_type`, `belong_category_stable_id`, `element_sub_type`, `occurrence_count`
-- 输出字段:元素词和出现次数。
-- 边界:用于长期策略学习和 Pattern 生成。
-
-#### L7. 数据库:`open_aigc`,表:`element_classification_mapping`
-
-- 输入字段:`source_table`, `source_element_id`, `post_id`, `element_name`, `element_type`, `global_element_id`, `global_category_stable_id`, `classification_path`
-- 输出字段:元素到全局类目的映射。
-- 边界:用于理解上游元素如何进入 Pattern。
-
-#### L8. 数据库:`open_aigc PG`,表:`public.pattern_mining_execution`
-
-- 输入字段:`id`, `snapshot_date`, `is_current`, `status`, `post_count`, `category_count`, `element_count`, `topic_itemset_count`, `cross_itemset_count`, `start_time`, `end_time`, `error_message`
-- 输出字段:Pattern 执行状态和规模。
-- 边界:新版 DemandAgent 直接读取 PG Pattern V2;旧 PG -> MySQL bridge 仅作为历史风险说明。
-
-#### L9. 数据库:`open_aigc PG`,表:`public.pattern_mining_element`
-
-- 输入字段:`execution_id`, `post_id`, `source_table`, `source_element_id`, `element_type`, `element_sub_type`, `name`, `category_path`, `point_type`, `point_text`
-- 输出字段:元素快照和类目路径。
-- 边界:用于 PG Pattern V2 解释和后续回扣判断,不再服务旧 MySQL 兜底。
-
-#### L10. 数据库:`open_aigc PG`,表:`public.pattern_itemset`
-
-- 输入字段:`execution_id`, `combination_type`, `item_count`, `support`, `absolute_support`, `dimensions`, `is_cross_point`, `is_cross_layer`
-- 输出字段:Pattern 组合、支持度、维度。
-- 边界:新版 DemandAgent 读取它生成 `evidence_pack`;ContentFindAgent 通过 `evidence_pack/source_evidence` 间接消费,不直接连 PG。
-
-#### L11. 数据库:`open_aigc PG`,表:`public.pattern_itemset_item`
-
-- 输入字段:`itemset_id`, `layer`, `point_type`, `dimension`, `category_id`, `category_path`, `element_name`, `element_id`, `post_count`
-- 输出字段:itemset 中的元素项。
-- 边界:用于分析 Pattern 词和组合效果。
-
-#### L12. 数据库:`loghubods`,表:`video_dimension_detail_add_column`
-
-- 输入字段:`视频id`, `视频地址`, `标题`, `一级品类`, `二级品类`, `上传渠道`, `dt`
-- 表现字段:`当日分发曝光pv`, `当日分发回流uv`, `rov_t0`, `流量池曝光`, `流量池播放`, `流量池回流`, `推荐曝光`, `推荐播放`, `推荐回流`, `总日回流uv`
-- 输出字段:表现、曝光、播放、回流、标签。
-- 边界:宽表只列策略学习关键字段。
-
-## 8. 缺口清单
-
-本节吸收原环境缺口报告的缺口项。凡是没有真实表、真实 key、真实接口或只读验证证据的能力,都不能在产品或技术文档里写成“已接入”。
-
-### 8.1 P0 必须补齐
-
-#### 新版发现内容列表承载
-
-- 需要补:全量发现内容列表表或 API。
-- 业务用途:保存所有召回的视频、笔记、作者,包括入池、观察、淘汰和最终入选。
-- 当前状态:新版 DDL / schema registry 已定义为 `content_agent_discovered_content_items`;云库是否已建表并可写,按 P0 DB 验收确认。旧版未发现可承担该职责的真实表。
-- 中文注释:旧版只保存最终结果,无法复盘“未入选的发现内容”。
-
-#### 判断/淘汰日志承载
-
-- 需要补:规则包执行记录、硬门槛、软评分、淘汰原因、停止原因表或 API。
-- 业务用途:记录每个发现内容为什么继续、入池、观察、停止或淘汰。
-- 当前状态:新版 DDL / schema registry 已定义为 `content_agent_rule_decisions`;云库是否已建表并可写,按 P0 DB 验收确认。旧版未发现可承担该职责的真实表。
-- 中文注释:`process_trace` 只能粗略复盘,不能替代结构化规则日志。
-
-#### 来源关系承载
-
-- 需要补:数据源、seed、query、平台内容、作者、标签、游走路径关系表或 API;或先用 sidecar/source edge artifact 承载。
-- 业务用途:记录内容从哪里来、经过哪条路径来、由哪个规则放行,并从 `case_id/post_id` 回溯到分类树节点。
-- 当前状态:新版 DDL / schema registry 已定义为 `content_agent_source_path_records`;云库是否已建表并可写,按 P0 DB 验收确认。旧版未发现可承担该职责的真实表。
-- 中文注释:没有关系承载,策略学习无法按来源和路径归因;最终只剩 `platform_content_id + demand_content_id + process_trace` 时,无法 exact 追到 Pattern 和分类树。
-
-#### 搜索线索承载
-
-- 需要补:有效 query、失败 query、标签、话题、相关搜索线索表或 API。
-- 业务用途:保存下一轮可继续尝试的搜索线索,以及失败线索。
-- 当前状态:新版 DDL / schema registry 已定义为 `content_agent_search_clues`;云库是否已建表并可写,按 P0 DB 验收确认。旧版未发现可承担该职责的真实表。
-- 中文注释:旧版只在结果和 run_context 里间接留下 query,不够做系统学习。
-
-#### show 联调后端
-
-- 需要补:`VITE_API_BASE_URL`, `VITE_CONTENTFIND_API_BASE_URL`
-- 业务用途:让 show 从静态沙盘切到真实后端接口。
-- 当前状态:missing
-- 中文注释:当前前端只能展示策略和静态样例,不能跑真实链路。
-
-### 8.2 P1 生产增强
-
-#### TikHub fallback
-
-- 需要补:`TIKHUB_API_KEY`
-- 业务用途:Crawapi 抖音搜索异常、限流或不可用时做备用召回。
-- 当前状态:missing
-- 中文注释:URL 已定位,缺 key;不能写成已接入。
-
-#### OSS 过程链接
-
-- 需要补:`ALIYUN_OSS_ACCESS_KEY_ID`, `ALIYUN_OSS_ACCESS_KEY_SECRET`, `ALIYUN_OSS_BUCKET`, `ALIYUN_OSS_REGION`, `ALIYUN_OSS_PREFIX`, `ALIYUN_OSS_PUBLIC_BASE_URL`
-- 业务用途:保存任务日志、HTML 报告、截图、run_context 文件的可访问链接。
-- 当前状态:missing
-- 中文注释:旧版有上传能力线索,但当前缺真实 OSS 配置。
-
-#### 外部调度源
-
-- 需要补:`SCHEDULE_QUERY_API`, `SCHEDULE_QUERY_API_KEY`
-- 业务用途:从外部系统接收任务、定时需求或批量触发策略运行。
-- 当前状态:missing
-- 中文注释:当前没有真实调度入口。
-
-#### AIGC token 治理
-
-- 需要补:将旧版 fallback token 换成密钥平台托管值。
-- 业务用途:避免旧代码硬编码 token,保证后处理计划查询/绑定安全可维护。
-- 当前状态:source-only
-- 中文注释:这是安全治理,不是新增业务能力。
-
-### 8.3 P2 产品待验证
-
-#### 小红书笔记搜索、作者主页、作者笔记、话题标签接口
-
-- 当前状态:无旧版实测接口。
-- 中文注释:产品可以设计小红书路径,但技术文档不能写成已接入。
-
-#### 快手、B站、视频号、票圈平台适配接口
-
-- 当前状态:无旧版实测接口。
-- 中文注释:这些平台暂时不能复用抖音字段口径。
-
-#### 相关搜索、视频 tag、共创作者、相似作者、相似内容接口
-
-- 当前状态:无稳定字段或实测。
-- 中文注释:可以作为游走策略方向,但必须标为待验证。
-
-#### 养号推荐流账号、cookie/session、设备、代理/风控配置
-
-- 当前状态:无真实接入配置。
-- 中文注释:养号是产品意图源,不能伪装成已有旧版 DB/API backing。
-
-#### 策略实验、规则包版本、Prompt 版本、预算回写表/API
-
-- 当前状态:无真实承载。
-- 中文注释:策略学习需要这些承载,但当前没有真实表或 API。
-
-#### Pattern -> Item Set -> 多 Case
-
-- 当前状态:PG `pattern_itemset_post / pattern_itemset_item / pattern_mining_category` 字段存在,DemandAgent 新版已用于生成 `evidence_pack`;旧 MySQL 同名能力只作历史迁移说明。
-- 中文注释:新版重要来源只指 PG Pattern V2,不指旧 `topic_pattern_*` 兜底。
-
-## 9. 多平台 crawler 接口(crawler.aiddit.com)
-
-本节记录 2026-06-11 对 `crawler.aiddit.com` 10 个平台接口的真实抓包实测。机器可读台账见 [crawler_endpoints.registry.json](crawler_endpoints.registry.json)(接口级,共 26 条 endpoint),原始 capture 在 `/tmp/aiddit_captures/`。所有接口共用 `CONTENTFIND_API_CRAWAPI_BASE_URL`,限流间隔 ≥15s。
-
-2026-06-17 追加快手/视频号 URL 解包与 OSS v2 批量验证:完整 raw 和临时 URL 只保存在 gitignore 下的 `data/platform_url_probe/20260617_115219/`,提交文档只保留脱敏摘要。
-
-- 快手:10 个 query,60 条 item,50 条 selected 正片 URL;selected 来源为 detail/search 各 25 条。20 条 selected URL 轻量验证均为 `video/mp4` 且 HTTP 200/206;no-referer 调 OSS v2 后 20/20 成功。
-- 视频号:10 个 query,18 页,46 条 item,41 条 selected 正片 URL;selected 全部来自 `$.search.video_url_list[0].video_url`,host 为 `findermp.video.qq.com`。20 条 selected URL 轻量验证均为 `video/mp4` 且 HTTP 200/206;no-referer 调 OSS v2 后 20/20 成功。
-- 两个平台使用生产当前 payload(把 `referer` dict 传给 OSS v2)时均 20/20 `oss_upload_response_invalid`;抽样 body 显示 `status=10000`、`oss_object=null`,错误包含 `dict object has no attribute encode`。去掉 `referer` 字段后同一批 selected URL 成功转存,因此历史 invalid 更像 OSS v2 payload 兼容问题,不是 URL 字段拆错。
-- URL 解包规则必须先做候选分类和轻量验证,不能再把 `video_url_list[0]` 当唯一事实;快手推荐 detail 正片优先、search fallback,视频号推荐 findermp 正片优先,同时过滤 image/avatar/BGM/page/ad/material。
-
-状态标签:✅=本批实测 code=0 可用;⛔=失败/超时/不可达(附错误码);—=本平台无该能力或本批未抓到独立接口。
-
-### 9.1 平台能力矩阵
-
-| 平台 | 搜索 | 详情 | 作者作品 | 账号画像 | 视频可下载 |
-|---|---|---|---|---|---|
-| 抖音 douyin | ✅ `/dou_yin/keyword` | ✅ `/dou_yin/detail` | ✅ `/dou_yin/blogger` | ✅ `/dou_yin/re_dian_bao/account_fans_portrait`;内容画像 ⛔404 `video_like_portrait` | ✅(iOS UA+Referer 抖音) |
-| 快手 kuaishou | ✅ `/kuai_shou/keyword_v2` | ✅ `/kuai_shou/detail` | — 无独立接口,依赖搜索聚合 | ✅ `/kuai_shou/account_info` | ✅(Range+Referer 快手) |
-| 视频号 shipinhao | ✅ `/shi_pin_hao/keyword`(抖动需重试) | — | ⛔25011 `blogger` | ⛔10001 `account_info` | ✅(Referer channels.weixin) |
-| 小红书 xiaohongshu | ✅ `/xiao_hong_shu/keyword` | ✅ `/xiao_hong_shu/detail` | ⛔超时 `blogger` | — | — 图文为主(note_card 嵌套) |
-| B站 bilibili | ✅ `/bilibili/keyword` | ✅ `/bilibili/detail` | ⛔ 无作者接口 | — | ✅(m4s 分轨+Referer B站) |
-| 公众号 weixin | ✅ `/wei_xin/keyword` | ✅ `/wei_xin/detail` | ⛔ blogger 不可达,作者仅 biz | ✅ `/wei_xin/account_info`(仅昵称/biz补全,无作品列表) | — 图文,无 tag |
-| youtube | ✅ `/youtube/keyword` | ✅ `/youtube/detail` | — 无作者作品接口 | — | ⛔ detail 无视频地址(video_url_list=[]) |
-| github | ✅ `/github/keyword` | — | — | — | ⛔ 非视频(代码仓库,topics 即 tag) |
-| 知乎 zhihu | ⛔10000 全失败 | ⛔ | ⛔ | ⛔ | ⛔ |
-| 头条 toutiao | ⛔10000 全失败 | ⛔ | ⛔ | ⛔ | ⛔ |
-
-### 9.2 每平台一句话状态
-
-- **抖音**:全链路最完整,搜索/详情/作者作品/账号画像/视频下载均通;仅内容级人群画像 404 不可用。详见 `接口台账/抖音.md`。
-- **快手**:搜索/详情/账号info/视频下载通;无独立作者作品接口,author_to_works 暂依赖搜索聚合。详见 `接口台账/快手.md`。
-- **视频号**:搜索可用但服务端抖动需重试+退避;视频可下载;blogger 与 account_info 失败。详见 `接口台账/视频号.md`。
-- **小红书**:搜索/详情通,以图文为主(内容字段嵌在 note_card 下);blogger 超时不可落地。详见 `接口台账/小红书.md`。
-- **B站**:搜索/详情/视频下载通(m4s 分轨需合并);集合内无作者作品接口。详见 `接口台账/B站.md`。
-- **公众号**:搜索/详情/account_info 通,图文;作者仅 biz、blogger 不可达、整平台无 tag。详见 `接口台账/公众号.md`。
-- **youtube**:搜索/详情通,但 detail 无视频地址、无作者作品接口;hashtag 须从 body_text 正则抽。详见 `接口台账/youtube.md`。
-- **github**:仅搜索,且搜的是代码仓库非视频;topics 即 tag。详见 `接口台账/github.md`。
-- **知乎**:整平台 blocked,关键词搜索两次均 code=10000,下游全部阻断。详见 `接口台账/知乎.md`。
-- **头条**:整平台 blocked,关键词搜索两次均 code=10000,下游全部阻断。详见 `接口台账/头条.md`。
-
-### 9.3 缺口
-
-沿用「缺口统一放最后」的口径,本批 crawler 平台的明确缺口如下,均不能在产品或技术文档里写成"已接入":
-
-- **知乎 / 头条整平台不可用**:`/zhi_hu/keyword` 与 `/tou_tiao_hao/keyword` 两次抓包(含变参重试)均 `code=10000 / msg=未知错误 / data=null`,与参数无关,判定平台/接口侧不可用;detail/blogger/account_info/视频下载因搜索失败全部无样本,待上游修复复抓到 code=0 后再解锁登记。
-- **视频号 blogger / account_info 失败 + 搜索抖动**:`/shi_pin_hao/blogger` `code=25011`、`/shi_pin_hao/account_info` `code=10001`(仅传 account_id 被拒,需补 app_id 或 account_name);搜索首次 25011、重试才 code=0,必须带重试+退避。author_to_works 边当前不可落地。
-- **小红书 blogger 超时**:`/xiao_hong_shu/blogger` 请求超时 `error=timed out` 无响应,需超时重试+退避;author_to_works 边当前不可落地。
-- **B站无作者接口**:集合内无 author_to_works 数据源,作者维度仅作标识(以 `mid` 为准,`uid` 恒为 0 弃用)。
-- **公众号作者仅 biz**:search item 的 `biz/nick_name` 为空须从 url 正则抽 `__biz`;blogger 不可达、`account_info.publish_count=null` 无作品列表,author_to_works 走不通;整平台 `topic_list=null` 无 tag,video_to_hashtag 不可走。
-- **抖音内容画像 404**:`/dou_yin/re_dian_bao/video_like_portrait` 实测 HTTP 404 Not Found,内容级人群画像不可用,需上游确认正确 path 或下线,游走层不要挂该边(账号级画像 `account_fans_portrait` 仍可用)。
-- **youtube detail 无视频地址**:`/youtube/detail` 结构完整但 `video_url_list=[] / image_url_list=[]`,视频/图片文件不可经本平台接口直取,须另走字幕或其它通道。
-- **github 非视频**:`/github/keyword` 搜的是代码仓库(repo),无 video 字段,不能复用视频字段口径;`topics` 即 tag(多数为空)。

+ 0 - 484
数据接口与来源/01_DemandAgent输入合同.md

@@ -1,484 +0,0 @@
-# DemandAgent 给下游的迭代需求
-
-## 1. 结论先行
-
-新版 ContentFindAgent 不能再依赖旧 MySQL Pattern 反查机制来恢复 Pattern、Case 和分类树血缘。
-
-旧 MySQL `topic_pattern_*` 链路已删除弃用,不进入新版 `evidence_pack`,不作为下游回溯依据。旧字段只允许作为迁移前后的字段对照出现,用来说明它们在 PG Pattern V2 中被哪些字段替换。
-
-新版 DemandAgent 的唯一正式 Pattern 证据源是 PG `open_aigc.public` Pattern V2 主事实表。DemandAgent 输出的 `pattern_source_system` 唯一合法值是 `pg_pattern_v2`。
-
-为了新版能从需求出发稳定跑通数据源、Query、判断、游走、资产沉淀和策略学习,DemandAgent 或上游证据层需要把生成需求时已经知道的证据一起传给下游。
-
-更准确地说:DemandAgent 补 `demand_content.ext_data.evidence_pack` 是 Exact Mode 的必要条件,但不是充分条件。ContentFindAgent 还必须在领取需求、生成发现内容、判断筛选、最终入库时读取、继承并沉淀结构化 `source_evidence`,最终结果才能从 `post_id/platform_content_id` 回到具体 Pattern、itemset 和分类树节点。
-
-下游必须新增或扩展的核心字段:
-
-| 字段 | 作用 |
-| --- | --- |
-| `pattern_source_system` | DemandAgent 输出中固定且唯一合法值为 `pg_pattern_v2`,标记 Pattern 证据来自 PG Pattern V2 主事实 |
-| `pattern_execution_id` | 标记需求来自哪次 PG `pattern_mining_execution` |
-| `mining_config_id` | 标记来源 Pattern 使用的具体挖掘配置 |
-| `source_kind` | 标记需求来源是聚类结果、Pattern 频繁项集、直接 Case,还是其他来源 |
-| `case_id_type` | 标准化 `caseid` 的语义,例如本链路中应明确为 `post_id` |
-| `source_post_id` | 标记本条需求证据中的原始支撑帖子 |
-| `category_bindings` | 绑定分类节点,例如分类 ID、路径、层级、来源类型、点类型和维度 |
-| `element_bindings` | 绑定具体元素,例如元素名、元素类型、点类型、所属 post |
-| `itemset_ids` | 绑定 Pattern 频繁项集 |
-| `itemset_items[]` | 绑定 itemset 内每个 item 对应的分类节点、路径、点类型和维度 |
-| `support` / `absolute_support` | 记录 Pattern 支持度,判断它是不是稳定组合 |
-| `matched_post_ids` | 记录支撑 Pattern 的原始帖子或视频 |
-| `video_ids` / `case_ids` | 记录可作为 Case seed 的素材 ID;Pattern 来源下二者都是 `post_id` 语义 |
-| `decode_case_ids` | PG-only 当前固定为空数组的兼容字段,不参与 Pattern exact 闭合 |
-| `seed_terms` | 记录真正给 Query 使用的下层特征或元素词 |
-| `source_evidence` | ContentFindAgent 从 `evidence_pack` 继承并追加运行态字段后形成的下游结果证据,不由 DemandAgent 直接输出 |
-| `source_certainty` / `validation_status` | 标记证据是否已经 DB 强校验通过 |
-| `run_id` / `demand_content_id` / `demand_task_id` | `run_id` 是 CFA 本次运行 ID;`demand_content_id` 是 DemandAgent 需求主键;`demand_task_id` 在云端单表模式下可以为空,只作为上游任务线索 |
-
-V1 不一定马上改复杂 DB schema。可以先在 `demand_content.ext_data` 里增加一个结构化 `evidence_pack`,先把证据传下来。
-
-MVP 边界:DemandAgent 只输出 PG `pattern_itemset.scope='topic'` 的分类 Pattern 证据。`topic_element`、script、paragraph、group scope 暂不输出,因为它们不能保证每条证据都有 `category_id/category_path` 回到分类树节点。
-
-## 2. 新版下游到底需要什么
-
-新版 ContentFindAgent 不是只拿一个需求词去搜内容。它需要知道这个需求从哪里来、为什么成立、应该怎么生成 Query、判断结果应该绑定回哪棵分类树,以及最终资产入库前是否可追溯。
-
-下游需要的对象主要有五类。
-
-| 对象 | 下游含义 | 为什么需要 |
-| --- | --- | --- |
-| Pattern | 分类或元素的稳定组合 | 用来判断需求是不是有结构支撑,而不是单条素材偶然出现 |
-| Case | 支撑 Pattern 或需求的原始素材 | 用来查看素材原文、解构点、平台调性和后续判断证据 |
-| 分类 / 元素绑定 | 需求对应分类树的哪个父节点或子节点 | 所有游走、判断和入库结果都要能绑定回分类树 |
-| 支撑素材 | `matched_post_ids`、`video_ids`、Case ID 等 | 用来做 Query 输入、Case 回扣、判断规则证据和策略学习 |
-| run_context | 一路记录从需求到结果的路径 | 策略学习、失败复盘、入库追溯都需要它 |
-
-三类“有需求”的数据源都应该作为 DemandAgent 下游。它们不能只给一个自然语言需求名,还要给来源证据。不然后续即使找到内容,也说不清这个内容为什么服务于当初那个需求。
-
-所有资产入库前至少要保留:
-
-| 必留信息 | 用途 |
-| --- | --- |
-| 来源路径 | 证明资产从哪个需求、Pattern、Case 或平台线索来 |
-| 判断结果 | 证明为什么继续、入池、观察、淘汰 |
-| 分类或元素绑定 | 证明资产属于分类树中的哪个节点 |
-| 平台调性说明 | 证明资产适合当前平台,不是跨平台硬套 |
-| run_context | 支持策略学习和后续复盘 |
-
-## 3. DemandAgent 当前给了什么
-
-### 3.1 DemandItem
-
-DemandAgent 当前生成的 `DemandItem` 是 LLM / tool 产生的备选业务需求,不是最终给下游消费的强校验证据。
-
-代码证据:
-
-- `/Users/samlee/Documents/works/DemandAgentNew/examples/demand/demand_build_agent_tools.py:37`
-- `/Users/samlee/Documents/works/DemandAgentNew/examples/demand/demand_build_agent_tools.py:113`
-
-当前保留字段:
-
-| 字段 | 当前含义 |
-| --- | --- |
-| `element_names` | 需求名称来源,后续会拼成 `demand_content.name` |
-| `reason` | 生成需求的理由 |
-| `desc` | 需求描述,后续写入 `suggestion` |
-| `type` | 来源类型,例如元素、关系、分类 |
-| `evidence_refs` | LLM 声明的证据引用发现内容,只能作为 DB 强校验输入,不能直接给下游当真实证据 |
-
-`evidence_refs` 里即使出现 `itemset_ids/source_post_id/seed_terms`,也只是声明。DemandAgent 必须用 PG DB 重新读取和校验,再升级成 `demand_content.ext_data.evidence_pack`。任一最终字段来自旧 MySQL `topic_pattern_*` 时,该 DemandItem 必须 rejected。
-
-### 3.2 demand_content
-
-新版 DemandAgent V1 同时保留两种受控输出模式:
-
-- `local_json`:本地回归 / fixture 模式,只写本地 `demand_content.json` 等 JSON 文件,不作为 CFA 生产读取来源。
-- `mysql_demand_content`:云端测试服模式,只写 MySQL `content-deconstruction-supply.demand_content` 单表。
-
-两种模式给下游的正式证据载体都必须是 `demand_content.ext_data.evidence_pack`。云端单表模式不新建 `demand_task`,因此 `demand_task_id` 可以为空;CFA 不能把它当作必填主键。P0F 以后,CFA 正式需求读取应使用 MySQL `demand_content`,本地 JSON 只保留为回归 fixture。
-
-代码证据:
-
-- `/Users/samlee/Documents/works/DemandAgentNew/examples/demand/run.py`
-- `/Users/samlee/Documents/works/DemandAgentNew/examples/demand/evidence_pack_builder.py`
-- `/Users/samlee/Documents/works/DemandAgentNew/examples/demand/local_output_sink.py`
-- `/Users/samlee/Documents/works/DemandAgentNew/examples/demand/mysql_demand_content_sink.py`
-
-当前本地 `demand_content.json` 字段:
-
-| `demand_content` 字段 | 来源 |
-| --- | --- |
-| `merge_leve2` | 当前执行品类 |
-| `name` | `element_names` 逗号拼接 |
-| `reason` | DemandItem.reason |
-| `suggestion` | DemandItem.desc |
-| `score` | 按 `name` 拆分后查权重并求平均 |
-| `ext_data` | JSON 对象,包含旧业务字段 `reason/desc/type/video_ids`,以及新版强校验字段 `evidence_pack` |
-| `dt` | 当天日期 |
-
-注意:这里的字段名是历史真实表字段 `merge_leve2`,不是 `merge_level2`。CLI 或函数参数可以使用 `merge_level2` 作为变量名,但落到 `demand_content`、本地 JSON、下游读取契约时必须保持 `merge_leve2`。
-
-`video_ids` 在新版 PG-only 链路中来自 DB 校验后的 `evidence_pack.video_ids/matched_post_ids`。`pattern_execution_id` 指 PG `pattern_mining_execution.id`,不是旧 MySQL execution,也不是 DemandAgent 本地 run id。
-
-真实 DB 中 `demand_content.ext_data` 是 MySQL JSON 字段,可以承载完整 `evidence_pack`;但当前生产样例仍多为 `reason/desc/type/video_ids`,还没有稳定落下新版 PG `evidence_pack`。这就是本次需求要补齐的输出契约。
-
-### 3.3 dwd_multi_demand_pool_di
-
-新版 local MVP 不写 Hive,只生成本地 `dwd_multi_demand_pool_di.json` 镜像。这个文件不是给 ContentFindAgent 做 exact 回溯的主输入。
-
-代码证据:
-
-- `/Users/samlee/Documents/works/DemandAgentNew/examples/demand/data_query_tools.py:128`
-- `/Users/samlee/Documents/works/DemandAgentNew/examples/demand/data_query_tools.py:194`
-- `/Users/samlee/Documents/works/DemandAgentNew/examples/demand/data_query_tools.py:227`
-
-当前映射字段:
-
-| 字段 | 当前含义 |
-| --- | --- |
-| `strategy` | 策略名 |
-| `demand_id` | 派生 ID |
-| `demand_name` | 需求名 |
-| `weight` | 分数 |
-| `type` | 从 `ext_data.type` 解析 |
-| `video_count` | `video_ids` 数量 |
-| `video_list` | `video_ids` |
-| `extend` | 当前主要保留品类 |
-| `dt` | 分区日期 |
-
-这一层不增加 `evidence_pack`,不承载 Pattern exact evidence。ContentFindAgent 的主输入只能是 `demand_content.json` 或未来 DB `demand_content.ext_data.evidence_pack`。
-
-## 4. 旧 MySQL Pattern 链路为什么删除弃用
-
-旧版 ContentFindAgent 的 Case 反查工具是 `get_goodcase_topic_point`。
-
-代码证据:
-
-- `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/tools/get_goodcase_topic_point.py:91`
-- `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/tools/get_goodcase_topic_point.py:96`
-- `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/tools/get_goodcase_topic_point.py:144`
-
-旧链路曾经是:
-
-```text
-features
--> topic_pattern_element.name = feature
--> post_id 交集
--> workflow_decode_task_result.channel_content_id
--> purpose_points / key_points / inspiration_points
-```
-
-这条链路已删除弃用,原因如下。
-
-| 问题 | 说明 |
-| --- | --- |
-| 不带 `execution_id` | 同一个词可能出现在多个 Pattern execution,不能证明来自当前需求那次执行 |
-| 只查元素名 | DemandAgent 当前有些需求名其实是分类节点,旧 CFA 只查 `topic_pattern_element.name` 会漏掉 |
-| 不查 itemset | 无法恢复 `itemset_id`、support、absolute support、matched posts |
-
-更关键的是,新版 DemandAgent 已切换为 PG Pattern V2 主事实。旧 MySQL `topic_pattern_element` 即使能查到 post,也不是新版 `evidence_pack` 的合法来源。
-
-因此旧链路不能作为任何兜底链路写入新版主血缘。它只保留在本文档中作为“删除弃用字段如何被 PG 字段替换”的迁移说明。
-
-## 5. 旧 MySQL 字段删除弃用与 PG 替换关系
-
-| 旧 MySQL 表/字段 | 新状态 | 新 PG 替换来源 |
-| --- | --- | --- |
-| `topic_pattern_execution.id` | 删除弃用 | `pattern_mining_execution.id`,即新版 `pattern_execution_id` |
-| `topic_pattern_execution.status` | 删除弃用 | `pattern_mining_execution.status` |
-| `topic_pattern_execution.cluster_name/merge_leve2/platform/account_name/post_limit/min_absolute_support/mining_configs` | 删除弃用为 Pattern 证据字段 | PG execution 是全局 Pattern 快照;业务侧 `merge_leve2/platform` 只保留在 Demand 输出上下文,不作为 Pattern 主事实 |
-| `topic_pattern_mining_config.id` | 删除弃用 | `pattern_mining_config.id`,即 `mining_config_id` |
-| `topic_pattern_mining_config.dimension_mode/target_depth` | 删除弃用 | 从 `pattern_mining_config.params` 解析或派生 |
-| `topic_pattern_category.id` | 删除弃用 | `pattern_mining_category.id` |
-| `topic_pattern_category.path` | 删除弃用 | `pattern_mining_category.path` |
-| `topic_pattern_category.category_nature` | 删除弃用 | `pattern_mining_category.category_nature` 和 `pattern_mining_category.classified_as` |
-| `topic_pattern_element.id/post_id/point_type/point_text/element_type/name/description/category_id/category_path` | 删除弃用 | `pattern_mining_element.id/post_id/point_type/point_text/element_type/name/description/category_id/category_path` |
-| `topic_pattern_itemset.id` | 删除弃用 | `pattern_itemset.id` |
-| `topic_pattern_itemset.mining_config_id` | 删除弃用 | `pattern_itemset.mining_config_id` |
-| `topic_pattern_itemset.support/absolute_support` | 删除弃用 | `pattern_itemset.support/absolute_support` |
-| `topic_pattern_itemset.matched_post_ids` | 删除弃用 | 从 `pattern_itemset_post` 按 `execution_id + itemset_id` 聚合生成 `matched_post_ids` |
-| `topic_pattern_itemset_item.id/itemset_id/point_type/dimension/category_id/category_path/element_name` | 删除弃用 | `pattern_itemset_item.id/itemset_id/point_type/dimension/category_id/category_path/element_name`,并可补充 PG 新字段 `layer/element_id/post_count` |
-
-新版 `evidence_pack` 中不允许出现旧 MySQL 专属来源。字段名可以保留通用业务含义,例如 `itemset_ids`、`matched_post_ids`、`category_bindings`,但它们的值必须来自 PG Pattern V2。
-
-## 6. 必须补给下游的字段清单
-
-| 字段 | 字段意义 | 下游用途 | 为什么不能靠 CFA 自己补 | 缺失风险 |
-| --- | --- | --- | --- | --- |
-| `pattern_source_system` | Pattern 证据来源系统,DemandAgent 输出中唯一合法值为 `pg_pattern_v2` | 判断反查目标是 PG Pattern V2 主事实 | CFA 只看需求单无法知道证据来自哪套 Pattern 表 | 把旧 MySQL 删除弃用链路误当 PG 主事实 |
-| `pattern_execution_id` | 当前需求来源的 Pattern 执行 ID | 数据源详情、Pattern 回查、策略学习 | CFA 只能按词反查,无法知道当时 execution | 同名词跨 execution 混淆 |
-| `mining_config_id` | 当前 itemset 所属挖掘配置 ID | 区分同一 execution 下不同 Pattern 配置 | CFA 事后只靠 post 反查会得到多个配置下的 itemset | 无法确定具体 Pattern 配置 |
-| `source_kind` | 来源类型:聚类结果、Pattern 频繁项集、直接 Case 等 | 决定后续流程:是否进 Query,是否先游走或先判断 | 只看 `type` 不够,不能区分真实来源路径 | 不同来源走错流程 |
-| `case_id_type` | `caseid` 的 ID 语义,Pattern case 链路中应明确为 `post_id` | 稳定对齐 `post_id/channel_content_id/platform_content_id` | CFA 只能猜这个 ID 是结果表 id、decode id 还是 post id | join 语义混乱 |
-| `source_post_id` | 本条证据里的原始支撑帖子 ID | 从最终 case 回到上游 matched post | 单个 post 可能支撑很多 itemset,CFA 不能知道哪条是来源 post | 只能得到多条可能路径,不能 exact |
-| `category_bindings` | 分类节点绑定,当前包含 `itemset_id/itemset_item_id/category_id/category_name/category_path/category_full_path/category_source_type/category_level/point_type/dimension/element_name` | 所有结果绑定回分类树节点 | CFA 按元素名查不到分类节点,且同名分类可能多处存在 | 入库后无法解释属于哪个分类 |
-| `element_bindings` | 元素绑定,当前包含 `itemset_id/itemset_item_id/category_id/point_type/dimension/element_name/matched_element_count/matched_post_count/matched_post_ids/sample_elements` | 作为 Query 输入素材、判断回扣证据 | CFA 可以按词查元素,但不知道哪个是 DemandAgent 采用的 | Query seed 可能跑偏 |
-| `itemset_ids` | Pattern 频繁项集 ID | 回查 Pattern 组合和稳定性 | CFA 旧链路不查 itemset | 不能证明 Pattern 是组合,不是散词 |
-| `itemset_items[]` | itemset 内每个 item 对应的 `category_id/category_path/point_type/dimension/element_name` | 直接还原 Pattern chip 到分类树节点 | CFA 只靠 `itemset_id` 还要二次猜 item 和节点 | 不能稳定解释具体命中的分类树节点 |
-| `support` | 相对支持度 | 判断 Pattern 是否稳定 | 需求池当前没有保存 | 无法按稳定性排序或过滤 |
-| `absolute_support` | 绝对支撑素材数 | 判断有多少素材支撑 | 需求池当前没有保存 | 不能区分单例和稳定模式 |
-| `matched_post_ids` | 支撑 itemset 的帖子 ID 列表 | 找 Case、看原始素材、做回扣判断 | `video_ids` 不等价于 itemset 的 matched posts | Case 来源不准,支撑证据不准 |
-| `video_ids` / `case_ids` | 可直接作为 Case seed 的素材 ID;Pattern 来源下固定是 `post_id` 语义 | 数据源 Case、Query 输入、判断证据 | CFA 可偶尔从 decode 表命中,但不稳定 | 如果语义不清,容易把 post_id 和 decode row id 混用 |
-| `decode_case_ids` | PG-only 当前固定为空数组的兼容字段 | 明确 decode row ID 不参与本次 Pattern exact 闭合 | 本次主链路不读旧 decode-case 表 | 不能把 decode 缺失误判成 Pattern 证据失败 |
-| `seed_terms` | 真正用于 Query 的下层特征或元素词 | Query 输入素材 | `name` 可能是分类、关系或宽泛表达,不一定适合作 Query | Query 输入不稳定 |
-| `source_evidence` | 下游最终结果继承的结构化证据,不是 DemandAgent 输出字段 | 写入 `demand_find_content_result` 或 source edge artifact | DemandAgent 只能给 `evidence_pack`,无法替下游结果落库 | 最终结果又断在 `process_trace` 文本 |
-| `source_certainty` | 证据可信度,进入下游时必须为 `db_validated` | 下游判断是否能 exact 回溯 | CFA 无法判断 LLM 声明是否已被 DB 校验 | 把备选证据误当真实证据 |
-| `validation_status` | DemandAgent 证据校验状态,进入需求池时必须为 `passed` | 下游只消费通过校验的任务 | CFA 事后补查无法恢复生成时的拒绝原因 | 失败证据流入下游 |
-| `run_id` | 贯穿下游任务的 run_context | 策略学习、失败复盘 | CFA 可创建自己的 run_context,但不能补上游生成过程 | 上下游链路断开 |
-| `demand_task_id` | DemandAgent 侧任务 ID | 回查生成日志和任务状态 | CFA 无法凭需求名可靠找到唯一任务 | 无法定位上游运行 |
-| `demand_content_id` | 下游领取的需求 ID | CFA 主任务绑定 | 这个字段 CFA 已能拿到,但应进入统一证据包 | run_context 不统一 |
-
-## 7. 检查过程与证据
-
-### 7.1 DemandAgent 文档审计
-
-`/Users/samlee/Documents/works/DemandAgent工程文档集/demand-agent-analysis.md:432` 已写明 DemandItem 到 `demand_content` 的映射:
-
-| 目标字段 | 来源 |
-| --- | --- |
-| `merge_leve2` | 当前执行品类 |
-| `name` | `element_names` 拼接 |
-| `reason` | DemandItem.reason |
-| `suggestion` | DemandItem.desc |
-| `score` | 权重 JSON 查分求平均 |
-| `ext_data` | `reason/desc/type/video_ids` |
-| `dt` | 当天日期 |
-
-历史文档曾把 `workflow_decode_task_result` 作为辅助回填逻辑;PG-only MVP 不再依赖它闭合 Pattern 证据。Agent 输出主要靠 Prompt 约束,最终是否进入需求池由 DemandAgent 代码按 PG DB 强校验决定。
-
-### 7.2 DemandAgent 代码审计
-
-DemandAgent 新版代码里直接读取 PG Pattern V2 的分类、元素、itemset 和支撑帖子字段。
-
-代码证据:
-
-- PG repository:`/Users/samlee/Documents/works/DemandAgentNew/examples/demand/pg_pattern_repository.py`
-- PG tool adapter:`/Users/samlee/Documents/works/DemandAgentNew/examples/demand/pattern_builds/pg_pattern_service.py`
-- Evidence 强校验:`/Users/samlee/Documents/works/DemandAgentNew/examples/demand/evidence_pack_builder.py`
-
-关键字段包括:
-
-| 表 | 关键字段 |
-| --- | --- |
-| `pattern_mining_execution` | `id`, `status`, `topic_itemset_count`, `post_count`, `category_count`, `element_count` |
-| `pattern_mining_config` | `id`, `execution_id`, `algorithm`, `scope`, `name`, `params` |
-| `pattern_itemset` | `id`, `execution_id`, `mining_config_id`, `scope`, `support`, `absolute_support`, `dimensions` |
-| `pattern_itemset_post` | `execution_id`, `itemset_id`, `post_id` |
-| `pattern_itemset_item` | `itemset_id`, `point_type`, `dimension`, `category_id`, `category_path`, `element_name` |
-| `pattern_mining_category` | `id`, `execution_id`, `source_type`, `path`, `level`, `parent_id`, `classified_as` |
-| `pattern_mining_element` | `execution_id`, `post_id`, `source_table`, `point_type`, `element_type`, `name`, `category_id`, `category_path` |
-
-也就是说,PG 主事实表已经具备 DemandAgent 输出下游证据链所需字段。新版问题不是“上游没有”,而是 DemandAgent 必须把这些字段校验后写进 `demand_content.ext_data.evidence_pack`,并要求下游继续继承。
-
-### 7.3 旧 ContentFindAgent 代码审计
-
-旧 CFA 的 `get_goodcase_topic_point` 只按 `topic_pattern_element.name` 精确匹配。
-
-关键 SQL:
-
-```sql
-SELECT DISTINCT post_id
-FROM topic_pattern_element
-WHERE name = %s
-```
-
-这个 SQL 不带 `execution_id`、`category_id`、`itemset_id`,也不查分类树节点。更重要的是,它读取的是已删除弃用的旧 MySQL `topic_pattern_element`,不能进入新版 PG `evidence_pack`。
-
-### 7.4 真实 DB 只读抽样
-
-本轮只做了只读查询,没有写 DB,也没有输出 secret。
-
-PG Pattern V2 主事实表只读校对结论:
-
-| PG 表 | 行数 | 字段结论 |
-| --- | --- |
-| `pattern_mining_execution` | 7 | 有 `id/status/snapshot_date/is_current/is_retired/post_filter/post_count/classify_execution_id/category_count/element_count/topic_itemset_count/topic_element_itemset_count` |
-| `pattern_mining_config` | 45 | 有 `id/execution_id/algorithm/scope/name/params/transaction_count/result_count/input_bucket_key` |
-| `pattern_mining_category` | 25816 | 有 `id/execution_id/source_stable_id/source_type/name/description/path/level/parent_id/category_nature/classified_as` |
-| `pattern_mining_element` | 1950755 | 有 `id/execution_id/post_id/source_table/source_element_id/element_type/name/category_id/category_path/point_type/point_text/topic_point_id` |
-| `pattern_itemset` | 449216 | 有 `id/execution_id/mining_config_id/scope/support/absolute_support/dimensions/is_cross_point/is_cross_layer`,没有 `matched_post_ids` 列 |
-| `pattern_itemset_item` | 1808629 | 有 `id/itemset_id/layer/point_type/dimension/category_id/category_path/element_name/element_id/post_count` |
-| `pattern_itemset_post` | 3910262 | 有 `itemset_id/post_id/execution_id`,是新版 `matched_post_ids` 的唯一来源 |
-
-需求池真实表只读校对结论:
-
-| 表 | 行数 | 字段 |
-| --- | --- | --- |
-| `demand_content` | 29888 | `id/merge_leve2/name/reason/suggestion/ext_data/score/dt/create_time/update_time` |
-| `demand_task` | 1987 | `id/execution_id/name/platform/status/log/create_time/update_time` |
-
-`demand_content.ext_data` 是 JSON 字段,可以承载完整 `evidence_pack`。当前生产样例仍主要是 `desc/reason/type/video_ids`,没有稳定出现新版 `pg_pattern_v2` 的 `evidence_pack`,所以这仍是待补齐的输出契约。
-
-### 7.5 subagent 交叉验证
-
-本轮拆了三个 subagent 交叉验证。
-
-| subagent | 验证范围 | 结论 |
-| --- | --- | --- |
-| PG/MySQL DB 字段侧 | PG Pattern V2、旧 MySQL `topic_pattern_*`、需求池表字段 | PG V2 能支撑新版证据链;旧 MySQL 字段必须删除弃用,只作为迁移对照 |
-| DemandAgent 代码侧 | PG repository、tool adapter、`evidence_pack_builder`、local output | 当前主路径读取 PG Pattern V2 并写本地 JSON,`evidence_pack.pattern_source_system` 为 `pg_pattern_v2` |
-| ContentFindAgentNew 下游侧 | `source_context.ext_data.evidence_pack`、`source_evidence`、校验器 | 下游需要继承 DemandAgent 的 PG `evidence_pack`,再追加运行态 `source_evidence` 字段 |
-
-三个方向结论一致。
-
-## 8. 建议的交付方式
-
-V1 建议先不要大改表结构,先扩展 `demand_content.ext_data`。
-
-推荐结构:
-
-```json
-{
-  "reason": "...",
-  "desc": "...",
-  "type": "...",
-  "video_ids": ["51978710"],
-  "evidence_pack": {
-    "pattern_source_system": "pg_pattern_v2",
-    "source_kind": "pattern_itemset",
-    "case_id_type": "post_id",
-    "source_post_id": "51978710",
-    "pattern_execution_id": 581,
-    "mining_config_id": 2082,
-    "itemset_ids": [1608352],
-    "itemset_items": [
-      {
-        "itemset_id": 1608352,
-        "category_id": 76006,
-        "category_path": "/理念/观念/个人观念/情感认同/国家民族认同/爱国情感",
-        "point_type": null,
-        "dimension": "需求",
-        "element_name": null
-      }
-    ],
-    "category_bindings": [],
-    "element_bindings": [],
-    "support": 0.0045734552921411235,
-    "absolute_support": 49,
-    "matched_post_ids": ["51978710"],
-    "video_ids": ["51978710"],
-    "case_ids": ["51978710"],
-    "decode_case_ids": [],
-    "seed_terms": ["爱国情感", "人物故事"],
-    "run_id": "...",
-    "demand_task_id": 123,
-    "demand_content_id": 456,
-    "source_certainty": "db_validated",
-    "validation_status": "passed"
-  }
-}
-```
-
-字段可以分批补齐:
-
-| 阶段 | 建议 |
-| --- | --- |
-| V1 必须 | `pattern_source_system`, `source_kind`, `case_id_type`, `source_post_id`, `pattern_execution_id`, `mining_config_id`, `itemset_ids`, `itemset_items[]`, `category_bindings`, `element_bindings`, `support`, `absolute_support`, `matched_post_ids`, `video_ids/case_ids`, `seed_terms`, `run_id`, `source_certainty`, `validation_status` |
-| V1 兼容字段 | `decode_case_ids`,PG-only 当前固定为空数组;如后续重新引入 decode row ID,需要另做下游契约 |
-| V1.1 增强 | 独立证据表、下游完整判断日志、topic_element 后续分类树补齐方案 |
-| 后续再拆表 | 如果 `evidence_pack` 变大,再考虑独立证据表或关系表 |
-
-注意:本次 MVP 只下发 `source_kind=pattern_itemset` 且 PG `scope='topic'` 的需求;如果来源不是该类 Pattern 频繁项集,暂不进入 exact 输出。
-
-### 8.1 DemandAgent 必须保证的真实性边界
-
-给下游 ContentFindAgent 的任务证据链必须真实。LLM 只能在 `evidence_refs` 里声明引用,不能直接决定最终证据。
-
-当前 MVP 中,LLM 生成需求时仍只写 `evidence_refs` 引用声明;DemandAgent 代码会基于 PG DB 只读校验并升级为 `evidence_pack`。推荐 `evidence_refs` 形态如下:
-
-```json
-{
-  "source_kind": "pattern_itemset",
-  "source_tool": "get_itemset_detail",
-  "itemset_ids": [1608352],
-  "category_ids": [76006, 76848],
-  "source_post_id": "51978710",
-  "case_ids": {
-    "pattern_itemset": ["51978710"]
-  },
-  "seed_terms": ["爱国情感", "人物故事"]
-}
-```
-
-LLM 最好显式声明 `source_kind=pattern_itemset`;如果漏填但已经给出 `itemset_ids` 或 `source_tool=get_itemset_detail`,DemandAgent 内部可以安全补齐为 `pattern_itemset`,然后继续真实 DB 强校验。`source_post_id` 必须来自 `get_itemset_detail` 的 `post_ids/matched_post_ids` 或已查询过的 `get_post_elements` 结果。`case_ids` 在待复看阶段可以按 `source_kind` 分组;最终 `evidence_pack.case_ids` 对 Pattern 来源固定为 post_id 列表,decode 表行 ID 另放 `decode_case_ids`。
-
-DemandAgent 代码必须按真实 DB 校验并补齐 `evidence_pack`。对于 `source_kind=pattern_itemset` 的需求,必须确认:
-
-| 校验项 | 必须满足 |
-| --- | --- |
-| `pattern_execution_id` | PG `pattern_mining_execution.id` 必须存在且 `status='success'` |
-| `source_kind` | 最终 `evidence_pack` 必须是 `pattern_itemset`;LLM 漏填时可由 `itemset_ids/source_tool` 安全推断,不是 Pattern itemset 则拒绝 |
-| `itemset_ids` | 每个 itemset 都属于当前 PG execution,且 `pattern_itemset.scope='topic'`,能查到 `mining_config_id/support/absolute_support/matched_post_ids` |
-| `mining_config_id` | 必须来自 PG `pattern_itemset.mining_config_id`,并能 join 到同一 execution 且 `scope='topic'` 的 `pattern_mining_config` |
-| `source_post_id` | 必须在对应 itemset 的 `pattern_itemset_post.post_id` 聚合结果中 |
-| `matched_post_ids` | 必须由 PG `pattern_itemset_post` 聚合得到,非空,`COUNT(DISTINCT post_id)` 必须等于 `absolute_support` |
-| `itemset_items[]` | 必须能从 PG `pattern_itemset_item` 查到,并能 join 到当前 execution 的 `pattern_mining_category` |
-| `category_bindings` | `category_id` 必须属于当前 `pattern_execution_id`,不能跨 execution 借节点 |
-| `element_bindings` | 必须能用 `execution_id + source_post_id + category_id + point_type + dimension/element_type + element_name(如有)` 精确绑定到 PG `pattern_mining_element`,且限定 `source_table='post_decode_topic_point_element'` |
-| `source_certainty` / `validation_status` | 进入 `demand_content` 时只能是 `db_validated` / `passed` |
-
-在当前官方 `local_json` 和 `mysql_demand_content` 两种路径中,任一必需字段查不到、跨 execution、post 不在 matched posts、category 不在当前 execution、source post 精确 element binding 不成立,当前 DemandItem 都失败,不写入下游可消费的 `demand_content`。本地模式失败项进入 `rejected_demand_items.json`;云端单表模式不写入 `demand_content` 成功记录。
-
-任一最终证据字段来自旧 MySQL `topic_pattern_*` 时,同样必须 rejected。新版 exact evidence 不允许 MySQL 兜底。
-
-对 `source_kind=pattern_itemset` 的需求,`case_ids` 按 `post_id` 语义直接等于已校验的 `matched_post_ids`,因为 PG Pattern V2 的主支撑关系来自 `pattern_itemset_post`;兼容字段 `decode_case_ids` 当前固定为空数组。`source_post_id/itemset_ids/itemset_items/mining_config_id/matched_post_ids/support/absolute_support` 不可为空且必须 DB 校验通过。
-
-### 8.2 V1 双输出方式
-
-V1 有两种受控输出方式:
-
-- `local_json`:通过 `examples.demand.run_existing_execution_local` 读取已有 PG `pattern_mining_execution.id`,把输出写到本地,只作为回归 fixture。
-- `mysql_demand_content`:通过 `examples.demand.run_existing_execution_mysql` 读取已有 PG `pattern_mining_execution.id`,只写 MySQL `demand_content` 单表,不写 `demand_task`、Hive、PG 或其他 MySQL 表。
-
-本地输出目录:
-
-```text
-examples/demand/test_output_data/{run_id}/
-  run_manifest.json
-  demand_task.json
-  demand_items.json
-  rejected_demand_items.json
-  demand_content.json
-  dwd_multi_demand_pool_di.json
-  feature_point_data.json
-  intermediate/result/
-  intermediate/data/
-  .run_context/
-  output/
-```
-
-其中只有 `demand_content.json[].ext_data.evidence_pack` 是给下游 exact evidence 的新增载体;`dwd_multi_demand_pool_di.json` 和 `feature_point_data.json` 只做原业务字段镜像,不增加 `evidence_pack`。
-
-云端测试服输出只看 `content-deconstruction-supply.demand_content.ext_data.evidence_pack`。CFA 领取真实需求池时,应优先从该 MySQL 单表解析证据包。
-
-### 8.3 ContentFindAgent 接到需求后必须做什么
-
-ContentFindAgent 后续领取 `demand_content` 时,不能只取 `id/name/suggestion/score/merge_leve2/dt`,还必须解析 `ext_data.evidence_pack`。
-
-它需要把 `source_kind/source_post_id/pattern_execution_id/mining_config_id/itemset_ids/itemset_items/category_bindings/matched_post_ids/source_certainty` 注入数据源、Query、判断、游走、入库上下文。最终结果必须保存结构化 `source_evidence` 到 `demand_find_content_result` 或 sidecar/source edge artifact。
-
-ContentFindAgentNew 生成 `source_evidence` 时,需要继承 DemandAgent 的 `evidence_pack`,并追加运行态字段:`discovery_start_source/previous_discovery_step/origin_path_id/search_query_id/query/search_query_generation_method/discovered_platform_content_id/discovery_relation/source_path_record_ids`。这些字段不由 DemandAgent 预写完整。当前 CFA validator 已强校验 `discovery_start_source/previous_discovery_step/origin_path_id/discovered_platform_content_id`,并校验 `source_evidence` 与 `evidence_pack` 的 `pattern_source_system/pattern_execution_id/source_post_id/case_id_type/mining_config_id/source_certainty/validation_status/itemset_ids/itemset_items/matched_post_ids/seed_terms` 保持一致;`search_query_id/query/search_query_generation_method/discovery_relation/source_path_record_ids` 当前由生成逻辑追加,后续如要作为硬门槛,需要在 CFA validator 中继续加严。
-
-如果新找到的 `platform_content_id/post_id` 不在 DemandAgent 的 `source_post_id/matched_post_ids` 中,只能标记为 `derived` 或 `discovered_content_item`,不能声称它 exact 属于某个 Pattern 节点。除非下游额外完成新 post 的解构和分类绑定,否则 DemandAgent 的证据只能证明“这个需求来源于某 Pattern”,不能证明“这个新 post 属于这个 Pattern”。
-
-## 9. 下游使用方式
-
-| 下游阶段 | 怎么使用这些字段 |
-| --- | --- |
-| 数据源 | 展示 Pattern、Case、分类/元素绑定和原始支撑素材 |
-| Query | 使用 `seed_terms` 作为 Query 输入素材,不直接把宽泛 `name` 当 Query |
-| Platform | 根据来源平台和 seed 生成平台适配 Query |
-| 判断 | 用分类/元素绑定、Case 原文和 support 做回扣判断 |
-| 游走 | 每次游走结果绑定回分类或元素节点,避免无限漂移 |
-| 资产清洗沉淀 | 入库前保留来源路径、判断结果、分类或元素绑定、平台调性说明、run_context 和结构化 `source_evidence` |
-| 策略学习 | 用 run_context 回看哪类来源、哪类 seed、哪类规则包有效 |
-
-## 10. 最终判断
-
-旧版 ContentFindAgent 的 MySQL Pattern 反查已删除弃用,不能承担新版主血缘,也不能作为新版 `evidence_pack` 的兜底来源。
-
-如果新版要求精准找到 Pattern、Case、分类或元素绑定,并让所有入库资产可追溯,DemandAgent 必须把生成需求时已经知道的证据一并传给下游。
-
-最小可行做法是:先在 `demand_content.ext_data` 中增加 `evidence_pack`。这样 V1 可以先让需求单携带真实上游证据,不需要马上重构 DB schema,也能避免下游继续靠猜。
-
-但这只是 Exact Mode 的上游基础。最终 `demand_find_content_result` 能不能从 `platform_content_id/post_id` 回到具体 Pattern 和分类树节点,还取决于 ContentFindAgent 是否读取、继承并沉淀结构化 `source_evidence`。

+ 0 - 13
数据接口与来源/captures/README.md

@@ -1,13 +0,0 @@
-# Captures Archived
-
-Raw capture files were moved out of the active mainline during the 2026-06-30
-cleanup pass.
-
-Current archive path:
-
-```text
-archive/2026-06-30-step1/data_interface_captures/captures/
-```
-
-The formal pipeline should use platform adapters and cloud-state records rather
-than loading raw capture files from this directory.

+ 0 - 994
数据接口与来源/crawler_endpoints.registry.json

@@ -1,994 +0,0 @@
-{
-  "registry_version": "crawler_endpoints.v1",
-  "updated_at": "2026-06-11",
-  "purpose": "crawler.aiddit.com 多平台接口的机读台账,接口级。汇总 10 个平台的关键词搜索、内容详情、作者作品、账号画像与视频下载接口的实测状态、输入输出字段和边界,供程序、脚本和审查流程按 source_id / platform 查询。",
-  "authority_note": "本文件是 crawler.aiddit.com 接口级机读索引;人读解释见 00_数据接口总览.md 多平台 crawler 接口一节;外部数据源主索引仍以 external_data_sources_registry.json 为准。每条目逐字段保留各平台 registry 片段原文,仅补 platform 字段。",
-  "status_legend": {
-    "verified": "本批真实抓包 code=0,结构、字段与边界已实测确认。",
-    "verified_unstable": "抓包可成功,但服务端抖动,需重试+退避才稳定(视频号搜索)。",
-    "blocked": "因接口报错、超时、参数限制或平台/接口侧不可用,本批未取得可用样本。",
-    "source_only": "有配置或源码线索,但当前未作为主链路实连。",
-    "missing": "缺真实接口、真实 key、稳定字段或可用配置。"
-  },
-  "source_type_legend": {
-    "http_api": "HTTP API。"
-  },
-  "access_mode_legend": {
-    "read": "只读。",
-    "not_connected": "当前不可连接或不应接入。"
-  },
-  "source_id_prefixes": {
-    "PLT": "平台接入接口。"
-  },
-  "endpoints": [
-    {
-      "platform": "douyin",
-      "source_id": "PLT_DOUYIN_KEYWORD",
-      "display_name": "抖音关键词搜索",
-      "source_type": "http_api",
-      "status": [
-        "verified"
-      ],
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/dou_yin/keyword",
-      "access_mode": "read",
-      "usage_stages": [],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "keyword",
-        "content_type",
-        "cursor",
-        "sort_type"
-      ],
-      "output_fields": [
-        "aweme_id",
-        "desc",
-        "caption",
-        "text_extra",
-        "cha_list",
-        "video.play_addr.url_list",
-        "video.download_addr.url_list",
-        "video.duration",
-        "author.uid",
-        "author.sec_uid",
-        "author.unique_id",
-        "author.nickname",
-        "author.signature",
-        "author.follower_count",
-        "statistics.digg_count",
-        "statistics.comment_count",
-        "statistics.share_count",
-        "statistics.collect_count",
-        "create_time",
-        "share_url",
-        "has_more",
-        "next_cursor"
-      ],
-      "v1_boundary": "verified code=0;搜索阶段 author 即含完整档案(sec_uid/粉丝数/签名/抖音号);单条卡片为抖音原生 raw 结构,字段名与 detail 归一化结构不同;视频下载需带 iOS UA + Referer=https://www.douyin.com/;限流间隔>=15s。"
-    },
-    {
-      "platform": "douyin",
-      "source_id": "PLT_DOUYIN_DETAIL",
-      "display_name": "抖音内容详情",
-      "source_type": "http_api",
-      "status": [
-        "verified"
-      ],
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/dou_yin/detail",
-      "access_mode": "read",
-      "usage_stages": [],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "content_id"
-      ],
-      "output_fields": [
-        "channel_content_id",
-        "content_link",
-        "title",
-        "body_text",
-        "topic_list",
-        "content_type",
-        "video_url_list",
-        "multi_bitrate",
-        "image_url_list",
-        "channel_account_id",
-        "channel_account_name",
-        "like_count",
-        "comment_count",
-        "share_count",
-        "collect_count",
-        "bgm_data",
-        "publish_timestamp",
-        "is_original",
-        "has_more",
-        "next_cursor"
-      ],
-      "v1_boundary": "verified code=0;归一化结构(channel_content_id/channel_account_id),字段名与搜索 raw 结构不同;无翻页(has_more=false);view_count/play_count 实测为 null;视频下载需 iOS UA + Referer;限流>=15s。"
-    },
-    {
-      "platform": "douyin",
-      "source_id": "PLT_DOUYIN_BLOGGER",
-      "display_name": "抖音博主主页作品列表",
-      "source_type": "http_api",
-      "status": [
-        "verified"
-      ],
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/dou_yin/blogger",
-      "access_mode": "read",
-      "usage_stages": [],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "account_id",
-        "cursor"
-      ],
-      "output_fields": [
-        "aweme_id",
-        "desc",
-        "text_extra",
-        "video.play_addr.url_list",
-        "author.sec_uid",
-        "author.nickname",
-        "statistics.digg_count",
-        "statistics.comment_count",
-        "statistics.share_count",
-        "statistics.collect_count",
-        "has_more",
-        "next_cursor"
-      ],
-      "v1_boundary": "verified code=0;account_id 必须传 sec_uid(MS4...);单条作品为 raw aweme 结构(同搜索);next_cursor 为毫秒时间戳游标原样回填 cursor;作者作品需按 aweme_id 与已发现内容去重;限流>=15s。"
-    },
-    {
-      "platform": "douyin",
-      "source_id": "PLT_DOUYIN_AUTHOR_PROFILE",
-      "display_name": "抖音账号粉丝画像",
-      "source_type": "http_api",
-      "status": [
-        "verified"
-      ],
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/dou_yin/re_dian_bao/account_fans_portrait",
-      "access_mode": "read",
-      "usage_stages": [],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "account_id",
-        "need_age",
-        "need_gender",
-        "need_province"
-      ],
-      "output_fields": [
-        "account.user_id",
-        "account.nickname",
-        "account.uniqueId",
-        "account.follower_count",
-        "account.aweme_count",
-        "account.total_favorited",
-        "account.first_tag_name",
-        "account.second_tag_name",
-        "account.fans_milestone",
-        "fans.gender",
-        "fans.age",
-        "fans.province",
-        "fans.city",
-        "fans.city_level",
-        "fans.phone_brand",
-        "posts.avg_like_count",
-        "posts.avg_comment_count",
-        "posts.percentile_like_count",
-        "posts.percentile_follower_count",
-        "has_more",
-        "next_cursor"
-      ],
-      "v1_boundary": "verified code=0;account_id 必须 sec_uid;fans 含 6 维人群分布(gender/age/province/city/city_level/phone_brand),展开 50+ 信号,percentage 带 % 需 strip,preference 为 TGI;无翻页;限流>=15s。"
-    },
-    {
-      "platform": "douyin",
-      "source_id": "PLT_DOUYIN_CONTENT_PORTRAIT",
-      "display_name": "抖音内容人群画像(内容画像)",
-      "source_type": "http_api",
-      "status": [
-        "blocked"
-      ],
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/dou_yin/re_dian_bao/video_like_portrait",
-      "access_mode": "read",
-      "usage_stages": [],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "content_id",
-        "need_age",
-        "need_gender",
-        "need_province"
-      ],
-      "output_fields": [],
-      "v1_boundary": "blocked;实测 HTTP 404 Not Found(error_body=<html><title>404: Not Found</title>);该 path 当前在 crawler.aiddit.com 不存在,内容级人群画像不可用,需上游确认正确 path 或下线;游走层不要挂该边。"
-    },
-    {
-      "platform": "kuaishou",
-      "source_id": "PLT_KUAISHOU_KEYWORD",
-      "display_name": "快手关键词搜索",
-      "source_type": "http_api",
-      "status": [
-        "verified"
-      ],
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/kuai_shou/keyword_v2",
-      "access_mode": "read",
-      "usage_stages": [],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "keyword"
-      ],
-      "output_fields": [
-        "channel_content_id",
-        "content_link",
-        "title",
-        "body_text",
-        "topic_list",
-        "content_type",
-        "video_url_list",
-        "image_url_list",
-        "channel_account_id",
-        "channel_account_name",
-        "view_count",
-        "like_count",
-        "collect_count",
-        "comment_count",
-        "share_count",
-        "publish_timestamp",
-        "has_more",
-        "next_cursor"
-      ],
-      "v1_boundary": "verified code=0;请求体仅 keyword;返回归一化结构;本次实测 has_more=false/next_cursor=null 单页返回;搜索结果只带 channel_account_id/name,无作者粉丝数/签名(需二次 account_info);title/body_text/topic_list 常为 null;视频可下载(206/mp4),下载带 Range + Referer=https://www.kuaishou.com/;限流>=15s。"
-    },
-    {
-      "platform": "kuaishou",
-      "source_id": "PLT_KUAISHOU_DETAIL",
-      "display_name": "快手内容详情",
-      "source_type": "http_api",
-      "status": [
-        "verified"
-      ],
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/kuai_shou/detail",
-      "access_mode": "read",
-      "usage_stages": [],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "content_id"
-      ],
-      "output_fields": [
-        "channel_content_id",
-        "content_link",
-        "content_type",
-        "title",
-        "body_text",
-        "topic_list",
-        "video_url_list",
-        "image_url_list",
-        "channel_account_id",
-        "channel_account_name",
-        "avatar",
-        "view_count",
-        "like_count",
-        "collect_count",
-        "comment_count",
-        "share_count",
-        "bgm_data",
-        "publish_timestamp",
-        "has_more",
-        "next_cursor"
-      ],
-      "v1_boundary": "verified code=0;归一化结构;无翻页;title/body_text/topic_list 常 null;同一内容 share_count detail(7)>search(0),以 detail 为准;视频可下载(206/mp4),下载带 Range + Referer;限流>=15s。"
-    },
-    {
-      "platform": "kuaishou",
-      "source_id": "PLT_KUAISHOU_ACCOUNT_INFO",
-      "display_name": "快手账号信息",
-      "source_type": "http_api",
-      "status": [
-        "verified"
-      ],
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/kuai_shou/account_info",
-      "access_mode": "read",
-      "usage_stages": [],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "account_id",
-        "is_cache"
-      ],
-      "output_fields": [
-        "channel_account_id",
-        "ks_id",
-        "digit_id",
-        "account_link",
-        "account_name",
-        "avatar_url",
-        "gender",
-        "description",
-        "tags",
-        "follower_count",
-        "publish_count",
-        "like_count",
-        "update_timestamp",
-        "has_more",
-        "next_cursor"
-      ],
-      "v1_boundary": "verified code=0;account_id 用 channel_account_id(如 3xfkwajatdh7p7i)非数字快手号;tags 已结构化(星座+地区);ip_location/collect_count/create_timestamp 实测 null;快手本批未抓到独立作者作品列表接口,author_to_works 在快手侧暂依赖搜索聚合;限流>=15s。"
-    },
-    {
-      "platform": "shipinhao",
-      "source_id": "PLT_SHIPINHAO_KEYWORD",
-      "display_name": "视频号关键词搜索",
-      "source_type": "http_api",
-      "status": "verified_unstable",
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/shi_pin_hao/keyword",
-      "access_mode": "read",
-      "usage_stages": [],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "keyword",
-        "cursor"
-      ],
-      "output_fields": [
-        "code",
-        "msg",
-        "data.has_more",
-        "data.next_cursor",
-        "data.data[].channel_content_id",
-        "data.data[].title",
-        "data.data[].content_type",
-        "data.data[].content_link",
-        "data.data[].video_url_list[].video_url",
-        "data.data[].video_url_list[].video_duration",
-        "data.data[].image_url_list[].image_url",
-        "data.data[].channel_account_id",
-        "data.data[].channel_account_name",
-        "data.data[].channel_account_avatar",
-        "data.data[].like_count",
-        "data.data[].collect_count",
-        "data.data[].comment_count",
-        "data.data[].share_count",
-        "data.data[].topic_list",
-        "data.data[].publish_timestamp"
-      ],
-      "v1_boundary": "搜索服务端抖动不稳定:首次 code=25011(视频号接口异常:获取搜索结果失败,见 sph_search.json),重试 code=0 成功有数据(sph_search_retry.json,has_more=true/next_cursor=12)。实现必须带重试+退避;限流≥15s。topic_list 常为 null,tag 需从 title 正则抽;publish_timestamp 为毫秒。"
-    },
-    {
-      "platform": "shipinhao",
-      "source_id": "PLT_SHIPINHAO_VIDEO_DL",
-      "display_name": "视频号视频直链下载",
-      "source_type": "http_api",
-      "status": "verified",
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "https://findermp.video.qq.com/.../stodownload (来自 keyword 返回的 video_url)",
-      "access_mode": "read",
-      "usage_stages": [],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "video_url(encfilekey,token)"
-      ],
-      "output_fields": [
-        "http_status",
-        "content_type",
-        "content_range",
-        "bytes_received",
-        "looks_like_video"
-      ],
-      "v1_boundary": "可下载:http_status=200/content_type=video/mp4/content_range=bytes 0-784921/784922/约784KB(sph_video_dl.json)。下载需带 Referer https://channels.weixin.qq.com/;encfilekey/token 有时效,需搜索后尽快下载。"
-    },
-    {
-      "platform": "shipinhao",
-      "source_id": "PLT_SHIPINHAO_BLOGGER",
-      "display_name": "视频号博主作品列表",
-      "source_type": "http_api",
-      "status": "blocked",
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/shi_pin_hao/blogger",
-      "access_mode": "read",
-      "usage_stages": [],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "account_id",
-        "is_cache"
-      ],
-      "output_fields": [],
-      "v1_boundary": "blocked:code=25011(视频号接口异常),data=null(sph_blogger.json)。与搜索同源抖动,作者作品展开当前不可用,author_to_works 边无法落地。"
-    },
-    {
-      "platform": "shipinhao",
-      "source_id": "PLT_SHIPINHAO_ACCOUNT_INFO",
-      "display_name": "视频号账号信息",
-      "source_type": "http_api",
-      "status": "blocked",
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/shi_pin_hao/account_info",
-      "access_mode": "read",
-      "usage_stages": [],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "account_id",
-        "is_cache",
-        "app_id|account_name(至少一个)"
-      ],
-      "output_fields": [],
-      "v1_boundary": "blocked:code=10001(参数异常:app_id或account_name至少需要一个),data=null(sph_account_info.json)。本次仅传 account_id 被拒;后续可补 channel_account_name 再试。"
-    },
-    {
-      "platform": "xiaohongshu",
-      "source_id": "PLT_XIAOHONGSHU_KEYWORD",
-      "display_name": "小红书关键词搜索",
-      "source_type": "http_api",
-      "status": "verified",
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/xiao_hong_shu/keyword",
-      "access_mode": "read",
-      "usage_stages": [],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "keyword",
-        "content_type",
-        "sort_type",
-        "publish_time",
-        "cursor"
-      ],
-      "output_fields": [
-        "code",
-        "msg",
-        "data.has_more",
-        "data.next_cursor",
-        "data.data[].id",
-        "data.data[].model_type",
-        "data.data[].note_card.type",
-        "data.data[].note_card.display_title",
-        "data.data[].note_card.desc",
-        "data.data[].note_card.image_list[].image_url",
-        "data.data[].note_card.user.user_id",
-        "data.data[].note_card.user.nick_name",
-        "data.data[].note_card.user.nickname",
-        "data.data[].note_card.user.avatar",
-        "data.data[].note_card.interact_info.liked_count",
-        "data.data[].note_card.interact_info.collected_count",
-        "data.data[].note_card.interact_info.comment_count",
-        "data.data[].note_card.interact_info.shared_count",
-        "data.data[].note_card.publish_timestamp"
-      ],
-      "v1_boundary": "verified:code=0 有数据(xhs_search.json,has_more=false/next_cursor=null)。关键提取规则:内容字段全部嵌在 note_card 下——内容ID取 item.id,其余取 item.note_card.*(type/desc/display_title/image_list/user.user_id/user.nick_name/interact_info.*/publish_timestamp),不在顶层找。以图文为主(image_list);视频直链不在搜索结果,需走 detail 二次请求。publish_timestamp 为秒级(注意与 detail 毫秒级不一致)。限流≥15s。"
-    },
-    {
-      "platform": "xiaohongshu",
-      "source_id": "PLT_XIAOHONGSHU_DETAIL",
-      "display_name": "小红书笔记详情",
-      "source_type": "http_api",
-      "status": "verified",
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/xiao_hong_shu/detail",
-      "access_mode": "read",
-      "usage_stages": [],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "content_id"
-      ],
-      "output_fields": [
-        "code",
-        "msg",
-        "data.has_more",
-        "data.next_cursor",
-        "data.data.channel_content_id",
-        "data.data.content_link",
-        "data.data.title",
-        "data.data.content_type",
-        "data.data.body_text",
-        "data.data.location",
-        "data.data.topic_list",
-        "data.data.image_url_list[].image_url",
-        "data.data.image_url_list[].image_type",
-        "data.data.video_url_list",
-        "data.data.channel_account_id",
-        "data.data.channel_account_name",
-        "data.data.like_count",
-        "data.data.collect_count",
-        "data.data.comment_count",
-        "data.data.share_count",
-        "data.data.publish_timestamp",
-        "data.data.is_original",
-        "data.data.is_cache"
-      ],
-      "v1_boundary": "verified:code=0 有数据(xhs_detail.json)。详情为扁平结构(非 note_card),字段在 data.data.* 直接取。body_text 比搜索 desc 完整,topic_list 结构化给 tag。video_url_list 本样本为 null(图文笔记)。publish_timestamp 为毫秒(1768657102000),与搜索接口秒级不一致,落库须按接口区分换算。is_cache=true 表缓存命中。"
-    },
-    {
-      "platform": "xiaohongshu",
-      "source_id": "PLT_XIAOHONGSHU_BLOGGER",
-      "display_name": "小红书博主作品列表",
-      "source_type": "http_api",
-      "status": "blocked",
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/xiao_hong_shu/blogger",
-      "access_mode": "read",
-      "usage_stages": [],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "account_id",
-        "cursor"
-      ],
-      "output_fields": [],
-      "v1_boundary": "blocked:请求超时 error=timed out(xhs_blogger.json),无响应。需加超时重试+退避;account_id 可由搜索 note_card.user.user_id 提供。author_to_works 边当前不可落地。"
-    },
-    {
-      "platform": "bilibili",
-      "source_id": "PLT_BILIBILI_KEYWORD",
-      "display_name": "Crawapi B站关键词搜索",
-      "source_type": "http_api",
-      "status": "verified",
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/bilibili/keyword",
-      "access_mode": "read",
-      "usage_stages": [
-        "platform_access",
-        "content_discovery"
-      ],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "keyword",
-        "cursor"
-      ],
-      "output_fields": [
-        "bvid",
-        "aid",
-        "title",
-        "arcurl",
-        "pic",
-        "description",
-        "author",
-        "mid",
-        "upic",
-        "tag",
-        "play",
-        "like",
-        "favorites",
-        "review",
-        "danmaku",
-        "duration",
-        "pubdate",
-        "typename",
-        "has_more",
-        "next_cursor"
-      ],
-      "v1_boundary": "verified code=0,单次返回 42 条;uid 恒为 0 需弃用以 mid 为准;title 含 <em class=keyword> 需剥;无作者作品接口,author_to_works 不可走。"
-    },
-    {
-      "platform": "bilibili",
-      "source_id": "PLT_BILIBILI_CONTENT_DETAIL",
-      "display_name": "Crawapi B站内容详情",
-      "source_type": "http_api",
-      "status": "verified",
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/bilibili/detail",
-      "access_mode": "read",
-      "usage_stages": [
-        "platform_access",
-        "content_enrichment"
-      ],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "content_id"
-      ],
-      "output_fields": [
-        "channel_content_id",
-        "content_link",
-        "title",
-        "body_text",
-        "content_type",
-        "topic_list",
-        "video_url_list",
-        "voice_data",
-        "image_url_list",
-        "channel_account_id",
-        "channel_account_name",
-        "play_count",
-        "like_count",
-        "collect_count",
-        "comment_count",
-        "share_count",
-        "publish_timestamp",
-        "has_more",
-        "next_cursor"
-      ],
-      "v1_boundary": "verified code=0,归一化单对象;video_url_list 视频轨 + voice_data.play_url 音频轨分轨需合并;channel_account_id == search.mid;无作者作品接口。"
-    },
-    {
-      "platform": "bilibili",
-      "source_id": "PLT_BILIBILI_VIDEO_DL",
-      "display_name": "B站视频下载(m4s 直链)",
-      "source_type": "http_api",
-      "status": "verified",
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "video_url_list[].video_url (bilivideo.cn m4s 直连 CDN)",
-      "access_mode": "read",
-      "usage_stages": [
-        "material_download"
-      ],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "video_url",
-        "referer"
-      ],
-      "output_fields": [
-        "http_status",
-        "content_type",
-        "content_range",
-        "bytes_received",
-        "looks_like_video"
-      ],
-      "v1_boundary": "verified 实测 206/octet-stream/1MB 真实视频字节,可下载;必须带 Referer https://www.bilibili.com/;URL 含 deadline 时效签名,过期回详情重取;DASH 分轨需合并。"
-    },
-    {
-      "platform": "weixin",
-      "source_id": "PLT_WEIXIN_KEYWORD",
-      "display_name": "Crawapi 公众号关键词搜索",
-      "source_type": "http_api",
-      "status": "verified",
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/wei_xin/keyword",
-      "access_mode": "read",
-      "usage_stages": [
-        "platform_access",
-        "content_discovery"
-      ],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "keyword",
-        "cursor"
-      ],
-      "output_fields": [
-        "url",
-        "title",
-        "cover_url",
-        "time",
-        "nick_name",
-        "biz",
-        "has_more",
-        "next_cursor"
-      ],
-      "v1_boundary": "verified code=0,单次 20 条;nick_name 与 biz 恒为空串,作者 biz 须从 url 正则抽 __biz;title 含 <em class=highlight> 需剥;无 blogger 接口 author_to_works 不可走。"
-    },
-    {
-      "platform": "weixin",
-      "source_id": "PLT_WEIXIN_CONTENT_DETAIL",
-      "display_name": "Crawapi 公众号文章详情",
-      "source_type": "http_api",
-      "status": "verified",
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/wei_xin/detail",
-      "access_mode": "read",
-      "usage_stages": [
-        "platform_access",
-        "content_enrichment"
-      ],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "content_link"
-      ],
-      "output_fields": [
-        "channel_content_id",
-        "content_link",
-        "title",
-        "body_text",
-        "content_type",
-        "topic_list",
-        "image_url_list",
-        "video_url_list",
-        "channel_account_id",
-        "channel_account_name",
-        "location",
-        "view_count",
-        "like_count",
-        "publish_timestamp",
-        "has_more",
-        "next_cursor"
-      ],
-      "v1_boundary": "verified code=0,归一化单对象;topic_list 恒 null(无 tag);channel_account_name 恒 null 昵称须 account_info 补;channel_account_id 是网关 hash 非 biz,两者均需沉淀。"
-    },
-    {
-      "platform": "weixin",
-      "source_id": "PLT_WEIXIN_ACCOUNT_INFO",
-      "display_name": "Crawapi 公众号号主信息",
-      "source_type": "http_api",
-      "status": "verified",
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/wei_xin/account_info",
-      "access_mode": "read",
-      "usage_stages": [
-        "platform_access",
-        "author_enrichment"
-      ],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "content_link"
-      ],
-      "output_fields": [
-        "channel_account_id",
-        "account_name",
-        "account_link",
-        "biz_info",
-        "wx_gh",
-        "gender",
-        "follower_count",
-        "publish_count"
-      ],
-      "v1_boundary": "verified code=0,公众号作者血缘唯一可达补全(用任一文章 content_link 反查);返回 account_name+biz_info+wx_gh;publish_count=null 无作品列表,author_to_works 仍走不通;粉丝/互动指标多为 null。"
-    },
-    {
-      "platform": "zhihu",
-      "source_id": "PLT_ZHIHU_KEYWORD",
-      "display_name": "Crawapi 知乎关键词搜索",
-      "source_type": "http_api",
-      "status": "blocked",
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/zhi_hu/keyword",
-      "access_mode": "read",
-      "usage_stages": [
-        "platform_access",
-        "content_discovery"
-      ],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "keyword",
-        "cursor",
-        "content_type"
-      ],
-      "output_fields": [],
-      "v1_boundary": "blocked:zhihu_search.json 与 zhihu_search_retry.json 两次均 code=10000 未知错误 data=null;整平台不可用,下游内容/作者/tag/下载全部阻断,需上游修复后重新抓包验证。"
-    },
-    {
-      "platform": "youtube",
-      "source_id": "PLT_YOUTUBE_KEYWORD",
-      "display_name": "Crawapi YouTube 关键词搜索",
-      "source_type": "http_api",
-      "status": [
-        "verified"
-      ],
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/youtube/keyword",
-      "access_mode": "read",
-      "usage_stages": [
-        "platform_access",
-        "content_discovery"
-      ],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "keyword",
-        "next_cursor"
-      ],
-      "output_fields": [
-        "video_id",
-        "title",
-        "author",
-        "channel_id",
-        "url",
-        "description_snippet",
-        "duration",
-        "view_count",
-        "published_time",
-        "thumbnails",
-        "has_more",
-        "next_cursor"
-      ],
-      "canonical_mapping": {
-        "video_id": "platform_content_id",
-        "channel_id": "platform_author_id",
-        "tags": null
-      },
-      "walk_edges": [
-        "search_page_to_content",
-        "video_to_author"
-      ],
-      "v1_boundary": "verified ✅,capture youtube_search.json code=0,单页约 20 条;has_more=true 时透传 next_cursor(URL-encoded 长 token)翻页。搜索结果无 hashtag 字段;channel_id 可能为空串。"
-    },
-    {
-      "platform": "youtube",
-      "source_id": "PLT_YOUTUBE_DETAIL",
-      "display_name": "Crawapi YouTube 内容详情",
-      "source_type": "http_api",
-      "status": [
-        "verified"
-      ],
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/youtube/detail",
-      "access_mode": "read",
-      "usage_stages": [
-        "platform_access",
-        "content_discovery"
-      ],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "content_id"
-      ],
-      "output_fields": [
-        "channel_content_id",
-        "title",
-        "content_link",
-        "body_text",
-        "content_type",
-        "channel_account_id",
-        "channel_account_name",
-        "video_url_list",
-        "image_url_list",
-        "topic_list",
-        "like_count",
-        "comment_count",
-        "publish_timestamp",
-        "is_original",
-        "has_more",
-        "next_cursor"
-      ],
-      "canonical_mapping": {
-        "channel_content_id": "platform_content_id",
-        "channel_account_id": "platform_author_id",
-        "tags": "body_text 正则抽 #xxx"
-      },
-      "walk_edges": [
-        "video_to_author",
-        "video_to_hashtag"
-      ],
-      "v1_boundary": "verified ✅但带重大限制:capture youtube_detail.json code=0,结构完整,但 video_url_list=[] 且 image_url_list=[] → 视频/图片文件不可直取;hashtag 须从 body_text 正则抽(topic_list=null);detail 无翻页(has_more=false)。"
-    },
-    {
-      "platform": "github",
-      "source_id": "PLT_GITHUB_KEYWORD",
-      "display_name": "Crawapi GitHub 关键词搜索(代码仓库)",
-      "source_type": "http_api",
-      "status": [
-        "verified"
-      ],
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/github/keyword",
-      "access_mode": "read",
-      "usage_stages": [
-        "platform_access",
-        "content_discovery"
-      ],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "keyword",
-        "token"
-      ],
-      "output_fields": [
-        "id",
-        "hl_name",
-        "hl_trunc_description",
-        "topics",
-        "language",
-        "followers",
-        "archived",
-        "type",
-        "repo.repository.name",
-        "repo.repository.owner_login",
-        "repo.repository.owner_id",
-        "repo.repository.updated_at",
-        "repo.repository.has_issues",
-        "has_more",
-        "next_cursor"
-      ],
-      "canonical_mapping": {
-        "id": "platform_content_id",
-        "repo.repository.owner_id": "platform_author_id",
-        "topics": "tags"
-      },
-      "walk_edges": [
-        "search_page_to_content",
-        "video_to_author",
-        "video_to_hashtag"
-      ],
-      "v1_boundary": "verified ✅,capture github_search.json code=0,单页约 10 条;非视频内容(代码仓库),无 video 字段;has_more=true 时透传 next_cursor(短页码如 '1')回传到请求 token 参数翻页;topics 即 tag(多数为空);content url 须由 hl_name 去 <em> 后拼 https://github.com/{owner/repo};多数 topics=[]、language=null。"
-    },
-    {
-      "platform": "toutiao",
-      "source_id": "PLT_TOUTIAO_KEYWORD",
-      "display_name": "Crawapi 头条号关键词搜索",
-      "source_type": "http_api",
-      "status": [
-        "blocked"
-      ],
-      "system": "crawler.aiddit.com",
-      "table_or_endpoint": "/crawler/tou_tiao_hao/keyword",
-      "access_mode": "read",
-      "usage_stages": [
-        "platform_access",
-        "content_discovery"
-      ],
-      "owner_module": "平台接入模块",
-      "required_env_vars": [
-        "CONTENTFIND_API_CRAWAPI_BASE_URL"
-      ],
-      "input_fields": [
-        "keyword",
-        "content_type",
-        "page"
-      ],
-      "output_fields": [],
-      "canonical_mapping": {},
-      "walk_edges": [],
-      "v1_boundary": "blocked ⛔:toutiao_search.json(keyword=彩虹, content_type=video, page=0)与 toutiao_search_retry.json(去掉 content_type)两次请求均返回 code=10000 / msg=未知错误 / data=null;无成功结构,无字段无翻页;下游链路跳过本平台,待 crawler 侧修复复抓到 code=0 后再解锁登记。"
-    }
-  ]
-}

+ 0 - 42
数据接口与来源/embedding接口.md

@@ -1,42 +0,0 @@
-# Embedding(文本向量化)接口 · 实测
-
-> 实测可用:**2026-06-17**。用途:「作用域定位」(scope-link)——把 5 棵分类树节点 + 提取出的候选作用域值向量化,做余弦最近邻对齐(对得上复用现有节点原名、对不上新建)。
-
-## 服务
-- 提供方:**火山方舟(Volcengine Ark)**
-- 模型:**Doubao-embedding-vision**(多模态向量化,支持文本 / 图片;本项目只用文本)
-- 接入方式:预置推理接入点(用 **ep-id** 调用,系统自动匹配预置服务)
-
-## 调用契约
-
-| 项 | 值 |
-|---|---|
-| Endpoint | `POST https://ark.cn-beijing.volces.com/api/v3/embeddings/multimodal` |
-| Auth | `Authorization: Bearer <ARK_API_KEY>`(密钥存 `.env`,**不入库 / 不入文档**) |
-| `model` | `ep-20260617180207-vmwg6` |
-| 输入 | `{"model":"ep-...","input":[{"type":"text","text":"<文本>"}]}` |
-| 输出 | `data.embedding` = 长度 **2048** 的浮点向量 |
-
-请求示例:
-```bash
-curl https://ark.cn-beijing.volces.com/api/v3/embeddings/multimodal \
-  -H "Authorization: Bearer $ARK_API_KEY" -H "Content-Type: application/json" \
-  -d '{"model":"ep-20260617180207-vmwg6","input":[{"type":"text","text":"撕裂共识"}]}'
-```
-
-## 关键坑(实测)
-- **vision 版必须走 `/embeddings/multimodal`**;标准 `/api/v3/embeddings`(input 为字符串数组)会返回 `InvalidParameter`。
-- 多模态接口是**单条调用**(一次请求 = 一个向量),批量需多次请求(可并发)。
-- 火山方舟用「**预置推理接入点**」即可(用模型 ID / ep-id 直接调,无需自建模型接入点);模型必须先在该 key 所属**项目**开通。
-- 图片向量化:把 input 项换成 `{"type":"image_url","image_url":{"url":"<url 或 data url>"}}`(同一接口,本项目暂不用)。
-
-## env(密钥放 .env,gitignore)
-```
-ARK_API_KEY=<火山方舟 API Key>
-ARK_EMBEDDING_EP=ep-20260617180207-vmwg6
-ARK_EMBEDDING_URL=https://ark.cn-beijing.volces.com/api/v3/embeddings/multimodal
-ARK_EMBEDDING_DIM=2048
-```
-
-## 在本项目的用途
-`scope-link`:① 一次性把 global_category(5 棵树 ~5000 节点)的节点名向量化、缓存成本地 `.npy`;② 提取出候选作用域值时即时向量化 → 与缓存做余弦最近邻 → 取 top-K 交模型判定「对齐现有 or 新建」。规模小(~20MB),**内存 + numpy 暴力最近邻即可,无需向量数据库**。

+ 0 - 630
数据接口与来源/external_data_sources_registry.json

@@ -1,630 +0,0 @@
-{
-  "registry_version": "external_data_sources.v1",
-  "updated_at": "2026-06-07",
-  "purpose": "机器可读的外部数据源总表,供程序、脚本和审查流程查询 ContentFindAgent 可以读取、待接入或明确不能当作已接入的数据来源。",
-  "authority_note": "外部数据源以本文件为机器可读索引;详细中文说明见 00_数据接口总览.md;CFA 自有 DB 表和 runtime 字段见 ../数据库字段总览/content_agent_schema_registry.json。",
-  "status_legend": {
-    "verified": "已有 DB / API 只读验证或旧版实测结果。",
-    "v1_active": "V1 主链路使用或必须接入的来源。",
-    "source_only": "已有配置或源码线索,但当前未作为主链路实连。",
-    "fixture_only": "只用于本地回归、fixture 或兼容导出,不作为生产事实来源。",
-    "blocked": "因 key、token、权限或副作用限制未实连。",
-    "missing": "缺真实接口、真实 key、稳定字段或可用配置。",
-    "deprecated_reference_only": "历史迁移对照,不能作为新版证据来源。"
-  },
-  "source_type_legend": {
-    "mysql_table": "MySQL 表。",
-    "postgres_table": "PostgreSQL 表。",
-    "odps_table": "ODPS / MaxCompute 表。",
-    "http_api": "HTTP API。",
-    "local_json": "本地 JSON 文件,只能作为 fixture、兼容导出或回放材料。",
-    "capability": "能力或外部系统入口,不一定是单一表或单一 API。",
-    "gap": "产品方向存在,但当前没有可用真实来源。"
-  },
-  "access_mode_legend": {
-    "read": "只读。",
-    "read_write": "可读写。",
-    "planned_read": "计划读取,当前未接入代码主链路。",
-    "not_connected": "当前不可连接或不应接入。"
-  },
-  "source_id_prefixes": {
-    "SRC": "数据源与种子。",
-    "QRY": "搜索意图。",
-    "PLT": "平台接入。",
-    "JDG": "判断与回扣。",
-    "WALK": "游走扩展。",
-    "ASSET": "结果沉淀。",
-    "LEARN": "策略学习。",
-    "GAP": "明确缺口。"
-  },
-  "sources": [
-    {
-      "source_id": "SRC_DEMAND_CONTENT_MYSQL",
-      "display_name": "DemandAgent 需求池 MySQL 单表",
-      "source_type": "mysql_table",
-      "status": ["verified", "v1_active"],
-      "system": "content-deconstruction-supply",
-      "table_or_endpoint": "demand_content",
-      "access_mode": "read",
-      "usage_stages": ["source_seed", "search_intent", "learning_review"],
-      "owner_module": "数据源与种子模块",
-      "current_code_refs": [
-        "content_agent/business_modules/source_seed/source_context.py",
-        "content_agent/integrations/demand_source.py",
-        "content_agent/run_service.py",
-        "content_agent/schemas.py"
-      ],
-      "required_env_vars": [
-        "CONTENT_SUPPLY_DB_HOST",
-        "CONTENT_SUPPLY_DB_PORT",
-        "CONTENT_SUPPLY_DB_NAME",
-        "CONTENT_SUPPLY_DB_USER",
-        "CONTENT_SUPPLY_DB_PASSWORD"
-      ],
-      "input_fields": ["id", "merge_leve2", "name", "reason", "suggestion", "ext_data", "score", "dt", "create_time", "update_time"],
-      "required_payload_paths": ["ext_data.evidence_pack"],
-      "output_fields": ["demand_content_id", "evidence_pack", "seed_terms", "source_post_id", "itemset_ids"],
-      "v1_boundary": "真实需求领取读取 demand_content.ext_data.evidence_pack;显式 demand_content_id、run_label、无 selector 默认 pg_pattern_v2 + passed 首条记录均由 CFA 自己的 DB env 接入。本地 JSON 只作 fixture / 回放。",
-      "secret_policy": "只登记 env key,不记录密码。"
-    },
-    {
-      "source_id": "SRC_DEMAND_CONTENT_LOCAL_JSON",
-      "display_name": "DemandAgent 本地 demand_content.json",
-      "source_type": "local_json",
-      "status": ["verified", "fixture_only"],
-      "system": "DemandAgentNew local_json",
-      "table_or_endpoint": "examples/demand/test_output_data/{run_id}/demand_content.json",
-      "access_mode": "read",
-      "usage_stages": ["source_seed", "search_intent"],
-      "owner_module": "数据源与种子模块",
-      "current_code_refs": [
-        "content_agent/business_modules/source_seed/source_context.py",
-        "tests/fixtures/real_case_source/source_context.json"
-      ],
-      "required_env_vars": [],
-      "input_fields": ["merge_leve2", "name", "reason", "suggestion", "score", "ext_data"],
-      "required_payload_paths": ["ext_data.evidence_pack"],
-      "output_fields": ["source_context.json", "pattern_seed_pack.json"],
-      "v1_boundary": "本地回归可用;生产读取和 P0F 应直接使用 MySQL demand_content,本地 JSON 不作为生产事实来源。"
-    },
-    {
-      "source_id": "SRC_WORKFLOW_DECODE_TASK_RESULT",
-      "display_name": "历史 Case 解构结果表",
-      "source_type": "mysql_table",
-      "status": ["verified"],
-      "system": "content-deconstruction-supply",
-      "table_or_endpoint": "workflow_decode_task_result",
-      "access_mode": "planned_read",
-      "usage_stages": ["source_seed", "search_intent", "pattern_recall"],
-      "owner_module": "数据源与种子模块",
-      "current_code_refs": [],
-      "required_env_vars": ["CONTENT_SUPPLY_DB_*"],
-      "input_fields": ["channel_content_id"],
-      "output_fields": ["title", "body_text", "images", "video_url", "channel", "purpose_points", "key_points", "inspiration_points", "topic_fusion_result"],
-      "v1_boundary": "可解释历史 Case,不替代 PG Pattern V2 evidence_pack。"
-    },
-    {
-      "source_id": "SRC_PG_PATTERN_ITEMSET",
-      "display_name": "PG Pattern V2 itemset 与支撑素材",
-      "source_type": "postgres_table",
-      "status": ["verified", "v1_active"],
-      "system": "open_aigc.public",
-      "table_or_endpoint": "pattern_itemset, pattern_itemset_post, pattern_itemset_item",
-      "access_mode": "planned_read",
-      "usage_stages": ["source_seed", "search_intent", "pattern_recall", "learning_review"],
-      "owner_module": "数据源与种子模块",
-      "current_code_refs": [],
-      "required_env_vars": ["OPEN_AIGC_PG_*", "PGVECTOR_DSN", "DATA_ENGINEERING_OPEN_AIGC_PG_DSN"],
-      "input_fields": ["execution_id", "itemset_id", "mining_config_id", "scope"],
-      "output_fields": ["support", "absolute_support", "matched_post_ids", "itemset_items", "category_id", "category_path", "element_name"],
-      "v1_boundary": "新版 evidence_pack 的正式 Pattern 主事实来源。CFA 可通过 DemandAgent evidence_pack 间接消费,不应临时回旧 MySQL 兜底。"
-    },
-    {
-      "source_id": "SRC_PG_PATTERN_CATEGORY",
-      "display_name": "PG Pattern V2 分类快照",
-      "source_type": "postgres_table",
-      "status": ["verified", "v1_active"],
-      "system": "open_aigc.public",
-      "table_or_endpoint": "pattern_mining_category",
-      "access_mode": "planned_read",
-      "usage_stages": ["source_seed", "pattern_recall", "learning_review"],
-      "owner_module": "数据源与种子模块",
-      "current_code_refs": [],
-      "required_env_vars": ["OPEN_AIGC_PG_*", "PGVECTOR_DSN", "DATA_ENGINEERING_OPEN_AIGC_PG_DSN"],
-      "input_fields": ["execution_id", "id", "path", "source_type"],
-      "output_fields": ["name", "description", "category_nature", "level", "parent_id", "element_count"],
-      "v1_boundary": "解释 PG Pattern V2 分类路径,不直接召回平台内容。"
-    },
-    {
-      "source_id": "SRC_PG_PATTERN_ELEMENT",
-      "display_name": "PG Pattern V2 元素快照",
-      "source_type": "postgres_table",
-      "status": ["verified", "v1_active"],
-      "system": "open_aigc.public",
-      "table_or_endpoint": "pattern_mining_element",
-      "access_mode": "planned_read",
-      "usage_stages": ["source_seed", "pattern_recall", "learning_review"],
-      "owner_module": "数据源与种子模块",
-      "current_code_refs": [],
-      "required_env_vars": ["OPEN_AIGC_PG_*", "PGVECTOR_DSN", "DATA_ENGINEERING_OPEN_AIGC_PG_DSN"],
-      "input_fields": ["execution_id", "post_id", "source_table", "source_element_id", "element_type", "element_sub_type", "name"],
-      "output_fields": ["category_path", "point_type", "point_text", "element_name"],
-      "v1_boundary": "用于 PG Pattern V2 解释和新发现内容回扣判断。"
-    },
-    {
-      "source_id": "SRC_OPEN_AIGC_POST",
-      "display_name": "上游历史内容素材表",
-      "source_type": "mysql_table",
-      "status": ["verified"],
-      "system": "open_aigc",
-      "table_or_endpoint": "post",
-      "access_mode": "planned_read",
-      "usage_stages": ["source_seed", "learning_review"],
-      "owner_module": "数据源与种子模块",
-      "current_code_refs": [],
-      "required_env_vars": ["OPEN_AIGC_MYSQL_*"],
-      "input_fields": ["post_id", "platform", "merge_leve1", "merge_leve2", "import_date"],
-      "output_fields": ["title", "body_text", "platform_account_id", "platform_account_name", "publish_timestamp", "like_count", "comment_count", "collect_count", "images"],
-      "v1_boundary": "上游素材理解来源,不是当前 CFA 主链路平台召回。"
-    },
-    {
-      "source_id": "SRC_OPEN_AIGC_TOPIC_POINT_ELEMENT",
-      "display_name": "上游选题点元素表",
-      "source_type": "mysql_table",
-      "status": ["verified"],
-      "system": "open_aigc",
-      "table_or_endpoint": "post_decode_topic_point_element",
-      "access_mode": "planned_read",
-      "usage_stages": ["source_seed", "pattern_recall", "learning_review"],
-      "owner_module": "数据源与种子模块",
-      "current_code_refs": [],
-      "required_env_vars": ["OPEN_AIGC_MYSQL_*"],
-      "input_fields": ["post_id", "topic_point_id", "element_type", "element_sub_type"],
-      "output_fields": ["element_name", "element_description", "element_source", "element_reason"],
-      "v1_boundary": "用于理解上游元素,不替代 workflow_decode_task_result。"
-    },
-    {
-      "source_id": "SRC_OPEN_AIGC_SCRIPT_ELEMENT",
-      "display_name": "上游脚本段落元素表",
-      "source_type": "mysql_table",
-      "status": ["verified"],
-      "system": "open_aigc",
-      "table_or_endpoint": "post_script_paragraph_field_element",
-      "access_mode": "planned_read",
-      "usage_stages": ["source_seed", "learning_review"],
-      "owner_module": "数据源与种子模块",
-      "current_code_refs": [],
-      "required_env_vars": ["OPEN_AIGC_MYSQL_*"],
-      "input_fields": ["post_id", "paragraph_id", "field_type", "element_type", "element_sub_type"],
-      "output_fields": ["element_name", "element_description"],
-      "v1_boundary": "策略学习和素材理解来源。"
-    },
-    {
-      "source_id": "SRC_OLD_MYSQL_TOPIC_PATTERN",
-      "display_name": "旧 MySQL topic_pattern 兼容层",
-      "source_type": "mysql_table",
-      "status": ["verified", "deprecated_reference_only"],
-      "system": "open_aigc_pattern",
-      "table_or_endpoint": "topic_pattern_element, topic_pattern_itemset, topic_pattern_category",
-      "access_mode": "not_connected",
-      "usage_stages": ["migration_reference"],
-      "owner_module": "数据源与种子模块",
-      "current_code_refs": [],
-      "required_env_vars": ["OPEN_AIGC_PATTERN_DB_*", "PATTERN_GLOBAL_DB_*", "CONTENTFIND_PATTERN_DB_*"],
-      "input_fields": ["execution_id", "itemset_id", "category_id"],
-      "output_fields": ["historical_pattern_fields"],
-      "v1_boundary": "只作历史迁移对照,不能进入新版 evidence_pack,不能作为 PG Pattern V2 兜底。"
-    },
-    {
-      "source_id": "PLT_DOUYIN_KEYWORD",
-      "display_name": "Crawapi 抖音关键词搜索",
-      "source_type": "http_api",
-      "status": ["verified", "v1_active"],
-      "system": "Crawapi Douyin",
-      "table_or_endpoint": "/crawler/dou_yin/keyword",
-      "access_mode": "read",
-      "usage_stages": ["platform_access", "content_discovery"],
-      "owner_module": "平台接入模块",
-      "current_code_refs": ["content_agent/integrations/douyin.py", "content_agent/business_modules/platform_access.py"],
-      "required_env_vars": ["CONTENTFIND_API_CRAWAPI_BASE_URL", "CONTENTFIND_DOUYIN_*"],
-      "input_fields": ["keyword", "content_type", "sort_type", "publish_time", "cursor", "account_id"],
-      "output_fields": ["platform_content_id", "desc", "author", "platform_author_id", "statistics", "has_more", "next_cursor"],
-      "v1_boundary": "V1 real 模式已接关键词搜索一页结果;默认每个 search_query 小预算处理。"
-    },
-    {
-      "source_id": "PLT_DOUYIN_DETAIL",
-      "display_name": "Crawapi 抖音内容详情",
-      "source_type": "http_api",
-      "status": ["verified"],
-      "system": "Crawapi Douyin",
-      "table_or_endpoint": "/crawler/dou_yin/detail",
-      "access_mode": "read",
-      "usage_stages": ["platform_access", "content_discovery"],
-      "owner_module": "平台接入模块",
-      "current_code_refs": ["content_agent/integrations/douyin.py", "content_agent/business_modules/platform_access.py"],
-      "required_env_vars": ["CONTENTFIND_API_CRAWAPI_BASE_URL", "CONTENTFIND_DOUYIN_*"],
-      "input_fields": ["content_id"],
-      "output_fields": ["channel_content_id", "content_link", "title", "body_text", "topic_list", "content_type", "video_url_list", "multi_bitrate", "image_url_list", "channel_account_id", "channel_account_name", "like_count", "comment_count", "share_count", "collect_count", "publish_timestamp", "is_original", "has_more", "next_cursor"],
-      "v1_boundary": "2026-06-11 真实 smoke 已确认 code=0,归一化结构(channel_content_id/channel_account_id)与搜索 raw 结构字段名不同;无翻页(has_more=false);view_count/play_count 实测 null;视频下载需 iOS UA + Referer=https://www.douyin.com/。历史漏登,本次补齐。"
-    },
-    {
-      "source_id": "PLT_DOUYIN_AUTHOR_WORKS",
-      "display_name": "Crawapi 抖音账号作品",
-      "source_type": "http_api",
-      "status": ["verified"],
-      "system": "Crawapi Douyin",
-      "table_or_endpoint": "/crawler/dou_yin/blogger",
-      "access_mode": "read",
-      "usage_stages": ["walk_strategy", "content_discovery"],
-      "owner_module": "平台接入模块",
-      "current_code_refs": [
-        "content_agent/integrations/douyin.py::CrawapiDouyinClient.fetch_author_works"
-      ],
-      "required_env_vars": ["CONTENTFIND_API_CRAWAPI_BASE_URL", "CONTENTFIND_DOUYIN_*"],
-      "input_fields": ["account_id", "sort_type", "cursor"],
-      "output_fields": ["platform_content_id", "desc", "author", "statistics", "has_more", "next_cursor"],
-      "v1_boundary": "P6 作者边使用;作品仍需逐条 Pattern 回扣和规则判断。"
-    },
-    {
-      "source_id": "PLT_DOUYIN_CONTENT_PROFILE",
-      "display_name": "热点宝内容点赞画像",
-      "source_type": "http_api",
-      "status": ["verified", "v1_active"],
-      "system": "Crawapi Douyin",
-      "table_or_endpoint": "/crawler/dou_yin/re_dian_bao/video_like_portrait",
-      "access_mode": "read",
-      "usage_stages": ["platform_access", "content_discovery", "rule_judgment"],
-      "owner_module": "平台接入模块",
-      "current_code_refs": ["content_agent/integrations/douyin.py"],
-      "required_env_vars": ["CONTENTFIND_API_CRAWAPI_BASE_URL", "CONTENTFIND_DOUYIN_*"],
-      "input_fields": ["content_id", "need_age", "need_gender", "need_province"],
-      "output_fields": ["age_profile", "gender_profile", "province_profile", "percentage", "preference"],
-      "v1_boundary": "作为内容 50+ 画像信号,不直接代表内容质量。"
-    },
-    {
-      "source_id": "PLT_DOUYIN_AUTHOR_PROFILE",
-      "display_name": "热点宝账号画像",
-      "source_type": "http_api",
-      "status": ["verified"],
-      "system": "Crawapi Douyin",
-      "table_or_endpoint": "/crawler/dou_yin/re_dian_bao/account_fans_portrait",
-      "access_mode": "planned_read",
-      "usage_stages": ["walk_strategy", "rule_judgment"],
-      "owner_module": "平台接入模块",
-      "current_code_refs": [],
-      "required_env_vars": ["CONTENTFIND_API_CRAWAPI_BASE_URL", "CONTENTFIND_DOUYIN_*"],
-      "input_fields": ["account_id", "need_age", "need_gender", "need_province"],
-      "output_fields": ["age_profile", "gender_profile", "province_profile", "percentage", "preference"],
-      "v1_boundary": "作者强不代表作品必然强;V1 不做账号画像兜底。"
-    },
-    {
-      "source_id": "PLT_DOUYIN_HOT_RANK",
-      "display_name": "今日热榜",
-      "source_type": "http_api",
-      "status": ["verified"],
-      "system": "Crawapi Hot Rank",
-      "table_or_endpoint": "/crawler/jin_ri_re_bang/content_rank",
-      "access_mode": "planned_read",
-      "usage_stages": ["source_seed", "search_intent"],
-      "owner_module": "平台接入模块",
-      "current_code_refs": [],
-      "required_env_vars": ["CONTENTFIND_API_CRAWAPI_BASE_URL", "CONTENTFIND_HOT_TOPIC_PATH"],
-      "input_fields": ["sort_type", "cursor"],
-      "output_fields": ["source", "jump_url", "type", "rankList.title", "rankList.heat", "has_more", "next_cursor"],
-      "v1_boundary": "热点只做探索入口,不等于最终内容源。"
-    },
-    {
-      "source_id": "PLT_TIKHUB_SEARCH_FALLBACK",
-      "display_name": "TikHub 抖音搜索备用通道",
-      "source_type": "http_api",
-      "status": ["blocked", "missing"],
-      "system": "TikHub",
-      "table_or_endpoint": "TIKHUB_DOUYIN_SEARCH_PATH",
-      "access_mode": "not_connected",
-      "usage_stages": ["platform_access"],
-      "owner_module": "平台接入模块",
-      "current_code_refs": [],
-      "required_env_vars": ["TIKHUB_API_KEY", "TIKHUB_BASE_URL", "TIKHUB_DOUYIN_SEARCH_PATH"],
-      "input_fields": ["keyword", "cursor", "sort_type", "publish_time", "filter_duration", "content_type", "search_id", "backtrace"],
-      "output_fields": ["business_data", "next_page", "backtrace"],
-      "v1_boundary": "缺 key,不能写成已接入。"
-    },
-    {
-      "source_id": "JDG_DECODE_API",
-      "display_name": "内容解构接口",
-      "source_type": "http_api",
-      "status": ["source_only"],
-      "system": "Aiddit AIGC Decode",
-      "table_or_endpoint": "https://aigc-api.aiddit.com/aigc/api/task/decode, https://aigc-api.aiddit.com/aigc/api/task/decode/result",
-      "access_mode": "read",
-      "usage_stages": ["pattern_recall", "rule_judgment"],
-      "owner_module": "发现内容与证据模块",
-      "current_code_refs": [
-        "content_agent/integrations/decode_api.py::AigcDecodeClient",
-        "content_agent/business_modules/content_discovery/pattern_recall/decode.py::decode_content"
-      ],
-      "required_env_vars": [],
-      "input_fields": ["platform_content_id", "platform_content_url", "configId"],
-      "output_fields": ["decode_status", "purpose_points", "key_points", "inspiration_points", "topic_fusion_result"],
-      "v1_boundary": "当前 CFA 主链路尚未接入;Pattern 回扣必须依赖解构强证据词。"
-    },
-    {
-      "source_id": "JDG_CATEGORY_MATCH_PATHS",
-      "display_name": "分类树 match-paths",
-      "source_type": "http_api",
-      "status": ["verified", "source_only"],
-      "system": "Aiddit Library",
-      "table_or_endpoint": "https://library.aiddit.com/api/search/categories/match-paths/v2",
-      "access_mode": "read",
-      "usage_stages": ["pattern_recall", "rule_judgment"],
-      "owner_module": "发现内容与证据模块",
-      "current_code_refs": [
-        "content_agent/integrations/category_match.py::CategoryMatchClient.match_paths",
-        "content_agent/business_modules/content_discovery/pattern_recall/category_match.py::match_decode_terms"
-      ],
-      "required_env_vars": [],
-      "input_fields": ["source_type", "top_k", "min_score", "items.term", "items.description"],
-      "output_fields": ["matched_category_paths", "score", "matched_terms"],
-      "v1_boundary": "在线向量匹配结果会漂移;2026-06-08 已确认当前 endpoint 为 v2,不能把某次分数写成稳定业务合同。"
-    },
-    {
-      "source_id": "QRY_OPENROUTER",
-      "display_name": "OpenRouter LLM 能力",
-      "source_type": "capability",
-      "status": ["source_only"],
-      "system": "OpenRouter",
-      "table_or_endpoint": "chat completion / tool use",
-      "access_mode": "not_connected",
-      "usage_stages": ["search_intent", "rule_judgment"],
-      "owner_module": "搜索意图模块",
-      "current_code_refs": [],
-      "required_env_vars": ["OPENROUTER_API_KEY", "OPENROUTER_BASE_URL"],
-      "input_fields": ["model", "messages", "tools", "system", "max_tokens", "temperature"],
-      "output_fields": ["content", "tool_calls", "usage", "finish_reason"],
-      "v1_boundary": "本轮只做 key metadata 探测;真实生成必须另开预算和策略开关。"
-    },
-    {
-      "source_id": "ASSET_DEMAND_FIND_CONTENT_RESULT",
-      "display_name": "旧版内容结果沉淀表",
-      "source_type": "mysql_table",
-      "status": ["verified"],
-      "system": "content-deconstruction-supply",
-      "table_or_endpoint": "demand_find_content_result",
-      "access_mode": "planned_read",
-      "usage_stages": ["asset_commit", "learning_review"],
-      "owner_module": "结果沉淀与来源反查模块",
-      "current_code_refs": [],
-      "required_env_vars": ["CONTENT_SUPPLY_DB_*"],
-      "input_fields": ["run_id", "query", "rank_no", "channel", "platform_content_id", "demand_content_id"],
-      "output_fields": ["video_url", "title", "author_name", "author_id", "process_trace", "crawler_plan_id", "produce_plan_id", "publish_plan_id", "web_html_url"],
-      "v1_boundary": "旧版只沉淀最终内容,不保存全量发现内容和结构化淘汰原因。"
-    },
-    {
-      "source_id": "ASSET_DEMAND_FIND_AUTHOR",
-      "display_name": "旧版作者资产表",
-      "source_type": "mysql_table",
-      "status": ["verified"],
-      "system": "content-deconstruction-supply",
-      "table_or_endpoint": "demand_find_author",
-      "access_mode": "planned_read",
-      "usage_stages": ["walk_strategy", "asset_commit", "learning_review"],
-      "owner_module": "结果沉淀与来源反查模块",
-      "current_code_refs": [],
-      "required_env_vars": ["CONTENT_SUPPLY_DB_*"],
-      "input_fields": ["author_id", "author_name", "author_link", "content_tags", "channel"],
-      "output_fields": ["elderly_ratio", "elderly_tgi", "is_good", "remark"],
-      "v1_boundary": "历史作者参考不进入 P6 V1 默认边;作者资产启用等 P6 作者边实现后再处理。"
-    },
-    {
-      "source_id": "LEARN_DEMAND_FIND_TASK",
-      "display_name": "旧版任务级 run_context 表",
-      "source_type": "mysql_table",
-      "status": ["verified"],
-      "system": "content-deconstruction-supply",
-      "table_or_endpoint": "demand_find_task",
-      "access_mode": "planned_read",
-      "usage_stages": ["learning_review"],
-      "owner_module": "策略学习模块",
-      "current_code_refs": [],
-      "required_env_vars": ["CONTENT_SUPPLY_DB_*"],
-      "input_fields": ["run_id", "demand_content_id", "status", "created_at", "token_coast"],
-      "output_fields": ["task_status", "cost", "demand_relation"],
-      "v1_boundary": "只能看任务级结果,不能还原全量发现内容过程。"
-    },
-    {
-      "source_id": "LEARN_DEMAND_TASK_OPRATE",
-      "display_name": "旧版调度和预算开关表",
-      "source_type": "mysql_table",
-      "status": ["source_only"],
-      "system": "content-deconstruction-supply",
-      "table_or_endpoint": "demand_task_oprate",
-      "access_mode": "planned_read",
-      "usage_stages": ["learning_review", "policy_version"],
-      "owner_module": "策略学习模块",
-      "current_code_refs": [],
-      "required_env_vars": ["CONTENT_SUPPLY_DB_*"],
-      "input_fields": ["is_open", "day_limit_coast", "update_time"],
-      "output_fields": ["enabled", "daily_budget"],
-      "v1_boundary": "策略控制数据,不是学习结论。"
-    },
-    {
-      "source_id": "ASSET_AIGC_PLAN_DETAIL",
-      "display_name": "AIGC 后处理计划详情",
-      "source_type": "http_api",
-      "status": ["source_only", "blocked"],
-      "system": "AIGC plan",
-      "table_or_endpoint": "produce plan detail / crawler plan / publish plan",
-      "access_mode": "not_connected",
-      "usage_stages": ["asset_commit", "publish_job"],
-      "owner_module": "结果沉淀与来源反查模块",
-      "current_code_refs": [],
-      "required_env_vars": ["AIGC_TOKEN", "CONTENTFIND_API_AIGC_TOKEN", "CONTENTFIND_API_READONLY_TOKEN", "CONTENTFIND_AIGC_READONLY_PRODUCE_PLAN_ID"],
-      "input_fields": ["platform_content_id", "merge_leve2", "produce_plan_id", "account_id", "sort_type"],
-      "output_fields": ["crawler_plan_id", "produce_plan_id", "produce_plan_name", "publish_plan_id"],
-      "v1_boundary": "写侧有副作用;V1 已拍板不自动触发发布接口。"
-    },
-    {
-      "source_id": "ASSET_OSS_LOG_HTML",
-      "display_name": "OSS 过程可视化链接",
-      "source_type": "capability",
-      "status": ["source_only", "blocked", "missing"],
-      "system": "Aliyun OSS",
-      "table_or_endpoint": "OSS object storage",
-      "access_mode": "not_connected",
-      "usage_stages": ["asset_commit", "run_record"],
-      "owner_module": "运行记录模块",
-      "current_code_refs": [],
-      "required_env_vars": ["ALIYUN_OSS_ACCESS_KEY_ID", "ALIYUN_OSS_ACCESS_KEY_SECRET", "ALIYUN_OSS_BUCKET", "ALIYUN_OSS_REGION", "ALIYUN_OSS_PREFIX", "ALIYUN_OSS_PUBLIC_BASE_URL"],
-      "input_fields": ["html_path", "object_key", "task_id"],
-      "output_fields": ["web_html_url"],
-      "v1_boundary": "缺真实 OSS 配置;不能写成已接入。"
-    },
-    {
-      "source_id": "LEARN_ODPS_DEMAND_POOL",
-      "display_name": "ODPS 需求池表现参考",
-      "source_type": "odps_table",
-      "status": ["verified"],
-      "system": "ODPS",
-      "table_or_endpoint": "dwd_multi_demand_pool_di",
-      "access_mode": "planned_read",
-      "usage_stages": ["learning_review"],
-      "owner_module": "策略学习模块",
-      "current_code_refs": [],
-      "required_env_vars": ["ODPS_ACCESS_ID", "ODPS_ACCESS_KEY", "ODPS_ENDPOINT", "ODPS_PROJECT"],
-      "input_fields": ["strategy", "demand_id", "demand_name", "weight", "type", "video_count", "video_list", "extend", "dt"],
-      "output_fields": ["demand_performance", "video_list"],
-      "v1_boundary": "表现参考,不是实时判断输入。"
-    },
-    {
-      "source_id": "LEARN_ODPS_FEATURE_POINT",
-      "display_name": "ODPS 特征点表现参考",
-      "source_type": "odps_table",
-      "status": ["verified"],
-      "system": "ODPS",
-      "table_or_endpoint": "feature_point_data",
-      "access_mode": "planned_read",
-      "usage_stages": ["learning_review"],
-      "owner_module": "策略学习模块",
-      "current_code_refs": [],
-      "required_env_vars": ["ODPS_ACCESS_ID", "ODPS_ACCESS_KEY", "ODPS_ENDPOINT", "ODPS_PROJECT"],
-      "input_fields": ["特征点", "分发视频量", "vid_list", "dt"],
-      "output_fields": ["曝光", "回流", "播放", "视频列表"],
-      "v1_boundary": "用于后续策略学习,不是 V1 实时评分表。"
-    },
-    {
-      "source_id": "LEARN_LOGHUB_VIDEO_DIMENSION",
-      "display_name": "视频表现宽表",
-      "source_type": "odps_table",
-      "status": ["verified"],
-      "system": "loghubods",
-      "table_or_endpoint": "video_dimension_detail_add_column",
-      "access_mode": "planned_read",
-      "usage_stages": ["learning_review"],
-      "owner_module": "策略学习模块",
-      "current_code_refs": [],
-      "required_env_vars": ["ODPS_ACCESS_ID", "ODPS_ACCESS_KEY", "ODPS_ENDPOINT", "ODPS_PROJECT"],
-      "input_fields": ["视频id", "视频地址", "标题", "一级品类", "二级品类", "上传渠道", "dt"],
-      "output_fields": ["曝光", "播放", "回流", "标签"],
-      "v1_boundary": "宽表只列策略学习关键字段。"
-    },
-    {
-      "source_id": "LEARN_GLOBAL_TAXONOMY",
-      "display_name": "open_aigc 全局分类与元素",
-      "source_type": "mysql_table",
-      "status": ["verified"],
-      "system": "open_aigc",
-      "table_or_endpoint": "global_category, global_element, element_classification_mapping",
-      "access_mode": "planned_read",
-      "usage_stages": ["pattern_recall", "learning_review"],
-      "owner_module": "策略学习模块",
-      "current_code_refs": [],
-      "required_env_vars": ["OPEN_AIGC_MYSQL_*"],
-      "input_fields": ["stable_id", "name", "source_type", "path", "source_element_id", "post_id", "classification_path"],
-      "output_fields": ["category_tree", "global_element", "classification_mapping"],
-      "v1_boundary": "长期概念分类来源;不替代在线 match-paths。"
-    },
-    {
-      "source_id": "GAP_XIAOHONGSHU_PLATFORM",
-      "display_name": "小红书笔记、作者、话题接口",
-      "source_type": "gap",
-      "status": ["missing"],
-      "system": "xiaohongshu",
-      "table_or_endpoint": "not_available",
-      "access_mode": "not_connected",
-      "usage_stages": ["platform_access", "walk_strategy"],
-      "owner_module": "平台接入模块",
-      "current_code_refs": [],
-      "required_env_vars": [],
-      "input_fields": [],
-      "output_fields": [],
-      "v1_boundary": "产品可以设计,但技术文档不能写成已接入。"
-    },
-    {
-      "source_id": "GAP_OTHER_PLATFORMS",
-      "display_name": "快手、B站、视频号、票圈等平台",
-      "source_type": "gap",
-      "status": ["missing"],
-      "system": "multi_platform",
-      "table_or_endpoint": "not_available",
-      "access_mode": "not_connected",
-      "usage_stages": ["platform_access"],
-      "owner_module": "平台接入模块",
-      "current_code_refs": [],
-      "required_env_vars": [],
-      "input_fields": [],
-      "output_fields": [],
-      "v1_boundary": "暂时不能复用抖音字段口径。"
-    },
-    {
-      "source_id": "GAP_RELATED_DISCOVERY",
-      "display_name": "相关搜索、视频 tag、共创作者、相似作者、相似内容",
-      "source_type": "gap",
-      "status": ["missing"],
-      "system": "platform_related_discovery",
-      "table_or_endpoint": "not_available",
-      "access_mode": "not_connected",
-      "usage_stages": ["walk_strategy"],
-      "owner_module": "游走策略模块",
-      "current_code_refs": [],
-      "required_env_vars": [],
-      "input_fields": [],
-      "output_fields": [],
-      "v1_boundary": "可作为游走方向,但必须标为待验证。"
-    }
-  ],
-  "stage_index": {
-    "source_seed": ["SRC_DEMAND_CONTENT_MYSQL", "SRC_DEMAND_CONTENT_LOCAL_JSON", "SRC_WORKFLOW_DECODE_TASK_RESULT", "SRC_PG_PATTERN_ITEMSET", "SRC_PG_PATTERN_CATEGORY", "SRC_PG_PATTERN_ELEMENT", "SRC_OPEN_AIGC_POST", "SRC_OPEN_AIGC_TOPIC_POINT_ELEMENT", "SRC_OPEN_AIGC_SCRIPT_ELEMENT", "PLT_DOUYIN_HOT_RANK"],
-    "search_intent": ["SRC_DEMAND_CONTENT_MYSQL", "SRC_DEMAND_CONTENT_LOCAL_JSON", "SRC_PG_PATTERN_ITEMSET", "QRY_OPENROUTER"],
-    "platform_access": ["PLT_DOUYIN_KEYWORD", "PLT_DOUYIN_DETAIL", "PLT_DOUYIN_AUTHOR_WORKS", "PLT_DOUYIN_CONTENT_PROFILE", "PLT_DOUYIN_AUTHOR_PROFILE", "PLT_TIKHUB_SEARCH_FALLBACK", "GAP_XIAOHONGSHU_PLATFORM", "GAP_OTHER_PLATFORMS"],
-    "content_discovery": ["PLT_DOUYIN_KEYWORD", "PLT_DOUYIN_DETAIL", "PLT_DOUYIN_CONTENT_PROFILE"],
-    "pattern_recall": ["JDG_DECODE_API", "JDG_CATEGORY_MATCH_PATHS", "SRC_WORKFLOW_DECODE_TASK_RESULT", "SRC_PG_PATTERN_ITEMSET", "SRC_PG_PATTERN_CATEGORY", "SRC_PG_PATTERN_ELEMENT", "LEARN_GLOBAL_TAXONOMY"],
-    "rule_judgment": ["PLT_DOUYIN_CONTENT_PROFILE", "PLT_DOUYIN_AUTHOR_PROFILE", "JDG_DECODE_API", "JDG_CATEGORY_MATCH_PATHS"],
-    "walk_strategy": ["PLT_DOUYIN_AUTHOR_WORKS", "ASSET_DEMAND_FIND_AUTHOR", "GAP_RELATED_DISCOVERY", "GAP_XIAOHONGSHU_PLATFORM"],
-    "asset_commit": ["ASSET_DEMAND_FIND_CONTENT_RESULT", "ASSET_DEMAND_FIND_AUTHOR", "ASSET_AIGC_PLAN_DETAIL", "ASSET_OSS_LOG_HTML"],
-    "learning_review": ["LEARN_DEMAND_FIND_TASK", "LEARN_DEMAND_TASK_OPRATE", "ASSET_DEMAND_FIND_CONTENT_RESULT", "ASSET_DEMAND_FIND_AUTHOR", "LEARN_ODPS_DEMAND_POOL", "LEARN_ODPS_FEATURE_POINT", "LEARN_LOGHUB_VIDEO_DIMENSION", "LEARN_GLOBAL_TAXONOMY", "SRC_PG_PATTERN_ITEMSET"]
-  },
-  "status_index": {
-    "v1_active": ["SRC_DEMAND_CONTENT_MYSQL", "SRC_DEMAND_CONTENT_LOCAL_JSON", "SRC_PG_PATTERN_ITEMSET", "SRC_PG_PATTERN_CATEGORY", "SRC_PG_PATTERN_ELEMENT", "PLT_DOUYIN_KEYWORD", "PLT_DOUYIN_CONTENT_PROFILE"],
-    "verified": ["SRC_DEMAND_CONTENT_MYSQL", "SRC_DEMAND_CONTENT_LOCAL_JSON", "SRC_WORKFLOW_DECODE_TASK_RESULT", "SRC_PG_PATTERN_ITEMSET", "SRC_PG_PATTERN_CATEGORY", "SRC_PG_PATTERN_ELEMENT", "SRC_OPEN_AIGC_POST", "SRC_OPEN_AIGC_TOPIC_POINT_ELEMENT", "SRC_OPEN_AIGC_SCRIPT_ELEMENT", "SRC_OLD_MYSQL_TOPIC_PATTERN", "PLT_DOUYIN_KEYWORD", "PLT_DOUYIN_DETAIL", "PLT_DOUYIN_AUTHOR_WORKS", "PLT_DOUYIN_CONTENT_PROFILE", "PLT_DOUYIN_AUTHOR_PROFILE", "PLT_DOUYIN_HOT_RANK", "JDG_CATEGORY_MATCH_PATHS", "ASSET_DEMAND_FIND_CONTENT_RESULT", "ASSET_DEMAND_FIND_AUTHOR", "LEARN_DEMAND_FIND_TASK", "LEARN_ODPS_DEMAND_POOL", "LEARN_ODPS_FEATURE_POINT", "LEARN_LOGHUB_VIDEO_DIMENSION", "LEARN_GLOBAL_TAXONOMY"],
-    "missing_or_blocked": ["PLT_TIKHUB_SEARCH_FALLBACK", "ASSET_AIGC_PLAN_DETAIL", "ASSET_OSS_LOG_HTML", "GAP_XIAOHONGSHU_PLATFORM", "GAP_OTHER_PLATFORMS", "GAP_RELATED_DISCOVERY"]
-  },
-  "env_var_index": {
-    "CONTENT_SUPPLY_DB_*": ["SRC_DEMAND_CONTENT_MYSQL", "SRC_WORKFLOW_DECODE_TASK_RESULT", "ASSET_DEMAND_FIND_CONTENT_RESULT", "ASSET_DEMAND_FIND_AUTHOR", "LEARN_DEMAND_FIND_TASK", "LEARN_DEMAND_TASK_OPRATE"],
-    "OPEN_AIGC_PG_*": ["SRC_PG_PATTERN_ITEMSET", "SRC_PG_PATTERN_CATEGORY", "SRC_PG_PATTERN_ELEMENT"],
-    "OPEN_AIGC_MYSQL_*": ["SRC_OPEN_AIGC_POST", "SRC_OPEN_AIGC_TOPIC_POINT_ELEMENT", "SRC_OPEN_AIGC_SCRIPT_ELEMENT", "LEARN_GLOBAL_TAXONOMY"],
-    "CONTENTFIND_API_CRAWAPI_BASE_URL": ["PLT_DOUYIN_KEYWORD", "PLT_DOUYIN_DETAIL", "PLT_DOUYIN_AUTHOR_WORKS", "PLT_DOUYIN_CONTENT_PROFILE", "PLT_DOUYIN_AUTHOR_PROFILE", "PLT_DOUYIN_HOT_RANK"],
-    "TIKHUB_API_KEY": ["PLT_TIKHUB_SEARCH_FALLBACK"],
-    "OPENROUTER_API_KEY": ["QRY_OPENROUTER"],
-    "AIGC_TOKEN": ["ASSET_AIGC_PLAN_DETAIL"],
-    "ALIYUN_OSS_*": ["ASSET_OSS_LOG_HTML"],
-    "ODPS_*": ["LEARN_ODPS_DEMAND_POOL", "LEARN_ODPS_FEATURE_POINT", "LEARN_LOGHUB_VIDEO_DIMENSION"]
-  },
-  "validation_rules": [
-    "每个 source_id 必须唯一。",
-    "status 只能使用 status_legend 中登记的值。",
-    "source_type 只能使用 source_type_legend 中登记的值。",
-    "required_env_vars 只登记变量名或通配符,不登记 secret 值。",
-    "deprecated_reference_only 的来源不能作为 v1_active 来源。",
-    "missing 或 blocked 的来源不能写成已接入。"
-  ]
-}

+ 0 - 113
数据接口与来源/platform_profiles/bilibili.json

@@ -1,113 +0,0 @@
-{
-  "schema_version": "platform_profile.v1",
-  "platform": "bilibili",
-  "platform_label": "B站",
-  "status": "partial",
-  "evidence": "2026-06-11 实测(接口台账/B站.md)",
-  "runtime": {
-    "rate_limit_seconds": 15,
-    "retry": null,
-    "video_download": {
-      "downloadable": true,
-      "headers": {
-        "User-Agent": "PC UA",
-        "Referer": "https://www.bilibili.com/"
-      },
-      "note": "实测 206 真实字节;.m4s DASH 分轨(视频轨+音频轨 voice_data.play_url 需合并)"
-    }
-  },
-  "heat": {
-    "note": "R3 第二步(2026-06-12):B站有播放量,量级大以播放为主;字段据抓包,权重/锚点起步值待该平台接入真跑标定",
-    "signals": [
-      {
-        "field": "play_count",
-        "weight": 0.4,
-        "floor": 5000,
-        "ceil": 10000000
-      },
-      {
-        "field": "digg_count",
-        "weight": 0.3,
-        "floor": 500,
-        "ceil": 500000
-      },
-      {
-        "field": "comment_count",
-        "weight": 0.1,
-        "floor": 50,
-        "ceil": 50000
-      },
-      {
-        "field": "share_count",
-        "weight": 0.1,
-        "floor": 20,
-        "ceil": 50000
-      },
-      {
-        "field": "collect_count",
-        "weight": 0.1,
-        "floor": 50,
-        "ceil": 200000
-      }
-    ]
-  },
-  "endpoints": {
-    "search": {
-      "path": "/crawler/bilibili/keyword",
-      "params": {
-        "keyword": "str"
-      },
-      "response_shape": "raw(B站原生,一次 42 条)"
-    },
-    "detail": {
-      "path": "/crawler/bilibili/detail",
-      "params": {
-        "content_id": "bvid"
-      },
-      "response_shape": "normalized(channel_*)"
-    }
-  },
-  "jump_key_raw": {
-    "platform_content_id": {
-      "search": "bvid",
-      "detail": "channel_content_id"
-    },
-    "platform_author_id": {
-      "search": "mid",
-      "trap": "uid 字段恒为 0,必须取 mid"
-    },
-    "tags": {
-      "search": "tag(逗号串需 split)",
-      "detail": "topic_list"
-    },
-    "video_url": {
-      "detail": "video_url_list[0].video_url(.m4s)"
-    }
-  },
-  "edges": {
-    "query_next_page": {
-      "status": "supported"
-    },
-    "search_page_to_content": {
-      "status": "supported"
-    },
-    "video_to_author": {
-      "status": "supported",
-      "note": "仅落 mid 标识"
-    },
-    "author_to_works": {
-      "status": "unsupported",
-      "note": "集合中无作者作品接口"
-    },
-    "author_work_to_content": {
-      "status": "unsupported"
-    },
-    "video_to_hashtag": {
-      "status": "supported",
-      "note": "tag split / topic_list"
-    },
-    "hashtag_to_query": {
-      "status": "supported"
-    }
-  }
-}

+ 0 - 52
数据接口与来源/platform_profiles/douyin.json

@@ -1,52 +0,0 @@
-{
-  "schema_version": "platform_profile.v1",
-  "platform": "douyin",
-  "platform_label": "抖音",
-  "status": "active",
-  "evidence": "2026-06-11 实测(接口台账/抖音.md)",
-  "runtime": {
-    "rate_limit_seconds": 15,
-    "retry": null,
-    "video_download": { "downloadable": true, "headers": { "User-Agent": "iOS UA", "Referer": "https://www.douyin.com/" }, "note": "实测 206/video/mp4;play_addr 地址有时效" }
-  },
-  "heat": {
-    "note": "R3 第二步(2026-06-12):抖音无播放量,用 赞+评+转+藏 四字段复合;权重/锚点为起步值,凭真跑主观调",
-    "signals": [
-      { "field": "digg_count",    "weight": 0.4, "floor": 10000, "ceil": 1000000 },
-      { "field": "comment_count", "weight": 0.2, "floor": 100,   "ceil": 50000 },
-      { "field": "share_count",   "weight": 0.2, "floor": 50,    "ceil": 20000 },
-      { "field": "collect_count", "weight": 0.2, "floor": 100,   "ceil": 100000 }
-    ]
-  },
-  "observable_fields": [
-    { "field": "statistics.digg_count", "availability": "supported", "source": "search.statistics.digg_count / detail.like_count" },
-    { "field": "statistics.comment_count", "availability": "supported", "source": "search.statistics.comment_count / detail.comment_count" },
-    { "field": "statistics.share_count", "availability": "supported", "source": "search.statistics.share_count / detail.share_count" },
-    { "field": "statistics.collect_count", "availability": "supported", "source": "search.statistics.collect_count / detail.collect_count" }
-  ],
-  "missing_observable_fields": [
-    { "field": "statistics.play_count", "missing_type": "natural_platform_missing", "platform": "douyin", "evidence": "跨平台字段映射.json" }
-  ],
-  "endpoints": {
-    "search":         { "path": "/crawler/dou_yin/keyword", "params": { "keyword": "str", "content_type": "视频|图文", "sort_type": "最多点赞|最多分享", "cursor": "str" }, "response_shape": "raw(aweme 原生结构)" },
-    "detail":         { "path": "/crawler/dou_yin/detail", "params": { "content_id": "str" }, "response_shape": "normalized(channel_* 归一化)" },
-    "blogger":        { "path": "/crawler/dou_yin/blogger", "params": { "account_id": "sec_uid", "cursor": "str" }, "response_shape": "raw" },
-    "author_profile": { "path": "/crawler/dou_yin/re_dian_bao/account_fans_portrait", "params": { "account_id": "sec_uid", "need_age": true, "need_gender": true, "need_province": true } },
-    "content_profile": { "path": "/crawler/dou_yin/re_dian_bao/video_like_portrait", "status": "blocked", "note": "实测 HTTP 404" }
-  },
-  "jump_key_raw": {
-    "platform_content_id": { "search": "aweme_id", "detail": "channel_content_id" },
-    "platform_author_id":  { "search": "author.sec_uid", "detail": "channel_account_id" },
-    "tags":                { "search": "text_extra[].hashtag_name + video_tag[].tag_name", "detail": "topic_list" },
-    "video_url":           { "search": "video.play_addr.url_list[0]", "detail": "video_url_list[0].video_url" }
-  },
-  "edges": {
-    "query_next_page":        { "status": "supported" },
-    "search_page_to_content": { "status": "supported" },
-    "video_to_author":        { "status": "supported", "note": "搜索阶段即得完整作者档案(sec_uid/粉丝/签名)" },
-    "author_to_works":        { "status": "supported", "note": "blogger 可翻页,实测一次 22 条" },
-    "author_work_to_content": { "status": "supported" },
-    "video_to_hashtag":       { "status": "supported", "note": "结构化 tag 直接可用" },
-    "hashtag_to_query":       { "status": "supported" }
-  }
-}

+ 0 - 30
数据接口与来源/platform_profiles/github.json

@@ -1,30 +0,0 @@
-{
-  "schema_version": "platform_profile.v1",
-  "platform": "github",
-  "platform_label": "github",
-  "status": "partial",
-  "evidence": "2026-06-11 实测(接口台账/github.md)",
-  "runtime": {
-    "rate_limit_seconds": 15,
-    "retry": null,
-    "video_download": { "downloadable": false, "note": "代码仓库,非视频内容" }
-  },
-  "endpoints": {
-    "search": { "path": "/crawler/github/keyword", "params": { "keyword": "str", "token": "" }, "pagination_note": "请求传 token、响应给 next_cursor(短页码),参数名不对称" }
-  },
-  "jump_key_raw": {
-    "platform_content_id": { "search": "id" },
-    "platform_author_id":  { "search": "owner_login" },
-    "tags":                { "search": "topics(多数为空)" },
-    "url_trap":            "无现成仓库 url 字段,须 hl_name 去 <em> 后拼 https://github.com/{owner/repo}"
-  },
-  "edges": {
-    "query_next_page":        { "status": "supported" },
-    "search_page_to_content": { "status": "supported", "note": "repo,非视频" },
-    "video_to_author":        { "status": "supported", "note": "content→owner" },
-    "author_to_works":        { "status": "unsupported" },
-    "author_work_to_content": { "status": "unsupported" },
-    "video_to_hashtag":       { "status": "weak", "note": "topics 常空" },
-    "hashtag_to_query":       { "status": "supported" }
-  }
-}

+ 0 - 127
数据接口与来源/platform_profiles/kuaishou.json

@@ -1,127 +0,0 @@
-{
-  "schema_version": "platform_profile.v1",
-  "platform": "kuaishou",
-  "platform_label": "快手",
-  "status": "partial",
-  "evidence": "2026-06-11 实测(接口台账/快手.md)",
-  "runtime": {
-    "rate_limit_seconds": 15,
-    "retry": null,
-    "video_download": {
-      "downloadable": true,
-      "headers": {
-        "User-Agent": "PC UA",
-        "Referer": "https://www.kuaishou.com/"
-      },
-      "note": "实测 206/video/mp4;kwaicdn 直链带 pkey 签名"
-    }
-  },
-  "heat": {
-    "note": "R3 第二步(2026-06-12):快手有播放量(唯一全字段),以播放为主;字段据抓包,权重/锚点起步值待该平台接入真跑标定",
-    "signals": [
-      {
-        "field": "play_count",
-        "weight": 0.4,
-        "floor": 1000,
-        "ceil": 5000000
-      },
-      {
-        "field": "digg_count",
-        "weight": 0.3,
-        "floor": 1000,
-        "ceil": 500000
-      },
-      {
-        "field": "comment_count",
-        "weight": 0.1,
-        "floor": 50,
-        "ceil": 50000
-      },
-      {
-        "field": "share_count",
-        "weight": 0.1,
-        "floor": 20,
-        "ceil": 20000
-      },
-      {
-        "field": "collect_count",
-        "weight": 0.1,
-        "floor": 50,
-        "ceil": 100000
-      }
-    ]
-  },
-  "observable_fields": [
-    { "field": "statistics.play_count", "availability": "supported", "source": "view_count" },
-    { "field": "statistics.digg_count", "availability": "supported", "source": "like_count" },
-    { "field": "statistics.comment_count", "availability": "supported", "source": "comment_count" },
-    { "field": "statistics.share_count", "availability": "supported", "source": "share_count" },
-    { "field": "statistics.collect_count", "availability": "supported", "source": "collect_count" }
-  ],
-  "missing_observable_fields": [],
-  "endpoints": {
-    "search": {
-      "path": "/crawler/kuai_shou/keyword_v2",
-      "params": {
-        "keyword": "str"
-      },
-      "response_shape": "normalized(channel_*)"
-    },
-    "detail": {
-      "path": "/crawler/kuai_shou/detail",
-      "params": {
-        "content_id": "str"
-      },
-      "response_shape": "normalized"
-    },
-    "account_info": {
-      "path": "/crawler/kuai_shou/account_info",
-      "params": {
-        "account_id": "str",
-        "is_cache": true
-      }
-    }
-  },
-  "jump_key_raw": {
-    "platform_content_id": {
-      "search": "channel_content_id"
-    },
-    "platform_author_id": {
-      "search": "channel_account_id"
-    },
-    "tags": {
-      "search": "topic_list(常 null,兜底标题/正文正则)"
-    },
-    "video_url": {
-      "search": "video_url_list[0].video_url"
-    }
-  },
-  "edges": {
-    "query_next_page": {
-      "status": "blocked",
-      "note": "实测单页 has_more=false,不猜测翻页"
-    },
-    "search_page_to_content": {
-      "status": "supported"
-    },
-    "video_to_author": {
-      "status": "supported",
-      "note": "作者档案需 account_info 二次补齐"
-    },
-    "author_to_works": {
-      "status": "blocked",
-      "note": "无独立作者作品接口"
-    },
-    "author_work_to_content": {
-      "status": "blocked"
-    },
-    "video_to_hashtag": {
-      "status": "supported",
-      "confidence": "weak",
-      "note": "topic_list 常 null,靠正则兜底"
-    },
-    "hashtag_to_query": {
-      "status": "supported"
-    }
-  }
-}

+ 0 - 47
数据接口与来源/platform_profiles/shipinhao.json

@@ -1,47 +0,0 @@
-{
-  "schema_version": "platform_profile.v1",
-  "platform": "shipinhao",
-  "platform_label": "视频号",
-  "status": "partial",
-  "evidence": "2026-06-11 实测(接口台账/视频号.md)",
-  "runtime": {
-    "rate_limit_seconds": 15,
-    "retry": { "search": { "trigger": "仅暂时性故障重试:code=25011 接口异常 / 超时 / 网络错误(code=0 空结果、参数/鉴权错 不重试)", "max_attempts": 3, "backoff_seconds": [1, 2, 4], "on_exhausted": "标 blocked·失败待查,游走绕行不卡流水线", "note": "实测首次 25011、重试 code=0;退避与 rate_limit_seconds=15 取较大值,故退避多被 15s 吸收(2026-06-11 拍板)" } },
-    "video_download": { "downloadable": true, "headers": { "User-Agent": "PC UA", "Referer": "https://channels.weixin.qq.com/" }, "note": "实测 200/video/mp4;findermp 带 encfilekey" }
-  },
-  "heat": {
-    "note": "R3 第一步(2026-06-12):点赞单信号=R3 前行为;视频号 comment/share/collect 实测常空,数据所限只能用点赞",
-    "signals": [
-      { "field": "digg_count", "weight": 1.0, "floor": 50, "ceil": 50000 }
-    ]
-  },
-  "observable_fields": [
-    { "field": "statistics.digg_count", "availability": "supported", "source": "like_count" }
-  ],
-  "missing_observable_fields": [
-    { "field": "statistics.comment_count", "missing_type": "natural_platform_missing", "platform": "shipinhao", "evidence": "跨平台字段映射.json" },
-    { "field": "statistics.share_count", "missing_type": "natural_platform_missing", "platform": "shipinhao", "evidence": "跨平台字段映射.json" },
-    { "field": "statistics.collect_count", "missing_type": "natural_platform_missing", "platform": "shipinhao", "evidence": "跨平台字段映射.json" },
-    { "field": "statistics.play_count", "missing_type": "natural_platform_missing", "platform": "shipinhao", "evidence": "跨平台字段映射.json" }
-  ],
-  "endpoints": {
-    "search":       { "path": "/crawler/shi_pin_hao/keyword", "params": { "keyword": "str", "cursor": "str" }, "response_shape": "normalized(channel_*)", "stability": "unstable" },
-    "blogger":      { "path": "/crawler/shi_pin_hao/blogger", "status": "blocked", "note": "实测 code=25011" },
-    "account_info": { "path": "/crawler/shi_pin_hao/account_info", "params": { "account_name": "str" }, "status": "blocked", "note": "以 registry/capture blocked 为准;2026-06-14 profile partial 不作为可靠运行能力" }
-  },
-  "jump_key_raw": {
-    "platform_content_id": { "search": "channel_content_id" },
-    "platform_author_id":  { "search": "channel_account_id" },
-    "tags":                { "search": "topic_list(实测 null,兜底标题正则 #xxx)" },
-    "video_url":           { "search": "video_url_list[0].video_url" }
-  },
-  "edges": {
-    "query_next_page":        { "status": "supported", "note": "抖动需重试" },
-    "search_page_to_content": { "status": "supported", "note": "抖动需重试" },
-    "video_to_author":        { "status": "supported", "note": "仅落 channel_account_id 标识" },
-    "author_to_works":        { "status": "blocked", "note": "blogger code=25011" },
-    "author_work_to_content": { "status": "blocked" },
-    "video_to_hashtag":       { "status": "supported", "note": "标题正则抽" },
-    "hashtag_to_query":       { "status": "supported" }
-  }
-}

+ 0 - 12
数据接口与来源/platform_profiles/toutiao.json

@@ -1,12 +0,0 @@
-{
-  "schema_version": "platform_profile.v1",
-  "platform": "toutiao",
-  "platform_label": "头条",
-  "status": "blocked",
-  "evidence": "2026-06-11 实测(接口台账/头条.md):/crawler/tou_tiao_hao/keyword 两次(含变参)均 code=10000 未知错误",
-  "runtime": { "rate_limit_seconds": 15 },
-  "endpoints": {
-    "search": { "path": "/crawler/tou_tiao_hao/keyword", "status": "blocked", "note": "code=10000,与参数无关,平台侧不可用;修复后复抓再登记" }
-  },
-  "edges": { "_all": "blocked(整平台所有边阻断)" }
-}

+ 0 - 93
数据接口与来源/platform_profiles/weixin.json

@@ -1,93 +0,0 @@
-{
-  "schema_version": "platform_profile.v1",
-  "platform": "weixin",
-  "platform_label": "公众号",
-  "status": "partial",
-  "evidence": "2026-06-11 实测(接口台账/公众号.md)",
-  "runtime": {
-    "rate_limit_seconds": 15,
-    "retry": null,
-    "video_download": {
-      "downloadable": false,
-      "note": "图文平台,无视频"
-    }
-  },
-  "heat": {
-    "note": "R3 第二步(2026-06-12):公众号只有阅读数+赞(赞常0),以阅读为主;字段据抓包,权重/锚点起步值待该平台接入真跑标定",
-    "signals": [
-      {
-        "field": "play_count",
-        "weight": 0.6,
-        "floor": 100,
-        "ceil": 500000
-      },
-      {
-        "field": "digg_count",
-        "weight": 0.4,
-        "floor": 10,
-        "ceil": 50000
-      }
-    ]
-  },
-  "endpoints": {
-    "search": {
-      "path": "/crawler/wei_xin/keyword",
-      "params": {
-        "keyword": "str",
-        "cursor": "0"
-      },
-      "response_shape": "item={biz,cover_url,nick_name,time,title,url};nick_name/biz 实测常为空"
-    },
-    "detail": {
-      "path": "/crawler/wei_xin/detail",
-      "params": {
-        "content_link": "文章url"
-      }
-    },
-    "account_info": {
-      "path": "/crawler/wei_xin/account_info",
-      "params": {
-        "content_link": "文章url"
-      },
-      "note": "唯一可达的作者补全(account_name/biz_info/wx_gh)"
-    }
-  },
-  "jump_key_raw": {
-    "platform_content_id": {
-      "search": "url(文章链接即标识)"
-    },
-    "platform_author_id": {
-      "search": "url 正则抽 __biz=([^&]+)",
-      "trap": "三套标识互不相同:biz / 网关hash channel_account_id / wx_gh;昵称只能 account_info 补"
-    },
-    "tags": {
-      "trap": "整平台无 hashtag/topic_list"
-    }
-  },
-  "edges": {
-    "query_next_page": {
-      "status": "supported"
-    },
-    "search_page_to_content": {
-      "status": "supported"
-    },
-    "video_to_author": {
-      "status": "supported",
-      "note": "仅落 biz(正则);昵称需 account_info 二次请求"
-    },
-    "author_to_works": {
-      "status": "unsupported",
-      "note": "blogger 不可达,无作品列表"
-    },
-    "author_work_to_content": {
-      "status": "unsupported"
-    },
-    "video_to_hashtag": {
-      "status": "unsupported",
-      "note": "整平台无 tag"
-    },
-    "hashtag_to_query": {
-      "status": "unsupported"
-    }
-  }
-}

+ 0 - 113
数据接口与来源/platform_profiles/xiaohongshu.json

@@ -1,113 +0,0 @@
-{
-  "schema_version": "platform_profile.v1",
-  "platform": "xiaohongshu",
-  "platform_label": "小红书",
-  "status": "partial",
-  "evidence": "2026-06-11 实测(接口台账/小红书.md)",
-  "runtime": {
-    "rate_limit_seconds": 15,
-    "retry": null,
-    "video_download": {
-      "downloadable": null,
-      "note": "图文为主(image_list);视频可下载性未实测"
-    }
-  },
-  "heat": {
-    "note": "R3 第二步(2026-06-12):小红书无播放量,收藏文化重,收藏权重抬高;字段据抓包,权重/锚点起步值待该平台接入真跑标定",
-    "signals": [
-      {
-        "field": "collect_count",
-        "weight": 0.35,
-        "floor": 100,
-        "ceil": 200000
-      },
-      {
-        "field": "digg_count",
-        "weight": 0.35,
-        "floor": 100,
-        "ceil": 500000
-      },
-      {
-        "field": "comment_count",
-        "weight": 0.15,
-        "floor": 20,
-        "ceil": 50000
-      },
-      {
-        "field": "share_count",
-        "weight": 0.15,
-        "floor": 10,
-        "ceil": 20000
-      }
-    ]
-  },
-  "endpoints": {
-    "search": {
-      "path": "/crawler/xiao_hong_shu/keyword",
-      "params": {
-        "keyword": "str",
-        "content_type": "图文|视频",
-        "sort_type": "综合",
-        "publish_time": "",
-        "cursor": ""
-      },
-      "response_shape": "嵌套:item={id,model_type,note_card},内容字段全在 note_card.* 下"
-    },
-    "detail": {
-      "path": "/crawler/xiao_hong_shu/detail",
-      "params": {
-        "content_id": "str"
-      },
-      "response_shape": "扁平(与 search 嵌套结构不同!)"
-    },
-    "blogger": {
-      "path": "/crawler/xiao_hong_shu/blogger",
-      "status": "blocked",
-      "note": "实测请求超时"
-    }
-  },
-  "jump_key_raw": {
-    "platform_content_id": {
-      "search": "item.id",
-      "detail": "channel_content_id"
-    },
-    "platform_author_id": {
-      "search": "note_card.user.user_id"
-    },
-    "tags": {
-      "search": "note_card.desc 正则 #xxx",
-      "detail": "topic_list"
-    },
-    "interactions": {
-      "search": "note_card.interact_info.{liked,collected,comment,shared}_count"
-    },
-    "timestamp_trap": "search 秒级 vs detail 毫秒级,同一笔记两接口不一致"
-  },
-  "edges": {
-    "query_next_page": {
-      "status": "supported"
-    },
-    "search_page_to_content": {
-      "status": "supported",
-      "note": "必须深入 note_card 嵌套抽取"
-    },
-    "video_to_author": {
-      "status": "supported",
-      "note": "note_card.user.user_id"
-    },
-    "author_to_works": {
-      "status": "blocked",
-      "note": "blogger 超时"
-    },
-    "author_work_to_content": {
-      "status": "blocked"
-    },
-    "video_to_hashtag": {
-      "status": "supported",
-      "note": "desc 正则 / detail topic_list"
-    },
-    "hashtag_to_query": {
-      "status": "supported"
-    }
-  }
-}

+ 0 - 94
数据接口与来源/platform_profiles/youtube.json

@@ -1,94 +0,0 @@
-{
-  "schema_version": "platform_profile.v1",
-  "platform": "youtube",
-  "platform_label": "youtube",
-  "status": "partial",
-  "evidence": "2026-06-11 实测(接口台账/youtube.md)",
-  "runtime": {
-    "rate_limit_seconds": 15,
-    "retry": null,
-    "video_download": {
-      "downloadable": false,
-      "note": "detail 的 video_url_list 实测为空,视频不可经 crawler 直取"
-    }
-  },
-  "heat": {
-    "note": "R3 第二步(2026-06-12):油管有播放量,share/collect 常空,用 播放+赞+评;字段据抓包,权重/锚点起步值待该平台接入真跑标定",
-    "signals": [
-      {
-        "field": "play_count",
-        "weight": 0.5,
-        "floor": 1000,
-        "ceil": 10000000
-      },
-      {
-        "field": "digg_count",
-        "weight": 0.3,
-        "floor": 100,
-        "ceil": 1000000
-      },
-      {
-        "field": "comment_count",
-        "weight": 0.2,
-        "floor": 20,
-        "ceil": 100000
-      }
-    ]
-  },
-  "endpoints": {
-    "search": {
-      "path": "/crawler/youtube/keyword",
-      "params": {
-        "keyword": "str"
-      },
-      "pagination_note": "next_cursor 是 400+ 字符 URL-encoded 长 token,须整体透传"
-    },
-    "detail": {
-      "path": "/crawler/youtube/detail",
-      "params": {
-        "content_id": "video_id"
-      },
-      "note": "无视频地址,仅正文/统计"
-    }
-  },
-  "jump_key_raw": {
-    "platform_content_id": {
-      "search": "video_id"
-    },
-    "platform_author_id": {
-      "search": "channel_id(UC...)",
-      "detail": "channel_account_id(32位hash)",
-      "trap": "双体系互不相同"
-    },
-    "tags": {
-      "search": "无",
-      "detail": "topic_list=null,须 body_text 正则 #xxx"
-    }
-  },
-  "edges": {
-    "query_next_page": {
-      "status": "supported",
-      "note": "长 token 游标"
-    },
-    "search_page_to_content": {
-      "status": "supported"
-    },
-    "video_to_author": {
-      "status": "supported"
-    },
-    "author_to_works": {
-      "status": "unsupported",
-      "note": "无作者作品接口"
-    },
-    "author_work_to_content": {
-      "status": "unsupported"
-    },
-    "video_to_hashtag": {
-      "status": "weak",
-      "note": "仅 detail.body_text 正则,搜索无 tag"
-    },
-    "hashtag_to_query": {
-      "status": "supported"
-    }
-  }
-}

+ 0 - 12
数据接口与来源/platform_profiles/zhihu.json

@@ -1,12 +0,0 @@
-{
-  "schema_version": "platform_profile.v1",
-  "platform": "zhihu",
-  "platform_label": "知乎",
-  "status": "blocked",
-  "evidence": "2026-06-11 实测(接口台账/知乎.md):/crawler/zhi_hu/keyword 两次(含变参)均 code=10000 未知错误",
-  "runtime": { "rate_limit_seconds": 15 },
-  "endpoints": {
-    "search": { "path": "/crawler/zhi_hu/keyword", "status": "blocked", "note": "code=10000,与参数无关,平台侧不可用;修复后复抓再登记" }
-  },
-  "edges": { "_all": "blocked(整平台所有边阻断)" }
-}

+ 0 - 150
数据接口与来源/walk_graph.json

@@ -1,150 +0,0 @@
-{
-  "schema_version": "walk_graph.v2",
-  "title": "游走图谱(全局边目录,平台无关;8 节点 + 10 边,复用 douyin_walk_strategy.v1.json 命名)",
-  "generated_at": "2026-06-11",
-  "source_of_truth": "product_documents/抖音游走策略/douyin_walk_strategy.v1.json",
-  "evidence": "实测抓包(2026-06-11 多平台探针) + 接口台账/*.md",
-  "companions": {
-    "platform_profiles": "platform_profiles/<platform>.json — 每平台:支持哪些边、真实 endpoint、jump_key 的 raw 字段、extract_rule 覆写、运行参数(限流/重试/下载头)",
-    "walk_policy": "walk_policy.json — 全局护栏:预算/深度/去重/回灌配额 + 判定→边通行证(edge_permissions)",
-    "field_mapping": "跨平台字段映射.json — 平台响应 raw 字段 → canonical 字段"
-  },
-  "constraints": [
-    "edge_id 全部来自策略文件 10 条,零新造。",
-    "node 全部来自策略文件 8 类,id_field 与策略 walk_node_catalog 一致。",
-    "本文件只写平台无关语义;一切平台差异在 platform_profiles/,一切数值护栏在 walk_policy.json。",
-    "source_path_type 对齐血缘三类:pattern_to_search_query / search_query_to_content / decision_to_asset(其余边记 null)。"
-  ],
-  "nodes": [
-    { "node_type": "pattern_seed",      "id_field": "policy_run_id",        "node_category": "input",           "can_expand": true,  "notes": "P2 种子词起点" },
-    { "node_type": "query",             "id_field": "search_query_id",      "node_category": "action_input",    "can_expand": true,  "notes": "初始 item query 或 tag query" },
-    { "node_type": "search_page",       "id_field": "search_page_id",       "node_category": "platform_result", "can_expand": true,  "notes": "带 has_more / next_cursor" },
-    { "node_type": "video",             "id_field": "platform_content_id",  "node_category": "content",         "can_expand": true,  "notes": "核心内容对象;作者作品产出的也是 video" },
-    { "node_type": "author",            "id_field": "platform_author_id",   "node_category": "entity",          "can_expand": true,  "notes": "来自已发现 video 的作者标识" },
-    { "node_type": "author_works_page", "id_field": "author_works_page_id", "node_category": "platform_result", "can_expand": true,  "notes": "作者作品列表页" },
-    { "node_type": "hashtag",           "id_field": "hashtag",              "node_category": "signal",          "can_expand": true,  "notes": "仅强相关 tag 可扩散" },
-    { "node_type": "asset",             "id_field": "asset_id",             "node_category": "asset",           "can_expand": false, "notes": "最终产出资产" }
-  ],
-  "edges": [
-    {
-      "edge_id": "query_next_page",
-      "from_node": "query",
-      "to_node": "search_page",
-      "edge_class": "navigate",
-      "endpoint_role": "search",
-      "jump_key": { "from_field": "next_cursor", "param": "cursor" },
-      "paginated": true,
-      "gate": "none",
-      "extract_rule": "has_more=true 时用 next_cursor 请求同一 search 端点的下一页",
-      "source_path_type": "pattern_to_search_query"
-    },
-    {
-      "edge_id": "search_page_to_content",
-      "from_node": "search_page",
-      "to_node": "video",
-      "edge_class": "navigate",
-      "endpoint_role": null,
-      "jump_key": { "from_field": "data.data[]", "param": null },
-      "paginated": false,
-      "gate": "none",
-      "extract_rule": "search 响应 data.data[] 拆每条卡片为 video(canonical: platform_content_id);raw 字段名见 platform_profiles",
-      "source_path_type": "search_query_to_content"
-    },
-    {
-      "edge_id": "video_to_author",
-      "from_node": "video",
-      "to_node": "author",
-      "edge_class": "navigate",
-      "endpoint_role": null,
-      "jump_key": { "from_field": "platform_author_id", "param": null },
-      "paginated": false,
-      "gate": "none",
-      "extract_rule": "从 video 取 canonical platform_author_id;各平台 raw 来源见 platform_profiles(jump_key_raw)",
-      "source_path_type": null
-    },
-    {
-      "edge_id": "author_to_works",
-      "from_node": "author",
-      "to_node": "author_works_page",
-      "edge_class": "navigate",
-      "endpoint_role": "blogger",
-      "jump_key": { "from_field": "platform_author_id", "param": "account_id" },
-      "paginated": true,
-      "gate": "decision_gated",
-      "extract_rule": "用 platform_author_id 拉作者作品页;翻页 next_cursor 回填 cursor;是否放行由 walk_policy.edge_permissions 按判定结果决定",
-      "source_path_type": null
-    },
-    {
-      "edge_id": "author_work_to_content",
-      "from_node": "author_works_page",
-      "to_node": "video",
-      "edge_class": "navigate",
-      "endpoint_role": null,
-      "jump_key": { "from_field": "data.data[]", "param": null },
-      "paginated": false,
-      "gate": "none",
-      "extract_rule": "作者作品页 data.data[] 拆为 video,按 platform_content_id 与已发现内容去重(commit 1cd1b94);产出的 video 同样进判定与回灌",
-      "source_path_type": "search_query_to_content"
-    },
-    {
-      "edge_id": "video_to_hashtag",
-      "from_node": "video",
-      "to_node": "hashtag",
-      "edge_class": "reseed",
-      "endpoint_role": null,
-      "jump_key": { "from_field": "tags", "param": null },
-      "paginated": false,
-      "gate": "keep_only",
-      "extract_rule": "从 video 的 canonical tags 抽;结构化来源优先、缺则正则 #([^\\s#]+),按平台覆写见 platform_profiles",
-      "source_path_type": null
-    },
-    {
-      "edge_id": "hashtag_to_query",
-      "from_node": "hashtag",
-      "to_node": "query",
-      "edge_class": "reseed",
-      "endpoint_role": "search",
-      "jump_key": { "from_field": "hashtag", "param": "keyword" },
-      "paginated": false,
-      "gate": "keep_only",
-      "extract_rule": "hashtag 文本生成新 query;归一化 keyword 去重(walk_policy.dedup),不污染 P2 初始 item query",
-      "source_path_type": "pattern_to_search_query"
-    },
-    {
-      "edge_id": "decision_to_asset",
-      "from_node": "video",
-      "to_node": "asset",
-      "edge_class": "navigate",
-      "endpoint_role": null,
-      "jump_key": { "from_field": "platform_content_id", "param": null },
-      "paginated": false,
-      "gate": "none",
-      "extract_rule": "判定命中 ADD_TO_CONTENT_POOL 后将 video 沉淀为 asset",
-      "source_path_type": "decision_to_asset"
-    },
-    {
-      "edge_id": "path_stop",
-      "from_node": "video",
-      "to_node": "video",
-      "edge_class": "navigate",
-      "endpoint_role": null,
-      "jump_key": { "from_field": "content_effect_status", "param": null },
-      "paginated": false,
-      "gate": "none",
-      "extract_rule": "rule_blocked / failed 内容停止当前路径扩散,不再开任何出边",
-      "source_path_type": null
-    },
-    {
-      "edge_id": "budget_downgrade",
-      "from_node": "video",
-      "to_node": "video",
-      "edge_class": "navigate",
-      "endpoint_role": null,
-      "jump_key": { "from_field": "budget.remaining_actions", "param": null },
-      "paginated": false,
-      "gate": "none",
-      "extract_rule": "全局预算/深度耗尽时按 walk_policy 降档继续或停边",
-      "source_path_type": null
-    }
-  ]
-}

+ 0 - 55
数据接口与来源/walk_policy.json

@@ -1,55 +0,0 @@
-{
-  "schema_version": "walk_policy.v1",
-  "title": "游走全局护栏 + 判定→边通行证(配 walk_graph.v2 使用)",
-  "generated_at": "2026-06-11",
-  "value_provenance": "基线=walk_engine v1 实际硬限(行为等价,M4 拍板 2026-06-11);2026-06-12 R7 拍板放宽 tag 1→3、作者 2→3(真跑 v1_run_4e24c1b85637 两项顶格实证)",
-  "global": {
-    "max_total_actions_per_run": { "value": 60, "provenance": "拍板 2026-06-11:沿用 v1 各边合计上限 60 作全局闸", "tbd": false },
-    "max_depth": { "value": 3, "provenance": "v1 各边 max_depth=3", "tbd": false },
-    "max_reseed_rounds": { "value": 1, "provenance": "拍板 2026-06-11:单层回灌(tag→query 不二次回灌),v1 等价", "tbd": false },
-    "gemini_max_workers": { "value": 2, "provenance": "拍板 2026-06-16:4核8G服务器资源收敛;判定总并发2,ffmpeg随单条链路自然不超过2", "tbd": false }
-  },
-  "edge_budgets": [
-    { "edge_id": "query_next_page",  "max_total_actions": 3,  "max_per_query": 1,    "max_pages": null, "provenance": "对齐 v1 实际硬限:rows[:3] 每 run ≤3 条翻页 query,每 query 仅 page_002 一跳;原理想值(30/2/3)留 M7 标定" },
-    { "edge_id": "video_to_author",  "max_total_actions": null, "max_per_content": 1, "max_pages": null, "provenance": "作者标识随 video 携带,loop 不单独执行此边、无预算消费;作者数上限见 author_to_works" },
-    { "edge_id": "author_to_works",  "max_total_actions": 3,  "max_works_per_author": 3, "max_pages": null, "provenance": "R7 放宽拍板 2026-06-12:2→3(真跑作者预算 2/2 顶格实证);每作者 ≤3 新作品不变;原理想值(10/翻3页)留后续标定" },
-    { "edge_id": "hashtag_to_query", "max_total_actions": 3,  "max_per_content": null, "max_tag_hops": null, "provenance": "R7 放宽拍板 2026-06-12:1→3(真跑 tag 预算 1/1 顶格、召回面收窄实证);原理想值(10/3/10)留后续标定" }
-  ],
-  "dedup": {
-    "query_key": "归一化 keyword(去空白/全半角/大小写统一)后全 run 去重;tag query 与 P2 初始 query 共用一个去重集",
-    "content_key": "platform_content_id 全 run 去重(跨 search/翻页/作者作品累积,commit 1cd1b94 语义)",
-    "author_key": "platform_author_id 全 run 去重(同作者只扩一次)"
-  },
-  "edge_permissions": {
-    "_meaning": "判定层→游走层的唯一接口:判定结果决定哪些出边放行(取代 walk_engine._can_expand_from_decision 硬编码,亦即原 path_stop/budget 包语义的新家);hashtag_to_query 的放行随 video_to_hashtag(同一 keep_only 回灌链)",
-    "_provenance": "现状行为来自 walk_engine v1 + E2E 实测(KEEP 内容 author_to_works success(low_budget));M4 拍板 2026-06-11",
-    "ADD_TO_CONTENT_POOL":     { "author_to_works": "allow",            "video_to_hashtag": "allow", "decision_to_asset": "allow" },
-    "KEEP_CONTENT_FOR_REVIEW": { "author_to_works": "allow_low_budget", "video_to_hashtag": { "value": "deny", "tbd": false, "note": "拍板 2026-06-11:从严 deny(v1 等价,防漂移)" }, "decision_to_asset": "deny" },
-    "TECHNICAL_RETRY_REQUIRED": { "author_to_works": "deny", "video_to_hashtag": "deny", "decision_to_asset": "deny", "note": "技术链路失败不触发业务游走或资产沉淀" },
-    "REJECT_CONTENT":          { "author_to_works": "deny", "video_to_hashtag": "deny", "decision_to_asset": "deny", "note": "即 path_stop:rule_blocked/failed 不开任何出边" }
-  },
-  "v4_walk_gate": {
-    "requires_allow_walk": true,
-    "source_field": "rule_decisions.jsonl[].decision_replay_data.allow_walk",
-    "deny_reason_code": "v4_allow_walk_denied",
-    "applies_to_edges": [
-      "query_next_page",
-      "hashtag_to_query",
-      "author_to_works"
-    ],
-    "raw_payload_fields": [
-      "decision_id",
-      "allow_walk",
-      "allow_walk_reason",
-      "walk_gate_snapshot"
-    ]
-  },
-  "reseed_quality_gate": {
-    "rule": "回灌边(reseed)仅从 gate=keep_only 放行的内容采集种子;V3 下 keep_only 的判据 = 判定 decision_action 经 edge_permissions 查表",
-    "tag_quality": "仅强相关 tag 可扩散(沿用 hashtag node 现有约束);弱平台(快手 topic_list 常空、youtube 仅正文正则)允许正则兜底但同样过质量门"
-  },
-  "budget_tiers": {
-    "normal": "edge_budgets 全额",
-    "low_budget": { "value": "halve_min_1", "provenance": "拍板 2026-06-11:各边预算减半向下取整至少 1,即 max(1, budget//2)", "tbd": false }
-  }
-}

+ 0 - 128
数据接口与来源/快手视频号_URL解包与OSS验证报告_20260617.md

@@ -1,128 +0,0 @@
-# 快手/视频号 URL 解包与 OSS 验证报告(2026-06-17)
-
-## 1. 目标与边界
-
-本轮目标是先把快手/视频号搜索与详情回包里的 URL 结构解析清楚,再用 OSS v2 批量验证“推荐规则选中的正片 URL 是否真的可归档”。本轮不修改生产 `play_url` 抽取逻辑,只做事实核验、交叉验证、诊断工具和文档更新。
-
-完整原始响应、临时 URL、OSS 原始响应只保存在 gitignore 下的 `data/platform_url_probe/20260617_115219/`。本文只记录脱敏摘要、字段路径、host、HTTP 状态、Range 状态和统计。
-
-## 2. Sub-agent 交叉结论
-
-### 需求审计
-
-- 本轮先结构解析,再对 selected URL 做 OSS v2 批量验证。
-- 历史 run 固定为快手 `v1_run_94448252f5cd`、视频号 `v1_run_35f4f7ccd1cc`。
-- 视频号也要扫描广告/素材 MP4,不能默认只有快手有广告素材。
-- 本轮不修改生产 `play_url` 抽取逻辑,不提交完整临时 URL。
-
-### 代码审计
-
-- 快手当前生产抽取:`content_agent/integrations/kuaishou.py` 的 `_extract_play_url()` 取 `video_url_list[0].video_url`。
-- 视频号当前生产抽取:`content_agent/integrations/shipinhao.py` 取 `video_url_list[0].video_url`。
-- builder 只把 client 归一化后的 `play_url` 写入 media record。
-- 现有 `platform_raw_payload` 只保留 `channel_content_id/channel_account_id`,不能完整反查原始字段路径。
-- OSS 当前会把 `play_url` 传给 `oss_upload.upload_video_from_env()`;Gemini 优先用 `oss_url`,否则 fallback 到 `play_url`。
-
-### 历史 run 审计
-
-- 快手 `TECHNICAL_RETRY_REQUIRED=14`:8 条 `no_play_url`,6 条 Gemini/OpenRouter 错误;技术重试直接原因不是 OSS。
-- 视频号 `TECHNICAL_RETRY_REQUIRED=6`:决策层直接原因是 Gemini/OpenRouter;但 30 条 media 全部为 `oss_upload_pending + oss_upload_response_invalid`。
-- 两个历史 run 都不能从 runtime 反查 selected URL 的原始字段路径。
-
-### 文档/台账审计
-
-- 旧台账说明快手/视频号直链可下载,但没有把“直链可下载”“Gemini 可下载”“OSS 可转存”拆开记录。
-- 快手文档建议下载带 Range/Referer/UA,但当前 `video_fetch` 快手默认头与文档不完全一致。
-- 视频号直链需 `Referer: https://channels.weixin.qq.com/` 用于本机下载;OSS v2 是否接受 headers/referer 需要单独验证。
-
-## 3. 历史 run 对照
-
-### 快手 `v1_run_94448252f5cd`
-
-- 决策统计:`ADD_TO_CONTENT_POOL=8`、`KEEP_CONTENT_FOR_REVIEW=9`、`REJECT_CONTENT=6`、`TECHNICAL_RETRY_REQUIRED=14`。
-- 媒体状态:`oss_uploaded=29`、`unavailable=8`。
-- OSS/媒体错误:`oss_upload_http_error=1`、`no_play_url=8`。
-- play_url host:`cc1.xyydnode.com=7`、`v5.oskwai.com=5`、`v4.kwaicdn.com=8`、`v23-3.kwaicdn.com=9`。
-- 字段路径证据不足:历史 runtime 无完整上游 raw item。
-
-### 视频号 `v1_run_35f4f7ccd1cc`
-
-- 决策统计:`ADD_TO_CONTENT_POOL=10`、`KEEP_CONTENT_FOR_REVIEW=5`、`REJECT_CONTENT=9`、`TECHNICAL_RETRY_REQUIRED=6`。
-- 媒体状态:`oss_upload_pending=30`。
-- OSS/媒体错误:`oss_upload_response_invalid=30`。
-- play_url host:`findermp.video.qq.com=30`。
-- 字段路径证据不足:历史 runtime 无完整上游 raw item。
-
-## 4. 真实接口结构探测
-
-### 快手
-
-- 真实搜索 query:10 个。
-- 搜索 item 样本:60 条。
-- URL 候选统计:`page=89`、`image=80`、`video_candidate=75`、`audio=1`。
-- selected URL:50 条。
-- selected 路径:`$.detail.video_url_list[0].video_url=25`、`$.search.video_url_list[0].video_url=25`。
-- selected host:`tymov2.a.kwimgs.com=17`、`hwmov.a.yximgs.com=7`、`v23-3.kwaicdn.com=5`、`v2.oskwai.com=6`、`ucmov.a.kwimgs.com=5`、`dxsc.oppass.cn=4`、`v4.kwaicdn.com=3`、`cc1.xyydnode.com=2`、`v1.oskwai.com=1`。
-
-### 视频号
-
-- 真实搜索 query:10 个。
-- 搜索页数:18 页。
-- 搜索 item 样本:46 条。
-- URL 候选统计:`image=95`、`video_candidate=41`、`audio=2`。
-- selected URL:41 条。
-- selected 路径:`$.search.video_url_list[0].video_url=41`。
-- selected host:`findermp.video.qq.com=41`。
-- 本轮保留 25011 retry:例如 `怀旧经典歌曲` page1 前两次 25011,第三次成功;`怀旧金曲` 重试后仍 25011,最终 item_count=0。
-
-## 5. OSS v2 批量验证
-
-### 第一组:生产当前 payload 形态
-
-生产当前链路会把 `referer` 作为 dict 字段传给 OSS v2。
-
-- 快手:20 条 selected URL 均 HTTP 200,但没有 `oss_object.cdn_url`,分类为 `oss_upload_response_invalid`。
-- 视频号:20 条 selected URL 均 HTTP 200,但没有 `oss_object.cdn_url`,分类为 `oss_upload_response_invalid`。
-- 抽样复打快手 selected URL 时,OSS body 为 `status=10000`、`oss_object=null`,错误包含 `dict object has no attribute encode`。
-
-### 第二组:no-referer payload 复验
-
-同一批 raw 样本重新抽 selected URL,去掉 `referer` 字段后调用 OSS v2,并发 2。
-
-- 快手:20/20 成功返回 `oss_object.cdn_url`。耗时约 0.891s 到 119.923s。
-- 视频号:20/20 成功返回 `oss_object.cdn_url`。耗时约 1.078s 到 19.248s。
-
-结论:本轮推荐规则选出的 selected URL 能被 OSS v2 成功归档。历史/生产里的 `oss_upload_response_invalid` 更像 OSS v2 payload 兼容问题,不是 URL 字段拆错。
-
-## 6. 推荐解包规则
-
-### 快手
-
-- 优先 detail 正片 URL:`$.detail.video_url_list[].video_url`。
-- detail 不可用时 fallback search 正片 URL:`$.search.video_url_list[].video_url`。
-- 不默认 `video_url_list[0]` 永远正确;多个候选按路径、HTTP 200/206、`content_type=video/mp4`、Range、duration、host 稳定性排序。
-- 过滤 `content_link`、`image_url_list`、头像、`bgm_data.play_url`、广告/素材字段。
-
-### 视频号
-
-- 优先 `$.search.video_url_list[].video_url` 中的 findermp 正片 URL。
-- 同样过滤封面、头像、音频/BGM、页面链接、广告/素材字段。
-- 广告关键词匹配不能用裸 `ad` 子串,否则会误伤 `stodownload/head` 这类正常 URL。
-- 本机下载/轻量验证继续带 `Referer: https://channels.weixin.qq.com/`;OSS v2 转存 payload 不应直接传 dict 型 `referer`。
-
-## 7. 生产改造建议
-
-1. 先修 OSS v2 payload:不要把 `referer` dict 直接传给 OSS v2。若上传服务需要 headers,需约定可编码字段格式,例如字符串 referer 或 `headers` object。
-2. 再补 runtime raw payload:记录 `selected_path`、`selected_host`、`probe_status`、`content_type`、`content_range`、`oss_payload_mode`、`oss_failure_type`。
-3. 最后改平台 adapter:快手 detail 优先、search fallback;视频号 findermp 正片优先;统一做 URL 候选分类过滤。
-
-## 8. 验证命令
-
-```bash
-.venv/bin/python -m pytest tests/test_platform_video_url_candidates.py -q
-CONTENT_AGENT_OSS_UPLOAD_URL=http://crawler-upload-v2.aiddit.com/crawler/oss/upload_stream \
-  .venv/bin/python tests/test_platform_video_url_candidates.py \
-  --real-probe --probe-oss --items-per-query 6 \
-  --kuaishou-detail-limit-per-query 3 \
-  --oss-limit-per-platform 20 --oss-timeout-seconds 180
-```

+ 0 - 206
数据接口与来源/接口台账/B站.md

@@ -1,206 +0,0 @@
-# B站(bilibili)接口台账
-
-> 唯一证据来源:`/tmp/aiddit_captures/bili_search.json`、`bili_detail.json`、`bili_video_dl.json`(2026-06-11 真实抓包)。
-> 全部接口走统一爬虫网关 `host = http://crawler.aiddit.com`。
-> 本文档字段表与示例值全部取自上述 capture 真实响应,未做任何编造。
-
-## 平台级结论(先读)
-
-- **关键词搜索可用**:`/crawler/bilibili/keyword` `code=0`,一次返回 42 条(`bili_search.json`),每条含 `author/uid(实为 mid)/mid/bvid/aid/tag/arcurl/play/favorites` 等。
-- **详情可用**:`/crawler/bilibili/detail` `code=0`,返回归一化结构,含 `topic_list / video_url_list / channel_account_id`(`bili_detail.json`)。
-- **视频可下载**:`video_url_list[].video_url`(`.m4s`)直接 GET 返回 `206 / application/octet-stream`,实测收到 1,048,576 字节真实视频字节(`bili_video_dl.json`)。**下载 m4s 需带 `Referer: https://www.bilibili.com/`**(B站防盗链)。
-- **集合中无作者作品接口**:本批 capture 内 **没有** “按 mid/uid 拉某 up 主全部作品” 的接口。作者维度只能从 search 结果里的 `mid/author` 取标识,无法直接游走到该作者的作品列表。`author_to_works` 边在 B站当前缺乏数据源支撑。
-- **tag 来源**:search 的 `tag`(逗号分隔字符串)+ detail 的 `topic_list`(数组)两处均可取 hashtag。
-
----
-
-## 接口 1:关键词搜索
-
-### ① 身份
-- `source_id`: **PLT_BILIBILI_KEYWORD**
-- 状态:**verified**(`bili_search.json` `code=0`,42 条)
-- 系统:crawler.aiddit.com
-
-### ② 请求标准
-- method:`POST`
-- path:`/crawler/bilibili/keyword`
-- 鉴权:网关侧统一处理,请求体无 token(capture 中 `request_body` 仅含业务参数)。
-- 参数表:
-
-| 参数 | 类型 | 必填 | 含义 | 真实值 |
-|---|---|---|---|---|
-| `keyword` | string | 是 | 搜索词 | `"彩虹"` |
-| `cursor` | string | 否 | 翻页游标,取上次响应的 `next_cursor`(页码语义,首页可省略) | 见 ④ |
-
-### ③ 真实 curl
-```bash
-curl -X POST 'http://crawler.aiddit.com/crawler/bilibili/keyword' \
-  -H 'Content-Type: application/json' \
-  -d '{"keyword":"彩虹"}'
-```
-
-### ④ 响应信封 + 翻页
-```json
-{ "code": 0, "msg": null,
-  "data": { "has_more": true, "next_cursor": "2", "data": [ {...}, ... 42 条 ] } }
-```
-- 信封:`code`(0=成功)/`msg`/`data`。
-- 翻页:外层 `data.has_more=true`,`data.next_cursor="2"`(页码字符串)。下一页把 `next_cursor` 回填到请求 `cursor`。
-- 列表在 `data.data`(数组,本次 42 条)。
-
-### ⑤ 字段台账(raw 名,取自 `bili_search.json` `data.data[0]`)
-
-| 字段名(raw) | 类型 | 含义 | 提取标注 | 真实示例值 |
-|---|---|---|---|---|
-| `bvid` | string | 内容ID(B站视频主键) | 直接可用 | `"BV1qd4y1x76w"` |
-| `aid` | int | 内容ID(av号,旧主键) | 直接可用 | `390658123` |
-| `title` | string | 标题(含 `<em class="keyword">` 高亮标签) | 需正则抽(剥 `<em>`) | `"《<em class=\"keyword\">彩虹</em>》TimeZ~..."` |
-| `arcurl` | string | 内容网页地址 | 直接可用 | `"http://www.bilibili.com/video/av390658123"` |
-| `pic` | string | 封面图(协议相对 `//`) | 需后处理(补 `https:`) | `"//i1.hdslb.com/bfs/archive/70c0...jpg"` |
-| `description` / `desc` | string | 简介 | 直接可用 | `"作词: 宋秉洋\n作曲: 宋秉洋..."` |
-| `author` | string | 作者昵称 | 直接可用 | `"碍音乐"` |
-| `mid` | int | 作者ID(UP主 mid,真实标识) | 直接可用 | `1919171246` |
-| `uid` | int | 作者ID字段(**本响应恒为 0,不可用**,以 `mid` 为准) | 需后处理(弃用,取 mid) | `0` |
-| `upic` | string | 作者头像 | 直接可用 | `"https://i2.hdslb.com/bfs/face/...jpg"` |
-| `tag` | string | hashtag(逗号分隔) | 需后处理(split `,`) | `"音乐分享官5.0,TimeZ,音乐分享官,彩虹"` |
-| `play` | int | 播放数 | 直接可用 | `2602076` |
-| `like` | int | 点赞数 | 直接可用 | `32770` |
-| `favorites` | int | 收藏数 | 直接可用 | `74847` |
-| `review` | int | 评论数 | 直接可用 | `1181` |
-| `danmaku` / `video_review` | int | 弹幕数 | 直接可用 | `3521` |
-| `duration` | string | 时长(`分:秒` 文本) | 需后处理(转秒) | `"3:58"` |
-| `pubdate` / `senddate` | int | 发布时间戳(秒) | 需后处理(×1000) | `1669795676` |
-| `typename` | string | 分区名 | 直接可用 | `"音乐综合"` |
-
-> 翻页字段:`data.has_more=true` / `data.next_cursor="2"`(真值)。
-
-### ⑥ 提取与后处理规则
-- 内容主键统一取 `bvid`(canonical `platform_content_id`),`aid` 作辅助。
-- `title` 必须正则剥离 `<em class="keyword">...</em>` 高亮标签。
-- 作者ID取 `mid`(`uid` 恒为 0,**禁止使用**)。
-- `tag` 按 `,` 切成数组 → hashtag。
-- `pic`/`upic` 协议相对地址需补 `https:`。
-- **B站集合中无作者作品接口**:拿到 `mid` 后无法在本批接口内拉该作者作品;`author_to_works` 在 B站不可走,作者维度仅作标识沉淀。
-
-### ⑦ 边界与失败
-- 限流:跨接口请求间隔 ≥ 15s。
-- 本接口 capture `code=0` 正常,无失败样本。
-
-### ⑧ 落点(canonical + 游走边)
-- canonical:`platform_content_id`(=`bvid`)、`platform_author_id`(=`mid`)、`title`、`content_url`(=`arcurl`)、`cover_url`(=`pic`)、`author_name`(=`author`)、`tags`(=`tag` split)、`stats.play/like/favorites/review/danmaku`、`publish_ts`、`has_more`/`next_cursor`。
-- 游走边:`search_page_to_content`(search→每条内容)。`video_to_author` 仅能落作者标识(mid/author),**`author_to_works` 无数据源、不可走**。`video_to_hashtag` 由 `tag` 提供。
-
----
-
-## 接口 2:内容详情
-
-### ① 身份
-- `source_id`: **PLT_BILIBILI_CONTENT_DETAIL**
-- 状态:**verified**(`bili_detail.json` `code=0`)
-
-### ② 请求标准
-- method:`POST`,path:`/crawler/bilibili/detail`
-- 参数表:
-
-| 参数 | 类型 | 必填 | 含义 | 真实值 |
-|---|---|---|---|---|
-| `content_id` | string | 是 | 内容ID,传 `bvid` | `"BV1qd4y1x76w"` |
-
-### ③ 真实 curl
-```bash
-curl -X POST 'http://crawler.aiddit.com/crawler/bilibili/detail' \
-  -H 'Content-Type: application/json' \
-  -d '{"content_id":"BV1qd4y1x76w"}'
-```
-
-### ④ 响应信封 + 翻页
-```json
-{ "code": 0, "msg": null,
-  "data": { "has_more": false, "next_cursor": null, "data": { ...归一化对象... } } }
-```
-- 详情为单对象,`has_more=false`、`next_cursor=null`(无翻页)。
-
-### ⑤ 字段台账(raw 名,取自 `bili_detail.json` `data.data`)
-
-| 字段名(raw) | 类型 | 含义 | 提取标注 | 真实示例值 |
-|---|---|---|---|---|
-| `channel_content_id` | string | 内容ID(=bvid) | 直接可用 | `"BV1qd4y1x76w"` |
-| `content_link` | string | 内容网页地址 | 直接可用 | `"https://www.bilibili.com/video/BV1qd4y1x76w/"` |
-| `title` | string | 标题(无高亮标签) | 直接可用 | `"《彩虹》TimeZ~在夕阳下最后的拥抱 记得你甜美的心跳"` |
-| `body_text` | string | 正文/简介 | 直接可用 | `"作词: 宋秉洋\n作曲: 宋秉洋\n制作人: 司捷"` |
-| `content_type` | string | 内容类型 | 直接可用 | `"video"` |
-| `topic_list` | string[] | hashtag/话题数组 | 直接可用 | `["发现《彩虹》","音乐分享官","彩虹","TimeZ","音乐分享官5.0"]` |
-| `video_url_list[].video_url` | string | **视频地址(m4s,可下载)** | 需二次请求下载(带 Referer) | `"https://xy115...bilivideo.cn:8082/.../907810383_nb3-1-30016.m4s?..."` |
-| `video_url_list[].video_duration` | int | 视频时长(秒) | 直接可用 | `238` |
-| `voice_data.play_url` | string | 音轨地址(m4s,与视频分轨) | 需二次请求下载 | `"https://xy111...bilivideo.cn:8082/.../907810383_nb3-1-30216.m4s?..."` |
-| `image_url_list[].image_url` | string | 封面图 | 直接可用 | `"http://i1.hdslb.com/bfs/archive/70c0...jpg"` |
-| `channel_account_id` | string | 作者ID(mid 字符串形态) | 直接可用 | `"1919171246"` |
-| `channel_account_name` | string | 作者昵称 | 直接可用 | `"碍音乐"` |
-| `play_count` | int | 播放数 | 直接可用 | `2602076` |
-| `like_count` | int | 点赞数 | 直接可用 | `33628` |
-| `collect_count` | int | 收藏数 | 直接可用 | `74847` |
-| `comment_count` | int | 评论数 | 直接可用 | `1181` |
-| `share_count` | int | 分享数 | 直接可用 | `5367` |
-| `publish_timestamp` | int | 发布时间戳(毫秒) | 直接可用 | `1669795676000` |
-
-### ⑥ 提取与后处理规则
-- `channel_account_id` 与 search 的 `mid` 一致(`"1919171246"`),可作作者主键对齐。
-- B站视频/音频分轨(DASH):`video_url_list` 为视频轨,`voice_data.play_url` 为音频轨,**需各自下载后合并**。
-- **B站集合中无作者作品接口**:详情拿到 `channel_account_id` 后同样无法拉作者作品。
-
-### ⑦ 边界与失败
-- m4s 地址带 `deadline` 时效签名,过期需重新取详情;下载须及时。
-- 限流 ≥ 15s。
-
-### ⑧ 落点(canonical + 游走边)
-- canonical:`platform_content_id`(=`channel_content_id`)、`platform_author_id`(=`channel_account_id`)、`title`、`body_text`、`content_url`(=`content_link`)、`tags`(=`topic_list`)、`video_url`(=`video_url_list[].video_url`)、`audio_url`(=`voice_data.play_url`)、`stats.*`、`publish_ts`。
-- 游走边:`video_to_hashtag`(=`topic_list`)、`video_to_author`(落 `channel_account_id` 标识);`author_to_works` 不可走。
-
----
-
-## 接口 3:视频下载(m4s 直链)
-
-### ① 身份
-- `source_id`: **PLT_BILIBILI_VIDEO_DL**
-- 状态:**verified**(`bili_video_dl.json`:`206 / octet-stream / 1MB 真实视频字节`)
-
-### ② 请求标准
-- method:`GET`(直连 B站 CDN,不经爬虫网关)
-- path:详情返回的 `video_url_list[].video_url`(`*.bilivideo.cn:8082/.../*.m4s`)
-- 鉴权:**必须带 `Referer: https://www.bilibili.com/`**(防盗链);URL 自带 `deadline` 签名。
-
-### ③ 真实 curl
-```bash
-curl -L 'https://xy115x231x41x20xy.mcdn.bilivideo.cn:8082/v1/resource/upgcxcode/83/03/907810383/907810383_nb3-1-30016.m4s?...' \
-  -H 'Referer: https://www.bilibili.com/' \
-  -o part_video.m4s
-```
-
-### ④ 响应信封 + 翻页
-- 二进制流,无 JSON 信封;实测 `HTTP 206 Partial Content`,`Content-Range: bytes 0-1048575/1766147`,`bytes_received=1048576`,`looks_like_video=true`(`bili_video_dl.json`)。
-
-### ⑤ 字段台账(取自 `bili_video_dl.json`)
-
-| 字段名(raw) | 类型 | 含义 | 提取标注 | 真实示例值 |
-|---|---|---|---|---|
-| `http_status` | int | HTTP 状态 | 直接可用 | `206` |
-| `content_type` | string | 内容类型 | 直接可用 | `"application/octet-stream"` |
-| `content_range` | string | 字节范围/总长 | 需后处理(解析总长) | `"bytes 0-1048575/1766147"` |
-| `bytes_received` | int | 本次收到字节数 | 直接可用 | `1048576` |
-| `looks_like_video` | bool | 字节嗅探判定为视频 | 直接可用 | `true` |
-
-### ⑥ 提取与后处理规则
-- **下载结论:bili_video_dl 实测 206,可下载**,得到真实视频字节。
-- m4s 必须带 `Referer: https://www.bilibili.com/`,否则防盗链拦截。
-- DASH 分轨:视频轨 + `voice_data.play_url` 音频轨需分别下载后合并(ffmpeg)。
-- **(实测 2026-06-16,新加坡云端)完整下载 + 合并验证通过**:视频轨(`...30016.m4s`)+ `voice_data.play_url` 音轨(`...30216.m4s`)→ `ffmpeg -i v.m4s -i a.m4s -c copy out.mp4` → 合并 mp4 含 `Video h264 (avc1)` + `Audio aac (HE-AAC) 44100Hz stereo`,可直接做原生视频提炼。
-- ⚠️ **下载坑(实测)**:`*.mcdn.bilivideo.cn` 节点用 httpx `follow_redirects=True` 会被带到 P2P 死节点、连接超时(两次都卡死);**改用 curl 且不跟随重定向**即 `206` 秒下。视频轨主 host `upos-sz-mirrorcos.bilivideo.com` 稳定。详见 [视频音频取数实测.md](../视频音频取数实测.md)。
-- 大文件用 `Range` 分块续传(`Content-Range` 给出总长 1766147)。
-
-### ⑦ 边界与失败
-- `deadline` 过期则直链失效,须回到详情接口重取。
-- 限流 ≥ 15s。
-
-### ⑧ 落点(canonical + 游走边)
-- canonical:`video_blob`(落地视频文件,供多模态分析)。
-- 无新增游走边;属 `video_to_*` 之后的物料下载阶段(提取标注:**需后处理下载**)。

+ 0 - 93
数据接口与来源/接口台账/github.md

@@ -1,93 +0,0 @@
-# GitHub 接口台账(接口文档撰写岗 D — 次级平台)
-
-> 唯一证据来源:`/tmp/aiddit_captures/github_search.json`(`code=0` ✅)。host:`http://crawler.aiddit.com`。
-> **平台级关键发现:GitHub 接口搜的是「代码仓库」,不是视频内容。** item 为 repo(`name`=仓库名 / `git_url` / `topics` 等),无视频地址、无播放/时长字段。topics 即 tag。本平台作为「次级 / 非视频」来源接入,下游不可按视频内容处理。
-
-平台仅一个接口:
-
-- `PLT_GITHUB_KEYWORD` — 关键词搜索代码仓库(verified ✅,**非视频**)
-
----
-
-## 接口:GitHub 关键词搜索 `PLT_GITHUB_KEYWORD`
-
-### ① 身份
-| 项 | 值 |
-|---|---|
-| source_id | `PLT_GITHUB_KEYWORD` |
-| display_name | Crawapi GitHub 关键词搜索(代码仓库) |
-| 平台 channel | GitHub |
-| 接口路径 | `/crawler/github/keyword` |
-| 状态 | **verified ✅**(capture `github_search.json`,`code=0`,返回 10 条 repo item + 翻页) |
-| 用途 | 关键词 → 代码仓库列表(**非视频内容源**) |
-
-### ② 请求标准
-- **method**:POST
-- **path**:`http://crawler.aiddit.com/crawler/github/keyword`
-- **鉴权**:抓包 `request_body` 含 `token` 字段,但本次为**空串** `""`(统一走 crawler 网关,base_url 由 `CONTENTFIND_API_CRAWAPI_BASE_URL` 提供)。
-- **Content-Type**:`application/json`
-
-| 参数 | 类型 | 必填 | 含义 | 抓包真值 |
-|---|---|---|---|---|
-| `keyword` | string | 是 | 搜索关键词 | `"彩虹"` |
-| `token` | string | 否 | 翻页 token(首页空串;下一页取 response 的 `next_cursor`) | `""` |
-
-### ③ 真实 curl
-```bash
-curl -X POST 'http://crawler.aiddit.com/crawler/github/keyword' \
-  -H 'Content-Type: application/json' \
-  -d '{"keyword":"彩虹","token":""}'
-```
-
-### ④ 响应信封 + 翻页
-信封:`{ code, msg, data }`,`code=0`。
-```
-data.has_more   = true
-data.next_cursor= "1"          ← 真值为短页码风格字符串(不是长 token)
-data.data       = [ {repo item}, ... ]   ← 本次抓包 10 条
-```
-**翻页规则**:`has_more=true` 时,把 response 的 `next_cursor`(如 `"1"`)作为下一次请求的 **`token`** 参数传回(请求参数名为 `token`,响应字段名为 `next_cursor`,二者对应)。GitHub 的 cursor 是**短页码字符串**,与 YouTube 的长 token 不同。
-
-### ⑤ 字段台账表(raw 名)
-来源:`github_search.json` → `data.data[]`。
-
-| 字段名(raw) | 类型 | 含义 | 提取标注 | 真实示例值 |
-|---|---|---|---|---|
-| `id` | string | **内容 id**(repo id,字符串) | 直接可用 | `702272852` |
-| `hl_name` | string | **标题 / 仓库全名**(`owner/repo`,含 `<em>` 高亮标记) | 需正则抽(去 `<em></em>`) | `v03413/BEpusdt` |
-| `repo.repository.name` | string | 仓库名(纯名,无 owner) | 直接可用 | `BEpusdt` |
-| `repo.repository.owner_login` | string | **作者**(owner 登录名) | 直接可用 | `v03413` |
-| `repo.repository.owner_id` | int | **作者 id**(owner 数字 id) | 直接可用 | `49953737` |
-| `hl_trunc_description` | string | 仓库描述(截断,含 `<em>` 高亮) | 需正则抽(去 `<em></em>`) | `一款更好用的个人加密货币收款网关...支持独角数卡 <em>彩</em><em>虹</em>易支付` |
-| `topics` | array<string> | **tag**(仓库 topics,多数为空数组) | 直接可用(即 tag) | `["vue"]`;`["markdown","youtube","pixiv",...]`;常见 `[]` |
-| `language` | string\|null | 主语言 | 直接可用 | `Go` / `PHP` / `null` |
-| `followers` | int | star/关注数 | 直接可用 | `1626` |
-| `archived` | bool | 是否归档 | 直接可用 | `false` |
-| `repo.repository.updated_at` | string(ISO) | 仓库更新时间 | 直接可用 | `2026-06-10T01:28:04.341Z` |
-| `repo.repository.has_issues` | bool | 是否开启 issue | 直接可用 | `true` |
-| `type` | string | 仓库可见性 | 直接可用 | `Public` |
-| `has_more` | bool | **翻页**:是否还有下一页 | 直接可用 | `true` |
-| `next_cursor` | string | **翻页**:下一页 token(短页码,回传到请求 `token`) | 直接可用 | `1` |
-
-> **内容 url / git_url 推导**:抓包 item 内**无独立 `link`/`git_url`/`html_url` 字段**,仓库 url 须由 `hl_name`(去高亮后的 `owner/repo`)拼出:`https://github.com/{owner}/{repo}`。提取标注=需后处理(拼接)。
-
-### ⑥ 提取与后处理规则
-- **本平台是代码仓库,非视频**:无 `video_id` / `duration` / `view_count` / `video_url` 等任何视频字段。下游不可按「视频内容」流程处理,应作为「仓库/文本资产」类内容。
-- `hl_name` 与 `hl_trunc_description` 含搜索高亮标记 `<em>...</em>`,落库前须正则 `</?em>` 去除。
-- **tag = `topics`**:直接作为 tags 落库;注意多数仓库 `topics=[]`(空),少数才有(如 `["vue"]`)。
-- 仓库 url 无现成字段,需用 `hl_name`(清洗后)拼 `https://github.com/{owner/repo}`。
-- `owner_login`/`owner_id` 即作者;GitHub 无「频道」概念,作者即仓库 owner。
-
-### ⑦ 边界与失败
-- 单次约 10 条;`has_more=true` 时透传 `next_cursor → token` 翻页。
-- 限流:请求间隔 **≥ 15s**。
-- 多数 item `topics=[]`、`language=null`,下游不可强假定非空。
-- 无视频/媒体文件,无 detail 接口抓包(本批次只验证了 search)。
-
-### ⑧ 落点(canonical + 游走边)
-- canonical 映射:`id → platform_content_id`、`owner_id`(或 `owner_login`)`→ platform_author_id`、`topics → tags`、拼出的 `https://github.com/{owner/repo}` → content url。
-- 游走边:
-  - `search_page_to_content`:搜索页 → 各 repo `id`。
-  - `video_to_author`(此处语义为 content→author):`id → owner_id/owner_login`。
-  - `video_to_hashtag`(此处语义为 content→tag):`id → topics[]`(topics 即 tag;空时不产生边)。
-- 标注:内容类型为「代码仓库」,非视频,沉淀时打类型标记以免误入视频处理链路。

+ 0 - 167
数据接口与来源/接口台账/youtube.md

@@ -1,167 +0,0 @@
-# YouTube 接口台账(接口文档撰写岗 D — 次级平台)
-
-> 唯一证据来源:`/tmp/aiddit_captures/youtube_search.json`、`/tmp/aiddit_captures/youtube_detail.json`(均 `code=0` ✅)。
-> host:`http://crawler.aiddit.com`。本文所有字段、示例值、翻页 token 均取自真实抓包,未编造。
-> **平台级关键发现:detail 接口归一化结构里 `video_url_list` / `image_url_list` 为空数组 → 视频文件不可经本平台接口直取**,须另走字幕 / 其它通道。详见各接口第 ⑥⑦ 段。
-
-平台含两个接口:
-
-- `PLT_YOUTUBE_KEYWORD` — 关键词搜索(verified ✅)
-- `PLT_YOUTUBE_DETAIL` — 内容详情(verified ✅,但**无视频地址**)
-
----
-
-## 接口一:YouTube 关键词搜索 `PLT_YOUTUBE_KEYWORD`
-
-### ① 身份
-| 项 | 值 |
-|---|---|
-| source_id | `PLT_YOUTUBE_KEYWORD` |
-| display_name | Crawapi YouTube 关键词搜索 |
-| 平台 channel | YouTube |
-| 接口路径 | `/crawler/youtube/keyword` |
-| 状态 | **verified ✅**(capture `youtube_search.json`,`code=0`,返回 20 条 item + 翻页 token) |
-| 用途 | 关键词 → 视频列表,搜索发现阶段入口 |
-
-### ② 请求标准
-- **method**:POST
-- **path**:`http://crawler.aiddit.com/crawler/youtube/keyword`
-- **鉴权**:抓包 `request_body` 内无 token / key 字段;统一走 crawler 网关,base_url 由环境变量 `CONTENTFIND_API_CRAWAPI_BASE_URL` 提供。
-- **Content-Type**:`application/json`
-
-| 参数 | 类型 | 必填 | 含义 | 抓包真值 |
-|---|---|---|---|---|
-| `keyword` | string | 是 | 搜索关键词 | `"彩虹"` |
-| `next_cursor` | string | 否 | 翻页游标(首页不传;取上一页 response 的 `next_cursor`) | 见 ④(长 token) |
-
-> 注:抓包仅传了 `keyword`。`next_cursor` 为翻页推断参数(response 已返回 `has_more=true` + `next_cursor` 长 token),首页无需传。
-
-### ③ 真实 curl
-```bash
-curl -X POST 'http://crawler.aiddit.com/crawler/youtube/keyword' \
-  -H 'Content-Type: application/json' \
-  -d '{"keyword":"彩虹"}'
-```
-
-### ④ 响应信封 + 翻页
-信封:`{ code, msg, data }`,`code=0` 为成功。
-```
-data.has_more   = true
-data.next_cursor= "EqMDEgblvanombkamANDQUFTQmdnRkVBRVlBMGdVZ2dFTE1VdEVURlZRWVRBeFdqaUNB...%3D%3D"   ← 真值为 URL-encoded 长 token(base64 风格,约 400+ 字符)
-data.data       = [ {video item}, ... ]   ← 本次抓包 20 条
-```
-**翻页规则**:`has_more=true` 时,把 response 的 `next_cursor`(长 token,原样、含 `%3D%3D` 等已编码字符)作为下一次请求的 `next_cursor` 传回。token 极长,存储时按字符串整体保存,不要做截断 / 二次 URL 编码。
-
-### ⑤ 字段台账表(raw 名)
-来源:`youtube_search.json` → `data.data[]`。
-
-| 字段名(raw) | 类型 | 含义 | 提取标注 | 真实示例值 |
-|---|---|---|---|---|
-| `video_id` | string | **内容 id**(YouTube 视频 id) | 直接可用 | `1KDLUPa01Z8` |
-| `title` | string | **标题** | 直接可用 | `宇宙人 Cosmos People【彩虹 Rainbow】歌詞版MV Lyrics Video @CosmosPeople` |
-| `author` | string | **作者**(频道名展示名) | 直接可用 | `滾石唱片 ROCK RECORDS` |
-| `channel_id` | string | **作者 id**(频道 id,部分 item 为空串) | 直接可用(可能为 `""`) | `UCPcF3KTqhD67ADkukx_OeDg`;空例 `lfg7Cj7scww` 条目为 `""` |
-| `url` | string | **内容 url**(watch 链接) | 直接可用 | `https://www.youtube.com/watch?v=1KDLUPa01Z8` |
-| `description_snippet` | string | 描述摘要(截断) | 直接可用 | `【ROCK 歌詞版MV】 歡迎訂閱滾石唱片YouTube官方頻道→ ...` |
-| `duration` | string | 时长(人读) | 需后处理(`mm:ss`→秒,可选) | `4:22` |
-| `view_count` | string | 播放数(带逗号 + "views" 文本) | 需正则抽(取数字) | `137,357 views` |
-| `published_time` | string | 发布相对时间(部分为空) | 需后处理(相对→绝对,可选) | `7 months ago`;空例 `rF4U0dH6mrI` 为 `""` |
-| `thumbnails[].url` | string | 封面图 url(数组,多档分辨率) | 直接可用 | `https://i.ytimg.com/vi/1KDLUPa01Z8/hq720.jpg?...` |
-| `has_more` | bool | **翻页**:是否还有下一页 | 直接可用 | `true` |
-| `next_cursor` | string | **翻页**:下一页游标(长 token) | 直接可用(透传) | `EqMDEgblvanombkamANDQUFTQmdn...%3D%3D` |
-
-### ⑥ 提取与后处理规则
-- `video_id` 即 canonical `platform_content_id`;`channel_id` 即 canonical `platform_author_id`(注意**会出现空串**,落库时需判空 / 回退)。
-- 视频可访问页面 = `url`(watch 链接),但**搜索结果不含可下载的视频文件地址**;视频文件须见接口二说明(detail 亦无)。
-- `view_count` 含逗号与 "views" 文本,需正则 `[\d,]+` 抽数字再去逗号。
-- 搜索结果**无 tag/hashtag 字段**(YouTube 搜索 item 不返回 topics/hashtags;hashtag 需从 detail 的 `body_text` 正则抽,见接口二)。
-
-### ⑦ 边界与失败
-- 单次约 20 条;`has_more=true` 需透传 `next_cursor` 翻页。
-- 限流:统一遵循 crawler 网关约束,**请求间隔 ≥ 15s**。
-- `channel_id` 可能为空串,下游不可强假定非空。
-
-### ⑧ 落点(canonical + 游走边)
-- canonical 映射:`video_id → platform_content_id`、`channel_id → platform_author_id`、`title/url/author` 直接落 content。无 tag 字段(tags 留空,或后续从 detail 抽)。
-- 游走边:
-  - `search_page_to_content`:搜索页 → 各 `video_id` 内容。
-  - `video_to_author`:`video_id → channel_id`(频道)。
-  - `video_to_hashtag`:搜索结果**无 hashtag**,此边在搜索阶段不产生;须经 detail 的 `body_text` 抽 `#xxx` 后产生(见接口二 ⑥)。
-
----
-
-## 接口二:YouTube 内容详情 `PLT_YOUTUBE_DETAIL`
-
-### ① 身份
-| 项 | 值 |
-|---|---|
-| source_id | `PLT_YOUTUBE_DETAIL` |
-| display_name | Crawapi YouTube 内容详情 |
-| 接口路径 | `/crawler/youtube/detail` |
-| 状态 | **verified ✅(带重大限制)**:capture `youtube_detail.json` `code=0`,结构完整,但 `video_url_list=[]` / `image_url_list=[]` → **无视频/图片地址** |
-| 用途 | 单视频 id → 完整正文 / 统计 / 频道信息(**不含可下载视频**) |
-
-### ② 请求标准
-- **method**:POST
-- **path**:`http://crawler.aiddit.com/crawler/youtube/detail`
-- **鉴权**:无显式 token(同接口一)。
-
-| 参数 | 类型 | 必填 | 含义 | 抓包真值 |
-|---|---|---|---|---|
-| `content_id` | string | 是 | 视频 id(= 搜索结果 `video_id`) | `1KDLUPa01Z8` |
-
-### ③ 真实 curl
-```bash
-curl -X POST 'http://crawler.aiddit.com/crawler/youtube/detail' \
-  -H 'Content-Type: application/json' \
-  -d '{"content_id":"1KDLUPa01Z8"}'
-```
-
-### ④ 响应信封 + 翻页
-信封:`{ code, msg, data }`,`code=0`。
-```
-data.has_more   = false
-data.next_cursor= null            ← detail 单条,无翻页
-data.data       = { 归一化内容对象 }   ← 注意是对象(嵌套 data.data),非数组
-```
-**翻页规则**:detail 无翻页(`has_more=false`,`next_cursor=null`)。
-
-### ⑤ 字段台账表(raw 名)
-来源:`youtube_detail.json` → `data.data{}`。
-
-| 字段名(raw) | 类型 | 含义 | 提取标注 | 真实示例值 |
-|---|---|---|---|---|
-| `channel_content_id` | string | **内容 id** | 直接可用 | `1KDLUPa01Z8` |
-| `title` | string | **标题** | 直接可用 | `宇宙人 Cosmos People【彩虹 Rainbow】歌詞版MV Lyrics Video ‪@CosmosPeople‬` |
-| `content_link` | string | **内容 url**(watch 链接) | 直接可用 | `https://www.youtube.com/watch?v=1KDLUPa01Z8` |
-| `body_text` | string | 完整描述正文(含 `#宇宙人 #彩虹 #歌詞版MV` 等 hashtag 与外链) | 需正则抽(hashtag、外链) | `【ROCK 歌詞版MV】... #宇宙人 #彩虹 #歌詞版MV ...` |
-| `content_type` | string | 内容类型 | 直接可用 | `video` |
-| `channel_account_id` | string | **作者 id**(频道账号 id,注意与搜索 `channel_id` 不同体系:32 位 hash) | 直接可用 | `c938a12dd25cf2d83a66e9ad3b6dcfd2` |
-| `channel_account_name` | string | **作者**(频道名) | 直接可用 | `滾石唱片 ROCK RECORDS` |
-| `video_url_list` | array | **视频文件地址列表** | **空数组 → 视频不可直取** | `[]` |
-| `image_url_list` | array | 图片地址列表 | **空数组** | `[]` |
-| `topic_list` | null | 话题列表 | 后处理(本条为 `null`,hashtag 走 `body_text` 抽) | `null` |
-| `like_count` | int | 点赞数 | 直接可用 | `1404` |
-| `comment_count` | int | 评论数 | 直接可用 | `44` |
-| `view_count` / `play_count` / `share_count` / `collect_count` | null | 各互动数(本条为 null) | 直接可用(可能 null) | `null` |
-| `publish_timestamp` | int(ms) | 发布时间戳(毫秒) | 需后处理(ms→datetime) | `1781159156471` |
-| `is_original` | bool | 是否原创 | 直接可用 | `false` |
-| `channel` | int | 平台内部 channel 编号 | 直接可用 | `17` |
-
-### ⑥ 提取与后处理规则
-- **⚠ 视频不可直取**:`video_url_list=[]` 且 `image_url_list=[]`。本接口**不返回任何可下载的视频/图片文件地址**,只有正文文本与统计。需要视频内容时,须另走**字幕通道 / 其它下载手段**,不能依赖本平台接口拿到 mp4。
-- hashtag:detail 的 `topic_list=null`,但 `body_text` 末尾含 `#宇宙人 #彩虹 #歌詞版MV`,需正则 `#([^\s#]+)` 抽取后才能产生 hashtag 游走边。
-- 作者 id 双体系:搜索 `channel_id`(`UC...`)与 detail `channel_account_id`(32 位 hash)**不是同一编码**,落 canonical `platform_author_id` 时须明确以哪一个为准(建议以搜索 `channel_id` 的 `UC...` 为稳定主键,detail hash 作辅助)。
-- `publish_timestamp` 为毫秒,落库需 `/1000` 转秒或转 datetime。
-
-### ⑦ 边界与失败
-- 本接口**最大限制即视频地址为空**,不可作为视频获取通道。
-- detail 互动字段多为 `null`(仅 `like_count` / `comment_count` 有值),下游统计不可强假定非空。
-- 限流:请求间隔 **≥ 15s**。
-
-### ⑧ 落点(canonical + 游走边)
-- canonical 映射:`channel_content_id → platform_content_id`、`channel_account_id → platform_author_id`(或回退搜索 `channel_id`)、`body_text` 抽出的 `#xxx → tags`。
-- 游走边:
-  - `video_to_author`:`channel_content_id → channel_account_id`。
-  - `video_to_hashtag`:从 `body_text` 抽 `#xxx` 产生(detail 是 hashtag 的唯一来源)。
-  - **无** video 文件下载边(`video_url_list` 空)。

+ 0 - 189
数据接口与来源/接口台账/公众号.md

@@ -1,189 +0,0 @@
-# 公众号(微信 wei_xin)接口台账
-
-> 唯一证据来源:`/tmp/aiddit_captures/gzh_search.json`、`gzh_detail.json`、`gzh_account_info.json`(2026-06-11 真实抓包)。
-> 全部接口走统一爬虫网关 `host = http://crawler.aiddit.com`,平台名 `wei_xin`。
-> 字段表与示例值全部取自 capture 真实响应,未做任何编造。
-
-## 平台级结论(先读)
-
-- **关键词搜索可用**:`/crawler/wei_xin/keyword` `code=0`,一次返回 20 条文章(`gzh_search.json`),item = `{title, time, nick_name, url, cover_url, biz}`。
-- **作者血缘缺口**:搜索结果里 **`nick_name` 与 `biz` 字段均为空字符串**,作者标识只能从 `url` 的 `__biz=...` 参数里**正则抽取**。即“作者只有 biz,没有独立 account_id”。
-- **详情可用**:`/crawler/wei_xin/detail` `code=0`,用文章 `content_link` 取归一化正文(`gzh_detail.json`)。
-- **号主信息走 account_info(可用)**:`/crawler/wei_xin/account_info` `code=0`,同样用 `content_link` 取号主 `account_name / biz_info / wx_gh`(`gzh_account_info.json`)。**这是补齐作者昵称的唯一可达路径。**
-- **blogger 接口不可达**:本批 capture 内 **没有** “按 biz/号 拉该公众号全部历史文章” 的 blogger/作品列表接口;`author_to_works` 在公众号走不通,只能逐篇 `content_link` → detail / account_info。
-- **无 tag**:公众号 detail `topic_list=null`,**整平台无 hashtag**,`video_to_hashtag` 不可走。
-
----
-
-## 接口 1:关键词搜索
-
-### ① 身份
-- `source_id`: **PLT_WEIXIN_KEYWORD**
-- 状态:**verified**(`gzh_search.json` `code=0`,20 条)
-
-### ② 请求标准
-- method:`POST`,path:`/crawler/wei_xin/keyword`
-- 参数表:
-
-| 参数 | 类型 | 必填 | 含义 | 真实值 |
-|---|---|---|---|---|
-| `keyword` | string | 是 | 搜索词 | `"彩虹"` |
-| `cursor` | string | 否 | 翻页游标,取上次 `next_cursor` | `"0"`(首页) |
-
-### ③ 真实 curl
-```bash
-curl -X POST 'http://crawler.aiddit.com/crawler/wei_xin/keyword' \
-  -H 'Content-Type: application/json' \
-  -d '{"keyword":"彩虹","cursor":"0"}'
-```
-
-### ④ 响应信封 + 翻页
-```json
-{ "code": 0, "msg": null,
-  "data": { "has_more": true, "next_cursor": "1", "data": [ {...}, ... 20 条 ] } }
-```
-- 翻页:`data.has_more=true`、`data.next_cursor="1"`(真值,页码字符串),回填到 `cursor`。
-
-### ⑤ 字段台账(raw 名,取自 `gzh_search.json` `data.data[0]`)
-
-| 字段名(raw) | 类型 | 含义 | 提取标注 | 真实示例值 |
-|---|---|---|---|---|
-| `url` | string | 文章地址(含 `__biz/mid/idx/sn`),既是内容定位也是作者定位 | 直接可用(且需正则抽 biz/内容id) | `"http://mp.weixin.qq.com/s?__biz=MzI5NjAxMTgzNg==&mid=2247483744&idx=1&sn=eb484d234c5579d567c87d9519351b74&..."` |
-| `title` | string | 标题(含 `<em class="highlight">` 高亮) | 需正则抽(剥 `<em>`) | `"<em class=\"highlight\">彩虹</em>股份(600707)深度分析报告"` |
-| `cover_url` | string | 封面图 | 直接可用 | `"https://mmbiz.qpic.cn/mmbiz_jpg/fNx5Iic5WA7X9043...jpeg"` |
-| `time` | int | 发布时间戳(秒) | 需后处理(×1000) | `1781151948` |
-| `nick_name` | string | 作者昵称(**本响应恒为空串,不可用**) | 需二次请求(走 account_info 补) | `""` |
-| `biz` | string | 作者标识(**本响应恒为空串,须从 url 抽 `__biz`**) | 需正则抽(从 `url`) | `""`(url 内 `__biz=MzI5NjAxMTgzNg==`) |
-
-> 翻页字段:`data.has_more=true` / `data.next_cursor="1"`(真值)。
-
-### ⑥ 提取与后处理规则
-- 内容ID:从 `url` 正则抽 `__biz` + `mid` + `idx` + `sn` 组合,或直接以 `url`(去 chksm/scene 后归一)为内容定位键。
-- **作者标识只有 biz,且 search item 的 `biz` 为空**:必须正则 `__biz=([^&]+)` 从 `url` 抽取(示例 `MzI5NjAxMTgzNg==`)。`nick_name` 为空,作者昵称须二次走 account_info。
-- `title` 正则剥离 `<em class="highlight">`。
-
-### ⑦ 边界与失败
-- 作者血缘弱:search 仅给 biz(还得自抽),昵称需补请求。
-- 限流 ≥ 15s。
-
-### ⑧ 落点(canonical + 游走边)
-- canonical:`platform_content_id`(从 `url` 抽 `__biz+mid+idx`)、`platform_author_id`(=`biz`,从 `url` 抽)、`title`、`content_url`(=`url`)、`cover_url`、`publish_ts`(=`time`)、`has_more`/`next_cursor`。
-- 游走边:`search_page_to_content`;`video_to_author` 仅落 `biz` 标识(昵称需 account_info 补);**`author_to_works` 走不通(无 blogger 接口)**;**`video_to_hashtag` 不可走(无 tag)**。
-
----
-
-## 接口 2:文章详情
-
-### ① 身份
-- `source_id`: **PLT_WEIXIN_CONTENT_DETAIL**
-- 状态:**verified**(`gzh_detail.json` `code=0`)
-
-### ② 请求标准
-- method:`POST`,path:`/crawler/wei_xin/detail`
-- 参数表:
-
-| 参数 | 类型 | 必填 | 含义 | 真实值 |
-|---|---|---|---|---|
-| `content_link` | string | 是 | 文章地址(search 的 `url`) | `"http://mp.weixin.qq.com/s?__biz=MzI5NjAxMTgzNg==&mid=2247483744&idx=1&sn=eb484d...&..."` |
-
-### ③ 真实 curl
-```bash
-curl -X POST 'http://crawler.aiddit.com/crawler/wei_xin/detail' \
-  -H 'Content-Type: application/json' \
-  -d '{"content_link":"http://mp.weixin.qq.com/s?__biz=MzI5NjAxMTgzNg==&mid=2247483744&idx=1&sn=eb484d234c5579d567c87d9519351b74&scene=7#rd"}'
-```
-
-### ④ 响应信封 + 翻页
-```json
-{ "code": 0, "msg": null,
-  "data": { "has_more": false, "next_cursor": null, "data": { ...归一化对象... } } }
-```
-- 单对象,无翻页。
-
-### ⑤ 字段台账(raw 名,取自 `gzh_detail.json` `data.data`)
-
-| 字段名(raw) | 类型 | 含义 | 提取标注 | 真实示例值 |
-|---|---|---|---|---|
-| `channel_content_id` | string | 内容ID(网关归一化 hash) | 直接可用 | `"d9ac580027959372cc141815a3f906a3"` |
-| `content_link` | string | 归一化文章地址 | 直接可用 | `"https://mp.weixin.qq.com/s?__biz=MzI5NjAxMTgzNg==&idx=1&mid=2247483744&sn=eb484d..."` |
-| `title` | string | 标题(无高亮) | 直接可用 | `"彩虹股份(600707)深度分析报告"` |
-| `body_text` | string | 正文全文 | 直接可用 | `"报告日期:2026年6月10日 | 当前股价:12.45元 ..."`(长文) |
-| `content_type` | string | 类型 | 直接可用 | `"note"` |
-| `topic_list` | null | 话题(**公众号恒为 null,无 tag**) | 需后处理(置空数组) | `null` |
-| `image_url_list[].image_url` | string | 正文配图 | 直接可用 | `"https://mmbiz.qpic.cn/mmbiz_jpg/fNx5Iic5WA7X140yn...jpeg"` |
-| `video_url_list` | null | 视频列表(本文为图文,无视频) | 需后处理 | `null` |
-| `channel_account_id` | string | 作者ID(网关归一化号主 hash,**非 biz**) | 直接可用 | `"969a9ad954ec605c67110cdb8c42f301"` |
-| `channel_account_name` | null | 作者昵称(**detail 恒为 null,须 account_info 补**) | 需二次请求 | `null` |
-| `location` | string | 发布地 | 直接可用 | `"广东"` |
-| `view_count` | int | 阅读数 | 直接可用 | `15` |
-| `like_count` | int | 点赞数 | 直接可用 | `0` |
-| `publish_timestamp` | int | 发布时间戳(毫秒) | 直接可用 | `1781151960000` |
-
-### ⑥ 提取与后处理规则
-- detail 的 `channel_account_id`(hash)与 account_info 的 `channel_account_id` 一致(`969a9ad9...`),可作号主主键对齐;但 **`channel_account_name=null`**,昵称必须走 account_info。
-- `topic_list=null` → 公众号无 hashtag,统一置空。
-- 注意:detail 的 `channel_account_id` 是网关 hash,**与作者血缘里的 `biz` 不是同一标识**,两者需都沉淀(biz 来自 url,hash 来自 detail/account_info)。
-
-### ⑦ 边界与失败
-- 昵称缺口:detail 不给昵称,须额外一次 account_info。
-- 限流 ≥ 15s。
-
-### ⑧ 落点(canonical + 游走边)
-- canonical:`platform_content_id`(=`channel_content_id`)、`platform_author_id`(=`channel_account_id` hash / 另存 `biz`)、`title`、`body_text`、`content_url`(=`content_link`)、`images`(=`image_url_list`)、`stats.view/like`、`publish_ts`、`location`。
-- 游走边:`video_to_author`(落号主 hash 标识,昵称待 account_info 补);`author_to_works` 不可走;`video_to_hashtag` 不可走(tag=null)。
-
----
-
-## 接口 3:号主信息(account_info)
-
-### ① 身份
-- `source_id`: **PLT_WEIXIN_ACCOUNT_INFO**
-- 状态:**verified**(`gzh_account_info.json` `code=0`)
-
-### ② 请求标准
-- method:`POST`,path:`/crawler/wei_xin/account_info`
-- 参数表:
-
-| 参数 | 类型 | 必填 | 含义 | 真实值 |
-|---|---|---|---|---|
-| `content_link` | string | 是 | 文章地址(用任一该号文章 `url` 反查号主) | `"http://mp.weixin.qq.com/s?__biz=MzI5NjAxMTgzNg==&mid=2247483744&..."` |
-
-### ③ 真实 curl
-```bash
-curl -X POST 'http://crawler.aiddit.com/crawler/wei_xin/account_info' \
-  -H 'Content-Type: application/json' \
-  -d '{"content_link":"http://mp.weixin.qq.com/s?__biz=MzI5NjAxMTgzNg==&mid=2247483744&idx=1&sn=eb484d234c5579d567c87d9519351b74&scene=7#rd"}'
-```
-
-### ④ 响应信封 + 翻页
-```json
-{ "code": 0, "msg": null,
-  "data": { "has_more": false, "next_cursor": null, "data": { ...号主对象... } } }
-```
-- 单对象,无翻页。
-
-### ⑤ 字段台账(raw 名,取自 `gzh_account_info.json` `data.data`)
-
-| 字段名(raw) | 类型 | 含义 | 提取标注 | 真实示例值 |
-|---|---|---|---|---|
-| `channel_account_id` | string | 号主ID(网关归一化 hash,与 detail 一致) | 直接可用 | `"969a9ad954ec605c67110cdb8c42f301"` |
-| `account_name` | string | **号主昵称(补齐 search/detail 缺口的关键字段)** | 直接可用 | `"韭菜看透本质"` |
-| `account_link` | string | 号主主页链接 | 直接可用 | `"https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=MzI5NjAxMTgzNg==&scene=124#wechat_redirect"` |
-| `biz_info` | string | 作者 biz(与 url `__biz` 一致) | 直接可用 | `"MzI5NjAxMTgzNg=="` |
-| `wx_gh` | string | 公众号原始ID(gh_ 号) | 直接可用 | `"gh_d6a68990fe9c"` |
-| `gender` | string | 性别 | 直接可用 | `"其他"` |
-| `follower_count` | null | 粉丝数(本响应为 null,不可用) | 需后处理 | `null` |
-| `publish_count` | null | 发文数(null,**无作品列表**) | 需后处理 | `null` |
-
-### ⑥ 提取与后处理规则
-- **account_info 是公众号作者血缘的唯一可达补全**:用任一文章 `content_link` 反查,拿到 `account_name`(昵称)+ `biz_info`(=biz)+ `wx_gh`,把 search/detail 缺的昵称补上。
-- 三个作者标识对齐:`biz_info`(=url 的 `__biz`) ↔ `channel_account_id`(detail/account_info 同一 hash) ↔ `wx_gh`。
-- **仍无作品列表**:`publish_count=null`,account_info **不返回该号历史文章**,`author_to_works` 仍走不通。
-
-### ⑦ 边界与失败
-- 互动/粉丝指标(`follower_count`/`publish_count`/`like_count` 等)本响应全为 null,作者画像信息有限。
-- 限流 ≥ 15s。
-
-### ⑧ 落点(canonical + 游走边)
-- canonical:`platform_author_id`(=`channel_account_id` / 另存 `biz_info`、`wx_gh`)、`author_name`(=`account_name`)、`author_url`(=`account_link`)、`gender`。
-- 游走边:`video_to_author` 的昵称补全终点;**`author_to_works` 不可走**(无作品列表数据源)。

+ 0 - 76
数据接口与来源/接口台账/头条.md

@@ -1,76 +0,0 @@
-# 头条(头条号 tou_tiao_hao)接口台账(接口文档撰写岗 D — 次级平台)
-
-> 唯一证据来源:`/tmp/aiddit_captures/toutiao_search.json`、`/tmp/aiddit_captures/toutiao_search_retry.json`。host:`http://crawler.aiddit.com`。
-> **平台级关键发现:整平台失败(blocked)。** 两次抓包(含一次去掉 `content_type` 的重试)**均返回 `code=10000 / msg="未知错误" / data=null`**,未拿到任何真实 item、字段或翻页结构。本平台**当前不可用**,下游不应接入。
-
-平台仅一个接口(失败):
-
-- `PLT_TOUTIAO_KEYWORD` — 关键词搜索(**blocked ⛔**,code=10000 两次失败)
-
----
-
-## 接口:头条号关键词搜索 `PLT_TOUTIAO_KEYWORD`
-
-### ① 身份
-| 项 | 值 |
-|---|---|
-| source_id | `PLT_TOUTIAO_KEYWORD` |
-| display_name | Crawapi 头条号关键词搜索 |
-| 平台 channel | 头条号(tou_tiao_hao) |
-| 接口路径 | `/crawler/tou_tiao_hao/keyword` |
-| 状态 | **blocked ⛔**(`toutiao_search.json` + `toutiao_search_retry.json` 均 `code=10000`,`data=null`) |
-| 用途 | 关键词 → 内容列表(**当前整平台不可用**) |
-
-### ② 请求标准
-- **method**:POST
-- **path**:`http://crawler.aiddit.com/crawler/tou_tiao_hao/keyword`
-- **鉴权**:抓包 `request_body` 无 token / key(同其它平台走 crawler 网关,base_url 由 `CONTENTFIND_API_CRAWAPI_BASE_URL` 提供)。
-- **Content-Type**:`application/json`
-
-| 参数 | 类型 | 必填 | 含义 | 抓包真值 |
-|---|---|---|---|---|
-| `keyword` | string | 是 | 搜索关键词 | `"彩虹"` |
-| `content_type` | string | 否 | 内容类型(首抓传 `video`,重试已去掉) | `"video"`(重试未传) |
-| `page` | int | 否 | 页码 | `0` |
-
-### ③ 真实 curl
-```bash
-# 首次(带 content_type)—— 返回 code=10000
-curl -X POST 'http://crawler.aiddit.com/crawler/tou_tiao_hao/keyword' \
-  -H 'Content-Type: application/json' \
-  -d '{"keyword":"彩虹","content_type":"video","page":0}'
-
-# 重试(去掉 content_type)—— 仍返回 code=10000
-curl -X POST 'http://crawler.aiddit.com/crawler/tou_tiao_hao/keyword' \
-  -H 'Content-Type: application/json' \
-  -d '{"keyword":"彩虹","page":0}'
-```
-
-### ④ 响应信封 + 翻页
-信封:`{ code, msg, data }`。两次抓包均为失败信封:
-```
-code = 10000
-msg  = "未知错误"
-data = null
-```
-**翻页规则**:无(`data=null`,无 `has_more` / `next_cursor`,无任何成功结构可记录)。
-
-### ⑤ 字段台账表(raw 名)
-**无可用字段** —— 两次响应 `data=null`,未返回任何 item,内容 id / 标题 / 作者 / url / tag / 翻页字段**全部未知**,不予编造。待平台修复(返回 `code=0`)后补抓再登记。
-
-| 字段名(raw) | 类型 | 含义 | 提取标注 | 真实示例值 |
-|---|---|---|---|---|
-| —(无) | — | 平台失败,无字段 | — | `data=null` |
-
-### ⑥ 提取与后处理规则
-不适用 —— 无成功响应。平台修复后再补。
-
-### ⑦ 边界与失败
-- **整平台 blocked**:`/crawler/tou_tiao_hao/keyword` 经两次真实请求(`toutiao_search.json` 带 `content_type=video`、`toutiao_search_retry.json` 去掉 `content_type`)**均返回 `code=10000 / msg="未知错误"`**。
-- 失败与参数无关(去掉 `content_type` 后仍 10000),判定为**平台/接口侧不可用**,非调用方参数问题。
-- 限流:统一约束请求间隔 **≥ 15s**(即便修复后亦适用)。
-- 处置:下游链路**跳过头条平台**;不要把它当作可用内容源;待 crawler 侧修复并复抓到 `code=0` 后再解锁登记。
-
-### ⑧ 落点(canonical + 游走边)
-- **无落点**:无内容数据,不产生 canonical `platform_content_id` / `platform_author_id` / `tags`,也不产生 `search_page_to_content` / `video_to_author` / `video_to_hashtag` 任何游走边。
-- registry 状态置 `blocked`,待修复后再定义映射。

+ 0 - 185
数据接口与来源/接口台账/小红书.md

@@ -1,185 +0,0 @@
-# 接口台账 · 小红书(xiao_hong_shu)
-
-> 唯一证据来源:`/tmp/aiddit_captures/` 下真实抓包。host=`http://crawler.aiddit.com`。
-> 整体状态:**部分可用** —— 搜索 `code=0` ✅、详情 `code=0` ✅、作者 blogger 请求超时 blocked。
-> 平台特征:以**图文为主**(`image_list` / `image_url_list`),搜索结果以**视频笔记**为主(`note_card.type=video`)。
-> **关键提取规则:搜索接口内容字段全部嵌在 `note_card` 嵌套下**(见下,这是核心抽取规则)。
-> 鉴权:抓包请求体无显式 token,统一走 crawler.aiddit.com 网关。
-
----
-
-## 1. 关键词搜索(PLT_XIAOHONGSHU_KEYWORD)
-
-### ① 身份
-- source_id:`PLT_XIAOHONGSHU_KEYWORD`
-- 接口名:小红书关键词搜索
-- 用途:按关键词检索笔记(搜索页 → 内容)
-- 状态:**verified** ✅(`code=0` 有数据)。证据:`xhs_search.json`。
-
-### ② 请求标准
-- method:`POST`
-- path:`/crawler/xiao_hong_shu/keyword`
-- 鉴权:无显式 token(走网关)
-- 参数表:
-
-| 名 | 类型 | 必填 | 示例 |
-|---|---|---|---|
-| keyword | string | 是 | `"彩虹"` |
-| content_type | string | 否 | `"视频"`(可选笔记类型过滤) |
-| sort_type | string | 否 | `"综合"` |
-| publish_time | string | 否 | `""` |
-| cursor | string | 否 | `""`(首页传空) |
-
-### ③ 请求示例(真实 request_body)
-```bash
-curl -X POST 'http://crawler.aiddit.com/crawler/xiao_hong_shu/keyword' \
-  -H 'Content-Type: application/json' \
-  -d '{"keyword":"彩虹","content_type":"视频","sort_type":"综合","publish_time":"","cursor":""}'
-```
-
-### ④ 响应信封 + 翻页
-- 信封:`{code, msg, data}`,`code=0`,`msg=null`
-- `data` = `{has_more, next_cursor, data:[...]}`
-- 翻页真实取值:`has_more=false`,`next_cursor=null`(本次单页到底)
-
-### ⑤ 字段台账(raw 名)——⚠️ 内容字段嵌在 note_card 下
-> 列表项位于 `data.data[]`,每项结构:`item = {id, model_type, note_card}`。
-> **真正的内容字段都在 `note_card` 嵌套里**,提取必须深入 `note_card`(及其子对象 `note_card.user`、`note_card.interact_info`)。
-
-| 字段名(raw 路径) | 类型 | 含义 | 提取标注 | 真实示例值 |
-|---|---|---|---|---|
-| id | string | 笔记ID(内容ID) | 直接可用 | `696b90ce000000002102be77` |
-| model_type | string | 模型类型 | 直接可用 | `note` |
-| **note_card.type** | string | 笔记类型(图文/视频) | 直接可用(嵌套) | `video` |
-| **note_card.display_title** | string/null | 笔记标题 | 直接可用(嵌套,常null) | `彩虹🌈` / `null` |
-| **note_card.desc** | string | 正文/描述(含话题) | 直接可用 / tag需正则抽(嵌套) | `"夏日雨后彩虹草原 1. 核心主体…"` |
-| **note_card.image_list[].image_url** | string | 图片地址(图文主体) | 直接可用(嵌套) | `https://ci.xiaohongshu.com/1040g00831remfdem…?imageView2/2/w/1080/format/webp` |
-| **note_card.user.user_id** | string | 作者ID | 直接可用(嵌套) | `63ee8949000000000f0102f3` |
-| **note_card.user.nick_name** | string | 作者昵称 | 直接可用(嵌套) | `一方盛景` |
-| **note_card.user.nickname** | string | 作者昵称(同义冗余字段) | 直接可用(嵌套) | `一方盛景` |
-| **note_card.user.avatar** | string | 作者头像 | 直接可用(嵌套) | `https://sns-avatar-qc.xhscdn.com/avatar/…?imageView2/2/w/80/format/jpg` |
-| **note_card.interact_info.liked_count** | int | 点赞数 | 直接可用(嵌套) | `7` / `2363` |
-| **note_card.interact_info.collected_count** | int | 收藏数 | 直接可用(嵌套) | `1` / `380` |
-| **note_card.interact_info.comment_count** | int | 评论数 | 直接可用(嵌套) | `1` / `137` |
-| **note_card.interact_info.shared_count** | int | 分享数 | 直接可用(嵌套) | `0` / `826` |
-| **note_card.publish_timestamp** | long | 发布时间(秒) | 需后处理(嵌套,秒级) | `1768657102` |
-| has_more | bool | 是否有下一页 | 直接可用 | `false` |
-| next_cursor | string/null | 下一页游标 | 直接可用 | `null` |
-
-> 注意:搜索结果**不直接给视频直链**(仅 `image_list` 封面图);取视频/全文需对 `id` 走详情接口(见 §2)二次请求。
-
-### ⑥ 提取与后处理规则
-- **note_card 嵌套抽取(核心规则)**:遍历 `data.data[]`,对每个 `item`:内容ID取 `item.id`,其余全部从 `item.note_card.*` 取——`note_card.type`、`note_card.desc`、`note_card.display_title`、`note_card.image_list`、`note_card.user.user_id`、`note_card.user.nick_name`、`note_card.interact_info.*`、`note_card.publish_timestamp`。不要在顶层找内容字段。
-- **以图文为主**:小红书内容主体是 `image_list`(搜索)/`image_url_list`(详情);即使 `type=video`,搜索阶段也只给封面图,视频直链需走详情。
-- **tag**:从 `note_card.desc` 用正则抽 `#xxx`(如 `#好运彩虹`、`#雨后彩虹🌈`)。
-- 时间戳为**秒级**(`publish_timestamp=1768657102`),与视频号毫秒级不同,落库注意单位。
-
-### ⑦ 边界与失败
-- 搜索本次单次成功,未观察到抖动;限流 ≥15s。
-- 视频直链不在搜索结果,需二次请求详情。
-
-### ⑧ 落点
-- canonical:`platform_content_id`(←id)、`title`(←note_card.display_title,缺则 desc 截断)、`body_text`(←note_card.desc)、`cover_url`(←note_card.image_list[0].image_url)、`platform_author_id`(←note_card.user.user_id)、`author_name`(←note_card.user.nick_name)、`tags`(从 desc 正则)、`like_count`/`collect_count`/`comment_count`/`share_count`(←note_card.interact_info.*)、`publish_time`。
-- 游走边:`search_page_to_content`、`video_to_author`(note_card.user.user_id)、`video_to_hashtag`(desc 抽 tag);作者展开 `author_to_works` 依赖 blogger 接口(当前 blocked)。
-
----
-
-## 2. 笔记详情(PLT_XIAOHONGSHU_DETAIL)
-
-### ① 身份
-- source_id:`PLT_XIAOHONGSHU_DETAIL`
-- 接口名:小红书笔记详情
-- 用途:按内容ID取全文/视频/精确互动数(内容补全 / 二次请求)
-- 状态:**verified** ✅(`code=0` 有数据)。证据:`xhs_detail.json`。
-
-### ② 请求标准
-- method:`POST` · path:`/crawler/xiao_hong_shu/detail`
-
-| 名 | 类型 | 必填 | 示例 |
-|---|---|---|---|
-| content_id | string | 是 | `696b90ce000000002102be77` |
-
-### ③ 请求示例
-```bash
-curl -X POST 'http://crawler.aiddit.com/crawler/xiao_hong_shu/detail' \
-  -H 'Content-Type: application/json' \
-  -d '{"content_id":"696b90ce000000002102be77"}'
-```
-
-### ④ 响应信封 + 翻页
-- `code=0`,`msg=null`,`data = {has_more, next_cursor, data:{...}}`(详情对象在 `data.data`,非嵌 note_card,字段扁平)
-- `has_more=false`,`next_cursor=null`
-
-### ⑤ 字段台账(raw 名)——详情为扁平结构(非 note_card)
-| 字段名(data.data 下) | 类型 | 含义 | 提取标注 | 真实示例值 |
-|---|---|---|---|---|
-| channel_content_id | string | 内容ID | 直接可用 | `696b90ce000000002102be77` |
-| content_link | string | 笔记链接 | 直接可用 | `https://www.xiaohongshu.com/explore/696b90ce000000002102be77` |
-| title | string | 标题 | 直接可用(可空) | `""` |
-| content_type | string | 类型 | 直接可用 | `video` |
-| body_text | string | 全文正文(含话题串) | 直接可用 / tag需正则抽 | `"夏日雨后彩虹草原\n1. 核心主体:…#彩虹的约定[话题]#…"` |
-| location | string | 归属地 | 直接可用 | `陕西` |
-| topic_list | array | 话题列表 | 直接可用 | `["彩虹的约定"]` |
-| image_url_list[].image_url | string | 图片地址 | 直接可用 | `https://ci.xiaohongshu.com/1040g00831remfdem…` |
-| image_url_list[].image_type | int | 图片类型 | 直接可用 | `2` / `1` |
-| video_url_list | array/null | 视频地址列表 | 需后处理下载(本样本null) | `null` |
-| channel_account_id | string | 作者ID | 直接可用 | `63ee8949000000000f0102f3` |
-| channel_account_name | string | 作者昵称 | 直接可用 | `一方盛景` |
-| like_count | int | 点赞数 | 直接可用 | `7` |
-| collect_count | int | 收藏数 | 直接可用 | `1` |
-| comment_count | int | 评论数 | 直接可用 | `1` |
-| share_count | int | 分享数 | 直接可用 | `0` |
-| publish_timestamp | long | 发布时间(ms) | 需后处理(/1000) | `1768657102000` |
-| is_original | bool | 是否原创 | 直接可用 | `false` |
-| is_cache | bool | 是否缓存命中 | 直接可用 | `true` |
-
-> 注意:详情接口的 `publish_timestamp` 为**毫秒**(`1768657102000`),与搜索接口的秒级(`1768657102`)不同——同一笔记两接口单位不一致,落库统一前必须按接口区分换算。
-
-### ⑥ 提取与后处理规则
-- 详情结构扁平,**不嵌 note_card**(与搜索不同),内容字段在 `data.data.*` 直接取。
-- 视频地址在 `video_url_list`(本样本 `null`,图文笔记常无);取到则需后处理下载。
-- 全文 `body_text` 比搜索 `desc` 完整,tag 从 `topic_list`(结构化)或 `body_text` 正则(`#xxx[话题]#`)抽取。
-
-### ⑦ 边界与失败
-- 本次成功;`is_cache=true` 表示命中缓存。限流 ≥15s。
-
-### ⑧ 落点
-- canonical:`platform_content_id`、`title`/`body_text`、`tags`(←topic_list)、`platform_author_id`(←channel_account_id)、`like_count`/`collect_count`/`comment_count`/`share_count`、`publish_time`、`location`。
-- 游走边:`video_to_author`、`video_to_hashtag`。
-
----
-
-## 3. 作者主页 blogger(PLT_XIAOHONGSHU_BLOGGER)
-
-### ① 身份
-- source_id:`PLT_XIAOHONGSHU_BLOGGER`
-- 接口名:小红书博主作品列表
-- 用途:作者 → 作品(author_to_works)
-- 状态:**blocked(请求超时)**。证据:`xhs_blogger.json`(`error: timed out`)。
-
-### ② 请求标准
-- method:`POST` · path:`/crawler/xiao_hong_shu/blogger`
-
-| 名 | 类型 | 必填 | 示例 |
-|---|---|---|---|
-| account_id | string | 是 | `63ee8949000000000f0102f3` |
-| cursor | string | 否 | `""` |
-
-### ③ 请求示例
-```bash
-curl -X POST 'http://crawler.aiddit.com/crawler/xiao_hong_shu/blogger' \
-  -H 'Content-Type: application/json' \
-  -d '{"account_id":"63ee8949000000000f0102f3","cursor":""}'
-```
-
-### ④ 响应信封
-- 无响应,`error: timed out` → **失败(超时)**。
-
-### ⑤ 字段台账
-- 无数据(blocked)。
-
-### ⑥/⑦ 边界与失败
-- blocked:请求超时,未返回。建议加超时重试 + 退避;`account_id` 可由搜索结果 `note_card.user.user_id` 提供。
-
-### ⑧ 落点
-- 期望游走边 `author_to_works`,**当前超时 blocked**。

+ 0 - 279
数据接口与来源/接口台账/快手.md

@@ -1,279 +0,0 @@
-# 快手 接口台账
-
-> 唯一证据来源:`/tmp/aiddit_captures/` 下真实抓包。host = `http://crawler.aiddit.com`。
-> 涉及 capture:`kuaishou_search.json`、`kuaishou_detail.json`、`kuaishou_account_info.json`、`kuaishou_video_dl.json`。
-> 鉴权:所有 `crawler.aiddit.com` 接口实测**无 Authorization 头**;仅需 base_url(env `CONTENTFIND_API_CRAWAPI_BASE_URL`)。
-> 限流约定:两次调用间隔 ≥ 15s。
-> 结构特点:快手三个接口**返回的都是归一化结构**(`channel_content_id` / `channel_account_id` 等),无抖音那种 raw 原生结构差异。
-
----
-
-## 2026-06-17 URL 解包与 OSS v2 批量验证
-
-本节来自 V4 诊断脚本 `tests/test_platform_video_url_candidates.py` 的真实探测。完整原始响应和临时 URL 仅保存在 gitignore 下的 `data/platform_url_probe/20260617_115219/`,本文只记录脱敏统计、字段路径、host 和状态。
-
-### 探测规模
-
-- 真实搜索 query:10 个。
-- 搜索 item 样本:60 条。
-- 抽取 URL 候选:`page=89`、`image=80`、`video_candidate=75`、`audio=1`。
-- 推荐规则选中正片 URL:50 条。
-- 其中 20 条 selected URL 做 OSS v2 批量验证。
-- 历史对照 run:`v1_run_94448252f5cd`。
-
-### 历史 run 对照
-
-- 历史 run 决策:`ADD_TO_CONTENT_POOL=8`、`KEEP_CONTENT_FOR_REVIEW=9`、`REJECT_CONTENT=6`、`TECHNICAL_RETRY_REQUIRED=14`。
-- 媒体状态:`oss_uploaded=29`、`unavailable=8`。
-- 失败侧:8 条 `no_play_url` 没有 `play_url/oss_url`;1 条 media 侧出现 `oss_upload_http_error`,但技术重试直接原因仍是 Gemini timeout。
-- 历史 runtime 不能反查原始字段路径:`platform_raw_payload` 只保留 `channel_content_id/channel_account_id`,没有完整 `video_url_list/image_url_list/content_link/bgm_data`。
-
-### 推荐 URL 解包规则
-
-- 递归抽取 search/detail 中所有 URL,不再把 `video_url_list[0]` 当作唯一事实。
-- 快手优先 detail 中的正片 URL:`$.detail.video_url_list[].video_url`。
-- detail 不存在或不可用时,fallback 到 search 正片 URL:`$.search.video_url_list[].video_url`。
-- 明确排除:`content_link` 页面链接、`image_url_list` 封面/图片、`avatar` 头像、`bgm_data.play_url` 音频、广告/素材字段。
-- 多候选排序看字段路径、HTTP 200/206、`content_type=video/mp4`、Range 支持、host 稳定性和 duration;不默认取第一个。
-
-### 本轮真实字段与 host
-
-- selected 字段路径:`$.detail.video_url_list[0].video_url=25`,`$.search.video_url_list[0].video_url=25`。
-- selected host 分布:`tymov2.a.kwimgs.com=17`、`hwmov.a.yximgs.com=7`、`v23-3.kwaicdn.com=5`、`v2.oskwai.com=6`、`ucmov.a.kwimgs.com=5`、`dxsc.oppass.cn=4`、`v4.kwaicdn.com=3`、`cc1.xyydnode.com=2`、`v1.oskwai.com=1`。
-- 所有 selected 样本轻量验证均为可下载视频:HTTP `200/206`,`content_type=video/mp4`,Range 返回字节范围或完整小文件。
-
-### OSS v2 验证结论
-
-- 使用生产当前 payload 形态(把 `referer` 作为 dict 字段传给 OSS v2)时:20/20 返回 HTTP 200 但 `oss_object.cdn_url` 缺失,分类为 `oss_upload_response_invalid`。
-- 抽样复打发现 OSS body 为 `status=10000`、`oss_object=null`,错误原因包含 `dict object has no attribute encode`。
-- 使用同一批 selected URL、去掉 `referer` 字段后做 no-referer 复验:20/20 成功返回 `oss_object.cdn_url`。
-- 结论:本轮 selected URL 规则能选出可被 OSS v2 归档的正片 URL;历史/生产里的 invalid 更像 OSS v2 payload 兼容问题,不是 URL 字段拆错。
-
-### 生产改造建议
-
-- 第一优先级:调整 OSS v2 调用 payload,不再把 `referer` dict 直接传给 v2;如 OSS 侧确需 headers,应与上传服务约定字符串/headers 字段格式后再传。
-- 第二优先级:生产 runtime 里补充 URL 候选摘要:`selected_path`、`selected_host`、`probe_status`、`content_type`、`content_range`、`oss_payload_mode`、`oss_failure_type`。
-- 第三优先级:后续再改平台 adapter 的 `play_url` 抽取逻辑,改为 detail 优先、候选分类过滤、search fallback。
-
----
-
-## 一、关键词搜索 PLT_KUAISHOU_KEYWORD
-
-### ① 身份
-- source_id:`PLT_KUAISHOU_KEYWORD`
-- 接口名:快手关键词搜索 / kuai_shou keyword_v2
-- 用途:按关键词拉一页内容卡片(视频)。
-- 状态:**verified**(capture `kuaishou_search.json`,`code=0`)
-
-### ② 请求标准
-- method:`POST`
-- path:`/crawler/kuai_shou/keyword_v2`
-- 鉴权:无
-- 参数表:
-
-| 名 | 类型 | 必填 | 示例 |
-|---|---|---|---|
-| keyword | string | 是 | `彩虹` |
-
-> 注:实测请求体只含 `keyword`(无 cursor / sort_type 字段)。
-
-### ③ 请求示例
-```bash
-curl -X POST http://crawler.aiddit.com/crawler/kuai_shou/keyword_v2 \
-  -H 'Content-Type: application/json' \
-  -d '{"keyword":"彩虹"}'
-```
-
-### ④ 响应信封 + 翻页
-- 信封:`{ "code":0, "msg":null, "data":{ "has_more":false, "next_cursor":null, "data":[...] } }`
-- `has_more`:`false`(本次实测单页返回,无翻页游标)
-- `next_cursor`:`null`
-- `data.data`:数组,本页 `9` 条卡片(归一化结构)。
-
-### ⑤ 字段台账(单条卡片 = `data.data[i]`,归一化名)
-
-| 字段名 | 类型 | 含义 | 提取标注 | 真实示例值 |
-|---|---|---|---|---|
-| `channel_content_id` | string | 内容 id | 直接可用 | `3xepqgwddgbickc` |
-| `content_link` | string | 内容链接 | 直接可用 | `https://www.gifshow.com/fw/photo/3xepqgwddgbickc` |
-| `title` | string | 标题(实测可能 null) | 直接可用 | `null` |
-| `body_text` | string | 正文(实测可能 null) | 直接可用 | `null` |
-| `topic_list` | string[] | 话题 tag(实测 null) | 直接可用 | `null` |
-| `content_type` | string | 内容类型 | 直接可用 | `video` |
-| `video_url_list[].video_url` | string | 视频地址 | 需后处理下载 | `http://v23-3.kwaicdn.com/upic/2025/05/11/07/...b.mp4?pkey=...&tag=...&clientCacheKey=3xepqgwddgbickc_b.mp4...` |
-| `video_url_list[].video_duration` | int(秒) | 时长 | 直接可用 | `4` |
-| `image_url_list[].image_url` | string | 封面图 | 直接可用 | `http://p66-plat.wsukwai.com/upic/...540p_*.kvif?...` |
-| `channel_account_id` | string | 作者 id | 直接可用 | `3xfkwajatdh7p7i` |
-| `channel_account_name` | string | 作者昵称 | 直接可用 | `夏若.` |
-| `view_count` | int | 播放/观看数 | 直接可用 | `18472` |
-| `like_count` | int | 点赞 | 直接可用 | `75` |
-| `collect_count` | int | 收藏 | 直接可用 | `4` |
-| `comment_count` | int | 评论 | 直接可用 | `0` |
-| `share_count` | int | 分享 | 直接可用 | `0` |
-| `publish_timestamp` | int(ms) | 发布时间戳(毫秒) | 直接可用 | `1746919311784` |
-| `data.has_more` | bool | 是否有下一页 | 直接可用 | `false` |
-| `data.next_cursor` | string | 下一页游标 | 直接可用 | `null` |
-
-> 搜索结果只带作者 `channel_account_id` + `channel_account_name`,**不含作者粉丝数/签名等档案**(这点与抖音不同)。要拿作者画像需二次请求 account_info(见三)。
-
-### ⑥ 提取与后处理规则
-- tag:`topic_list` 实测为 null,快手搜索阶段常无结构化 tag;若 `body_text` 有内容可正则 `#([^\s#]+)` 抽取,否则 tag 为空。
-- 时间:`publish_timestamp` 已是毫秒,直接用。
-- 视频可下载性结论(capture `kuaishou_video_dl.json`):`http_status=206`、`content_type=video/mp4`、`content_range=bytes 0-546454/546455`、`bytes_received=546455`、`looks_like_video=true` → **可下载**。
-- **下载头**:快手 CDN(`*.kwaicdn.com`)URL 自带 `pkey` / `tag` 签名,实测带 `Range: bytes=0-` 即可 206;建议补 `Referer: https://www.kuaishou.com/`。相比抖音对 UA 不敏感,但保留通用 UA 更稳。
-
-### ⑦ 边界与失败
-- 限流 ≥ 15s。
-- 空值约定:`title`/`body_text`/`topic_list` 常为 null,提取层需容忍。
-- 本次实测 `has_more=false`,单页即止;若后续版本返回 next_cursor 再实现翻页。
-
-### ⑧ 落点
-| 归一字段 | canonical 名 | 游走边 |
-|---|---|---|
-| `channel_content_id` | `platform_content_id` | search_page_to_content |
-| `channel_account_id` | `platform_author_id` | video_to_author |
-| `channel_account_name` | `author_display_name` | video_to_author |
-| `topic_list` | `tags` | video_to_hashtag |
-| `like_count`/`comment_count`/`share_count`/`collect_count` | `statistics.*` | — |
-| `has_more`/`next_cursor` | `has_more`/`next_cursor` | search_page_to_content |
-
----
-
-## 二、内容详情 PLT_KUAISHOU_DETAIL
-
-### ① 身份
-- source_id:`PLT_KUAISHOU_DETAIL`
-- 接口名:快手内容详情 / kuai_shou detail
-- 用途:按 content_id 拉单条归一化详情(视频、互动数、BGM、作者)。
-- 状态:**verified**(capture `kuaishou_detail.json`,`code=0`)
-
-### ② 请求标准
-- method:`POST`
-- path:`/crawler/kuai_shou/detail`
-- 鉴权:无
-- 参数表:
-
-| 名 | 类型 | 必填 | 示例 |
-|---|---|---|---|
-| content_id | string | 是 | `3xepqgwddgbickc` |
-
-### ③ 请求示例
-```bash
-curl -X POST http://crawler.aiddit.com/crawler/kuai_shou/detail \
-  -H 'Content-Type: application/json' \
-  -d '{"content_id":"3xepqgwddgbickc"}'
-```
-
-### ④ 响应信封 + 翻页
-- 信封:`{ "code":0, "msg":null, "data":{ "has_more":false, "next_cursor":null, "data":{...} } }`
-- 详情无翻页:`has_more=false`、`next_cursor=null`。
-
-### ⑤ 字段台账(`data.data`,归一化名)
-
-| 字段名 | 类型 | 含义 | 提取标注 | 真实示例值 |
-|---|---|---|---|---|
-| `channel_content_id` | string | 内容 id | 直接可用 | `3xepqgwddgbickc` |
-| `content_link` | string | 内容链接 | 直接可用 | `https://www.gifshow.com/fw/photo/3xepqgwddgbickc` |
-| `content_type` | string | 内容类型 | 直接可用 | `video` |
-| `title` / `body_text` / `topic_list` | string / string[] | 标题/正文/话题(实测均 null) | 直接可用 | `null` |
-| `video_url_list[].video_url` | string | 视频地址 | 需后处理下载 | `http://v4.kwaicdn.com/ksc1/...b.mp4?pkey=...&clientCacheKey=3xepqgwddgbickc_b.mp4&tt=b...` |
-| `video_url_list[].video_duration` | int(秒) | 时长 | 直接可用 | `4` |
-| `image_url_list[].image_url` | string | 封面图 | 直接可用 | `http://hw2.a.kwimgs.com/upic/...540p_*.kvif?...` |
-| `channel_account_id` | string | 作者 id | 直接可用 | `3xfkwajatdh7p7i` |
-| `channel_account_name` | string | 作者昵称 | 直接可用 | `夏若.` |
-| `avatar` | string | 作者头像 | 直接可用 | `http://p4.a.yximgs.com/kimg/uhead/...:200x200.heif` |
-| `view_count` | int | 观看数 | 直接可用 | `18472` |
-| `like_count` | int | 点赞 | 直接可用 | `75` |
-| `collect_count` | int | 收藏 | 直接可用 | `4` |
-| `comment_count` | int | 评论 | 直接可用 | `0` |
-| `share_count` | int | 分享 | 直接可用 | `7` |
-| `bgm_data.title` / `bgm_data.author` / `bgm_data.play_url` | string | BGM | 直接可用 | `没去学校(DJ版)` / `kiss` / `https://v4-plat-fdl.wsukwai.com/ksc2/*.m4a?...` |
-| `publish_timestamp` | int(ms) | 发布时间戳 | 直接可用 | `1746919311784` |
-
-> 注:同一条内容 `share_count` 在 detail=`7`、在 search=`0`,以 detail 为准(search 该字段可能滞后)。
-
-### ⑥ 提取与后处理规则
-- tag:`topic_list` 实测 null;按需从 `body_text` 正则抽取。
-- 视频地址:`video_url_list[].video_url` 为 kwaicdn 直链,带签名参数,直接下载。
-- 时间:`publish_timestamp` 毫秒,直接用。
-- 视频可下载性:同搜索结论(`kuaishou_video_dl.json`:206 / video/mp4 / looks_like_video=true)。**下载带 `Range: bytes=0-` + `Referer: https://www.kuaishou.com/`**。
-
-### ⑦ 边界与失败
-- 限流 ≥ 15s。
-- 空值:`title`/`body_text`/`topic_list` 常 null。
-
-### ⑧ 落点
-| 归一字段 | canonical 名 | 游走边 |
-|---|---|---|
-| `channel_content_id` | `platform_content_id` | — |
-| `channel_account_id` | `platform_author_id` | video_to_author |
-| `channel_account_name` | `author_display_name` | video_to_author |
-| `topic_list` | `tags` | video_to_hashtag |
-| `like_count`/`comment_count`/`share_count`/`collect_count` | `statistics.*` | — |
-
----
-
-## 三、账号信息 PLT_KUAISHOU_ACCOUNT_INFO
-
-### ① 身份
-- source_id:`PLT_KUAISHOU_ACCOUNT_INFO`
-- 接口名:快手账号信息 / kuai_shou account_info
-- 用途:按作者 id 拉作者档案(昵称、粉丝数、签名、标签、性别),补齐搜索/详情缺失的作者画像。
-- 状态:**verified**(capture `kuaishou_account_info.json`,`code=0`)
-
-### ② 请求标准
-- method:`POST`
-- path:`/crawler/kuai_shou/account_info`
-- 鉴权:无
-- 参数表:
-
-| 名 | 类型 | 必填 | 示例 |
-|---|---|---|---|
-| account_id | string | 是 | `3xfkwajatdh7p7i` |
-| is_cache | bool | 否 | `true` |
-
-### ③ 请求示例
-```bash
-curl -X POST http://crawler.aiddit.com/crawler/kuai_shou/account_info \
-  -H 'Content-Type: application/json' \
-  -d '{"account_id":"3xfkwajatdh7p7i","is_cache":true}'
-```
-
-### ④ 响应信封 + 翻页
-- 信封:`{ "code":0, "msg":null, "data":{ "has_more":false, "next_cursor":null, "data":{...} } }`
-- 无翻页:`has_more=false`、`next_cursor=null`。
-
-### ⑤ 字段台账(`data.data`)
-
-| 字段名 | 类型 | 含义 | 提取标注 | 真实示例值 |
-|---|---|---|---|---|
-| `channel_account_id` | string | 作者 id | 直接可用 | `3xfkwajatdh7p7i` |
-| `ks_id` / `digit_id` | string | 快手号 | 直接可用 | `1514225378` |
-| `account_link` | string | 主页链接 | 直接可用 | `https://www.kuaishou.com/profile/3xfkwajatdh7p7i` |
-| `account_name` | string | 作者昵称 | 直接可用 | `夏若.` |
-| `avatar_url` | string | 头像 | 直接可用 | `http://p4.a.yximgs.com/kimg/uhead/...:750x750.heif` |
-| `gender` | string | 性别 | 直接可用 | `女` |
-| `description` | string | 签名 | 直接可用 | `可以帮我破1000吗\n谢谢🙏...` |
-| `tags` | string[] | 账号标签(星座/地区) | 直接可用 | `["摩羯座","贵州 安顺市"]` |
-| `follower_count` | int | 粉丝数 | 直接可用 | `511` |
-| `publish_count` | int | 作品数 | 直接可用 | `184` |
-| `like_count` | int | 总获赞 | 直接可用 | `1598` |
-| `update_timestamp` | int(ms) | 抓取时间 | 直接可用 | `1781158922013` |
-
-### ⑥ 提取与后处理规则
-- 作者档案:把 `account_name`/`description`/`tags`/`follower_count` 合并进作者节点。
-- tag:`tags` 已结构化(星座+地区),直接用作账号级标签。
-- 空值:`ip_location`/`collect_count`/`create_timestamp` 实测 null。
-
-### ⑦ 边界与失败
-- 限流 ≥ 15s。
-- `account_id` 用 `channel_account_id`(如 `3xfkwajatdh7p7i`),非数字快手号。
-- 快手无独立的"作者作品列表"接口 capture;"作者→作品"游走在快手侧暂依赖搜索结果聚合(本批未抓到 kuaishou blogger 接口)。
-
-### ⑧ 落点
-| 字段 | canonical 名 | 游走边 |
-|---|---|---|
-| `channel_account_id` | `platform_author_id` | video_to_author |
-| `account_name` | `author_display_name` | video_to_author |
-| `tags` | `tags`(账号级) | — |
-| `follower_count`/`like_count` | `statistics.*`(账号聚合) | — |

+ 0 - 344
数据接口与来源/接口台账/抖音.md

@@ -1,344 +0,0 @@
-# 抖音 接口台账
-
-> ⚠️ 2026-06-29 更新:本项目(acquisition)抖音**已改走 piaoquantv 后端**,绕开下方 aiddit host 的强限流(`code:10000`)。
-> - host:`http://crawapi.piaoquantv.com`(keyword + detail 同 host)
-> - body 必带:`account_id=7450041106378522636`、`cookie_batch=default`(缺 account_id 报 `code:22001 强制登录`)
-> - 返回结构与 aiddit 一致(`data.data[].aweme_id`;detail 的 `video_url_list[].video_url`),解析器无需改。
-> - env:`CK_DOUYIN_BASE_URL` / `CK_DOUYIN_ACCOUNT_ID` / `CK_DOUYIN_COOKIE_BATCH`(见 .env)。
-> - 下方 aiddit 抓包记录保留作历史参考。
->
-> 唯一证据来源:`/tmp/aiddit_captures/` 下真实抓包。host = `http://crawler.aiddit.com`。
-> 涉及 capture:`douyin_search.json`、`douyin_detail.json`、`douyin_blogger.json`、`douyin_profile_author.json`、`douyin_profile_content.json`、`douyin_video_dl.json`。
-> 鉴权:所有 `crawler.aiddit.com` 接口实测**无 Authorization 头**;仅需 base_url(env `CONTENTFIND_API_CRAWAPI_BASE_URL`)。
-> 限流约定:两次调用间隔 ≥ 15s。
-
----
-
-## 一、关键词搜索 PLT_DOUYIN_KEYWORD
-
-### ① 身份
-- source_id:`PLT_DOUYIN_KEYWORD`
-- 接口名:抖音关键词搜索 / dou_yin keyword
-- 用途:按关键词拉一页内容卡片(视频),用于搜索召回与种子发现。
-- 状态:**verified**(capture `douyin_search.json`,`code=0`)
-
-### ② 请求标准
-- method:`POST`
-- path:`/crawler/dou_yin/keyword`
-- 鉴权:无 Authorization
-- 参数表:
-
-| 名 | 类型 | 必填 | 示例 |
-|---|---|---|---|
-| keyword | string | 是 | `彩虹` |
-| content_type | string | 是 | `视频` |
-| cursor | string | 是(首页传空串) | `""` |
-| sort_type | string | 是 | `最多点赞` |
-
-### ③ 请求示例
-```bash
-curl -X POST http://crawler.aiddit.com/crawler/dou_yin/keyword \
-  -H 'Content-Type: application/json' \
-  -d '{"keyword":"彩虹","content_type":"视频","cursor":"","sort_type":"最多点赞"}'
-```
-
-### ④ 响应信封 + 翻页
-- 信封:`{ "code":0, "msg":null, "data":{ ... } }`
-- `data.has_more`:`true`(还有下一页)
-- `data.next_cursor`:`"12"`(下一页把它原样填回请求 `cursor`)
-- `data.data`:数组,本页 `11` 条卡片(注意:**单条卡片是抖音原生 aweme 结构,字段名为 raw 名**,与 detail 接口的归一化结构不同)。
-
-### ⑤ 字段台账(单条卡片 = `data.data[i]`,raw 名)
-
-| 字段名 | 类型 | 含义 | 提取标注 | 真实示例值 |
-|---|---|---|---|---|
-| `aweme_id` | string | 内容 id | 直接可用 | `7522164415848893735` |
-| `desc` / `caption` | string | 标题/正文(含话题) | 直接可用 | `原来彩虹真的是圆形的,一定变得超级幸运!#治愈系风景 #彩虹 #旅行` |
-| `text_extra[].hashtag_name` | string[] | 话题 tag | 直接可用(或从 desc `#xxx` 正则抽) | `治愈系风景` / `彩虹` / `旅行` |
-| `cha_list[].cha_name` | string[] | 挑战/话题名(同 tag) | 直接可用 | `治愈系风景` |
-| `video.play_addr.url_list` | string[] | 播放地址(无水印,多 CDN 备选) | 需后处理下载 | `https://v11-wha.douyinvod.com/.../mime_type=video_mp4...`(首选)|
-| `video.download_addr.url_list` | string[] | 带水印下载地址 | 需后处理下载 | `https://v9-chc.douyinvod.com/.../main.mp4?...logo_type=aweme_search_suffix` |
-| `video.duration` | int(ms) | 时长(毫秒) | 直接可用 | `13282` |
-| `author.uid` | string | 作者数字 id | 直接可用 | `96721168101` |
-| `author.sec_uid` | string | 作者加密 id(**翻主页/粉丝画像必须用它**) | 直接可用 | `MS4wLjABAAAAZrIhB0ThEPqj37arlq_4Z6l3jW-E3koi0rGjtSb9jtA` |
-| `author.short_id` | string | 作者短 id | 直接可用 | `778897743` |
-| `author.unique_id` | string | 抖音号 | 直接可用 | `Ayuan3230` |
-| `author.nickname` | string | 作者昵称 | 直接可用 | `源Dream` |
-| `author.signature` | string | 作者签名 | 直接可用 | `川西 组团 旅拍μ:Ayuan3230 …` |
-| `author.follower_count` | int | 作者粉丝数 | 直接可用 | `166947` |
-| `author.gender` | int | 作者性别(0=未知) | 直接可用 | `0` |
-| `statistics.digg_count` | int | 点赞 | 直接可用 | `5034215` |
-| `statistics.comment_count` | int | 评论 | 直接可用 | `121615` |
-| `statistics.share_count` | int | 分享 | 直接可用 | `2679616` |
-| `statistics.collect_count` | int | 收藏 | 直接可用 | `232358` |
-| `create_time` | int(秒) | 发布时间戳 | 需后处理(×1000) | `1751515440` |
-| `share_url` | string | 分享短链 | 直接可用 | `https://www.iesdouyin.com/share/video/7522164415848893735/?...` |
-| `data.has_more` | bool | 是否有下一页 | 直接可用 | `true` |
-| `data.next_cursor` | string | 下一页游标 | 直接可用 | `"12"` |
-
-> **重点:搜索阶段即得完整作者档案** —— `author` 对象内已包含 `sec_uid` / 粉丝数 / 签名 / 抖音号,无需为拿基础作者信息再发请求。后续翻主页(blogger)、拉粉丝画像(account_fans_portrait)都直接复用 `author.sec_uid`。
-
-### ⑥ 提取与后处理规则
-- tag:优先用 `text_extra[].hashtag_name`(结构化);若缺失则对 `desc` 用正则 `#([^\s#]+)` 抽取。
-- 时间:`create_time` 为秒级,统一 ×1000 转毫秒。
-- 视频地址:`video.play_addr.url_list` 为无水印多 CDN,取第一个可用;`download_addr` 为带水印(URL 含 `logo_type=aweme_search_suffix`)。
-- 视频可下载性结论(capture `douyin_video_dl.json`,对应本卡片同一 play_addr):`http_status=206`、`content_type=video/mp4`、`content_range=bytes 0-1048575/2399605`、`bytes_received=1048576`(1MB 分片)、`looks_like_video=true` → **可下载**。
-- **下载必带头**:抖音视频 CDN 要带 **iOS UA + Referer** 才能 206,否则易 403:
-  - `User-Agent`:iOS 客户端 UA(如 `com.ss.iphone.ugc.Aweme/...` 或移动 Safari UA)
-  - `Referer: https://www.douyin.com/`
-  - 用 `Range: bytes=0-` 触发 206 分片。
-
-### ⑦ 边界与失败
-- 限流:两次调用间隔 ≥ 15s。
-- 空值约定:`video.download_addr` 可能缺失,回退 `play_addr`;`create_time` 缺失记为 null。
-- 翻页终止:`has_more=false` 或 `next_cursor=null` 时停止。
-
-### ⑧ 落点(canonical 名 + 游走边)
-| raw 字段 | canonical 名 | 游走边 |
-|---|---|---|
-| `aweme_id` | `platform_content_id` | search_page_to_content |
-| `author.sec_uid` | `platform_author_id` | video_to_author |
-| `author.nickname` | `author_display_name` | video_to_author |
-| `text_extra[].hashtag_name` | `tags` | video_to_hashtag |
-| `statistics.*` | `statistics.like/comment/share/collect` | — |
-| `has_more` / `next_cursor` | `has_more` / `next_cursor` | search_page_to_content |
-
----
-
-## 二、内容详情 PLT_DOUYIN_DETAIL
-
-### ① 身份
-- source_id:`PLT_DOUYIN_DETAIL`
-- 接口名:抖音内容详情 / dou_yin detail
-- 用途:按 content_id 拉单条归一化详情(标题、多码率视频、互动数、BGM)。
-- 状态:**verified**(capture `douyin_detail.json`,`code=0`)
-
-### ② 请求标准
-- method:`POST`
-- path:`/crawler/dou_yin/detail`
-- 鉴权:无
-- 参数表:
-
-| 名 | 类型 | 必填 | 示例 |
-|---|---|---|---|
-| content_id | string | 是 | `7522164415848893735` |
-
-### ③ 请求示例
-```bash
-curl -X POST http://crawler.aiddit.com/crawler/dou_yin/detail \
-  -H 'Content-Type: application/json' \
-  -d '{"content_id":"7522164415848893735"}'
-```
-
-### ④ 响应信封 + 翻页
-- 信封:`{ "code":0, "msg":null, "data":{ "has_more":false, "next_cursor":null, "data":{...} } }`
-- 详情无翻页:`has_more=false`、`next_cursor=null`。
-- 内层 `data.data` 为**归一化结构**(字段名与搜索的 raw 结构不同)。
-
-### ⑤ 字段台账(`data.data`,归一化名)
-
-| 字段名 | 类型 | 含义 | 提取标注 | 真实示例值 |
-|---|---|---|---|---|
-| `channel_content_id` | string | 内容 id | 直接可用 | `7522164415848893735` |
-| `content_link` | string | 内容链接 | 直接可用 | `https://www.douyin.com/video/7522164415848893735` |
-| `title` | string | 标题 | 直接可用 | `原来彩虹真的是圆形的,一定变得超级幸运!...` |
-| `body_text` | string | 正文(含话题) | 直接可用 | `原来彩虹真的是圆形的,一定变得超级幸运!#治愈系风景 #彩虹 #旅行` |
-| `topic_list` | string[] | 话题 tag | 直接可用 | `["治愈系风景","彩虹","旅行"]` |
-| `content_type` | string | 内容类型 | 直接可用 | `video` |
-| `video_url_list[].video_url` | string | 视频地址(默认码率) | 需后处理下载 | `https://www.douyin.com/aweme/v1/play/?video_id=v0d00fg10000d1i1etvog65uanqirggg&ratio=1080p&line=0` |
-| `multi_bitrate.{540p,720p,1080p}.video_url` | string | 多码率视频地址 | 需后处理下载 | `.../ratio=540p&line=0` 等 |
-| `video_url_list[].video_duration` | int(秒) | 时长 | 直接可用 | `13` |
-| `image_url_list[].image_url` | string | 封面图 | 直接可用 | `https://p3-pc-sign.douyinpic.com/...tplv-dy-360p.jpeg?...` |
-| `channel_account_id` | string | 作者 id(= sec_uid) | 直接可用 | `MS4wLjABAAAAZrIhB0ThEPqj37arlq_4Z6l3jW-E3koi0rGjtSb9jtA` |
-| `channel_account_name` | string | 作者昵称 | 直接可用 | `源Dream` |
-| `like_count` | int | 点赞 | 直接可用 | `5034215` |
-| `comment_count` | int | 评论 | 直接可用 | `121615` |
-| `share_count` | int | 分享 | 直接可用 | `2679616` |
-| `collect_count` | int | 收藏 | 直接可用 | `232359` |
-| `bgm_data.title` / `bgm_data.author` / `bgm_data.play_url` | string | BGM 信息 | 直接可用 | `@源Dream创作的原声` / `源Dream` / `https://sf6-cdn-tos.douyinstatic.com/.../*.mp3` |
-| `publish_timestamp` | int(ms) | 发布时间戳(毫秒) | 直接可用 | `1751515440000` |
-| `is_original` | bool | 是否原创 | 直接可用 | `false` |
-
-### ⑥ 提取与后处理规则
-- tag:直接取 `topic_list`(已结构化)。
-- 视频地址:优先 `multi_bitrate.1080p.video_url`;`video_url_list[].video_url` 是 `douyin.com/aweme/v1/play` 跳转地址,实际下载会 302 到 `douyinvod.com` CDN。
-- 时间:`publish_timestamp` 已是毫秒,直接用。
-- 视频可下载性:同搜索接口结论(`douyin_video_dl.json`:206 / video/mp4 / looks_like_video=true)。**下载带 iOS UA + `Referer: https://www.douyin.com/` + `Range: bytes=0-`**。
-
-### ⑦ 边界与失败
-- 限流 ≥ 15s。
-- 归一化结构与搜索 raw 结构字段名不同,提取层需分别映射。
-- 空值:`view_count` / `play_count` 实测为 null,不可作为指标。
-
-### ⑧ 落点
-| raw/归一字段 | canonical 名 | 游走边 |
-|---|---|---|
-| `channel_content_id` | `platform_content_id` | — |
-| `channel_account_id` | `platform_author_id` | video_to_author |
-| `channel_account_name` | `author_display_name` | video_to_author |
-| `topic_list` | `tags` | video_to_hashtag |
-| `like_count`/`comment_count`/`share_count`/`collect_count` | `statistics.*` | — |
-
----
-
-## 三、博主作品列表 PLT_DOUYIN_BLOGGER
-
-### ① 身份
-- source_id:`PLT_DOUYIN_BLOGGER`
-- 接口名:抖音博主主页作品 / dou_yin blogger
-- 用途:按作者 `sec_uid` 翻主页作品,实现"作者→作品"游走。
-- 状态:**verified**(capture `douyin_blogger.json`,`code=0`)
-
-### ② 请求标准
-- method:`POST`
-- path:`/crawler/dou_yin/blogger`
-- 鉴权:无
-- 参数表:
-
-| 名 | 类型 | 必填 | 示例 |
-|---|---|---|---|
-| account_id | string | 是(传 sec_uid) | `MS4wLjABAAAAZrIhB0ThEPqj37arlq_4Z6l3jW-E3koi0rGjtSb9jtA` |
-| cursor | string | 是(首页空串) | `""` |
-
-### ③ 请求示例
-```bash
-curl -X POST http://crawler.aiddit.com/crawler/dou_yin/blogger \
-  -H 'Content-Type: application/json' \
-  -d '{"account_id":"MS4wLjABAAAAZrIhB0ThEPqj37arlq_4Z6l3jW-E3koi0rGjtSb9jtA","cursor":""}'
-```
-
-### ④ 响应信封 + 翻页
-- 信封:`{ "code":0, "data":{ "has_more":..., "next_cursor":..., "data":[...] } }`
-- `has_more`:`true`
-- `next_cursor`:`"1776248100000"`(毫秒时间戳形式的游标,原样回填 `cursor`)
-- `data.data`:数组,本页 `22` 条作品。**单条结构与搜索卡片相同(raw aweme 结构:`aweme_id`/`author`/`statistics`/`video`/`text_extra`)**。
-
-### ⑤ 字段台账
-> 单条作品字段与"一、关键词搜索 ⑤"完全一致(raw 名:`aweme_id`、`desc`、`text_extra[].hashtag_name`、`video.play_addr.url_list`、`author.sec_uid`、`statistics.digg_count` 等),不再重复;翻页字段见下。
-
-| 字段名 | 类型 | 含义 | 提取标注 | 真实示例值 |
-|---|---|---|---|---|
-| `data.has_more` | bool | 是否有下一页 | 直接可用 | `true` |
-| `data.next_cursor` | string | 下一页游标(回填 cursor) | 直接可用 | `"1776248100000"` |
-| `data.data[].aweme_id` | string | 作品 id | 直接可用 | 同搜索结构 |
-
-### ⑥ 提取与后处理规则
-- 翻页:`next_cursor` 原样填回 `cursor`,直到 `has_more=false`。
-- 去重:作者作品需与已发现内容(搜索结果)按 `aweme_id` 去重(参考 commit `1cd1b94` 教训)。
-- 视频下载:同搜索/详情结论(206/mp4/可下载,带 iOS UA + Referer)。
-
-### ⑦ 边界与失败
-- 限流 ≥ 15s。
-- `account_id` 必须传 `sec_uid`(`MS4...`),传数字 uid 取不到。
-- 空页:`has_more=false` 停止。
-
-### ⑧ 落点
-| raw 字段 | canonical 名 | 游走边 |
-|---|---|---|
-| `account_id`(入参 sec_uid) | `platform_author_id` | author_to_works |
-| `data.data[].aweme_id` | `platform_content_id` | author_to_works |
-| `text_extra[].hashtag_name` | `tags` | video_to_hashtag |
-| `has_more`/`next_cursor` | `has_more`/`next_cursor` | author_to_works |
-
----
-
-## 四、账号粉丝画像 PLT_DOUYIN_AUTHOR_PROFILE
-
-### ① 身份
-- source_id:`PLT_DOUYIN_AUTHOR_PROFILE`
-- 接口名:抖音账号粉丝画像 / dou_yin re_dian_bao account_fans_portrait
-- 用途:拉作者账号的人群画像(性别/年龄/省份/城市等级/手机品牌等 50+ 信号)+ 账号自身指标 + 作品分位。
-- 状态:**verified**(capture `douyin_profile_author.json`,`code=0`,内含 6 类人群分布信号)
-
-### ② 请求标准
-- method:`POST`
-- path:`/crawler/dou_yin/re_dian_bao/account_fans_portrait`
-- 鉴权:无
-- 参数表:
-
-| 名 | 类型 | 必填 | 示例 |
-|---|---|---|---|
-| account_id | string | 是(传 sec_uid) | `MS4wLjABAAAAZrIhB0ThEPqj37arlq_4Z6l3jW-E3koi0rGjtSb9jtA` |
-| need_age | bool | 是 | `true` |
-| need_gender | bool | 是 | `true` |
-| need_province | bool | 是 | `true` |
-
-### ③ 请求示例
-```bash
-curl -X POST http://crawler.aiddit.com/crawler/dou_yin/re_dian_bao/account_fans_portrait \
-  -H 'Content-Type: application/json' \
-  -d '{"account_id":"MS4wLjABAAAAZrIhB0ThEPqj37arlq_4Z6l3jW-E3koi0rGjtSb9jtA","need_age":true,"need_gender":true,"need_province":true}'
-```
-
-### ④ 响应信封 + 翻页
-- 信封:`{ "code":0, "msg":null, "data":{ "has_more":false, "next_cursor":null, "data":{...} } }`
-- 无翻页:`has_more=false`、`next_cursor=null`。
-- 内层 `data.data` 三块:`account`(账号信息)、`fans`(粉丝画像 6 维)、`posts`(作品指标分位)。
-
-### ⑤ 字段台账(`data.data`)
-
-| 字段名 | 类型 | 含义 | 提取标注 | 真实示例值 |
-|---|---|---|---|---|
-| `account.user_id` | string | 账号 id(=sec_uid) | 直接可用 | `MS4wLjABAAAAZrIhB0ThEPqj37arlq_4Z6l3jW-E3koi0rGjtSb9jtA` |
-| `account.nickname` | string | 昵称 | 直接可用 | `源Dream` |
-| `account.uniqueId` | string | 抖音号 | 直接可用 | `Ayuan3230` |
-| `account.follower_count` | int | 粉丝数 | 直接可用 | `166947` |
-| `account.aweme_count` | int | 作品数 | 直接可用 | `1101` |
-| `account.total_favorited` | int | 总获赞 | 直接可用 | `17754802` |
-| `account.first_tag_name` / `second_tag_name` | string | 一/二级行业标签 | 直接可用 | `旅行` / `旅行记录与分享` |
-| `account.fans_milestone.first_10w_fans_date` | string | 涨粉里程碑 | 直接可用 | `20250702` |
-| `fans.gender.data.{male,female}.percentage` | string | 粉丝性别占比 | 需后处理(去 `%`) | male `71.30%` / female `28.70%` |
-| `fans.age.data.{区间}.percentage` | string | 粉丝年龄分布 | 需后处理 | `24-30` → `26.88%` |
-| `fans.province.data.{省}.percentage` | string | 粉丝省份分布(34 项) | 需后处理 | `广东` → `12.62%` |
-| `fans.city_level.data.{级}.percentage` | string | 城市等级分布 | 需后处理 | `新一线` → `22.99%` |
-| `fans.phone_brand.data.{品牌}.percentage` | string | 手机品牌分布 | 需后处理 | `苹果` → `39.62%` |
-| `fans.*.data.{key}.preference` | string | 该项 TGI 偏好值 | 需后处理(float) | `苹果` preference `129.22` |
-| `posts.avg_like_count` | float | 平均点赞 | 直接可用 | `437.6` |
-| `posts.avg_comment_count` | float | 平均评论 | 直接可用 | `8.5667` |
-| `posts.percentile_like_count` | int | 点赞分位 | 直接可用 | `470` |
-| `posts.percentile_follower_count` | int | 粉丝分位 | 直接可用 | `13` |
-
-> 6 维粉丝信号:`gender`、`age`、`province`、`city`、`city_level`、`phone_brand`(均为 `{name, data:{key:{percentage, preference}}}` 结构,展开即 50+ 信号点)。
-
-### ⑥ 提取与后处理规则
-- 百分比:`percentage` 是带 `%` 的字符串,需 strip `%` 转 float。
-- `preference` 为 TGI(>100 表示该人群偏好高于大盘),float 解析。
-- 入参 `account_id` 必须 sec_uid。
-
-### ⑦ 边界与失败
-- 限流 ≥ 15s。
-- 注意区分:**本接口(account_fans_portrait)成功**;**内容画像 video_like_portrait 实测失败(见下)**。
-
-### ⑧ 落点
-| 字段 | canonical 名 | 游走边 |
-|---|---|---|
-| `account.user_id` | `platform_author_id` | video_to_author |
-| `account.nickname` | `author_display_name` | — |
-| `account.first_tag_name`/`second_tag_name` | `tags`(账号级) | — |
-| `posts.avg_like_count` 等 | `statistics.*`(账号聚合) | — |
-
----
-
-## 五、内容画像 video_like_portrait —— BLOCKED
-
-### ① 身份
-- source_id:`PLT_DOUYIN_CONTENT_PORTRAIT`
-- 接口名:抖音内容(视频)人群画像 / dou_yin re_dian_bao video_like_portrait
-- 用途:本应拉单条内容的点赞人群画像。
-- 状态:**blocked** —— 原因:实测 **HTTP 404 Not Found**(capture `douyin_profile_content.json`,`http_error=404`,`error_body=<html><title>404: Not Found</title>...`)。
-
-### ② 请求标准(实测路径)
-- method:`POST`
-- path:`/crawler/dou_yin/re_dian_bao/video_like_portrait`
-- 入参:`{ "content_id":"7522164415848893735", "need_age":true, "need_gender":true, "need_province":true }`
-
-### ③ 请求示例
-```bash
-curl -X POST http://crawler.aiddit.com/crawler/dou_yin/re_dian_bao/video_like_portrait \
-  -H 'Content-Type: application/json' \
-  -d '{"content_id":"7522164415848893735","need_age":true,"need_gender":true,"need_province":true}'
-# 实测返回 HTTP 404
-```
-
-### ④–⑧
-- 该路径当前在 crawler.aiddit.com 上不存在(404),内容级人群画像**不可用**。需上游确认正确 path 或下线该能力;在 registry 中标 `blocked`,游走层不要挂该边。

+ 0 - 63
数据接口与来源/接口台账/知乎.md

@@ -1,63 +0,0 @@
-# 知乎(zhi_hu)接口台账
-
-> 唯一证据来源:`/tmp/aiddit_captures/zhihu_search.json`、`zhihu_search_retry.json`(2026-06-11 真实抓包)。
-> 全部接口走统一爬虫网关 `host = http://crawler.aiddit.com`,平台名 `zhi_hu`。
-
-## 平台级结论(先读)
-
-- **整平台 blocked**:知乎关键词搜索两次请求(含变参重试)**均返回 `code=10000 / msg="未知错误" / data=null`**(`zhihu_search.json`、`zhihu_search_retry.json`)。
-- 因 search 即失败,**detail / blogger / account_info / 视频下载 全部无法触达,无任何真实样本**,本批 capture 仅有 search 一条且失败。
-- 知乎平台 **当前不可用**,下游游走(内容、作者、tag、下载)全部阻断。
-
----
-
-## 接口 1:关键词搜索(唯一接口,失败)
-
-### ① 身份
-- `source_id`: **PLT_ZHIHU_KEYWORD**
-- 状态:**blocked**(`zhihu_search.json` + `zhihu_search_retry.json` 两次 `code=10000`)
-
-### ② 请求标准
-- method:`POST`,path:`/crawler/zhi_hu/keyword`
-- 参数表(依两次请求体):
-
-| 参数 | 类型 | 必填 | 含义 | 真实值 |
-|---|---|---|---|---|
-| `keyword` | string | 是 | 搜索词 | `"彩虹"` |
-| `cursor` | string | 否 | 翻页游标(重试时尝试) | `""` |
-| `content_type` | string | 否 | 内容类型(重试时尝试) | `"综合"` |
-
-### ③ 真实 curl
-```bash
-# 第一次(最小参数)
-curl -X POST 'http://crawler.aiddit.com/crawler/zhi_hu/keyword' \
-  -H 'Content-Type: application/json' \
-  -d '{"keyword":"彩虹"}'
-
-# 第二次(变参重试,仍失败)
-curl -X POST 'http://crawler.aiddit.com/crawler/zhi_hu/keyword' \
-  -H 'Content-Type: application/json' \
-  -d '{"keyword":"彩虹","cursor":"","content_type":"综合"}'
-```
-
-### ④ 响应信封 + 翻页
-```json
-{ "code": 10000, "msg": "未知错误", "data": null }
-```
-- 两次请求响应一致:`code=10000`、`msg="未知错误"`、`data=null`。
-- **无 `has_more`/`next_cursor`**(data 为 null),翻页不可得。
-
-### ⑤ 字段台账
-- **无字段可登记**:`data=null`,未取得任何内容/标题/视频地址/作者/tag/互动数/翻页字段。
-
-### ⑥ 提取与后处理规则
-- 不适用(无数据返回)。
-
-### ⑦ 边界与失败
-- **失败实证**:`zhihu_search.json` `code=10000 / "未知错误"`;变参重试 `zhihu_search_retry.json` 同样 `code=10000 / "未知错误"`。两次重试均失败 → 判定**知乎整平台 blocked**。
-- 限流 ≥ 15s(与其余平台一致,但本平台已 blocked,限流不再是瓶颈)。
-- 后续若上游修复,需重新抓包验证 search 是否回到 `code=0`,再补 detail/作者/下载链路。
-
-### ⑧ 落点(canonical + 游走边)
-- canonical:无(无数据)。
-- 游走边:`search_page_to_content`、`video_to_author`、`author_to_works`、`video_to_hashtag` **全部阻断**,知乎暂不接入游走。

+ 0 - 252
数据接口与来源/接口台账/视频号.md

@@ -1,252 +0,0 @@
-# 接口台账 · 视频号(shi_pin_hao)
-
-> 唯一证据来源:`/tmp/aiddit_captures/` 下真实抓包。host=`http://crawler.aiddit.com`。
-> 整体状态:**部分可用(搜索抖动 + 作者接口失败,但视频可下载)**。
-> - 搜索:首次 `code=25011` 失败,重试 `code=0` 成功 → 服务端不稳定,需重试。
-> - 作者 blogger:`code=25011` blocked;account_info:`code=10001` blocked。
-> - 视频下载:`200 / video/mp4` 可下载 ✅。
-> 鉴权:抓包请求体无显式 token 字段,统一走 crawler.aiddit.com 网关。
-
----
-
-## 2026-06-17 URL 解包与 OSS v2 批量验证
-
-本节来自 V4 诊断脚本 `tests/test_platform_video_url_candidates.py` 的真实探测。完整原始响应和临时 URL 仅保存在 gitignore 下的 `data/platform_url_probe/20260617_115219/`,本文只记录脱敏统计、字段路径、host 和状态。
-
-### 探测规模
-
-- 真实搜索 query:10 个。
-- 搜索请求页数:18 页(page1 + cursor page2,部分 query 无结果或无 page2)。
-- 搜索 item 样本:46 条。
-- 抽取 URL 候选:`image=95`、`video_candidate=41`、`audio=2`。
-- 推荐规则选中正片 URL:41 条。
-- 其中 20 条 selected URL 做 OSS v2 批量验证。
-- 历史对照 run:`v1_run_35f4f7ccd1cc`。
-
-### 历史 run 对照
-
-- 历史 run 决策:`ADD_TO_CONTENT_POOL=10`、`KEEP_CONTENT_FOR_REVIEW=5`、`REJECT_CONTENT=9`、`TECHNICAL_RETRY_REQUIRED=6`。
-- 媒体状态:30 条全部为 `oss_upload_pending`,失败原因全部为 `oss_upload_response_invalid`。
-- 6 条技术重试的决策直接原因均在 Gemini/OpenRouter 链路;但这 6 条 media 侧同时带有 OSS invalid 症状。
-- 历史 runtime 不能反查原始字段路径:`platform_raw_payload` 只保留 `channel_content_id/channel_account_id`,没有完整 `video_url_list/image_url_list/content_link/bgm_data`。
-
-### 25011 与翻页观察
-
-- 本轮真实探测保留了 25011 retry 事件。
-- 例:`怀旧经典歌曲` page1 前两次返回 `25011: 视频号接口异常: 获取搜索结果失败`,第 3 次返回 `code=0` 且 `item_count=9`。
-- 例:`歌词同步滚动呈现` page1 第 1 次返回 25011,重试后成功。
-- 例:`怀旧金曲` page1 重试后仍为 25011,最终 item_count=0。
-- page 返回数量不固定:本轮见到 0、9、11、12 条;page2 也可能返回 0、9、11、12 条。
-
-### 推荐 URL 解包规则
-
-- 递归抽取搜索 item 中所有 URL,视频号也按广告/素材规则扫描,不能假设没有广告 MP4。
-- 优先选择 `$.search.video_url_list[].video_url` 中的 findermp 正片 URL。
-- 明确排除:`image_url_list` 封面/图片、`channel_account_avatar` 头像、音频/BGM、页面链接、广告/素材字段。
-- 广告关键词不能用裸 `ad` 子串匹配,否则会误伤 `stodownload/head` 这类正常视频号 URL;应按字段路径 token 或分隔符匹配。
-- 下载/轻量验证需带 `Referer: https://channels.weixin.qq.com/`。
-
-### 本轮真实字段与 host
-
-- selected 字段路径:`$.search.video_url_list[0].video_url=41`。
-- selected host:`findermp.video.qq.com=41`。
-- 所有 selected 样本轻量验证均为可下载视频:HTTP `200/206`,`content_type=video/mp4`,Range 返回字节范围或完整小文件。
-
-### OSS v2 验证结论
-
-- 使用生产当前 payload 形态(把 `referer` 作为 dict 字段传给 OSS v2)时:20/20 返回 HTTP 200 但 `oss_object.cdn_url` 缺失,分类为 `oss_upload_response_invalid`。
-- 快手抽样复打证明该 invalid 与 `referer` dict payload 强相关;去掉 `referer` 后 OSS v2 能返回 `oss_object.cdn_url`。
-- 使用同一批视频号 selected URL、去掉 `referer` 字段后做 no-referer 复验:20/20 成功返回 `oss_object.cdn_url`。
-- 结论:视频号 selected URL 本身可下载、可转存;历史 30/30 invalid 更像 OSS v2 payload 兼容问题,不是 findermp URL 字段拆错。
-
-### 生产改造建议
-
-- 第一优先级:调整 OSS v2 调用 payload,不再把 `referer` dict 直接传给 v2;如 OSS 侧确需 headers,应与上传服务约定字符串/headers 字段格式后再传。
-- 第二优先级:保留视频号 25011 retry 事件到 query failure / raw payload,区分“无结果”和“重试耗尽”。
-- 第三优先级:生产 runtime 里补充 URL 候选摘要:`selected_path`、`selected_host`、`probe_status`、`content_type`、`content_range`、`oss_payload_mode`、`oss_failure_type`。
-
----
-
-## 1. 关键词搜索(PLT_SHIPINHAO_KEYWORD)
-
-### ① 身份
-- source_id:`PLT_SHIPINHAO_KEYWORD`
-- 接口名:视频号关键词搜索
-- 用途:按关键词检索视频号内容(搜索页 → 内容)
-- 状态:**verified(不稳定)** —— 首次 `code=25011` 失败,重试 `code=0` 成功有数据。证据:`sph_search.json`(失败)+ `sph_search_retry.json`(成功)。
-
-### ② 请求标准
-- method:`POST`
-- path:`/crawler/shi_pin_hao/keyword`
-- 鉴权:无显式 token(走网关)
-- 参数表:
-
-| 名 | 类型 | 必填 | 示例 |
-|---|---|---|---|
-| keyword | string | 是 | `"彩虹"` |
-| cursor | string | 否 | `""`(首页传空) |
-
-### ③ 请求示例(真实 request_body)
-```bash
-curl -X POST 'http://crawler.aiddit.com/crawler/shi_pin_hao/keyword' \
-  -H 'Content-Type: application/json' \
-  -d '{"keyword":"彩虹","cursor":""}'
-```
-
-### ④ 响应信封 + 翻页
-- 信封:`{code, msg, data}`
-- 成功(retry):`code=0`,`msg=null`,`data` 为 `{has_more, next_cursor, data:[...]}`
-- 失败(首次):`code=25011`,`msg="视频号接口异常: 获取搜索结果失败"`,`data=null`
-- 翻页真实取值:`has_more=true`,`next_cursor=12`(数字型游标,翻下一页时回填到 `cursor`)
-
-### ⑤ 字段台账(raw 名)
-> 列表项位于 `data.data[]`,每项为内容对象。
-
-| 字段名 | 类型 | 含义 | 提取标注 | 真实示例值 |
-|---|---|---|---|---|
-| channel_content_id | string | 内容ID | 直接可用 | `finderobjv03rf47ax8UTAiVchU7Uo2uexnucqpQD/XO8kw7opFqUs=` |
-| title | string | 标题(含话题串) | 直接可用 / tag需正则抽 | `"…网友拍到圆形彩虹… #彩虹#祝你好运连连…"` |
-| content_type | string | 内容类型 | 直接可用 | `video` |
-| content_link | string | 内容链接(export 编码) | 直接可用 | `export/UzFfBgAAxJqjXGR8HB7MjMzT4DCK…` |
-| video_url_list[].video_url | string | 视频地址(findermp) | 需后处理下载 | `https://findermp.video.qq.com/251/20302/stodownload?encfilekey=…` |
-| video_url_list[].video_duration | int | 视频时长(秒) | 直接可用 | `8` |
-| image_url_list[].image_url | string | 封面图地址 | 直接可用 | `https://findermp.video.qq.com/251/20304/stodownload?…` |
-| channel_account_id | string | 作者ID | 直接可用 | `28ee8b6605e2288793f5386abaef5769` |
-| channel_account_name | string | 作者昵称 | 直接可用 | `掌上巴彦淖尔` |
-| channel_account_avatar | string | 作者头像 | 直接可用 | `https://wx.qlogo.cn/finderhead/ver_1/…/132` |
-| like_count | int | 点赞数 | 直接可用 | `92` / `5513` |
-| collect_count | int/null | 收藏数 | 直接可用(常为null) | `null` |
-| comment_count | int/null | 评论数 | 直接可用(常为null) | `null` |
-| share_count | int/null | 分享数 | 直接可用(常为null) | `null` |
-| topic_list | array/null | 话题列表 | 需正则抽(列表常null,需从title解析) | `null`(标题内 `#彩虹` 需正则) |
-| publish_timestamp | long | 发布时间(ms) | 需后处理(/1000) | `1780904037000` |
-| has_more | bool | 是否有下一页 | 直接可用 | `true` |
-| next_cursor | int | 下一页游标 | 直接可用 | `12` |
-
-### ⑥ 提取与后处理规则
-- **话题/tag**:`topic_list` 在搜索结果里基本为 `null`,tag 需从 `title` 用正则抽取(形如 `#彩虹`、`#见者好运`,以空格或 `#` 分隔)。
-- **视频下载**:`video_url_list[].video_url` 为 findermp.video.qq.com 直链;实测(`sph_video_dl.json`)`http_status=200`、`content_type=video/mp4`、`content_range=bytes 0-784921/784922`、约 **784KB 可下载**。下载时需带 **Referer `https://channels.weixin.qq.com/`**。
-- 时间戳为毫秒,落库前 `/1000`。
-
-### ⑦ 边界与失败
-- **搜索不稳定(需重试)**:同一请求首次返回 `code=25011: 视频号接口异常: 获取搜索结果失败`(`sph_search.json`),重试返回 `code=0` 并带数据(`sph_search_retry.json`)。**实现必须带重试**(建议指数退避,≥15s 限流间隔)。
-- 限流:平台请求间隔 ≥15s。
-
-### ⑧ 落点
-- canonical:`platform_content_id`(←channel_content_id)、`title`、`video_url`(←video_url_list[0].video_url)、`platform_author_id`(←channel_account_id)、`author_name`(←channel_account_name)、`tags`(从title正则)、`like_count`、`publish_time`。
-- 游走边:`search_page_to_content`(搜索→内容)、`video_to_author`(内容→作者,channel_account_id)、`video_to_hashtag`(title 抽 tag)。
-
----
-
-## 2. 视频下载(PLT_SHIPINHAO_VIDEO_DL)
-
-### ① 身份
-- source_id:`PLT_SHIPINHAO_VIDEO_DL`
-- 接口名:视频号视频直链下载
-- 用途:下载搜索/详情返回的 `video_url`
-- 状态:**verified** ✅。证据:`sph_video_dl.json`。
-
-### ② 请求标准
-- method:`GET`(直链)
-- path:findermp.video.qq.com 直链(非 crawler 网关),来自 `video_url_list[].video_url`
-- 鉴权:URL 自带 `encfilekey`/`token`;需 Header `Referer: https://channels.weixin.qq.com/`
-
-### ③ 请求示例
-```bash
-curl -L 'https://findermp.video.qq.com/251/20302/stodownload?encfilekey=Cvvj5Ix3eew…&token=ic1n0xDG6aw9Kicy8QAXnibeBK0' \
-  -H 'Referer: https://channels.weixin.qq.com/' \
-  -o sph_video.mp4
-```
-
-### ④ 响应信封
-- 非 JSON,二进制流。`http_status=200`,`content_type=video/mp4`,`content_range=bytes 0-784921/784922`,`bytes_received=784922`,`looks_like_video=true`。
-
-### ⑤ 字段台账
-| 字段名 | 类型 | 含义 | 提取标注 | 真实示例值 |
-|---|---|---|---|---|
-| (binary) | bytes | mp4 视频流 | 需后处理下载 | 784922 bytes |
-| content_type | header | 内容类型 | 直接可用 | `video/mp4` |
-
-### ⑥ 提取与后处理
-- 直接落地为 `.mp4` 文件;约 784KB(本样本)。后续可送多模态分析。
-
-### ⑦ 边界与失败
-- token/encfilekey 有时效,需在搜索后尽快下载。缺 Referer 可能被拒。
-
-### ⑧ 落点
-- 物料落点:`video_local_path` / `video_url`;不新增游走边(从属于内容)。
-
----
-
-## 3. 作者主页 blogger(PLT_SHIPINHAO_BLOGGER)
-
-### ① 身份
-- source_id:`PLT_SHIPINHAO_BLOGGER`
-- 接口名:视频号博主作品列表
-- 用途:作者 → 作品(author_to_works)
-- 状态:**blocked(code=25011)**。证据:`sph_blogger.json`。
-
-### ② 请求标准
-- method:`POST` · path:`/crawler/shi_pin_hao/blogger`
-
-| 名 | 类型 | 必填 | 示例 |
-|---|---|---|---|
-| account_id | string | 是 | `28ee8b6605e2288793f5386abaef5769` |
-| is_cache | bool | 否 | `false` |
-
-### ③ 请求示例
-```bash
-curl -X POST 'http://crawler.aiddit.com/crawler/shi_pin_hao/blogger' \
-  -H 'Content-Type: application/json' \
-  -d '{"account_id":"28ee8b6605e2288793f5386abaef5769","is_cache":false}'
-```
-
-### ④ 响应信封
-- `code=25011`,`msg="视频号接口异常"`,`data=null` → **失败**。
-
-### ⑤ 字段台账
-- 无数据(blocked),字段未知。
-
-### ⑥/⑦ 边界与失败
-- blocked:作者作品接口当前不可用(25011),与搜索同源抖动。可考虑用搜索结果的 `channel_account_id` 暂时无法展开作品列表。
-
-### ⑧ 落点
-- 期望游走边 `author_to_works`,**当前不可用**。
-
----
-
-## 4. 作者信息 account_info(PLT_SHIPINHAO_ACCOUNT_INFO)
-
-### ① 身份
-- source_id:`PLT_SHIPINHAO_ACCOUNT_INFO`
-- 接口名:视频号账号信息
-- 用途:作者画像补全
-- 状态:**blocked(code=10001 参数异常)**。证据:`sph_account_info.json`。
-
-### ② 请求标准
-- method:`POST` · path:`/crawler/shi_pin_hao/account_info`
-
-| 名 | 类型 | 必填 | 示例 |
-|---|---|---|---|
-| account_id | string | (实测被拒) | `28ee8b6605e2288793f5386abaef5769` |
-| is_cache | bool | 否 | `false` |
-| app_id 或 account_name | string | **至少一个**(据错误提示) | —(本次未传) |
-
-### ③ 请求示例
-```bash
-curl -X POST 'http://crawler.aiddit.com/crawler/shi_pin_hao/account_info' \
-  -H 'Content-Type: application/json' \
-  -d '{"account_id":"28ee8b6605e2288793f5386abaef5769","is_cache":false}'
-```
-
-### ④ 响应信封
-- `code=10001`,`msg="参数异常: app_id或account_name至少需要一个"`,`data=null` → **失败**。
-
-### ⑤ 字段台账
-- 无数据(blocked)。
-
-### ⑥/⑦ 边界与失败
-- blocked:本次只传了 `account_id`,网关要求 `app_id` 或 `account_name` 至少其一。后续若拿到 `account_name`(搜索结果有 `channel_account_name`)可再试。
-
-### ⑧ 落点
-- 期望补全 `platform_author_id` 画像,**当前参数不满足,blocked**。

+ 0 - 59
数据接口与来源/视频音频取数实测.md

@@ -1,59 +0,0 @@
-# 视频/音频取数与多模态喂入 · 实测
-
-> 实测日期 2026-06-16,在阿里云海外开发机(新加坡,出口 47.245.103.121)上,对 `crawler.aiddit.com` 各平台 detail 接口 + 真实下载 + Gemini(经 OpenRouter)逐一实测。结论以本文为准;样例 id 见各小节,原始抓包在 `captures/`。
-
-## 1. 平台可取数据矩阵(实测)
-
-| 平台 | 图文(图片+文字) | 视频文件 | 音频(口播) | 取数难度 |
-|---|---|---|---|---|
-| 小红书 | ✅ 图片可下载 | ❌ 详情不给视频直链(视频帖也只给封面图) | — | 最简单 |
-| 抖音 | ✅ | ✅ 干净 mp4,多码率 540/720/1080 | ✅ 含在 mp4 | 简单 |
-| 快手 | (图文未单测) | ✅ 干净 mp4 | ✅ 含在 mp4 | 简单 |
-| B站 | (仅封面图) | ✅ 但 DASH 分轨 m4s | ✅ 但在 `voice_data` 单独音轨 | 较麻烦(需合并) |
-
-实测证据:
-- 小红书视频帖 `6976f6fd000000001a01eede`:`content_type=video` 但 `video_url_list=null`,只有 `image_url_list`(封面)。图文帖 `67e4bdf50000000006028a59`:5 张图,图片下载 200 image/jpeg。
-- 抖音 `7612631899479648866`:`video_url_list[0].video_url` + `multi_bitrate{540p,720p,1080p}`;540p 下载 206 `video/mp4`(13.5MB / 1.2s)。
-- B站 `BV1qd4y1x76w`:视频轨 m4s + 音频轨在 `voice_data.play_url`;合并后 ffmpeg 验流含 `Video h264 (avc1)` + `Audio aac (HE-AAC) 44100Hz stereo`。
-- 快手 `3xepqgwddgbickc`:`video_url_list` 一条,206 `video/mp4`。
-
-## 2. 下载方法(实测可用)
-
-### 图片:不用下载
-把图片 URL 直接作为 `image_url` 给 Gemini,模型自己抓取(各平台图片/封面同此)。
-
-### 抖音 / 快手 视频:干净 mp4
-1. detail → `video_url_list[0].video_url`(抖音可把 `ratio=default` 改 `ratio=540p` 控体积)。
-2. GET 该 URL,必带:
-   - `User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15`(iOS UA)
-   - `Referer: https://www.douyin.com/`(快手用 `https://www.kuaishou.com/`)
-   - 跟随重定向(302 → CDN)
-3. 得到完整 mp4(音视频一体)。新加坡云端直连 CDN `douyinvod.com` 可达。
-
-### B站 视频:DASH 分轨,必须合并
-1. detail →
-   - 视频轨:`video_url_list[0].video_url`(`..._nb3-1-30016.m4s`,**30016=视频 AVC**)
-   - 音频轨:**`voice_data.play_url`**(`..._nb3-1-30216.m4s`,**30216=音频 AAC**)——不在 video_url_list,藏在 `voice_data` 字段(dict,含 id_str/duration/play_url)。
-2. 下两轨,**用 curl 且不跟随重定向**:
-   ```bash
-   curl -s --connect-timeout 15 -A 'Mozilla/5.0' -e 'https://www.bilibili.com/' -o v.m4s "<video_url>"
-   curl -s --connect-timeout 15 -A 'Mozilla/5.0' -e 'https://www.bilibili.com/' -o a.m4s "<voice_data.play_url>"
-   ```
-   ⚠️ **坑**:B站 m4s 的 `*.mcdn.bilivideo.cn` 节点,用 httpx `follow_redirects=True` 会被带到 P2P 死节点、连接超时(实测两次都卡死);curl 不跟随重定向直接 206 秒下。视频轨主 host 是 `upos-sz-mirrorcos.bilivideo.com`(稳)。
-3. 合并:`ffmpeg -i v.m4s -i a.m4s -c copy out.mp4` → 含视频+音频的 mp4。
-
-## 3. 喂给 Gemini 的方式(实测,经 OpenRouter)
-
-通道:`OPENROUTER_API_KEY`(**无直连 GEMINI key**,`.env` 里 GEMINI_API_KEY 为空);`POST {OPENROUTER_BASE_URL}/chat/completions`;model `google/gemini-3-flash-preview`。
-
-- **图片**:`{"type":"image_url","image_url":{"url": <图片URL>}}` —— 传 URL,模型抓取。✅
-- **视频**:**必须 base64 data URL** —— `{"type":"video_url","video_url":{"url":"data:video/mp4;base64,<...>"}}`。
-  - ⚠️ 实测:把**远程视频 URL 直接**当 video_url(不 base64)→ 返回 HTTP 200 但**无有效结果**(无 choices/usage),等于没处理。OpenRouter 对 Gemini 只接受 **YouTube 远程链接**,其余一律要 base64。
-- provider 实际落 **Google AI Studio**,按 `video_tokens` 计费(约 25.6k tokens / 4分钟 540p / ≈$0.0157)。
-
-## 4. 接入建议(优先级)
-1. **抖音、快手**:mp4 干净、音视频一体,原生视频提炼直接可用 → 优先接。
-2. **小红书**:走图文(图片 URL 给 Gemini);视频帖够不着,放弃。
-3. **B站**:可做,但要「取 `voice_data` 音轨 + ffmpeg 合并 + 下载不跟随重定向」三处特殊处理 → 建议排在抖音/快手之后。
-
-> 另:新加坡开发机网络——下载抖音/快手/B站 CDN、调 OpenRouter/Gemini、连 RDS、调 crawler 均通;唯一不通的是内网 GitLab(仅构建期用)。运行期"下载→提炼→落库"可全在云端一条龙。

+ 0 - 235
数据接口与来源/跨平台字段映射.json

@@ -1,235 +0,0 @@
-{
-  "schema_version": "cross_platform_field_map.v1",
-  "title": "跨平台字段映射 canonical → raw 路径",
-  "generated_at": "2026-06-11",
-  "evidence": "/tmp/aiddit_captures/*.json 真实抓包 + tech_documents/数据接口与来源/接口台账/*.md(⑤字段台账)",
-  "canonical_source_of_truth": "content_agent/integrations/douyin.py 归一化 _normalize_content_item",
-  "notes": [
-    "单元格 = 该平台真实 raw 字段路径;拿不准/无数据标 '—'。",
-    "抖音有两套:search=raw 原生 aweme 结构 / detail=归一化结构(channel_*),分两列。",
-    "快手/视频号/B站detail/小红书detail/公众号/youtube 的 detail 多为归一化结构(channel_*)。",
-    "小红书搜索内容字段全部嵌在 note_card 下(item.note_card.*)。",
-    "公众号作者标识只能从 url 正则抽 __biz;detail/account_info 另给网关 hash channel_account_id。",
-    "时间戳单位不一致:抖音search/小红书search/B站search/公众号/视频号=秒;抖音detail/快手/小红书detail/B站detail/youtube=毫秒。",
-    "头条、知乎整平台 blocked(code=10000/null),无字段,不在列内。"
-  ],
-  "platforms": [
-    "抖音搜索(raw)",
-    "抖音详情(归一化)",
-    "快手",
-    "视频号",
-    "小红书(note_card)",
-    "小红书详情(扁平)",
-    "B站搜索(raw)",
-    "B站详情(归一化)",
-    "公众号",
-    "youtube搜索",
-    "youtube详情"
-  ],
-  "mappings": {
-    "platform_content_id": {
-      "抖音搜索(raw)": "aweme_id",
-      "抖音详情(归一化)": "channel_content_id",
-      "快手": "channel_content_id",
-      "视频号": "channel_content_id",
-      "小红书(note_card)": "item.id",
-      "小红书详情(扁平)": "channel_content_id",
-      "B站搜索(raw)": "bvid",
-      "B站详情(归一化)": "channel_content_id",
-      "公众号": "url 正则抽 __biz+mid+idx (search) / channel_content_id (detail)",
-      "youtube搜索": "video_id",
-      "youtube详情": "channel_content_id"
-    },
-    "platform_content_format": {
-      "抖音搜索(raw)": "派生(content_type=视频→video)",
-      "抖音详情(归一化)": "content_type",
-      "快手": "content_type",
-      "视频号": "content_type",
-      "小红书(note_card)": "item.note_card.type",
-      "小红书详情(扁平)": "content_type",
-      "B站搜索(raw)": "派生(均 video)",
-      "B站详情(归一化)": "content_type",
-      "公众号": "content_type (=note,图文)",
-      "youtube搜索": "派生(均 video)",
-      "youtube详情": "content_type"
-    },
-    "description": {
-      "抖音搜索(raw)": "desc / caption",
-      "抖音详情(归一化)": "body_text (标题=title)",
-      "快手": "body_text (常 null)",
-      "视频号": "title (含话题串,无独立正文)",
-      "小红书(note_card)": "item.note_card.desc (标题=item.note_card.display_title)",
-      "小红书详情(扁平)": "body_text (标题=title)",
-      "B站搜索(raw)": "description / desc (标题=title,含<em>高亮需剥)",
-      "B站详情(归一化)": "body_text (标题=title)",
-      "公众号": "body_text (detail;search 仅 title 含<em>高亮)",
-      "youtube搜索": "description_snippet (标题=title)",
-      "youtube详情": "body_text (标题=title)"
-    },
-    "platform_author_id": {
-      "抖音搜索(raw)": "author.sec_uid",
-      "抖音详情(归一化)": "channel_account_id (=sec_uid)",
-      "快手": "channel_account_id",
-      "视频号": "channel_account_id",
-      "小红书(note_card)": "item.note_card.user.user_id",
-      "小红书详情(扁平)": "channel_account_id",
-      "B站搜索(raw)": "mid (uid 恒为 0 弃用)",
-      "B站详情(归一化)": "channel_account_id (=mid 字符串)",
-      "公众号": "url 正则抽 __biz (search,biz字段恒空) / channel_account_id 网关hash + biz_info (account_info)",
-      "youtube搜索": "channel_id (UC...,可能空串)",
-      "youtube详情": "channel_account_id (32位hash,与search channel_id 不同体系)"
-    },
-    "author_display_name": {
-      "抖音搜索(raw)": "author.nickname",
-      "抖音详情(归一化)": "channel_account_name",
-      "快手": "channel_account_name (档案补 account_info.account_name)",
-      "视频号": "channel_account_name",
-      "小红书(note_card)": "item.note_card.user.nick_name",
-      "小红书详情(扁平)": "channel_account_name",
-      "B站搜索(raw)": "author",
-      "B站详情(归一化)": "channel_account_name",
-      "公众号": "— (search nick_name 恒空;须 account_info.account_name 补)",
-      "youtube搜索": "author",
-      "youtube详情": "channel_account_name"
-    },
-    "tags": {
-      "抖音搜索(raw)": "text_extra[].hashtag_name / cha_list[].cha_name (缺则 desc 正则 #xxx)",
-      "抖音详情(归一化)": "topic_list",
-      "快手": "topic_list (常 null,回退 body_text 正则) / account_info.tags(账号级)",
-      "视频号": "topic_list (常 null,从 title 正则 #xxx)",
-      "小红书(note_card)": "item.note_card.desc 正则 #xxx",
-      "小红书详情(扁平)": "topic_list (结构化) / body_text 正则",
-      "B站搜索(raw)": "tag (逗号分隔,split ',')",
-      "B站详情(归一化)": "topic_list",
-      "公众号": "— (topic_list 恒 null,整平台无 hashtag)",
-      "youtube搜索": "— (搜索无 hashtag)",
-      "youtube详情": "body_text 正则 #xxx (topic_list 为 null,detail 是唯一来源)"
-    },
-    "statistics.digg_count": {
-      "抖音搜索(raw)": "statistics.digg_count",
-      "抖音详情(归一化)": "like_count",
-      "快手": "like_count",
-      "视频号": "like_count",
-      "小红书(note_card)": "item.note_card.interact_info.liked_count",
-      "小红书详情(扁平)": "like_count",
-      "B站搜索(raw)": "like",
-      "B站详情(归一化)": "like_count",
-      "公众号": "like_count (detail,常 0)",
-      "youtube搜索": "—",
-      "youtube详情": "like_count"
-    },
-    "statistics.comment_count": {
-      "抖音搜索(raw)": "statistics.comment_count",
-      "抖音详情(归一化)": "comment_count",
-      "快手": "comment_count",
-      "视频号": "comment_count (常 null)",
-      "小红书(note_card)": "item.note_card.interact_info.comment_count",
-      "小红书详情(扁平)": "comment_count",
-      "B站搜索(raw)": "review",
-      "B站详情(归一化)": "comment_count",
-      "公众号": "—",
-      "youtube搜索": "—",
-      "youtube详情": "comment_count"
-    },
-    "statistics.share_count": {
-      "抖音搜索(raw)": "statistics.share_count",
-      "抖音详情(归一化)": "share_count",
-      "快手": "share_count (detail 准,search 滞后)",
-      "视频号": "share_count (常 null)",
-      "小红书(note_card)": "item.note_card.interact_info.shared_count",
-      "小红书详情(扁平)": "share_count",
-      "B站搜索(raw)": "— (search 无独立 share)",
-      "B站详情(归一化)": "share_count",
-      "公众号": "—",
-      "youtube搜索": "—",
-      "youtube详情": "share_count (常 null)"
-    },
-    "statistics.collect_count": {
-      "抖音搜索(raw)": "statistics.collect_count",
-      "抖音详情(归一化)": "collect_count",
-      "快手": "collect_count",
-      "视频号": "collect_count (常 null)",
-      "小红书(note_card)": "item.note_card.interact_info.collected_count",
-      "小红书详情(扁平)": "collect_count",
-      "B站搜索(raw)": "favorites",
-      "B站详情(归一化)": "collect_count",
-      "公众号": "—",
-      "youtube搜索": "—",
-      "youtube详情": "collect_count (常 null)"
-    },
-    "statistics.play_count": {
-      "抖音搜索(raw)": "— (raw 无 play_count,归 0)",
-      "抖音详情(归一化)": "— (play_count 实测 null)",
-      "快手": "view_count",
-      "视频号": "— (无)",
-      "小红书(note_card)": "— (无)",
-      "小红书详情(扁平)": "— (无)",
-      "B站搜索(raw)": "play",
-      "B站详情(归一化)": "play_count",
-      "公众号": "view_count (阅读数)",
-      "youtube搜索": "view_count (带逗号+'views' 文本,正则抽)",
-      "youtube详情": "view_count / play_count (常 null)"
-    },
-    "create_time": {
-      "抖音搜索(raw)": "create_time (秒,×1000)",
-      "抖音详情(归一化)": "publish_timestamp (毫秒)",
-      "快手": "publish_timestamp (毫秒)",
-      "视频号": "publish_timestamp (毫秒)",
-      "小红书(note_card)": "item.note_card.publish_timestamp (秒)",
-      "小红书详情(扁平)": "publish_timestamp (毫秒)",
-      "B站搜索(raw)": "pubdate / senddate (秒,×1000)",
-      "B站详情(归一化)": "publish_timestamp (毫秒)",
-      "公众号": "time (秒,search) / publish_timestamp (毫秒,detail)",
-      "youtube搜索": "published_time (相对文本'7 months ago',部分空)",
-      "youtube详情": "publish_timestamp (毫秒)"
-    },
-    "has_more": {
-      "抖音搜索(raw)": "data.has_more",
-      "抖音详情(归一化)": "data.has_more (=false)",
-      "快手": "data.has_more (实测 false)",
-      "视频号": "data.has_more",
-      "小红书(note_card)": "data.has_more",
-      "小红书详情(扁平)": "data.has_more (=false)",
-      "B站搜索(raw)": "data.has_more",
-      "B站详情(归一化)": "data.has_more (=false)",
-      "公众号": "data.has_more",
-      "youtube搜索": "data.has_more",
-      "youtube详情": "data.has_more (=false)"
-    },
-    "next_cursor": {
-      "抖音搜索(raw)": "data.next_cursor (页码串,如 '12')",
-      "抖音详情(归一化)": "data.next_cursor (=null)",
-      "快手": "data.next_cursor (实测 null)",
-      "视频号": "data.next_cursor (数字游标)",
-      "小红书(note_card)": "data.next_cursor",
-      "小红书详情(扁平)": "data.next_cursor (=null)",
-      "B站搜索(raw)": "data.next_cursor (页码串)",
-      "B站详情(归一化)": "data.next_cursor (=null)",
-      "公众号": "data.next_cursor (页码串)",
-      "youtube搜索": "data.next_cursor (base64 长 token,透传不截断)",
-      "youtube详情": "data.next_cursor (=null)"
-    },
-    "platform_content_url": {
-      "抖音搜索(raw)": "share_url",
-      "抖音详情(归一化)": "content_link",
-      "快手": "content_link",
-      "视频号": "content_link (export 编码)",
-      "小红书(note_card)": "— (需拼 explore/{id})",
-      "小红书详情(扁平)": "content_link",
-      "B站搜索(raw)": "arcurl",
-      "B站详情(归一化)": "content_link",
-      "公众号": "url (search) / content_link (detail)",
-      "youtube搜索": "url (watch 链接)",
-      "youtube详情": "content_link"
-    }
-  },
-  "extraction_pitfalls": [
-    "小红书 note_card 嵌套:搜索内容字段全部在 item.note_card.* 与 item.note_card.user.* / interact_info.* 下,不要在顶层找;详情却是扁平 channel_*。",
-    "公众号 biz 正则:作者标识须从 url 用 __biz=([^&]+) 抽;search 的 biz/nick_name 恒空;detail/account_info 给的是网关 hash,与 biz 不是同一标识,两者都要沉淀。",
-    "时间戳秒/毫秒不一致:同一平台不同接口也可能不同(小红书 search 秒、detail 毫秒;公众号 search 秒、detail 毫秒);落库前按接口统一换算。",
-    "抖音 raw vs 归一化两套字段名:search=aweme_id/author.sec_uid/statistics.digg_count;detail=channel_content_id/channel_account_id/like_count,提取层分别映射。",
-    "B站:title 含 <em class='keyword'> 高亮需剥;uid 恒为 0 弃用以 mid 为准;play/like/favorites/review 对应 play/digg/collect/comment。",
-    "youtube:作者 id 双体系(search channel_id=UC..., detail=32位 hash),建议以 UC... 为稳定主键;view_count 带逗号与 'views' 文本需正则。",
-    "公众号/youtube 搜索阶段无 tag;youtube 须从 detail.body_text 正则抽 #xxx;公众号整平台无 hashtag。"
-  ]
-}

+ 0 - 41
数据接口与来源/跨平台字段映射.md

@@ -1,41 +0,0 @@
-# 跨平台字段映射(canonical → 各平台 raw 路径)
-
-> 机读件:`跨平台字段映射.json`。本文与之同一张表,供人读。
-> 证据:`/tmp/aiddit_captures/*.json` 真实抓包 + `接口台账/*.md` 的 ⑤ 字段台账。
-> canonical 名以 `content_agent/integrations/douyin.py` 归一化 `_normalize_content_item` 为准。
-> `—` = 该平台无此字段 / 拿不准。头条、知乎整平台 blocked(`code=10000`/`data=null`),不入列。
-
-## 一、对照表
-
-抖音两套:**搜索=raw 原生 aweme 结构**、**详情=归一化 channel_\* 结构**,分两列。B站同理(搜索 raw / 详情归一)。小红书搜索字段嵌在 `note_card` 下、详情为扁平结构。
-
-| canonical | 抖音搜索(raw) | 抖音详情(归一) | 快手 | 视频号 | 小红书(note_card) | 小红书详情(扁平) | B站搜索(raw) | B站详情(归一) | 公众号 | youtube搜索 | youtube详情 |
-|---|---|---|---|---|---|---|---|---|---|---|---|
-| **platform_content_id** | `aweme_id` | `channel_content_id` | `channel_content_id` | `channel_content_id` | `item.id` | `channel_content_id` | `bvid` | `channel_content_id` | url 抽 `__biz+mid+idx` / detail `channel_content_id` | `video_id` | `channel_content_id` |
-| **platform_content_format** | 派生(视频→video) | `content_type` | `content_type` | `content_type` | `item.note_card.type` | `content_type` | 派生(video) | `content_type` | `content_type`(note) | 派生(video) | `content_type` |
-| **description** | `desc`/`caption` | `body_text`(标题`title`) | `body_text`(常null) | `title`(含话题串) | `item.note_card.desc`(标题`display_title`) | `body_text`(标题`title`) | `description`/`desc`(标题`title`含`<em>`) | `body_text`(标题`title`) | `body_text`(detail;search仅`title`含`<em>`) | `description_snippet`(标题`title`) | `body_text`(标题`title`) |
-| **platform_author_id** | `author.sec_uid` | `channel_account_id`(=sec_uid) | `channel_account_id` | `channel_account_id` | `item.note_card.user.user_id` | `channel_account_id` | `mid`(uid恒0弃用) | `channel_account_id`(=mid) | url 抽 `__biz` / account_info `biz_info`+hash | `channel_id`(UC…可能空) | `channel_account_id`(32位hash) |
-| **author_display_name** | `author.nickname` | `channel_account_name` | `channel_account_name`(补account_info) | `channel_account_name` | `item.note_card.user.nick_name` | `channel_account_name` | `author` | `channel_account_name` | —(search空;account_info `account_name`补) | `author` | `channel_account_name` |
-| **tags** | `text_extra[].hashtag_name`/`cha_list[].cha_name` | `topic_list` | `topic_list`(常null,正则) | `topic_list`(常null,title正则) | `item.note_card.desc` 正则#xxx | `topic_list`/body_text正则 | `tag`(逗号split) | `topic_list` | —(无hashtag) | —(搜索无) | `body_text`正则#xxx |
-| **statistics.digg_count** | `statistics.digg_count` | `like_count` | `like_count` | `like_count` | `…interact_info.liked_count` | `like_count` | `like` | `like_count` | `like_count`(常0) | — | `like_count` |
-| **statistics.comment_count** | `statistics.comment_count` | `comment_count` | `comment_count` | `comment_count`(常null) | `…interact_info.comment_count` | `comment_count` | `review` | `comment_count` | — | — | `comment_count` |
-| **statistics.share_count** | `statistics.share_count` | `share_count` | `share_count`(detail准) | `share_count`(常null) | `…interact_info.shared_count` | `share_count` | —(search无) | `share_count` | — | — | `share_count`(常null) |
-| **statistics.collect_count** | `statistics.collect_count` | `collect_count` | `collect_count` | `collect_count`(常null) | `…interact_info.collected_count` | `collect_count` | `favorites` | `collect_count` | — | — | `collect_count`(常null) |
-| **statistics.play_count** | —(归0) | —(null) | `view_count` | — | — | — | `play` | `play_count` | `view_count`(阅读) | `view_count`(带'views'正则) | `view_count`/`play_count`(常null) |
-| **create_time** | `create_time`(秒) | `publish_timestamp`(ms) | `publish_timestamp`(ms) | `publish_timestamp`(ms) | `…publish_timestamp`(秒) | `publish_timestamp`(ms) | `pubdate`/`senddate`(秒) | `publish_timestamp`(ms) | `time`(秒)/detail `publish_timestamp`(ms) | `published_time`(相对文本) | `publish_timestamp`(ms) |
-| **has_more** | `data.has_more` | `data.has_more`(false) | `data.has_more`(false) | `data.has_more` | `data.has_more` | `data.has_more`(false) | `data.has_more` | `data.has_more`(false) | `data.has_more` | `data.has_more` | `data.has_more`(false) |
-| **next_cursor** | `data.next_cursor`(页码串) | null | null | `data.next_cursor`(数字) | `data.next_cursor` | null | `data.next_cursor`(页码串) | null | `data.next_cursor`(页码串) | `data.next_cursor`(base64长token) | null |
-| **platform_content_url** | `share_url` | `content_link` | `content_link` | `content_link`(export编码) | —(拼 explore/{id}) | `content_link` | `arcurl` | `content_link` | `url`/detail `content_link` | `url`(watch) | `content_link` |
-
-> github 为代码仓库非视频:`platform_content_id=id`、`platform_author_id=owner_id/owner_login`、`tags=topics`、url 须拼 `https://github.com/{owner}/{repo}`;不入主表。
-
-## 二、提取陷阱注(落库前必读)
-
-1. **小红书 note_card 嵌套**:搜索接口内容字段全部在 `item.note_card.*`(及 `note_card.user.*`、`note_card.interact_info.*`)下,**不要在顶层找**;但详情接口是扁平 `channel_*` 结构,两接口映射不同。
-2. **公众号 biz 正则**:作者标识须从 `url` 用 `__biz=([^&]+)` 抽(search 的 `biz`/`nick_name` 恒为空串);detail/account_info 另给网关 `channel_account_id`(hash),**与 biz 不是同一标识**,两者都要沉淀;昵称只能走 `account_info.account_name` 补。
-3. **时间戳秒/毫秒不一致**:抖音search/B站search/公众号search/小红书search=**秒**;抖音detail/快手/B站detail/小红书detail/youtube/视频号=**毫秒**。同一平台不同接口也可能不同(小红书、公众号 search 秒 vs detail 毫秒)。落库前按接口统一换算。
-4. **抖音 raw vs 归一化两套**:搜索=`aweme_id`/`author.sec_uid`/`statistics.digg_count`;详情=`channel_content_id`/`channel_account_id`/`like_count`。同一条内容字段名不同,提取层分别映射。
-5. **高亮标签剥离**:B站 `title`、公众号 `title`、github `hl_name`/`hl_trunc_description` 含 `<em>` 高亮,落库前正则去 `</?em[^>]*>`。
-6. **B站作者**:`uid` 恒为 0,**禁用**,以 `mid` 为作者主键;`play/like/favorites/review` 对应 play/digg/collect/comment。
-7. **youtube 双作者体系**:search `channel_id`(`UC...`)与 detail `channel_account_id`(32位 hash)不是同一编码,建议以 `UC...` 为稳定主键;`view_count` 带逗号与 `views` 文本需正则抽数字。
-8. **tag 缺口**:公众号整平台无 hashtag(`topic_list` 恒 null);youtube 搜索无 tag,须从 `detail.body_text` 正则 `#([^\s#]+)` 抽;快手/视频号 `topic_list` 常 null,靠 desc/title 正则。