前言 B站(Bilibili)是国内最大的弹幕视频社区,用户关注的UP主列表包含了个人订阅偏好信息。在某些场景下,我们可能需要导出自己关注的UP主列表——比如迁移账号、备份关注列表、或者分析自己的观看偏好。B站官方并没有提供”一键导出关注列表”的功能,但通过分析其API接口,我们可以编写爬虫程序自动获取。
B站的关注列表API需要用户登录态(Cookie)才能访问,且采用分页返回数据。本文基于真实项目源码,介绍如何通过Cookie认证、分页请求、JSON解析,完整获取B站用户关注的所有UP主列表,并进行去重处理和数据导出。同时也会介绍两个基础爬虫案例作为对比学习。
技术背景 本项目的核心技术栈:
requests :发送带Cookie的HTTP GET请求
json :解析API返回的JSON数据,结构化存储结果
Cookie认证 :解析Cookie字符串,通过 requests 的 cookies 参数传递
B站关注列表API分析:
1 GET https://api.bilibili.com/x/relation/followings?vmid={user_id}&pn={page}&ps={size}
参数
说明
vmid
目标用户的UID
pn
页码,从1开始
ps
每页条数,最大50
返回数据结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 { "code" : 0 , "message" : "0" , "data" : { "list" : [ { "mid" : "UP主UID" , "uname" : "UP主名称" , "face" : "头像URL" , "sign" : "个性签名" , ... } ] } }
实现思路 整体流程 1 2 3 4 5 6 1. 解析Cookie字符串 → 构造cookies字典 2. 设置请求头(User-Agent、Referer、Origin) 3. 循环分页请求API → 累积到followings列表 4. 检测list为空 → 终止分页 5. 打印所有UP主信息 6. 保存结果到JSON文件
去重策略 项目中去重体现在两个层面:
API层面 :B站API本身不会返回重复数据,但通过 pn 逐页请求确保覆盖完整
数据层面 :保存时以UP主的 mid 作为唯一标识,后续可基于 mid 进行 set() 去重
核心代码解析 1. Cookie解析与请求头构造 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import requestsimport jsondef get_followings (user_id, cookies_str, output_file=None ): """获取B站用户关注的所有UP主 :param user_id: 目标用户的B站UID :param cookies_str: 浏览器中复制的Cookie字符串 :param output_file: 输出文件路径(可选) :return: 所有关注的UP主列表 """ followings = [] pn = 1 ps = 50 cookies = {} for item in cookies_str.split('; ' ): if '=' in item: key, value = item.split('=' , 1 ) cookies[key] = value 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://space.bilibili.com/' + user_id, 'Accept' : 'application/json, text/plain, */*' , 'Accept-Language' : 'zh-CN,zh;q=0.9,en;q=0.8' , 'Origin' : 'https://www.bilibili.com' , }
关键点解析 :
Cookie字符串解析时使用 split('=', 1),maxsplit=1 确保只按第一个等号分割,因为Cookie值中可能包含 = 字符
Referer 头设置为用户空间URL,B站API会校验此字段,缺失会导致请求失败
Origin 设置为 https://www.bilibili.com,模拟从B站页面发起的请求
2. 分页循环请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 print (f"正在获取用户 {user_id} 的关注列表..." )while True : url = f"https://api.bilibili.com/x/relation/followings?vmid={user_id} &pn={pn} &ps={ps} " try : response = requests.get(url, headers=headers, cookies=cookies, timeout=10 ) data = response.json() if data['code' ] != 0 : print (f"API返回错误: {data.get('message' , '未知错误' )} " ) break list_data = data['data' ]['list' ] if not list_data: break followings.extend(list_data) print (f"已获取 {len (list_data)} 个UP主 (第 {pn} 页)" ) pn += 1 except Exception as e: print (f"请求出错: {e} " ) break print (f"\n总共获取到 {len (followings)} 个关注的UP主\n" )
分页终止策略 :使用 while True 无限循环,当 list_data 为空列表时 break 跳出循环。这是处理未知总页数的经典模式——不知道总共有多少页,但知道空列表意味着结束。
3. 结果打印与文件保存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 for i, f in enumerate (followings, 1 ): print (f"{i} . {f['uname' ]} - https://space.bilibili.com/{f['mid' ]} " ) if output_file: result = { "total" : len (followings), "followings" : [ { "name" : f['uname' ], "mid" : f['mid' ], "url" : f"https://space.bilibili.com/{f['mid' ]} " , "face" : f.get('face' , '' ), "sign" : f.get('sign' , '' ) } for f in followings ] } with open (output_file, 'w' , encoding='utf-8' ) as f: json.dump(result, f, ensure_ascii=False , indent=2 ) print (f"\n结果已保存到: {output_file} " ) return followings
4. 主程序调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 if __name__ == "__main__" : print ("=" * 50 ) print ("B站关注UP主获取工具" ) print ("=" * 50 ) user_id = "3546731104963406" cookies_str = "buvid3=B32AEEAD-55B6-89B8-28BF-0F84F090844572692infoc; b_nut=1771848572; SESSDATA=c474c976%2C1787400602%2C984cf%2A21CjC6pQfyRWm9_jIqM07mFckrqxWpO0_Gk_SR2Zw7ozxRiGcvyXli9; bili_jct=59fd69163e2c2ce7532cc360027b66ed; DedeUserID=3546731104963406; ..." output_file = "bilibili_followings.json" get_followings(user_id, cookies_str, output_file)
获取Cookie的步骤 :
在浏览器中登录B站
打开开发者工具(F12)→ Network标签
访问 https://space.bilibili.com/你的UID/fans/follow
找到 followings API请求,复制请求头中的Cookie值
粘贴到 cookies_str 变量中
拓展:基础爬虫案例对比 案例一:网页标题抓取器(crawler1.py) 作为对比,先看一个更基础的爬虫——获取任意网页的标题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import requestsimport bs4import validatorsimport chardetimport logginglogging.basicConfig(level=logging.INFO, format ='%(asctime)s - %(levelname)s - %(message)s' ) def get_page_title (url ): """从给定的URL获取网页标题""" if not url or not validators.url(url): logging.error("Invalid URL" ) return None session = requests.Session() try : response = session.get(url, timeout=10 ) if response.status_code == 200 : encoding = chardet.detect(response.content)['encoding' ] soup = bs4.BeautifulSoup(response.content, 'html.parser' , from_encoding=encoding) title = soup.title.string if soup.title else None return title elif 300 <= response.status_code < 400 : logging.warning(f"Redirect occurred for URL: {url} with status code: {response.status_code} " ) return None else : logging.error(f"Failed to get the page with status code: {response.status_code} " ) return None except requests.RequestException as e: logging.error(f"An error occurred: {e} " ) return None if __name__ == '__main__' : url = 'https://www.baidu.com' title = get_page_title(url) if title: print (f"The title of the page is: {title} " )
这个例子展示了几个重要的爬虫基础:
validators.url() :URL格式校验
chardet.detect() :自动检测网页编码,解决乱码问题
Session :复用TCP连接,提升多次请求的效率
案例二:东方财富API数据获取(crawler2.py) 另一个对比案例——调用东方财富的股市数据API:
1 2 3 4 5 6 7 8 9 import jsonimport requestsif __name__ == '__main__' : url = "https://80.push2.eastmoney.com/api/qt/clist/get?cb=jQuery112406361842533949766_1721734704644&pn=1&pz=20&po=1&np=1&ut=bd1d9ddb04089700cf9c27f6f7426281&fltt=2&invt=2&dect=1&wbp2u=|0|0|0|web&fid=f3&fs=i:1.000001,i:0.399001,i:0.399005&fields=f1,f2,f3,f4,f5,f6,f12,f14" res = requests.get(url) data = json.loads(res.text) print (data)
这个例子展示了直接调用API的简单模式——不需要Cookie认证,直接GET请求即可获取JSON数据。
运行效果 运行B站关注UP主获取工具后,控制台输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ================================================== B站关注UP主获取工具 ================================================== 正在获取用户 3546731104963406 的关注列表... 已获取 50 个UP主 (第 1 页) 已获取 50 个UP主 (第 2 页) 已获取 50 个UP主 (第 3 页) 已获取 30 个UP主 (第 4 页) 总共获取到 180 个关注的UP主 1. 某UP主 - https://space.bilibili.com/123456 2. 另一个UP主 - https://space.bilibili.com/789012 ... 180. 最后一个UP主 - https://space.bilibili.com/999999 结果已保存到: bilibili_followings.json
保存的JSON文件格式:
1 2 3 4 5 6 7 8 9 10 11 12 { "total" : 180 , "followings" : [ { "name" : "UP主名称" , "mid" : "123456" , "url" : "https://space.bilibili.com/123456" , "face" : "https://i0.hdslb.com/..." , "sign" : "这是个性签名" } ] }
总结与优化方向 本项目实现了B站关注UP主列表的完整获取流程,核心技术点包括:
Cookie认证 :解析Cookie字符串为字典,通过 requests.get(cookies=...) 传递登录态
分页迭代 :while True + 空列表检测,处理未知总页数的分页API
异常处理 :try/except 捕获网络异常,确保程序不会因单次请求失败而崩溃
超时控制 :timeout=10 防止请求长时间阻塞
结构化输出 :JSON格式保存,包含UP主名称、UID、主页URL、头像和签名
三个爬虫案例的对比:
特性
B站关注列表
网页标题抓取
东方财富API
认证方式
Cookie
无
无
数据格式
JSON API
HTML
JSONP
分页
需要
不需要
可选
编码处理
UTF-8
chardet检测
UTF-8
后续优化方向:
去重处理 :基于 mid 使用 set() 或字典去重,防止分页边界数据重复
Cookie刷新 :Cookie有效期有限,可集成Selenium自动获取新Cookie
批量获取 :支持传入多个UID,批量导出多个用户的关注列表
差异对比 :对比两次导出结果,发现新增/取消关注的UP主
分类统计 :按UP主分区(如科技、游戏、美食)进行统计可视化