乐于分享
好东西不私藏

写了一个可以自动下载B站视频全集的脚本

写了一个可以自动下载B站视频全集的脚本

最近孩子说想学围棋,于是,我到B 站着了一套《少儿动画学围棋》, 30 集,画风和内容我感觉都挺适合小孩子。

但我不能直接在他的ipad上安装哔哩哔哩,否则很有可能他会深陷哔哩哔哩无法自拔。

所以,我就想着把视频下载下来,然后发送到ipad上让他查看。

听起来是个十分钟的需求。我先去找了现成工具,折腾了小半天,最终还是决定自己写个脚本来解决,顺手把 B 站视频到底怎么取到的,从头搞明白了一遍。这篇就讲这个过程,以及那些工具到底卡在哪。

现成工具没解决的,恰恰是我唯一在意的

网上这类「 B 站视频下载」的工具不少,网页版、客户端、浏览器插件都有。我试了几个,体验大同小异,卡点也高度一致。

第一个是次数。免费用户每天能下的集数有限, 30 集要分两三天下完,或者付费。第二个更烦——大部分网页工具是「一个链接下一集」的模式, 30 集就要复制粘贴 30 次,每次还要手动选清晰度、确认、等待。第三个最隐蔽:下完一堆文件,名字是 video_1.mp4video_2.mp4 这种,或者干脆是一串视频 ID 。选集本来有名字的,「第 1 节:围棋的规则」「第 2 节:棋盘和棋子」,到了本地全没了。

判断一个工具值不值得用,我习惯先看它有没有解决核心问题,再看它顺不顺手。这几个工具的核心问题恰恰没解决:我要的是「整套、一次、按原标题命名」,它们给的是「单集、多次、命名靠运气」。顺手与否都谈不上了。

于是我换了个思路:与其找一个替我点 30 次的工具,不如搞清楚这些工具背后到底在做什么——然后让命令行一次干完。

那个 URL 里,其实只有一个编号

先看你从浏览器复制出来的地址长什么样:

https://www.bilibili.com/video/BV1TM411h7Zb/?p=1&spm_id_from=...

这个URL中真正有用的就两段。其中BV1TM411h7Zb 是这个视频的编号, B 站内部叫 BVID ;?p=1 表示选集里的第几集。后面那些 spm_id_fromvd_source 全是来源统计参数,删掉不影响打开。早年的视频还有个数字版的 av 号, BV 和 av 之间能用一个固定算法互转,现在统一用 BV 。

关键在于:这个链接本身不包含任何视频文件。你把这个链接给浏览器,浏览器并不是直接从这个地址下视频,而是拿着这个编号去请求 B 站的几个数据接口。而我们的下载工具脚本,就是模仿的浏览器请求的这套动作。

网页不放视频,视频在 API 后面

B 站是前后端分离的。打开页面时,你看到的标题、选集列表、播放器,都是页面上的 JavaScript 再去请求几个返回 JSON 的接口、拿到数据后渲染出来的。

第一个要敲的接口是视频信息接口,大致长这样:

api.bilibili.com/x/web-interface/view?bvid=BV1TM411h7Zb

它返回这个视频的标题、 av 号,以及一个叫 pages 的数组——这就是右边那栏「选集」。数组里每一项对应一集,带着两个我特别在意的字段:一个是 cid,每集自己的内部编号,取视频流必须用它;另一个是 part,也就是这一集的标题,「第 1 节:围棋的规则」就存在这里。

我之前用工具下完命名全乱,原因就在这。很多工具图省事,只解析了 ?p=1 到 ?p=30 这一串地址,没去敲这个 view 接口,自然拿不到 part,只能用序号凑个文件名。标题不是拿不到,是它们没去拿。

第二个接口才是去换真正的播放地址:

api.bilibili.com/x/player/playurl?bvid=...&cid=...&fnval=4048

注意这里要带上刚才拿到的 cidfnval=4048 是在告诉服务器:给我 DASH 格式。它返回的不是一个视频文件,而是一组直链——不同清晰度的视频流、不同码率的音频流,分门别类列出来。

412 :那道你绕不过去的门

我第一次直接用命令行去访问这些接口,结果是 HTTP 412 Precondition Failed。这个报错是整件事里最值得说的一段。

412 的意思是「前置条件不满足」。 B 站这几年给接口加了一套叫 WBI 签名的风控:请求参数得按它的规则加盐、做 MD5 ,算出一个 w_rid 字段一起发过去;而那个盐值还藏在另一个接口返回的图片文件名里,得先去取、去解。同时它还会校验你浏览器里的 cookie 和请求来源。少了签名,或者没有一个合法的登录态,服务器直接把你挡在门外,连数据都不给。

这就解释了为什么那些下载工具都要你「先登录」或者「导入 cookie 」。不是它们想收集你什么,是 B 站这道门,没有钥匙真过不去。我的判断是,这一层以后只会更严,不会更松——所以任何宣称「免登录免配置一键下载」的工具,要么是在用别人的公共账号顶着,要么哪天就突然失效了。

解决办法说穿了也朴素:让程序去读我本机 Chrome 里那份已经登录过 B 站的 cookie ,带着它去敲门。登录态还顺带决定你能取到多高的清晰度——1080P 和大会员清晰度,是要登录才给的。

DASH :为什么下完还要合一道

过了 412 , playurl 接口吐回来的那组直链,是音视频分离的。

这套叫 DASH ,和 YouTube 是一个路子:视频是视频的流,声音是声音的流,两个独立文件。视频流的编码可能是 H.264 、 H.265 或者更新的 AV1 ;音频是 AAC 居多。好处是服务器能按你的网速灵活切清晰度,坏处是你下到手的是两半,得自己拼。

拼这一步交给 ffmpeg 。它把视频流和音频流封装进同一个 mp4 容器里,不重新编码,所以很快,画质也不损失。我特意让脚本优先挑 H.264 那一路视频流——因为 iPad 解 H.264 最省电最稳, AV1 反而可能卡。

把这几步连起来,整条链就清楚了:

从 URL 获取到 BV 号和选集号 → 访问 view 接口拿到每集的 cid 和标题 → 用 cid 带着 WBI 签名和 cookie 去获取 playurl ,换回音视频直链 → 分别下载两条流 → ffmpeg 合成一个 mp4 → 按「序号 + 选集标题」命名存好。

我没有手写上面每一步。这些细节——BV 转 av 、 WBI 签名怎么算、清晰度参数怎么填、 DASH 怎么解析——全都会随 B 站风控调整而变。社区里有个叫 yt-dlp 的开源工具,把这些易变的脏活都封装好了,还在持续跟进。我要做的,只是把它和 ffmpeg 串起来,配好「读 Chrome cookie 」「优先 H.264 」「用选集标题命名」这几个参数。写出来那个脚本,核心就十来行。

跑起来之后, 30 集排着队下完,文件夹里整整齐齐:01 第 1 节:围棋的规则.mp402 第 2 节:棋盘和棋子.mp4……扔进 iPad ,按顺序点开就行。中途断了再跑,已经下好的会自动跳过。

它能用,但我得先把边界讲清楚

这套东西现在跑得很顺,但我不打算把它说成「万能下载器」。它有几条很硬的适用边界,先说清楚,免得你照着做却卡在半路。

第一,目前只在 macOS 上验证过。卡点不在下载本身,在读 cookie 这一步——新版 Chrome 在 Windows 上给 cookie 加了一层和系统账户绑定的加密,直接读经常失败; macOS 反而能正常读。所以这版脚本就是给 Mac 用的, Windows 我没有适配。

第二,它依赖你本机那个登录过 B 站的 Chrome 。换台机器、退了登录、或者 cookie 过期,都得重新来。这是 412 那道门换来的代价,绕不开。

第三,它只解决「下载选集」这一件事。番剧、付费课、互动视频这些有额外授权的内容,是另一套逻辑,这个脚本不碰,我也不建议去碰。

30 个 mp4 基本3分钟左右也就都搞定了。同时在初步搞明白哔哩哔哩把「一个 URL 怎么变成一个本地文件」的过程。

下次再遇到一个「看起来十分钟、用起来折腾一下午」的需求,我大概还是会先拆开看看,门后面到底是什么。