前阵子在群里看到有人问:“B站视频怎么批量下载?网上那些解析站是怎么做的?”接着又有人甩出“查成分工具”“充电视频解析”这类词。我当时就意识到,很多人并不是真的缺工具,而是搞不懂B站网页端那一大堆参数和API接口到底是怎么回事。只要你把URL里的参数看懂了,把常见接口的调用逻辑摸清了,那些所谓的“工具”绝大部分你都能自己写出来。
这篇小教程不搞大而全的源码框架,就围绕一件事展开:B站的网页版参数和API接口,从URL拆解到实际请求,再到几个热门工具的原理还原。适合想入门爬虫、想自己做B站小工具、或者单纯想弄明白“解析站到底做了什么”的读者。文章里会穿插Python示例,但我尽量让你不贴代码也能看懂逻辑。
1. 先搞懂B站URL里的那些“参数”到底是什么
1.1 从一次“想下载视频”说起
假设你现在就想把一个B站视频下载到本地。你复制了地址栏链接,大概是这样的:
https://www.bilibili.com/video/BV1xx411c7XX?p=2&t=30&spm_id_from=333.999.0.0如果你直接拿这个链接去请求,能拿到页面,但拿不到视频文件本身。因为真正的视频地址藏在页面里,需要API接口根据参数去换。所以第一步,你得能看懂这个URL里哪些参数是干什么用的。很多时候工具没效果,不是你代码写错了,而是参数不对。
我见过不少新手在网上搜“B站API”,搜到一两个接口就开干,结果请求返回一串看不懂的JSON,最后卡在“怎么从响应里找到视频地址”这一步。根本原因就是没建立“参数→接口→响应”这个整体认知。
1.2 一个视频页URL的完整拆解
拿上面那个URL举例,拆开来看:
| 参数 | 示例值 | 含义 | 是否需要手动设置 |
|---|---|---|---|
bvid | BV1xx411c7XX | 视频唯一ID,B站现在的通行标识 | 必须 |
p | 2 | 分P序号,表示第几个分P | 可选 |
t | 30 | 播放进度,单位秒,用于自动跳转 | 可选 |
spm_id_from | 333.999.0.0 | 流量来源追踪参数 | 可选 |
vd_source | 一串随机字符 | 渠道来源参数,用于统计 | 可选 |
bvid是核心,几乎所有视频相关接口都要用到它。它和早期的av号是一一对应的,接口里也经常返回aid(av号的数字形式),所以你在请求里写bvid=BV1xx411c7XX或者aid=170001效果一样,但现在官方更推荐用bvid。
p参数在分P视频里特别重要。很多人下载多P视频只下到第一P,就是因为只传了bvid没传p。而进一步说,API内部真正识别分P靠的不是p本身,而是通过p去查询该分P对应的cid,cid才是每个分P自己的唯一ID。这个关系后面实操部分会用到。
spm_id_from这种参数,属于“追踪参数”。简单说,B站用它来统计你是从哪个页面进来的,比如从搜索页进来就是search,从首页推荐进来就是333.999.0.0这类。普通用户用不上,但如果你在做流量分析,能通过它判断用户来源。
1.3 参数不止在URL里,还在请求体里
这里要澄清一个很多人的误区:B站网页版的参数不只在URL问号后面,POST请求的Form Data里、请求头Header里,都塞满了参数。比如视频的点赞、投币、评论操作,很多都是POST请求,参数藏在请求体里。
以取流(获取视频播放地址)为例,在浏览器开发者工具里能看到:
https://api.bilibili.com/x/player/playurl?bvid=BV1xx411c7XX&cid=123456&fnval=16&qn=125光看URL地址栏里的页面参数是不够的,你得学会看XHR请求里的Query String Parameters。这些参数决定了接口返回什么格式的数据。
2. 搞懂B站API的“常用接口全家桶”与鉴权分层
看完参数再看接口。B站的接口多得数不清,但对做小工具来说,真正高频的其实就那十几个。我把它们按功能分个类,对应的路径也列出来,这样你以后遇到需求就知道该找谁。
2.1 视频信息类接口:一切操作的起点
- 视频详情:
GET https://api.bilibili.com/x/web-interface/view - 视频状态(播放量、点赞、硬币等):
GET https://api.bilibili.com/x/web-interface/stat - 视频播放地址:
GET https://api.bilibili.com/x/player/playurl
view接口是我个人使用频率最高的。你只要传一个bvid,它会返回视频标题、简介、UP主信息、分区、所有分P的cid列表、时长、封面图等。这基本上就是B站视频的“身份证全集”。
举个例子,请求view接口后你会在JSON里看到类似这样的结构(简化):
{ "code": 0, "data": { "bvid": "BV1xx411c7XX", "title": "视频标题", "pubdate": 1600000000, "owner": {"mid": 123456, "name": "UP主名"}, "pages": [ {"page": 1, "cid": 111111, "part": "P1标题"}, {"page": 2, "cid": 222222, "part": "P2标题"} ] } }注意看pages数组,每个分P有自己的cid。拿到cid之后,你才能继续去请求播放地址。所以标准链路是:bvid→view接口 →cid→playurl接口 → 视频流地址。
2.2 弹幕和评论接口:做文本分析的富矿
- 弹幕:
GET https://api.bilibili.com/x/v2/dm/web/seg.so - 评论:
GET https://api.bilibili.com/x/v2/reply
弹幕接口比较特殊,它返回的不是JSON,而是一种二进制流格式(protobuf),直接请求会看到一堆乱码。你需要用protobuf解析规则去解码,或者用一些现成的库。分段弹幕接口的oid参数就是视频的cid,还有个segment_index表示弹幕分段的序号,从1开始。
评论接口反而是标准的JSON接口,参数有type=1表示视频类型,oid填视频cid,pn和ps控制分页。如果你想分析某个视频的评论情感倾向或者做词云,这个接口是主力。
2.3 用户信息和作品列表接口
- 用户名片:
GET https://api.bilibili.com/x/web-interface/card - 用户投稿列表:
GET https://api.bilibili.com/x/space/wbi/arc/search
传一个mid(用户ID),能看到用户的昵称、头像、粉丝数、签名等。而arc/search接口能列出该用户的所有公开投稿视频。
这里要注意,查询某个用户的历史投稿,虽然通常习惯用mid参数,但实际请求中往往需要携带WBI签名后的dm_img_list、dm_img_str等参数组合。这类接口对风控要求比较高,频繁调用容易触发验证码。
2.4 鉴权分层:哪些能裸调,哪些要穿衣服
如果你用过一段时间B站接口,会看到三种典型的返回错误:-403、-412、-352。它们基本对应了B站的三层鉴权逻辑:
| 返回码 | 含义 | 常见原因 |
|---|---|---|
-403 | 访问被拒绝 | 请求头缺失、签名过期、当前账号权限不足 |
-412 | 请求被拦截 | 触发风控,IP被限制,需要验证码 |
-352 | 风控校验失败 | 缺少WBI签名或签名错误 |
从访问权限上看,B站接口大致分三层:
- 第一层:完全公开,只靠参数就能请求,比如
view接口大部分数据不登录也能拿。 - 第二层:需要登录Cookie,比如高清晰度视频流(1080P以上)、点赞投币等操作接口。
- 第三层:需要WBI签名,比如部分
space接口、search接口。
WBI签名是B站网页端的一个“防爬”机制,原理不复杂:从nav接口拿到一个mixin_key,然后把请求参数按照一定规则排序、拼接、计算MD5,得到一个w_rid参数,同时带上wts时间戳。相当于给每个请求加了一个动态签名。我自己的经验是,写工具时能避开WBI就尽量避开,比如用户投稿列表可以尝试通过view接口配合其他公开数据替代,如果确实需要,就参考现成的B站开源SDK里的实现,不必自己从头硬啃。
3. 实操:用参数和API拼出一个“取流下载”流程
理论看再多,不如动手走一遍。这一节我们完成一个最小可用的下载流程:拿到一个bvid,解析出视频流地址,然后把视频和音频下载到本地。
3.1 必备工具与约定
语言用Python,依赖库主要就是requests。为了绕过简单的防盗链,我们还需要手动构造请求头。这里约定一下:
User-Agent:伪装成浏览器。Referer:必须设置为https://www.bilibili.com/,否则视频流请求会被拒绝。
3.2 第一步:根据bvid获取cid
先把视频信息拉下来:
import requests session = requests.Session() session.headers.update({ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Referer": "https://www.bilibili.com/" }) bvid = "BV1xx411c7XX" view_api = "https://api.bilibili.com/x/web-interface/view" resp = session.get(view_api, params={"bvid": bvid}).json() data = resp["data"] cid = data["pages"][0]["cid"] # 取第一个分P这一步里params参数会自动帮你拼Query String,注意requests库会对参数做URL编码,这正好省去手动编码的麻烦。
3.3 第二步:请求playurl获取视频流地址
拿到cid之后,请求playurl接口:
play_api = "https://api.bilibili.com/x/player/playurl" params = { "bvid": bvid, "cid": cid, "fnval": 16, # 16 表示请求DASH格式 "qn": 125, # 清晰度:125为4K,120为2K,116为1080P60,112为1080P "fourk": 1 } play_data = session.get(play_api, params=params).json() video_url = play_data["data"]["dash"]["video"][0]["baseUrl"] audio_url = play_data["data"]["dash"]["audio"][0]["baseUrl"]fnval=16和qn=125是这里最关键的参数。fnval=16表示要求返回DASH格式,意思是视频和音频分离——视频流只有画面没有声音,音频流只有声音没有画面。设置qn=125时,要留意你账号的清晰度权限,没有大会员时直接要4K流可能会被降级或返回错误码。这个细节很多人第一次都会忽略。
3.4 第三步:下载音视频并合并
video_content = session.get(video_url).content audio_content = session.get(audio_url).content with open("video.m4s", "wb") as f: f.write(video_content) with open("audio.m4s", "wb") as f: f.write(audio_content)下载完你会得到两个文件,video.m4s和audio.m4s。下一步用ffmpeg把它们合并成一个完整的MP4:
ffmpeg -i video.m4s -i audio.m4s -c copy output.mp4到这里,一个最小化的B站视频下载流程就跑通了。整个流程的逻辑链非常清晰:bvid + p→view接口→cid→playurl接口→音视频流地址→ 下载 →ffmpeg合并。
3.5 进阶:处理分P、清晰度与请求频率
如果你要下载多P视频,注意pages数组的顺序和p参数一一对应。写循环时依次取每个分P的cid,再重复第二步就行。
清晰度这块我再补充一句。qn参数并不是越高越好,还要看视频本身支持的最高清晰度和账号权限。判断方法很简单:看playurl返回的accept_description列表,比如["高清 1080P", "高清 720P"],这就是当前账号能拿到的全部档位。
请求频率方面,如果你需要批量处理,建议在每次请求之间加time.sleep(1),不要并发请求太猛。B站的风控对短时间高频请求非常敏感,我见过一个脚本因为循环里没加延时,跑了不到两分钟IP直接触发-412。
4. 那些热门小工具到底是怎么实现的
搞懂了上面的链路,你再回头看网上流传的各种B站小工具,就会有一种“原来如此”的感觉。下面聊几个典型的热搜词。
4.1 m4s文件合并工具:就是封装了ffmpeg
很多人第一次接触m4s文件,是因为B站客户端缓存或者直接下载DASH流之后,发现文件播不了。其实m4s就是一种无封装的音视频流格式,本质上和mp4里的编码数据差别不大,只是剥掉了容器外壳。
所谓“m4s文件合并工具”,核心工作就是调用ffmpeg做remux。网上一些图形化的合并工具,只是把我在3.4节里写的命令行封装成了可视化界面。如果你在命令行里手输过那条ffmpeg命令,你就已经超过了90%的“工具使用者”。
顺带一提,有些工具还能直接处理“下载下来只有视频没有声音”的问题,那是因为它只抓了video流没抓audio流,合并自然无从谈起。
4.2 查成分工具:本质是聚合公开接口数据
“查成分”是B站社区里一个很有趣的玩法。输入一个UID,工具会分析这个用户的点赞、投币、收藏、关注列表,然后生成一个“成分分布”,比如“关注了哪些领域的UP主”“经常看什么类型的视频”。
这类工具的实现思路也很直白:
- 先通过
card接口拿用户基本信息和关注数。 - 再通过
space系列的关注列表接口,拉取最近关注的UP主。 - 然后根据这些UP主的视频分区、标签做聚合统计。
整个过程没有任何“黑科技”,就是把公开接口返回的数据做了个透视表。难点在于:
- 接口有频率限制,做批量分析时得控制节奏。
- 用户如果设置了隐私,很多数据拉不到。
- 关注列表很长时,分页参数和排序参数要处理好。
我做过一个简单的版本,核心逻辑不到200行Python,效果已经够用。所以如果你看到某些“查成分”网站收费,要知道它贵在维护和界面,而不是技术本身。
4.3 充电视频解析的原理边界
关于充电专属视频,我必须先把态度说清楚:这篇文章不教学如何绕过付费,也不鼓励任何人去破解付费内容。但从技术角度,理解它的权限机制是合理的。
充电专属视频在接口层面和普通视频的区别,主要体现在数据返回上。当你请求一个充电专属视频的playurl接口时,接口会先判断当前账号是否有观看权限。没有权限的话,返回数据里的dash字段会是空的,或者整体返回code为非0的错误信息。
网上有些人说“能解析充电视频”,实现思路无非是:
- 拿一个已经充电且有权限的账号Cookie去请求。
- 或者找到缓存时的临时授权地址。
这些手段本质上都在“借用权限”,既不合法,也随时会被风控识别。了解这个原理的意义在于:当你自己开发B站相关工具时,遇到“接口正常但dash为空”的情况,能迅速想到可能是权限问题,而不是代码写错了。
4.4 敏捷开发里的“必要参数”思路
做工具时我习惯先列一个“必要参数清单”,再对着接口文档逐一核对。比如下载工具的必要参数是bvid和cid,而qn、fnval属于可调参数。这样一旦请求失败,排查范围会小很多。
很多开源项目里的参数比我上面写的要多得多,比如platform=html5、high_quality=1、device=phone之类。这些参数不是没用,而是它们要么是为特定客户端设计的,要么是历史遗留。新手别照抄,要以自己实际测试为准。
5. 调用B站API容易踩的坑与应对建议
接口本身不难,真正劝退新手的往往是下面这些“软性问题”。我把这几年积累的经验一次性列出来。
5.1 请求头:User-Agent和Referer是生命线
请求头是B站第一道防线。很多人的请求代码用默认UA甚至裸requests.get,返回一上来就是-412,这就是UA被识别了。
我自己的标准配置是:
headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Referer": "https://www.bilibili.com/", }尤其Referer这一项,视频流地址的请求必须带,否则即使拿到了baseUrl,下载时也会被防盗链拒绝,返回403。这算是B站最顽固的防盗链策略之一。
5.2 清晰度、Cookie和大会员的关系
没有登录态时,大多数视频最高只能拿480P,少数甚至只有360P。想要1080P乃至更高,账号必须登录,部分高码率还需要大会员。
处理方式就是在请求时带上Cookie请求头,从浏览器里复制下来粘贴到代码里即可。但注意Cookie会过期,长期运行的脚本需要定期更新。如果你只写一个自己用的小工具,这是最省事的方案。
5.3 缓存时间戳与临时链接有效期
从playurl接口拿到的baseUrl是有有效期的,通常只有几分钟到一两个小时。我之前遇到过这样的情况:批量下载任务排队到一半,前面下载太慢,后面的链接就过期失效了。
解决思路是:不要一次性把所有分P的baseUrl都取出来存着,而是下载前才现取对应分P的地址。这样每个链接都是新鲜的,过期概率小很多。
5.4 不要碰“批量注册”“批量点赞”这类黑产脚本
我知道写到这里一定会有人说“那点评赞这种怎么做”。我的建议是:不要做。B站的风控体系会从设备指纹、IP、行为模式多个维度检测异常,这类脚本封号风险极高,而且也没有正当使用场景。
个人学习时的合理用法是:备份自己的公开数据、做数据分析可视化、给喜欢的视频做离线弹幕词云。这些方向既能学到东西,也不触碰红线。
5.5 常见错误速查表
| 现象 | 最可能原因 | 排查方法 |
|---|---|---|
接口返回-404 | 视频不存在、bvid传错、被UP主删除 | 先用浏览器打开视频确认 |
返回-403 | 请求头缺失、无权限 | 补全Referer和UA,检查账号登录态 |
返回-412 | 请求频繁、IP被风控 | 降低频率,更换IP,过段时间再试 |
dash字段为空 | 权限不足或视频格式不支持 | 检查账号是否登录,切换fnval值 |
| 播放地址下载403 | Referer缺失 | 确认请求头里加了Referer: https://www.bilibili.com/ |
| Cookie失效 | 登录过期 | 重新从浏览器复制Cookie |
5.6 顺手提一下“B站网页版修改快捷键”
热搜词里有“b站网页版修改快捷键”,这其实是B站网页播放器的一个快捷键设置页面。顺带说一句,B站在网页端的快捷键体系是通过本地存储保存的,不是通过API。所以如果你想去改它,思路是操作浏览器的localStorage,而不是调接口。理解了“网页功能≠全部走API”这个道理,很多困惑都会解开。
最后再说点实在的
我在实际写B站相关脚本的过程中,最深的一个体会是:接口文档不重要,重要的是参数语义。B站官方没有公开过正式的API文档,所有接口地址、参数说明都是社区开发者通过抓包总结出来的。所以网上流传的“接口大全”随时可能过期,你要学会自己打开浏览器开发者工具,找到Network面板里那一个个XHR请求,自己看参数、看响应。
另一个经验是:能请求到结果不代表你理解了过程。建议你亲手把view接口、playurl接口的返回JSON完整看一遍,不急着写代码,先弄清楚每个字段是什么意思。这个“读JSON”的能力,比背十个接口路径都管用。
最后建议你从小需求入手练手,比如写一个脚本,把自己最近投币过的视频信息导成表格,或者统计某个UP主最近一年视频的播放量趋势。整个过程会强迫你搞懂参数、签名、分页、异常处理,等这些基本功练扎实了,再回来看“解析站”们的原理,你会发现一切都很透明。