Files
douyin_video_download/PROGRESS.md
whale-fall-code 2bb83f3685 初始提交:抖音视频批量采集流水线
App 端免签名接口(设备注册 + detail + CDN 直链),一体化流水线
下载→抽音→传COS→记录→删本地,AI 作品四档判定,四份文档:
README(使用指南)、PROGRESS(批次进度)、RE(逆向全过程)、AI(判定规则)。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 16:31:10 +08:00

391 lines
23 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 任务进度 / 踩坑记录
> 项目介绍和各代码文件说明见 [README.md](README.md)。本文档记录接口踩坑、
> 各批筛选与下载进度、已知问题与待办,随批次滚动更新。
## 抖音视频批量采集 → 音频提取 → COS 上传
把一批 `douyin.com/video/...` 链接跑成 ASR 可用的数据集:
**下载无水印 mp4 → 抽 mp3 → 探时长 → 传腾讯 COS → 出清单(jsonl / xlsx)**。
全程纯 Python,不依赖第三方解析服务、不依赖 JS 运行时、**不需要登录**。
---
## 1. 结论先行:接口怎么走通的
> 这些结论的验证过程和完整时间线见 [RE.md](RE.md)(逆向全过程复盘)。
> 这里只放结论,改代码前先看。
| 路线 | 状态 | 说明 |
|---|---|---|
| web `/aweme/v1/web/aweme/detail/` | **要登录** | 匿名请求返回 HTTP 200 **空 body**。真实原因在响应头 `X-Whale-Throughput-Abort-Data`(base64 JSON):`{"content":"anonymous","name":"强制登录"}`。跟 a_bogus 签名对不对**无关**——伪造的随机 a_bogus 和完全不签名,表现一模一样。 |
| App `/aweme/v1/aweme/detail/` | **可用,且不用签名** | 先打 `/service/2/device_register/` 拿 `device_id` / `install_id`,带上就能取详情。**不需要 X-Gorgon、不需要 Cookie、不需要登录。** 这是本项目默认路径。 |
| CDN `play_addr` 直链 | 免鉴权 | `aweme.video.play_addr.url_list` 里的 douyinvod 地址没有防盗链,裸 `requests` 直接下。 |
| `/aweme/v1/play/?video_id={uri}` | 免鉴权 | 302 跳 CDN,零鉴权。下载走的就是它。 |
### 两层限流(跑批量必然撞上)
1. **设备级 detail 配额**:单个 `device_id` 约 **575 次** detail 调用后被限,表现为 HTTP 200 空 body。
→ `douyin/batch.py` 的 `DevicePool` 到配额就换设备;用 `generation` 标记当前设备,避免多个 worker 同时发现失败把设备连换好几次。
2. **IP 级注册配额**:短时间内注册约 **11~12** 次后,**所有** register host 一律返回 `device_id='0'`。
→ `Device.register()` 指数退避重试(默认 5 次,30s 起步翻倍),让批处理**停下来等**,而不是空转着把剩余任务全标成失败。
第 2 条是踩过的坑:早期没有退避,一次注册受限直接烧掉 678 条任务。
### 作品拿不到时怎么区分
看 `filter_detail.filter_reason`:
- `images_base` → 图文作品(图集),本项目不处理,抛 `NotAVideo`
- `status_self_see` / `status_audit_self_see` → 仅作者自见,实际不可用
---
## 2. 目录结构
```
├── pipeline.py 【核心】一体化流水线:下载→抽音→上传→记录→删本地
├── scripts/
│ ├── clean_ai.py 从链接 jsonl 里筛出 AI 相关作品,出链接清单
│ ├── jsonl_to_xlsx.py jsonl → xlsx(认 records/records.jsonl 和 records/manifest.jsonl 两种)
│ └── export_sample_xlsx.py records.jsonl → 交付用样本 xlsx(支持 --ai-only / --limit)
├── tests/
│ ├── test_smoke.py 离线冒烟测试(不联网)
│ ├── test_meta.py 标题/标签拆分、记录追加的测试
│ ├── test_aiclass.py AI 判定的测试(用例全取自真实数据)
│ └── test_pipeline.py 流水线端到端测试(ffmpeg 真跑,网络和 COS 打桩)
│
├── douyin/
│ ├── app.py 【核心】设备注册 + App 端 detail
│ ├── batch.py 并发批处理、DevicePool 设备轮换
│ ├── download.py 带 Range 续传的下载、去重 Registry
│ ├── meta.py 标题/标签拆分、records.jsonl 追加写
│ ├── aiclass.py 判定作品是不是 AI 相关,分四档,见 §4.5
│ ├── cos.py COS 上传 + 上传后大小校验
│ ├── audio.py ffmpeg/ffprobe 薄封装:找可执行文件、拼命令、探时长
│ ├── urls.py 短链还原、aweme_id 提取
│ ├── client.py Aweme/DouyinError 等共用类型 + web 后端请求路径
│ ├── signer.py a_bogus 签名(仅 web 后端用,当前跑不起来,见下)
│ └── crypto.py SM3 / RC4 / 自定义 Base64(未完成的纯 Python 签名器的积木)
│
├── data/ 源数据(外部抓来的,clean_ai.py 的输入。删了没法重新筛,别动)
│ ├── 抖音链接20260827_40087.jsonl 40087 条
│ ├── 抖音ai视频_20260831_87110.jsonl 87110 条(其实是露营帐篷爬取,AI 占 3.3%)
│ ├── 抖音ai视频_20260901_57261.jsonl 57261 条(同上,AI 占 2.8%)
│ ├── 抖音ai视频_20260902_42391.jsonl 42391 条(9 月 AI 批,AI 占 90.9%)
│ ├── 抖音ai视频_20260904_62171.jsonl 62171 条(同上,AI 占 88.3%)
│ ├── 抖音ai视频20260908_118469.jsonl 118469 条(同上,AI 占 95.1%)
│ ├── 所有作品.csv 4839 条,字段与 jsonl 不同,见 §4.5 第 8 条
│
├── records/ 流水线产物与断点记录
│ ├── records.jsonl 全字段元信息(含 raw),断点续跑靠它
│ ├── pipeline_failed.txt 失败清单:aweme_id / 阶段 / 原因(重跑据此续传)
│ └── manifest.jsonl 老产物:第一批 5393 条的 3 字段清单,见 §5
│
├── exports/ 交付/导出产物(xlsx / csv)
├── lists/ 各批的链接清单,也是"哪批下了什么"的凭据
└── logs/ 各批跑批日志
`work/` 是流水线的临时目录,跑的时候自建、传完即删,仓库里不留。
**web 后端目前是跑不起来的,别把它当后路。** 具体状态:
- `client.py` 里 `Aweme` / `DouyinClient` / `DouyinError` 这几个类型被 `app.py`、`batch.py`
正常用着,**不是死代码**;只有它的 web 请求路径闲置。
- `signer.py` 两个后端都不可用:`NodeSigner` 要 `node_signer/sign.js`,**这个目录不在项目里**;
`ABogusSigner` 是纯 Python 实现,它自己的文档写着"剩字节拼装顺序待从 ab.js 逆出来",没写完。
- `crypto.py` 的 SM3 / RC4 / 自定义 Base64 对过官方测试向量(`test_smoke.py` 每次都验),
但它只是那个**没写完的** `ABogusSigner` 的积木,`signer.py` 并不 import 它。
所以真要走 web 端,得先补 `node_signer/`(或者把 ab.js 逆完)**再**准备登录 Cookie。
App 端被封的话这里没有现成退路,得先做上面这件事。
**本地媒体文件已全部删除**(`downloads/` 的 5393 个 mp4、`audio/` 的 5393 个 mp3),东西都在 COS 上。旧的分阶段脚本依赖本地文件,要用得先重新下载;`pipeline.py` 不受影响——它本来就是边跑边删。
输入链接文件 `all_video_links(1).txt` 也已不在,重跑需要重新提供。
---
## 3. 环境
```bash
pip install -r requirements.txt
```
另需系统里有 `ffmpeg` / `ffprobe`。实测环境:Python 3.11.0 + ffmpeg 2025-07-31 (gyan.dev full build)。
COS 凭证走环境变量,别写进命令行(会留在 shell history 里):
```powershell
$env:COS_SECRET_ID = "..."
$env:COS_SECRET_KEY = "..."
```
---
## 4. 一体化流水线(推荐)
```bash
$env:COS_SECRET_ID="..."; $env:COS_SECRET_KEY="..."
# 先小批试跑,加 --keep-local 不删本地文件,人工看一眼产出
python pipeline.py --from-file links.txt --region ap-chongqing --limit 20 --keep-local
# 确认没问题再放开
python pipeline.py --from-file links.txt --region ap-chongqing --workers 8
# 出 xlsx(--columns full 带上标题、作者、标签、发布时间)
python scripts/jsonl_to_xlsx.py -i records/records.jsonl --columns full -o "exports/抖音-四川话.xlsx"
# 出交付样本 xlsx(--ai-only 只导 AI 档,--limit 限量)
python scripts/export_sample_xlsx.py --ai-only -o "exports/AI视频样本.xlsx"
```
每条视频端到端走完 9 步才算完:下载 → 抽 mp3 → 探时长 → 校验 → 传两个对象
→ 核对大小 → 写 record → 删本地。**磁盘峰值只有 `workers × (mp4+mp3)`,
约 100 MB,与总量无关**——分阶段跑得先摊 53 GB。
元信息全进 `records/records.jsonl`,一行一条,`raw` 内嵌,字段见下。断点续跑靠它,
重跑同一批会跳过已完成的(连下载都不会发生)。COS 上同名对象一律覆盖。
`pipeline.py` 的 `--records` / `--failed` 默认就指向 `records/` 目录。
### 两道闸门
删本地文件是唯一不可逆的操作,所以卡两道,任一不过就保留文件、记 failed、继续下一条:
1. **抽取闸门** —— mp3 用 `ffprobe -count_frames` **实际解码**算时长,与 mp4 比对,
差超过 `max(1s, 2%)` 判定截断。
注意不能用 `format=duration`:它读的是 LAME 的 Xing 头,声明的是原始总帧数。
实测把 mp3 截断到 20%,`format=duration` 报的仍是完整的 10.0078s,一字不差;
`ffmpeg -f null -` 同样拦不住(rc=0、无 stderr)。只有实解码能发现。
2. **上传闸门** —— 不信 `upload_file` 没抛异常,而是 `head_object` 取回
`Content-Length` 跟本地字节数比对,两个对象都对上才删。
### records.jsonl 字段
```json
{"aweme_id":"...","title":"剥掉标签的纯标题","desc":"服务端原样",
"tags":["..."],"author":{"nickname":"...","uid":"...","sec_uid":"..."},
"create_time":1582635538,"create_time_str":"2020-02-25 21:38:58",
"duration_sec":41.702993,"duration_ms_api":41750,
"stats":{"digg":0,"comment":0,"collect":0,"share":0,"play":0},
"music":{...},"cover_url":"...","video":{"uri":"...","width":1280,"height":720},
"cos_mp4":"cos://...","cos_mp3":"cos://...","size_mp4":0,"size_mp3":0,
"source_url":"...","fetched_at":0,"raw":{ ...服务端原始 JSON... }}
```
三条要注意的:
- **`aweme_id` 必须是每行第一个字段**。续跑扫描用正则从行首抠 id,不解析整行
(几百 MB 的文件差别是 ~1 秒对十几秒)。重排字段不会报错,只会让启动悄悄变慢。
- **`duration_sec` 和 `duration_ms_api` 不是一回事**。前者是 ffprobe 实测,后者是
服务端声明值,实测同一条 41703 vs 41750 ms。切分要用 `duration_sec`。
- **`tags` 取自 `desc` 的正则全集,不是 `text_extra`**。实测 12 条样本里有 2 条的
标签在 `desc` 里有、`text_extra` 里没有,以后者为准会静默丢标签。
`raw` 内嵌导致单行几十 KB、整个文件十几 GB,所以读取一律逐行流式,
`jsonl_to_xlsx.py` / `export_sample_xlsx.py` 已经改成流式了。
---
## 4.5 从链接 jsonl 里筛 AI 作品
> 判定规则的逐条详解(词表选词标准、误收数据、否决清单、维护指南)见 [AI.md](AI.md)。
`clean_ai.py` 吃的是外部抓来的链接 jsonl(带 desc / hashtags / author / ai 等字段),
判定逻辑在 `douyin/aiclass.py`,分四档,取最强的一档:
| 档 | 依据 | 含义 |
|---|---|---|
| `platform` | `ai.is_aigc` | 抖音自己打的 AIGC 标 |
| `strong` | 文案/标签有 AIGC、AI 生成工具名、ai+创作类词、AI 赛事标签 | 作品声明是 AI 做的 |
| `topic` | 只提到 AI 本身(ai 教程、人工智能、大模型、ChatGPT) | 在讲 AI,不一定是 AI 做的 |
| `author` | 只有作者昵称或音乐标题带 AI | 最弱,会误收 |
```bash
python scripts/clean_ai.py -i 抖音链接xxx.jsonl --stats # 先看分布
python scripts/clean_ai.py -i 抖音链接xxx.jsonl --tier platform,strong -o ai # 出 ai.jsonl + ai.txt
python pipeline.py --from-file ai.txt --region ap-chongqing # 直接接流水线
```
**要注意的**(都是从真实数据里挖出来的,改规则前先看 `test_aiclass.py`):
1. **`platform` 是唯一能抓到"作者没标但确实是 AI"的信号。** 实测 1744 条被平台标了
AIGC,文案标签里一个 ai 字都没有(脑筋急转弯、历史解说、原创动画)。纯关键词过滤
会整片漏掉。反过来 9000+ 条有明确 AI 信号却没被平台标——两路互补,都得要。
2. **`Al`(大写 A + 小写 L)冒充 AI。** 标签 `抖音al` 出现 129 次,文案里也有 `#Al创作`。
不归一化就漏。反过来 `ai` 是 email/detail/again/hair 的常见片段,只能认前后不接
字母数字的独立 `ai`。
3. **音乐标题默认是 `@<作者昵称>创作的原声`**,40087 条里 32555 条是这个壳。昵称带 AI
会连"创作"一起命中,等于让作者昵称从 music 这条路偷偷升到 `strong`——实测 771 条
这样混进来。现在先剥壳,剥完只当弱信号(全库剥完只剩 16 条还有命中,本来就没价值)。
4. **中文泛词不单独成立。** `动画`/`绘画`/`分身`/`动漫` 必须跟 AI 词同现,否则 830 条
`#原创动画` 全被误收。`剪映`(375)/`关键帧`(314)/`蒙版`(203)/`转场` 是剪辑术语,不是 AI。
5. **工具名不含 ai 字样的一大票**:即梦 / seedance / libtv / flovaai / 小云雀 /
runninghub / oiioii / shotlab / veo3,只能靠名单,名单要跟着平台活动更新。
6. **一批工具名本身是常用词,必须要求跟 ai/aigc 同现**(见 `_AMBIG_GEN` / `_AMBIG_CHAT`)。
实测误判:豆包 224 条(狗名、豆沙包)、万相 17 条(《龙神万相》)、海螺 13 条
(三峰的"海螺"帐篷)、元宝 11 条(316 不锈钢元宝勺)、星流 1 条("明星流量"子串)、
清影 1 条("起舞弄清影")。而真命中的**无一例外**都同时带 AI 字样,所以这道门槛
不掉召回——旧文件重跑 15218 → 15204,掉的正好是这些。
7. **英文工具名要挡词中词,但只挡字母,不挡数字和中文。** `udio` 藏在 `Grass Studio`
里 17 次、`flux` 藏在 `Fluxxwave` 曲名里。可是数据里满地都是 `Seedance2.0`、`sora2`、
`runninghub0.21`、`libtv创作者`、`flovaai超创`,一刀切加 `` 会丢 3500+ 条真命中。
8. **别放开"数字人"/"换脸"/"分身"当独立信号。** 数字人民币含"数字人"、微信分身含"分身"、
穿搭视频也说"这套穿搭能换脸"。实测放开只进误判,一条真的都捞不到。
同理否掉的还有:lora(撞 valorant)、tripo(撞 tripod 三脚架)、seaweed(海藻)、
度加("亏损幅度加大")、万彩("29万彩礼")、生数("诞生数副名场面")、节点("关键节点")、
抽卡(原神)、捏脸(燕云十六声)、国漫(《剑来》真人制作)、对口型(真人对口型)。
这 8 条否决理由都写成了测试,见 `test_rejected_candidates_stay_none`。
9. **AI 还有第三种冒充写法:罗马数字。** `#AⅠ漫剧` 用的是 U+2160,跟 `Al` 一个套路。
10. **csv 的原声标题多一截 ` - 昵称`**(`@X创作的原声 - X`),剥壳正则要一起覆盖,
否则第 3 条那个作者昵称升档的 bug 会在 csv 输入上复发。
### platform 档要不要求文案佐证
抖音给 **AI 配音 / AI 封面**的普通带货视频也打 AIGC 标。露营帐篷那两批里,
`platform` 档有一半(2045 条)文案和作者昵称都零信号,点开是"四脚伞帐篷带广告位"
"抖音私密账号是什么"。`--platform-needs-text` 就是卡这个的,作者昵称带 AI 也算佐证。
**但这个开关会误伤一类真 AI**:@栖光 那种做 AI 古风美图的,文案只写
`#古风 #国风古韵 #意境`,一个 AI 字都不会写。这类只有平台标签能抓到。
所以无佐证的那批不是垃圾,得按"是不是带货"再分一刀(见 `lists/platform无佐证_*.txt`)。
### 抖音的 AIGC 字段:认 risk_infos.type,别认 is_aigc_media
`raw.risk_infos.type` 是数字码,一一对应:
| type | content | 含义 |
|---|---|---|
| 44 | 作者声明:内容由 AI 生成 | 作者主动声明,19946 条 |
| 47 | 疑似使用了 AI 生成技术,请谨慎甄别 | 平台检测,措辞不确定,515 条 |
| 76 / 328 | 作品含 AI 生成内容 | 平台判定,448 条 |
**这几个才是 AI。**下面这些是别的免责声明,跟 AI 无关:
| type | content | 条数 |
|---|---|---|
| 45 | 作者声明:虚构演绎,仅供娱乐 | 868 |
| 336 / 351 | 个人观点,仅供参考 | 111 |
| 54 | 作者声明:内容来源于网络 | 13 |
| 335 | 作者声明:内容含营销推广信息 | 10 |
**已知 bug**:爬虫那层把「`risk_infos` 有内容」就当 `is_aigc=true`,把上面这些
免责声明也算进 `platform` 档了,全库误算 **1029 条**。修法见 §7。
另外 `raw.is_aigc_media` 全库 24611 条**全是 `false`**,名字最像却完全没用,别信。
### 各批筛选结果
| 源文件 | 总条数 | AI 占比 | 出的清单 |
|---|---|---|---|
| 抖音链接20260827_40087.jsonl | 40087 | 39.1% | `ai_videos.txt` 15218 |
| 抖音ai视频_20260831_87110.jsonl | 87110 | **3.3%** | `ai_87110_严格.txt` 1366 |
| 抖音ai视频_20260901_57261.jsonl | 57261 | **2.8%** | `ai_57261.txt` 776 |
| 抖音ai视频_20260902_42391.jsonl | 42391 | 90.9% | `ai_42391.txt` 37703 |
| 抖音ai视频_20260904_62171.jsonl | 62171 | 88.3% | `ai_62171.txt` 52360 |
| 抖音ai视频20260908_118469.jsonl | 118469 | 95.1% | `ai_118469.txt` 106447 |
| 所有作品.csv | 4839 | 未筛 | `csv_links.txt` 4839(全量下的) |
前两个文件(87110 / 57261)名字叫"ai视频",**实际是露营帐篷带货爬取**
(标签 TOP:户外露营 10568、帐篷 9346),AI 只占 3% 左右。9 月的三批
(42391 / 62171 / 118469)才是真 AI 池,且 platform 档占绝对大头——这三批
`allow_download=false` 里 `aigc_forbidden` 多达 2.8 万 / 3.7 万 / 7.5 万条,
但 App 端接口不受此限(见下)。拿到新文件先跑 `--stats` 看一眼,别信文件名。
`aigc_forbidden` 那道限制**只作用于网页端,App 端接口不受影响**——已用 20 条探针
验过,9 条 web 侧 `allow_download=false` 的全都正常下下来了。不用 `--exclude-forbidden`。
### lists/ 里各清单的关系
| 清单 | 条数 | 是什么 |
|---|---|---|
| `all_video_links(3).txt` | 3557 | 第一批输入 |
| `ai_videos.txt` | 15218 | 40087 批筛出的 AI |
| `csv_links.txt` | 4839 | 所有作品.csv 全量(未筛) |
| `ai_87110_严格.txt` / `ai_57261.txt` | 1366 / 776 | 露营两批筛出的 AI |
| `ai_42391.txt` / `ai_62171.txt` / `ai_118469.txt` | 37703 / 52360 / 106447 | 9 月三批筛出的 AI(platform+strong 档) |
| `ai_合并待下载.txt` | 66411 | 62171 全量 + 42391 部分 + 118469 无,合并去重后的跑批输入 |
| `ai_待下载.txt` | 2611 | 旧池剩余(platform 无佐证两档 + 零星),已下过 99%,基本作废 |
| `platform无佐证_非带货.txt` | 776 | AI 古风/漫剧/语录图文 |
| `platform无佐证_带货.txt` | 1331 | 待下(不建议):帐篷等实拍带货 |
截至 2026-09-29,9 月三批的下载进度都是 97%+(118469 批 103702/106447、
42391 批 37254/37703、62171 批 50989/52360),三批合计还有约 4565 条
不在 records.jsonl 里(含图文、已删除等下不动的)。
## 5. 自检
```bash
python tests/test_smoke.py # 离线冒烟:SM3/RC4/base64 自测、id 提取、Registry
python tests/test_meta.py # 标题/标签拆分、records.jsonl 追加与续跑
python tests/test_aiclass.py # AI 判定(用例全取自真实数据)
python tests/test_pipeline.py # 端到端:ffmpeg 真跑,网络和 COS 打桩
```
### 老产物 manifest.jsonl
`records/manifest.jsonl` 是第一批 5393 条四川话数据的清单(`cos_mp4` / `cos_mp3` / `duration` 三字段),
`exports/抖音-四川话.xlsx` 就是它的 xlsx 版。**它和 `records/records.jsonl` 零重合**——那 5393 条不在
`records/records.jsonl` 里,所以这个文件是这批数据在 COS 上的唯一记录,别删。
生成它的那套分阶段脚本(`main.py` / `extract_audio.py` / `probe_durations.py` /
`upload_cos.py`)已经删掉,功能全被 `pipeline.py` 覆盖了。这个项目没有 git,删了就是真没了——
但那四个脚本做的事 `pipeline.py` 一条命令全包含,唯一不能复现的是 manifest.jsonl 的三字段老格式。
其中 `probe_durations.probe()` 被 `pipeline.py` 用着,已搬进 `douyin/audio.py`。
---
## 6. 跑批结果
`records/records.jsonl` 累计 **219144 条 / 8676.02 小时 / 2483.43 GB(mp4)**,
全部在 `cos://asr-dig-data-1253181912/0005/zh/`。
9 月三批 AI 池(42391 / 62171 / 118469)已基本下完,各批进度 97%+。
早期按 AI 判档的拆分(platform 有佐证 5423 条 / strong 9036 条等)是 9/1 时点的
快照,那之后 records.jsonl 又进了十几万科目的 9 月 AI 批,旧数字只作历史参考。
### COS 前缀 0005/zh/ 混了八批
```
5393 四川话 只在 manifest.jsonl 里,不在 records.jsonl
3517 第一批链接
14884 AI 筛选批(40087 源)
4813 csv 全量批
2093 87110 + 57261 两批
37254 42391 批
50989 62171 批
103702 118469 批
```
**按前缀取数会一次拿到全部**,要区分只能走 `records.jsonl`。
## 7. 待办 / 已知问题
**该修的**
- **`is_aigc` 把免责声明当成了 AIGC**,全库误算 1029 条(见 §4.5)。爬虫那层应改成
`risk_infos.type in {44, 47, 76, 328}`,并把 `type` 一起存进记录——这样才能区分
「作者声明」(44) 和「平台检测」(47),两者可信度不同。改完 `platform` 档会掉约 1029 条,
真 AI 一条不少(那些走 strong)。
- **图文作品(`aweme_type` 68/61/55/163)不支持**,需要 `images[]` 分支。这是目前失败
的绝对大头:87110 批 38 条失败里 33 条是图文,AI 池里累计 241 条下不了。
图文在按 aweme_id 排序的清单里**扎堆在头部**(早期 AI 内容多是 Midjourney 出图),
所以跑批前几百条失败率会飙到 15%,后面掉到 2-3%,别被吓到。
**已知取舍**
- 最长一条 3398.988 秒(约 57 分钟),若 ASR 侧对单条时长有上限需要另行切分
- `split_desc` 在标签名以 `#名字` 形式也出现在正文里时会连正文一起删(见
`test_split_repeated_tagname_in_body_is_lossy`);服务端给了可信偏移时不会
- 设备配额轮换的触发条件是**每 550 次 detail**,但到底是按 IP 还是按 UA/设备指纹限流
一直没验证过。实测跑到第 3 次轮换都正常,暂时不用管
- `topic` / `author` 两档从没进过下载清单。`author` 档现在 14 条**全部来自音乐标题**
(`【AI配文风景滤镜】的原声` 这类抖音模板),作者昵称一条都没有——因为凡是作者叫 AI 的,
作品本身也都标了 AI,昵称这条线提供不了增量,它的价值只是给 `platform` 档做佐证
**安全**
- 聊天记录里出现过一组 COS 密钥,**建议轮换**。凭证只能走 `COS_SECRET_ID` /
`COS_SECRET_KEY` 环境变量,`CosUploader` 不读命令行参数