# 抖音 接口台账

> ⚠️ 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`,游走层不要挂该边。
