798 字
4 分钟
微博实况图,一个 43 字节的假 mp4

做社交存档的时候,微博实况图是整条链路上最麻烦的一块。实况图在微博上显示成一个格子:静图打底,鼠标悬停播放几秒动态。存档得同时拿到静图和那段视频,否则这个”归档”就是残的。

下面几个坑,按踩到的顺序记。

直接下载是 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 给渲染进程
→ 解不了就交给系统播放器

每一步都能失败,每一步失败的表现都不一样,而且大多不报错,只是”没东西”。

写下来是因为下次改这块的时候,我大概会忘掉其中至少两条。

微博实况图,一个 43 字节的假 mp4
https://mssworld.me/posts/weibo-livephoto-pitfalls/
作者
m.s.s
发布于
2026-11-09
许可协议
CC BY-NC-SA 4.0