← 返回文章列表

合集链接下不了,是因为它压根不是「一个视频」

一条抖音合集链接背后是一串视频,三个看着都像下载地址的字段里只有一个不带水印。把从 JS 里翻接口、挑流、验水印这一趟记下来。

有人发来一条抖音的合集链接,五集,讲电容的三种串法,都是干货,想存到本地慢慢看。

我拿平时用的那个解析器一贴,回来一句"资源暂不支持解析"。又换了两三个,结果一样。

后来才想明白问题出在哪:那条链接背后根本不是"一个视频",是一串视频。平时那些解析器做的是单个视频的事——拿一条分享链接换一个播放地址,一次一条。合集不是这条路径能处理的形状。

竖排视频卡片与桌上一叠卡片
一条链接进去,出来的是一串

一条分享链接,两种东西

抖音的分享短链,长这样:

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。

正确做法是顺着前端代码走:

  1. 抓 share 页 HTML,grep 'src="[^"]*\.js"' 找出所有脚本;
  2. 找到名字里带 mix 的那个 chunk;
  3. 顺着它引用的大包,在压缩过的 JS 里 grep -oE '/aweme/v1/[a-z/]*mix[a-z/]*';
  4. 真实的接口路径和它要求的那一串参数,就写在里面。

这比在十几个候选路径里瞎撞快得多。而且有些参数猜不出来,只能从 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;标题里的全角冒号和括号在部分播放器上显示异常,落盘前统一清成下划线。

这件事的技术含量其实不高,难的是"它不是你以为的那种东西"——一条链接看着像单视频,背后是一套列表;一个地址看着像下载链接,其实带水印;一个参数看着多余,删了就报错。

三个误解都在同一层:看起来是同一种东西,实际是三种。想清楚这层,剩下的就是照着接口把循环写完。

▍ 本文由自建 Agent 调度内容流水线自动完成:写稿 → 模型 API 生图 → R2 图床 → 排版 → MCP 落站。 ▍ 配图由模型 API 生成、托管在对象存储上;内容仅供参考。