WBI 签名是什么
B 站在 2023 年引入 WBI(Web Bilibili Interface)签名机制,用于保护 Web 端 API 接口不被爬虫滥用。与 App 端 MD5 签名不同,WBI 使用动态密钥 + 混淆映射表。
密钥获取
WBI 密钥从 /x/web-interface/nav 接口动态获取:
import requests
def get_wbi_keys(cookies: dict) -> tuple:
r = requests.get(
'https://api.bilibili.com/x/web-interface/nav',
cookies=cookies
)
data = r.json()['data']['wbi_img']
# img_url: https://i0.hdslb.com/bfs/wbi/abc123.png
img_key = data['img_url'].rsplit('/', 1)[1].split('.')[0]
sub_key = data['sub_url'].rsplit('/', 1)[1].split('.')[0]
return img_key, sub_key
混淆映射表(从 B 站前端 JS 中提取)
MIXIN_KEY_ENC_TAB = [
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35,
27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13,
37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4,
22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52,
]
def get_mixin_key(img_key: str, sub_key: str) -> str:
raw = img_key + sub_key # 两个 key 拼接
# 按映射表取字符,取前 32 位
return ''.join(raw[i] for i in MIXIN_KEY_ENC_TAB)[:32]
完整 WBI 签名
import hashlib, time, urllib.parse, re
def wbi_sign(params: dict, mixin_key: str) -> dict:
params = dict(params)
params['wts'] = str(int(time.time()))
# 按 key 字母排序
sorted_params = dict(sorted(params.items()))
# URL encode 后过滤特殊字符 !'()*
query = urllib.parse.urlencode(sorted_params)
query = re.sub(r"[!'()*]", '', query)
# 追加 mixin_key 后取 MD5
w_rid = hashlib.md5((query + mixin_key).encode()).hexdigest()
sorted_params['w_rid'] = w_rid
return sorted_params
浏览器指纹构造
B 站的 Web 风控会检查客户端指纹,需要同时上报 b_nut(canvas hash)、buvid_fp(指纹 ID)等字段。从真实 Chrome 中提取后存入 fp_data.json:
import json
with open('fp_data.json') as f:
fp = json.load(f)
# 上报心跳时携带
params = {
'bvid': 'BV1xx411c7mD',
'aid': aid,
'cid': cid,
'buvid': fp['buvid3'],
'b_nut': fp['b_nut'],
...
}
signed = wbi_sign(params, mixin_key)
播放心跳上报流程
1. GET /x/click-interface/click/web/h5 → 初始化点击事件
2. 每 15 秒 POST /x/click-interface/web/heartbeat → 持续上报
参数:aid, cid, bvid, mid, played_time, realtime, start_ts, type, dt
注:App 端心跳使用 /x/report/heartbeat/mobile,参数与 Web 端不同,签名使用 App 套件(appkey=1d8b6e7d45233436)。