有人发来一条抖音的合集链接,五集,讲电容的三种串法,都是干货,想存到本地慢慢看。
我拿平时用的那个解析器一贴,回来一句"资源暂不支持解析"。又换了两三个,结果一样。
后来才想明白问题出在哪:那条链接背后根本不是"一个视频",是一串视频。平时那些解析器做的是单个视频的事——拿一条分享链接换一个播放地址,一次一条。合集不是这条路径能处理的形状。

一条分享链接,两种东西
抖音的分享短链,长这样:
https://v.douyin.com/xxxxx/
单视频的短链,跟着重定向走一趟,能拿到 aweme_id——单个作品的身份号。合集的短链跟过去看到的是另一个东西:
https://www.iesdouyin.com/share/mix/detail/<19位数字>/?schema_type=24&...
路径里这串 19 位数字就是 mix_id,合集的身份号。拿到它就知道"是哪一套",但还不知道"这套里有哪几集"——这两个信息是分开的两个接口。
顺便一句:别去爬那个 share 页面。它是单页应用的空壳,HTML 里一个视频数据都没有,grep aweme_id 恒为 0。真正干活的脚本在它加载的那些 JS 里。
能用的那条接口,是从 JS 里翻出来的
这是整件事里最值得记的一步。
找接口不是靠猜路径。老教程里写的 /aweme/v1/web/mix/aweme/ 现在已经回 Blocked by ArgusSecurityPlugin,要签名参数;照着自己编几个路径去试,基本全是 Url doesn't match。
正确做法是顺着前端代码走:
- 抓 share 页 HTML,
grep 'src="[^"]*\.js"'找出所有脚本; - 找到名字里带
mix的那个 chunk; - 顺着它引用的大包,在压缩过的 JS 里
grep -oE '/aweme/v1/[a-z/]*mix[a-z/]*'; - 真实的接口路径和它要求的那一串参数,就写在里面。
这比在十几个候选路径里瞎撞快得多。而且有些参数猜不出来,只能从 JS 里读——我这次要的 version_code=280500 就是,换别的数值直接回"参数不合法"。
最终能用的那条,免签名、免登录,GET 就通:
https://www.iesdouyin.com/aweme/v1/mix/aweme/?mix_id=<19位mix_id>&count=20&cursor=0&device_type=&device_platform=web&version_code=280500&version_name=28.5.0&aid=1128
返回的 aweme_list 里,每一项的结构和单视频完全一样。另外带 has_more 和 max_cursor,翻页时把 cursor 换成上一页给的 max_cursor,直到 has_more 变假。
两个参数特别容易翻车:device_type= 后面必须留空占位,整个参数不能删;version_code 必须正是那个值。少一个、错一个,回来的都是含糊的错误提示,不会告诉你哪里不对。

三个地址,只有一个不带水印
每一集的 video 对象里挂着好几组地址,长得都像"视频下载链接",但用途完全不同:
| 字段 | 水印 | 说明 |
|---|---|---|
video.bit_rate[].play_addr |
无 | 按清晰度分档,挑宽高与码率最大的那条 |
video.play_addr_h264 / play_addr |
无 | 兜底用,清晰度低一些 |
video.download_addr |
有 | App 里点"保存到相册"拿的就是这条 |
那条带水印的 download_addr 和干净的那几条是不同的文件,CDN 路径都不一样,不是同一个 URL 换换参数就能变干净。所以"把带水印的改成无水印的"这种想法不成立,压根不是一条路。
我这次取的是 bit_rate 里标着 720 的那一档,出来是 720×1280 的 HEVC,无水印。五集一共 272MB。
还有个细节:这些直链大概半小时就过期。过期了不用改代码,重新跑一遍列表接口,拿一批新的就行。
验证水印,别整帧看
下完必须验。我的做法是抽两个时间点各截一帧,但只裁左上角那一块放大看:
ffmpeg -v error -y -ss 3 -i in.mp4 -frames:v 1 -vf 'crop=iw*0.55:ih*0.18:0:0,scale=1400:-2' tl_3.png
ffmpeg -v error -y -ss 200 -i in.mp4 -frames:v 1 -vf 'crop=iw*0.55:ih*0.18:0:0,scale=1400:-2' tl_200.png
为什么不整帧看:这类教学视频的作者常在左上角叠自己的白色描边字,整帧缩到手机大小一看,那两行字很容易被当成水印,白折腾一轮。放大之后能分清"作者自己贴的字"和"平台水印"——前者有描边、位置固定,后者是半透明、带平台标识。
这里有个反直觉的结论:抖音的水印在下载版里是漂移的,同一个视频在不同时间点,水印位置不一样。所以事后想用裁剪、delogo、打码去处理,全都无效——你盖住一处,别处还在。
唯一的解法是拿本来就干净的那条源。这就是为什么前面挑地址那一步不能省。
走不通的那些路
这一节是给下次省时间的。我试过并且失败的方向:
| 路径 | 结果 |
|---|---|
/aweme/v1/web/mix/aweme/ |
Blocked by ArgusSecurityPlugin,要签名 |
/web/api/mix/aweme/、/web/api/v2/mix/aweme/ |
Url doesn't match,路径不存在 |
/web/api/mix/detail/?mix_id= |
能通,但只给元信息:合集名、作者、一共有几集,没有视频列表 |
/web/api/mix/list/ |
恒回"参数不合法" |
| 各种第三方解析 API | 单视频能解,喂合集就回"暂不支持" |
| 带签名的 Python 下载库 | 在手机端装不上,依赖要编译 Rust,签名链也不稳 |
| 合集分享页 HTML 本身 | 空壳,里面没有数据 |
另外几个小坑:合集被改名或下架之后,aweme_list 会返回空,所以要先只拉列表确认集数,再开始下载;有些作品是 HEVC,个别老播放器不认,得转成 H.264;标题里的全角冒号和括号在部分播放器上显示异常,落盘前统一清成下划线。
这件事的技术含量其实不高,难的是"它不是你以为的那种东西"——一条链接看着像单视频,背后是一套列表;一个地址看着像下载链接,其实带水印;一个参数看着多余,删了就报错。
三个误解都在同一层:看起来是同一种东西,实际是三种。想清楚这层,剩下的就是照着接口把循环写完。