# 跨平台字段映射(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 正则。
