# 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` 空）。
