B站 UP 主全量视频抓取:wbi 签名与系列筛选实战

# B站 UP 主全量视频抓取:wbi 签名与系列筛选实战 > 摘要:新版 B站接口全面引入 wbi 签名,让"拉取某 UP 主全部视频"从几行 requests 变成一道坎。本文给出完整的 wbi 签名实现(mixin key 算法 + nav 接口取密钥),并用真实案例演示:从 262 个视频中精确筛出 128 期系列节目,包括处理标题不带编号的"漏网之鱼"。 ## 背景 想批量整理某 B站 UP 主的某个系列视频(比如周更栏目),第一步就是拿到 TA 的**全部视频列表**。老接口 `x/space/arc/search` 已废弃,新接口 `x/space/wbi/arc/search` 强制要求 wbi 签名——参数里要带 `w_rid` 和 `wts`,否则直接 412。 ## 正文 ### 1. wbi 签名原理 wbi 签名的核心是 **mixin key**: 1. 请求 `x/web-interface/nav` 拿 `wbi_img.img_url` 和 `sub_url` 2. 取两个 URL 文件名(去扩展名)拼起来:`img_key + sub_key` 3. 按固定的 64 位重排表 `mixinKeyEncTab` 重排,取前 32 位 → mixin key 4. 参数按 key 排序,拼上 `wts`(当前时间戳),再拼 mixin key,整体做 MD5 → `w_rid` 代码实现(Python): ```python import hashlib, time, urllib.parse from functools import reduce mixinKeyEncTab = [ 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(orig: str) -> str: return reduce(lambda s, i: s + orig[i], mixinKeyEncTab, '')[:32] def enc_wbi(params: dict, img_key: str, sub_key: str) -> dict: mixin_key = get_mixin_key(img_key + sub_key) params['wts'] = round(time.time()) params = dict(sorted(params.items())) query = urllib.parse.urlencode(params) params['w_rid'] = hashlib.md5((query + mixin_key).encode()).hexdigest() return params ``` ### 2. 拿密钥 + 分页拉全量 密钥从 `x/web-interface/nav` 拿(该接口本身不需要签名): ```python def get_wbi_keys(session): d = session.get('https://api.bilibili.com/x/web-interface/nav').json() img = d['data']['wbi_img']['img_url'] sub = d['data']['wbi_img']['sub_url'] return img.rsplit('/', 1)[-1].split('.')[0], sub.rsplit('/', 1)[-1].split('.')[0] ``` 分页拉取(`ps=50` 每页,翻到 `count` 为止): ```python all_videos = [] pn = 1 while True: params = enc_wbi({'mid': MID, 'ps': 50, 'pn': pn, 'order': 'pubdate'}, img_key, sub_key) d = session.get('https://api.bilibili.com/x/space/wbi/arc/search', params=params, timeout=20).json() vlist = d['data']['list']['vlist'] all_videos.extend(vlist) if len(all_videos) >= d['data']['page']['count'] or not vlist: break pn += 1 time.sleep(1) # 温柔一点,别把自己送进风控 ``` 几个细节: - **必须带登录 cookie**(浏览器导出,含 SESSDATA),否则 nav 拿不到完整响应 - **请求头**:`User-Agent` 用真实 Chrome 版本号,`Referer` 指向 UP 主空间页 - 请求库推荐 `curl_cffi`(`impersonate='chrome'` 模拟浏览器指纹),比裸 requests 稳得多 ### 3. 系列筛选:不只是关键词匹配 262 个视频里筛出 128 期系列,难点不在"关键词",而在**标题格式不统一**: | 标题形态 | 例子 | 处理 | |---------|------|------| | 标准编号 | 「Github一周热点126期」 | 正则 `(\d+)期` | | 无「期」字 | 「Github一周热点19」 | 正则 `热点(\d+)` | | 无编号 | 「GitHub本周必收藏的5个热点项目!…」 | 人工映射表兜底 | | 无关键词 | 「AI Agent的记忆系统、超省钱的…」 | 按发布时间窗口 + 人工确认 | 结论:**纯正则筛不全,必须加一张"特殊标题 → 期号"的映射表**,配合发布时间窗口校验(相邻期号间隔约一周,可用于发现漏网视频)。 ## 踩坑记录 1. **wbi 接口裸调必 412**——不是被风控,是缺 `w_rid`。先确认签名实现正确,再谈别的 2. **nav 接口拿到的 key 会轮换**——建议每次会话重新拉一次,不要缓存复用 3. **搜索接口 ≠ 全量**:`x/web-interface/search/type` 只能搜到部分视频,全量必须走 `arc/search` ## 总结 - wbi 签名 = mixinKeyEncTab 重排 + wts + MD5,半小时能实现 - 全量列表抓取的正确姿势:nav 取密钥 → 签名分页 → 落盘 JSON - 系列筛选要用「正则 + 映射表 + 时间窗口」三件套,别只靠关键词 下一步:拿到列表后如何批量下载视频字幕(限流、假残缺排查),见系列下一篇。
posted @ 2026-08-15 10:52  劉均年  阅读(4)  评论(0)    收藏  举报