起因
朋友丢过来一个链接,问我能不能写个接口,输入豆包的分享链接就返回无水印的视频直链。
需求很明确:部署在 PHP 环境里,访问 ?url=分享链接 就吐 JSON 结果。
听起来像是抖音去水印那一套,心想应该不难。
第一阶段:抄作业
先去 GitHub 上找了一圈,发现有个浏览器js插件,正好是类似效果的。直接拉到本地看源码,核心流程大概是这样:
- 请求 thread 页面的 HTML
- 从 HTML 里提取
fallback_api这个接口地址 - 把这个地址里的参数改成
logo_type=unwatermarked, channel=no, codec_type=8 - 请求修改后的接口,返回的是一段加密数据
- 用 AES-128-CBC 解密这段数据,得到真正的视频直链
加密这块研究了半天。

数据体前面 4 个字节是 qAAB 标记头(0xA8 0x00 0x01 0x00),跳过这 4 字节后是密文。
解密用的 key 和 iv 是从 URL 参数里的 key_seed 派生出来的:先 SHA-512 哈希一次,再拼接一个固定盐值做第二次 SHA-512,前 16 字节是 key,后 16 字节是 iv。
盐值是个写死的十六进制字符串。
第一版写好测试,发现 thread 类型的链接完美工作,拿到了无水印直链。
第二阶段:碰壁
正当我以为搞定的时候,朋友又丢来一种新链接:/video-sharing?share_id=xxx&video_id=xxx。和 thread 完全不同,这种链接是豆包内生成 AI 视频后分享出来的。
试了一下,页面完全不包含 fallback_api。不管怎么请求都是空的。
对比了一下两种页面的 HTML 大小:thread 页面大概 70KB 而且包含了完整的 SSR 数据(服务端渲染),video-sharing 页面直接飙到 140KB,全是 SPA 壳子,真正的数据得靠 JS 动态加载。
尝试过的路子
chain/single 接口:给 share_id 请求 /aweme/v2/chain/single,返回的是一个 HTML 登录页。这条路不通。
get_play_info 接口:POST /samantha/media/get_play_info,这个能通,返回了视频的分辨率、时长、封面这些元数据。但 fallback_api 字段是空的。
用 share_id 当 thread ID:试试请求 /thread/{share_id},确实返回了 70KB 的 HTML,里面也有 _ROUTER_DATA,但 fallback_api 依然不存在。而且 SSR 数据里显示的是”会话过期,请重新登录”。
get_video_share_info:POST 请求 /creativity/share/get_video_share_info,这个是后来发现的。能返回视频的 prompt(生成提示词)、用户信息、以及一个带水印的播放链接。
浏览器扩展插件的启示
为了搞清楚别人到底是怎么做的,又去下了一个浏览器扩展的完整源码分析。
它用了 Chrome 的 debugger API,在用户打开豆包聊天页面的时候,拦截 doubao.com/im/chain/single 这个接口的响应。这个接口返回的数据里确实包含了 fallback_api。
但问题在于:
- 这个接口需要 protobuf 编码的请求体
- 需要用户登录后的 Cookie
- 而且请求格式是豆包聊天服务的私有协议
换言之,在服务端 PHP 里根本无法复刻。这条路是浏览器专属的,必须用户已经登录了豆包才行。
第三阶段:认命和取舍
经过一轮轮的测试,基本把豆包视频的解析边界摸清楚了:
| 链接类型 | SSR 数据 | fallback_api | 无水印 |
|---|---|---|---|
| /thread/xxx(公开分享) | 有 | 有 | 可以 |
| /thread/xxx(需登录) | 有,但缺 fallback | 无 | 不行 |
| /video-sharing(公开分享) | 无 | 无 | 不行 |
| /video-sharing(登录态) | N/A | 有(需拦截) | 仅浏览器可行 |
最终的结论是:thread 类型的公开分享链接可以做到无水印直链解析;video-sharing 类型在不登录的情况下,只能拿到带水印的视频链接和元数据(或许也能拿到无水印的链接,只是我还没研究透)。
最终的 PHP 实现
整合下来,处理流程分成两条线:
thread 链接:
- GET 请求页面 HTML
- 正则提取 video_id 和 fallback_api URL
- 修改 fallback_api 参数(去水印标识)
- 请求修改后的接口获取加密数据
- 提取 key_seed,派生 AES key/iv,解密得到无水印直链
video-sharing 链接:
- POST 请求
get_video_share_info获取 share 元数据(prompt、用户名) - POST 请求
get_play_info获取视频详情(分辨率、时长、封面) - 返回带水印的直链 + 元数据,附加说明告知需要登录才能去水印
收尾
整个研究过程大概花了一个晚上,大部分时间花在试各种 API 端点上。倒是搞清楚了一件事:豆包对视频分享的权限控制分得很细。thread 是对话分享方式,数据都打在页面里;video-sharing 是直接分享视频,把敏感数据藏到了后面。
如果之后有兴趣搞更完整的方案,但现在暂时不折腾了。