背景
笔者是在b站听歌的(一方面买不起各种会员,一方面有的歌喜欢的是翻唱版本)。所以打算爬取B站的音频(视频偶尔会下架)。
方案
爬取资源,简单来说,就是找到你所需资源的url,然后通过http实现资源的下载。我尝试过多种爬取方案。
python脚本
python脚本爬取,是你能在网上找到的最多的方案,但存在诸多问题:1、每次运行脚本需要手动获取cookies、url,下载极不方便;2、音频url地址不都是能直接获取的,实测2022年的视频有SSR能直接获取window.__playinfo__全局变量,而2024年的视频不再有该全局变量,改为从接口获取。所以说为了所有视频都能爬取,继续python脚本方案的话就需要无头浏览器了,方案太重了。
浏览器插件
十分的不赶巧,浏览器插件版本(manifest.json 版本)小于v3的要被废弃了,使用低版本的会有警告。v3版本浏览器插件实现起来比较复杂,一般用户 permissions 权限是受限制的(如:不能拦截网页请求的响应内容,cookies查看受限等。)实现起来繁琐,chrome 官方的文档阅读起来也不方便。
油猴脚本(本质还是浏览器插件)
其实我们的需求十分简单,打开某个视频觉得不错,想下载下来。油猴脚本能直接在document中嵌入脚本,嵌入的脚本不受CORS、CSP限制。我们操作起来就顺手得多。下面就用油猴实现这个简单的需求。
实现
获取音视频url: 拦截xhr来获取资源的地址。
let json = ''
// 拦截xhr的发送
const send = window.XMLHttpRequest.prototype.send
window.XMLHttpRequest.prototype.send = function () {
// 这里的拦截,只是想做额外的事情,即获取url。装饰器模式
send.call(this, ...arguments)
setTimeout(() => {
const onreadystatechange = this.onreadystatechange
this.onreadystatechange = function () {
if (onreadystatechange) onreadystatechange.call(this, ...arguments)
if (
this.responseURL.startsWith(
'https://api.bilibili.com/x/player/wbi/playurl',
)
) {
json = JSON.parse(this.responseText)
}
}
}, 20)
}
下载: 由于不存在跨域限制;cookies、referrer等防盗链限制不需要我们额外处理,直接下载即可。
function download(url, fullName) {
fetch(url)
.then(res => res.blob())
.then(blob => {
var a = document.createElement('a')
a.href = URL.createObjectURL(blob)
a.download = fullName
document.body.appendChild(a)
a.click()
a.remove()
})
}
值得注意的是B站的音、视频是分开的,需要合成。笔者的脚本支持两种模式。(目前没有完美的方案)
直接下载:分别下载视频、音频。缺点是需要手动合成(如借助ffmpeg工具)。
流式合成:借助webRTC,MediaRecorder 录制视频再下载。缺点是需完整播放视频。
效果展示
总的来说,效果我还是满意的。(ps:深色模式,懒狗笔者还没完成)
完整脚本见:https://github.com/user-9902/scripts-for-tempermonkey/blob/main/bili_plugin/dist.js
安装等详见项目的 README.md
—————————————————— 分割线 ———————————————————
2024/12/09 深色模式功能补完
2024/12/10 视频下载支持流式合成模式