B站API开发实战:从URL参数到WBI签名,手把手搭建查成分工具
2026/9/18 18:59:53 网站建设 项目流程

直接说结论:B站这套参数和API的东西,就是搞B站周边开发绕不开的核心。你在网上看到的查成分工具、m4s合并工具、充电视频解析脚本、甚至一些AI小助手,本质上全是靠B站网页端和APP端暴露出来的那些接口在跑。如果你对“参数”和“API”这两个词还停留在“听过但不知道怎么用”的阶段,这篇教程就是给你准备的。我会从最简单的URL参数拆起,一路讲到接口鉴权、WBI签名、弹幕拉取,最后直接带你把一个能用的“查成分小工具”跑通。不整虚的,全是实操。

1. 先从B站URL参数说起:链接里的字母和数字是干嘛的

1.1 BV号、av号、cid到底怎么区分

先看一个最常见的视频链接:

https://www.bilibili.com/video/BV1xx411c7mD?p=2&vd_source=abcdefg

这里面最主要的就是BV1xx411c7mD,这就是B站视频的“身份证”,官方叫bvid。2020年之前B站用的是av号,纯数字,比如av170001。现在两者都在用,但接口里更推荐bvid,因为它是经过编码的,别人一眼看不出视频发布时间和序号。

你可能会问,av号好好的为什么要改成BV号?直接原因就是av号太容易被遍历了,纯数字自增意味着爬虫可以一个接一个把全站视频都抓一遍,平台没法做风控。BV号本质上是av号经过base58编码再打乱顺序的结果,具体算法是av号先和一个固定数字做异或,再经过位运算和base58映射,最后得到一串看起来随机的字符。不过现在官方已经给了反转接口,你完全不需要自己实现那套编码解码。

再说cid,这个是视频分P的唯一ID。一个视频如果有多P,每一P都有自己独立的cid。你调用播放地址、弹幕接口时,真正起作用的是cid而不是bvid。所以参数之间的关系是这样的:拿到bvid后,先通过视频信息接口把整个合集的所有分P和对应cid列表取出来,再根据p参数选中具体某一P的cid,最后用这个cid去要播放地址和弹幕。

1.2 p参数、t参数、autoplay参数这些小东西

URL里除了BV号和cid,还有一堆看起来不起眼的参数,最典型的是以下几种:

  • p=2:指定分P的序号,比如一个视频有10P,你要直接跳到第2P,就在URL里加?p=2。有些工具脚本解析下载时,也是靠这个参数来确定要下载哪个分P。
  • t=120:指定视频开始播放的时间位置,单位是秒。比如?t=120表示从第120秒开始播放,这个在分享某个精彩片段时非常实用。
  • autoplay=0:禁止自动播放。默认情况下从收藏夹或推荐点进去可能会自动播放,加上这个参数可以强制不自动播放。
  • danmaku=0:隐藏弹幕,加载视频时直接把弹幕关掉。
  • vd_source:访问来源统计参数,B站靠它追踪用户是从哪个渠道进来的,一般不影响视频内容本身。

这些参数别看简单,实际写工具时经常用得到。比如你想做“从第N秒开始下载转GIF”的脚本,就完全可以通过URL里的t参数来定位起始时间;你想做批量抓取多P视频的下载器,就必须处理好p参数和cid的映射关系。很多人忽略这一点,写出来的脚本只在单P视频上能用,一遇到合集就崩溃。

1.3 UID和用户页参数:从链接看UP主

用户空间的URL和视频页完全不一样:

https://space.bilibili.com/170001

这里的170001就是用户UID,B站所有用户维度接口——粉丝数、关注数、投稿列表、动态流、充电专属视频——都要拿这个UID来查。几乎所有“查成分”工具的核心,就是围绕这个UID去调B站的各种用户接口。

有意思的是,B站部分页面还支持动态参数,比如https://space.bilibili.com/170001/dynamic会直接跳转到动态页,/upload/video会跳到投稿页,/article会跳到专栏页。你在网页端见到的这些路径,本质上就是B站前端路由的参数化结果。理解这个结构之后,你做用户数据采集时就知道该往哪个URL打请求了。

2. B站公开API地图:这些接口分别能干什么

2.1 视频信息接口:一切视频操作的起点

只要你拿到一个bvid,第一步基本都是调视频信息接口:

https://api.bilibili.com/x/web-interface/view?bvid=BV1xx411c7mD

这个接口返回的是JSON格式,里面包含了你能想到的所有视频元信息:

  • data.aid:视频的av号
  • data.cid:默认分P的cid
  • data.pages:所有分P的数组,每个元素包含cidpagepart(分P标题)
  • data.owner:UP主信息,包括midnameface
  • data.stat:播放量、弹幕数、点赞数、投币数、收藏数、分享数
  • data.desc:视频简介
  • data.pubdate:发布时间戳

实测下来,这个接口的稳定性非常好,只需要带一个最基本的User-Agent头就能返回数据。但要注意,如果视频已经被删除或撞了版权变成“仅限会员观看”,返回的code可能是-404。还有一类视频是“充电专属”,返回字段里会出现badgepay这个标记,表示不是所有人都能看到完整内容的。

这个接口是所有B站开发者的老朋友,我自己的工具链里它有90%以上的调用占比。不管你是做数据统计、视频下载还是内容筛选,第一步永远是它。

2.2 视频流地址接口:真正拿到播放地址的地方

视频能不能下载、能拿到多高清晰度,全看playurl接口:

https://api.bilibili.com/x/player/playurl?bvid=BV1xx411c7mD&cid=111222&qn=64&fnval=16

这里cid是必填的,必须传视频具体分P的cid。fnval是关键参数,我一般直接填16,这个值代表返回DASH格式,也就是把视频画面和音频分开返回。qn代表清晰度,64是1080P,80是1080P高码率,但高清晰度通常要登录Cookie,大会员才能解锁4K和杜比。

用DASH格式返回后,你会拿到两个列表:dash.videodash.audio。每个列表里有多个不同编码和清晰度的分片地址,每个分片文件就是传说中的m4s文件。这也就是为什么你在网上会看到一堆“m4s文件合并工具”——因为直接下载下来的视频流和音频流是分开的,必须合并才能播放。

这里提醒一句:playurl接口拿到的下载地址有效时间很短,一般几小时到一天不等,而且有防盗链校验,直接复制到浏览器地址栏不一定能下载成功,需要在请求里带上Referer头。写代码时千万别把这一点漏了,否则就是各种403。

2.3 弹幕接口:XML和JSON两种格式的坑

B站弹幕接口有两个版本,都很有用:

老版本是XML格式:

https://api.bilibili.com/x/v1/dm/list.so?oid=110222

这个接口直接返回弹幕XML,解析起来很方便,但整个视频所有弹幕一次性拉完,数据量大了以后加载很慢。

新版本是分段JSON接口:

https://api.bilibili.com/x/v2/dm/web/seg.so?type=1&oid=110222&segment_index=1

segment_index按分钟分段,一个视频被切成了很多个时间片,每个时间片的弹幕单独拉取。好处是省流量,坏处是你得先知道视频总分钟数,再循环去拉。我第一次写弹幕分析工具时没搞明白这个,把segment_index忘写了,结果只有前几分钟的弹幕,排查了半天才发现问题。

弹幕数据里比较有用的字段是progress(弹幕出现在视频中的时间点,毫秒)、mode(弹幕类型,滚动/顶部/底部)、color(弹幕颜色)、mid(发送者UID哈希,但注意这个值做过处理,不是完整UID)。做弹幕情感分析或者高能片段提取,主要用的就是progress字段。

2.4 用户信息、动态和关注接口:查成分的基础

查成分工具能查到的信息,基本都来自这几个接口:

# 用户基本信息 https://api.bilibili.com/x/web-interface/card?mid=170001 # 用户关注列表 https://api.bilibili.com/x/relation/followings?vmid=170001&pn=1&ps=50 # 用户动态列表 https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space?host_mid=170001

card接口返回粉丝数、关注数、性别、等级、签名等基本信息。followings接口能一页一页翻关注列表,配合ps参数控制每页条数。动态接口返回用户发的所有动态,包括转发、图文、视频更新等。

抖音那句“关注列表决定你是谁”,放在B站查成分场景里也是成立的。很多人会通过拉取目标用户关注了哪些UP主,来判断他的立场和喜好。但要注意,关注列表接口现在对未登录用户做了限制,翻不了几页就会要求登录。实际开发时建议带上自己账号的Cookie,能明显提高成功率。

3. 实战:写一个能跑的B站小工具

3.1 需求拆解:输入UID,查“成分”

网上热度很高的“B站输入uid查成分工具”,说白了就是一个整合接口的页面。我们今天的实战目标就更明确一点:输入用户的UID,输出他的基本信息、粉丝数、关注数、最近发布的视频列表、以及最近动态。拆解一下,只需要三步:

  1. card接口拿基础信息。
  2. space投稿接口拿最近视频。
  3. dynamic接口拿最近动态。

3.2 请求头配置和WBI签名

B站接口请求最基础的三样东西:User-Agent、Referer、Cookie。UA我一般用浏览器的完整UA,Referer填https://www.bilibili.com/,Cookie至少要带buvid3b_nut,这两个是B站的匿名访问标识,很多接口没有它们直接拒绝。

但有一批用户维度的接口对风控更严格,光有UA和Cookie还不够,还得带WBI签名。WBI签名的过程是这样的:先把所有参数按key的字典序排序,拼接成字符串,然后在这个字符串末尾加上一个固定的盐值ea1db124af3c7062474693fa704f4ff8,再做MD5,得到w_rid,同时在请求里带上当前Unix时间戳wts。最后把w_ridwts作为附加参数拼到URL里。

这套签名看起来复杂,写起来其实就几行代码:

import time import hashlib from urllib.parse import urlencode def wbi_sign(params: dict, salt: str = "ea1db124af3c7062474693fa704f4ff8") -> dict: params = dict(params) params["wts"] = int(time.time()) # 按key字典序排序 items = sorted(params.items()) query = urlencode(items) params["w_rid"] = hashlib.md5((query + salt).encode()).hexdigest() return params

实测下来,有的接口必须签名,有的不签名也能返回,但为了稳定我还是全部加上。这里再提醒一句:盐值不是永久固定的,B站偶尔会更新,如果某天你发现签名接口全部报错,可以打开B站网页版,随便点开一个视频,在开发者工具里找到所有w_rid开头的请求,从URL里反推当前盐值。

3.3 完整代码流程演示

我用Python写一个小例子,核心逻辑大概长这样:

import requests session = requests.Session() session.headers.update({ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": "https://www.bilibili.com/", }) def get_user_card(mid: int) -> dict: url = "https://api.bilibili.com/x/web-interface/card" resp = session.get(url, params={"mid": mid}) data = resp.json() if data["code"] == 0: return data["data"] else: raise RuntimeError(f"接口报错: {data['code']} {data['message']}") def get_recent_videos(mid: int, page_size: int = 10) -> list: url = "https://api.bilibili.com/x/space/wbi/arc/search" params = {"mid": mid, "pn": 1, "ps": page_size, "order": "pubdate"} params = wbi_sign(params) resp = session.get(url, params=params) data = resp.json() if data["code"] == 0: return data["data"].get("list", {}).get("vlist", []) else: raise RuntimeError(f"接口报错: {data['code']} {data['message']}") if __name__ == "__main__": mid = 170001 card = get_user_card(mid) print(f"UP主: {card['name']}, 粉丝: {card['fans']}, 关注: {card['attention']}") videos = get_recent_videos(mid) for v in videos: print(f"视频: {v['title']} (bvid={v['bvid']})")

这里唯一需要解释的是/x/space/wbi/arc/search这个接口,它是查用户投稿列表的,属于需要WBI签名的接口。如果不用签名,大概率返回-403

实际跑起来之后,输入一个UID,几秒钟就能拉回用户画像和近期投稿,完全满足查成分的需求。

3.4 接口报错后怎么快速定位问题

开发过程中最常见的报错就那几种,我遇到的基本都能对上号:

  • code=-412:请求被风控拦截了。一般是请求频率太高或者IP有异常,解决方案是降低频率、加随机延时、清理无效Cookie。
  • code=-101:未登录。有些接口必须带登录Cookie,不带直接报这个。登录后的Cookie里最关键的是SESSDATA字段。
  • code=-403:权限不足或者是WBI签名不对。先检查签名逻辑,再检查账号权限。
  • code=-404:视频不存在、已删除、或者当前账号无权限看充电视频。

排查的顺序我总结过,先看参数是否完整,再看Cookie是否有效,然后核对签名,最后看请求头。90%的问题都是出在这四个环节上。

4. 参数和API的高级玩法:m4s合并、充电视频、倍速

4.1 m4s文件为什么存在,怎么合并

前面提到playurl接口的fnval=16会返回DASH格式,视频流和音频流是分离的。B站为什么要这么做?因为DASH是流媒体自适应协议,可以根据用户网速动态切换清晰度,而且音视频分离后,可以在不重新编码视频的情况下单独切换音轨、字幕。但代价就是每个分片文件不是标准的MP4容器,而是m4s格式,电脑上的大多数播放器直接打不开。

合并m4s其实非常简单,用ffmpeg一条命令就够:

ffmpeg -i video.m4s -i audio.m4s -c copy output.mp4

-c copy表示不重新编码直接拷贝流,速度飞快,一个几十分钟的视频几秒钟就合并完了。需要注意如果视频有封面、弹幕、字幕等附加流,可能还需要加-map 0:v -map 1:a来指定映射,否则ffmpeg会挑第一个视频流和第一个音频流。

网上那些m4s合并工具的底层逻辑就是这个。如果你自己写,只需要把playurl接口返回的dash.video[0].baseUrldash.audio[0].baseUrl下载下来,再调ffmpeg合并就行。工具本身不复杂,难点在于处理不同编码(AV01、HEVC、AVC)和不同音频格式(AAC、杜比、高音质)的兼容性。

4.2 充电视频解析的正确打开方式

B站的“充电专属视频”热度一直很高,相关的解析网站和工具层出不穷。先说清楚原理:这类视频调用/x/web-interface/view接口时,data.badgepay字段会是true,同时视频状态也会带一个rights.elec的标记。如果你尝试用playurl接口拿播放地址,B站校验当前账号没有充电记录,就会返回-403或者直接返回空数据。

但如果你真的充过电,用自己账号的Cookie去请求,就能正常拿到DASH流,后续处理和普通视频一样。换句话说,这类工具的核心其实是“鉴权”,而不是“破解”。平台在服务端做了权限校验,正常的下载都能完成。

这里必须多说一句:不要想着绕过权限校验去抓别人的充电视频,这在平台规则上属于违规行为,账号很容易被风控甚至封禁。自己写工具自用、备份自己购买的内容完全没问题,但公开传播和破解就踩线了。你能从别人的开源项目里学到很多接口调用思路,但没必要去复刻那些灰色玩法。

4.3 网页端倍速、快捷键和自定义参数

“B站1.75倍速设置方法”、“网页版修改快捷键”这两个热词,其实就是前端播放器的参数和事件处理问题。

B站播放器默认提供0.5、0.75、1.0、1.25、1.5、2.0这几个倍速档位,但如果你直接在浏览器控制台里找到video元素,然后设置video.playbackRate = 1.75,就能突破档位限制。更进一步,你可以用浏览器书签脚本或者油猴脚本,在播放页加载后自动执行:

const video = document.querySelector('video'); video.playbackRate = 1.75;

快捷键方面,B站播放器注册了一批默认键盘事件,比如空格是播放/暂停,D键是切换弹幕,F键是全屏。想改成自己喜欢的键位,可以通过拦截键盘事件来实现:

document.addEventListener('keydown', function (e) { if (e.key === 'd') { // 跳过默认的弹幕开关,改成别的操作 e.preventDefault(); // do something else } }, true);

这里用到的是浏览器事件捕获机制,在B站自己的处理函数之前把事件截获掉。道理不复杂,但需要你对前端事件流有一定理解。

4.4 常见AI接口报错和B站接口报错的对照

写B站工具这几年,我还经常在社区里看到有人把AI接口的报错和B站接口的报错混在一起问。比如“api error: 400 invalid schema for function 'artifact'”,这其实是调用某些大模型接口时参数schema校验失败,意思是传给AI的参数结构不符合定义,而不是B站的问题。

这类问题排查思路和B站接口一样,先看参数类型对不对,再看必填项是否齐全。AI接口的schema错误往往是因为少传了nameparameters这些字段,或者在parameters里用了错误的JSON格式。我自己的习惯是先打印出完整请求体,和官方文档示例对比一遍,90%的错误都能自己看出来。

5. 常见问题与排查技巧实录

5.1 请求频繁被风控的应对思路

B站的风控不会提前通知你,表现就是某一次请求开始,所有接口突然返回-412。这个状态码的意思是请求被Web应用防火墙拦截了,通常是IP被暂时加入了黑名单。

我的应对思路是这几个:

  • 降低请求频率,同一IP的并发请求控制在每秒1-2次以内。
  • 每次请求之间加随机延时,比如time.sleep(random.uniform(0.5, 1.5))
  • 清理之前请求中带过的无效Cookie,因为有些无效Cookie反而会触发风控。
  • 如果只是临时触发,等待10-30分钟一般会自动恢复。
  • 换一个网络出口再等一段时间,也能解决部分IP维度的问题。

不要小看频率控制这个问题,很多人的工具刚写完时跑得好好的,挂了几个小时后突然所有接口都返回-412,就是因为没控制频率。我写批量采集脚本时,永远会先做一小批测试,再放开全量爬。

5.2 SESSDATA失效怎么提前感知

登录Cookie里的SESSDATA是有有效期的,B站网页端登录的SESSDATA一般几个月有效,但过期时间不固定。一旦过期,所有需要登录的接口都会返回-101

提前感知的方法是在程序启动时,先请求一次https://api.bilibili.com/x/web-interface/nav,这个接口会返回当前登录状态。如果data.isLoginfalse,直接退出并提示重新登录。这样比等到真正调业务接口时才报错要友好得多。

另外补充一个细节,bili_jct这个Cookie是CSRF令牌,调POST接口或者需要写操作的接口时会用到,调GET接口时一般用不上。

5.3 参数类型对不上导致的400错误

B站接口虽然整体很宽容,但有个别接口对参数类型有严格要求。比如mid必须传int,不能传字符串;ps不能超过50;pn必须大于等于1。如果你传了错误类型,返回值可能是code=-400或者code=400

排查方法就是在调用前打印完整的请求URL:

resp = session.get(url, params=params) print(resp.url) # 关键:打印实际请求的URL

然后肉眼检查URL里的参数有没有被错误编码、类型对不对。这个方法救了我不下十次,很多看起来像“签名错误”的报错,最后发现是参数类型问题。

5.4 视频下载后无法播放大全

下载下来的m4s文件合并好后无法播放,这个问题的根源往往是视频流和音频流编码不兼容。比如视频流是AV1编码,音频流是AAC,这在旧版播放器上可能会出问题。

解决办法有几个,按优先级排序:

  • 在playurl接口里通过fnvalfourk参数组合,指定优先返回HEVC或者AVC编码的视频流。
  • 用ffmpeg合并时加上-strict experimental参数,让格式兼容性更好。
  • 最终极的办法是-c:v libx264 -c:a aac重新编码,但这样会损失画质且耗时较长。

我个人的习惯是优先用dash.video列表里codecs字段包含avc的那个流,兼容性好,体积也在可接受范围内。

写在最后

B站参数和API这套东西,其实没有你想象中那么难,难的是把一个个零散的接口串成一个能解决问题的工具。多翻翻网页版开发者工具里的请求记录、多看看社区里的开源项目,比死记文档管用得多。我自己踩过最大的坑就是盲目追求“最新接口”而忽略了基础参数的使用,结果绕了一大圈发现官方文档里早就写好了。如果你准备动手写自己的工具,建议先把这个页面的view接口跑通,再往playurl和弹幕扩展,循序渐进,很快就上手了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询