做社交存档的时候,微博实况图是整条链路上最麻烦的一块。实况图在微博上显示成一个格子:静图打底,鼠标悬停播放几秒动态。存档得同时拿到静图和那段视频,否则这个”归档”就是残的。
下面几个坑,按踩到的顺序记。
直接下载是 403
API 返回的 pic_infos 里有个 video_url,看着就是视频地址。直接 GET,403。
微博对这个域名的媒体做了来源校验,从别的上下文请求一律拒绝。真正能用的是播放接口:
GET https://video.weibo.com/media/play?livephoto=<urlencode 后的原始地址>Referer: https://weibo.com/三个必须:
- Referer 必须是
https://weibo.com/,别的值直接 403 - 不要带 Cookie,带了反而被拒
- 不要自定义 Host,用 requests 时如果顺手把 API 请求的 Host 头带过去,同样 403
这三点都写进注释了。因为每次重构都有机会手贱把 Host 加回去。
接口会返回假文件
更麻烦的是,它不总是返回视频。限流、签名过期、目标不存在的时候,它返回一个 HTTP 200 的 JSON 错误体。把响应直接写进文件,磁盘上就多一个 .mp4:43 字节,内容是 {"code":"403",...}。
播放器打开一片黑。校验脚本也看不出来——文件存在、大小非零、扩展名对。
所以落盘前必须验:
if size < 64 or head[:1] in (b"{", b"<", b"["): # 不是视频,重下三个条件缺一不可:空文件靠大小识别,JSON 和 HTML 错误页靠首字节识别。
签名地址不能剥参数
video_url 上带 Expires 和 ssig,是短时效签名。很容易想到:既然最终要换成 play 接口,那先把查询参数剥掉再拼,干净。
不行。签名参数是播放接口校验的一部分,剥了就 403,跟第一条同一个下场。
所以元数据里存的一直是 API 返回的原始带签名地址,下载时才转 play URL。这样重跑、校验、迁移都不会因为地址被改坏而失效。
HEVC 在窗口里放不了
下载下来的是 H.264 QuickTime,正常情况下 Electron 能播。但有一部分是 HEVC(hvc1)。
而这个应用启动时关掉了 GPU——历史上 WebGL 崩过几次,直接一刀切。代价就是 Chromium 解不了 HEVC。
这不是下载失败,是渲染限制。所以卡片上得给一个”用系统播放器打开”的兜底,走 shell.openPath。判断依据也简单:文件在、ftyp 有、视频元素报错。
另外本地文件走自定义协议 social-archiver://,返回的 MIME 一律标 video/mp4,否则 <video> 连认都不认。
整条链路
API 元数据(带签名 URL) → 下载时转 play URL(Referer + 无 Cookie + 无 Host) → 落 .part → 校验大小与首字节 → os.replace 成正式文件 → 自定义协议 + video/mp4 给渲染进程 → 解不了就交给系统播放器每一步都能失败,每一步失败的表现都不一样,而且大多不报错,只是”没东西”。
写下来是因为下次改这块的时候,我大概会忘掉其中至少两条。