python

B站获取关注UP主列表与去重:Cookie认证与分页API全解析

2026-07-06 #python#爬虫

前言

B站(Bilibili)是国内最大的弹幕视频社区,用户关注的UP主列表包含了个人订阅偏好信息。在某些场景下,我们可能需要导出自己关注的UP主列表——比如迁移账号、备份关注列表、或者分析自己的观看偏好。B站官方并没有提供”一键导出关注列表”的功能,但通过分析其API接口,我们可以编写爬虫程序自动获取。

B站的关注列表API需要用户登录态(Cookie)才能访问,且采用分页返回数据。本文基于真实项目源码,介绍如何通过Cookie认证、分页请求、JSON解析,完整获取B站用户关注的所有UP主列表,并进行去重处理和数据导出。同时也会介绍两个基础爬虫案例作为对比学习。

技术背景

本项目的核心技术栈:

  • requests:发送带Cookie的HTTP GET请求
  • json:解析API返回的JSON数据,结构化存储结果
  • Cookie认证:解析Cookie字符串,通过 requestscookies 参数传递

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 requests
import json

def 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 # 页码,从1开始
ps = 50 # 每页50条(API最大值)

# 解析cookies字符串为字典
# 浏览器复制的Cookie格式:"key1=value1; key2=value2; ..."
cookies = {}
for item in cookies_str.split('; '):
if '=' in item:
key, value = item.split('=', 1) # maxsplit=1,防止value中含等号
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, # 关键:Referer必须对应用户空间
'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:
# 构造API URL,pn为当前页码
url = f"https://api.bilibili.com/x/relation/followings?vmid={user_id}&pn={pn}&ps={ps}"
try:
# 发送带cookies和headers的GET请求,设置10秒超时
response = requests.get(url, headers=headers, cookies=cookies, timeout=10)
data = response.json()

# 检查API返回状态码
if data['code'] != 0:
print(f"API返回错误: {data.get('message', '未知错误')}")
break

# 获取当前页的UP主列表
list_data = data['data']['list']
if not list_data:
break # 列表为空说明已获取完毕

# 累积到总列表
followings.extend(list_data)
print(f"已获取 {len(list_data)} 个UP主 (第 {pn} 页)")
pn += 1 # 页码+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
# 打印所有关注的UP主信息
for i, f in enumerate(followings, 1):
print(f"{i}. {f['uname']} - https://space.bilibili.com/{f['mid']}")

# 保存到JSON文件
if output_file:
result = {
"total": len(followings), # 总数
"followings": [
{
"name": f['uname'], # UP主名称
"mid": f['mid'], # UP主UID(唯一标识)
"url": f"https://space.bilibili.com/{f['mid']}", # 主页URL
"face": f.get('face', ''), # 头像URL
"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)

# 目标用户UID(从B站空间URL获取)
user_id = "3546731104963406"

# Cookie字符串(从浏览器开发者工具 → Network → 请求头中复制)
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的步骤

  1. 在浏览器中登录B站
  2. 打开开发者工具(F12)→ Network标签
  3. 访问 https://space.bilibili.com/你的UID/fans/follow
  4. 找到 followings API请求,复制请求头中的Cookie值
  5. 粘贴到 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 requests
import bs4
import validators
import chardet
import logging

# 设置日志记录
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def get_page_title(url):
"""从给定的URL获取网页标题"""
# URL有效性校验
if not url or not validators.url(url):
logging.error("Invalid URL")
return None

# 使用Session以复用TCP连接,提高性能
session = requests.Session()
try:
response = session.get(url, timeout=10)

if response.status_code == 200:
# 使用chardet自动检测编码
encoding = chardet.detect(response.content)['encoding']
# 解析HTML,指定检测到的编码
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 json
import requests

if __name__ == '__main__':
# 东方财富API,获取全球主要指数数据
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

后续优化方向:

  1. 去重处理:基于 mid 使用 set() 或字典去重,防止分页边界数据重复
  2. Cookie刷新:Cookie有效期有限,可集成Selenium自动获取新Cookie
  3. 批量获取:支持传入多个UID,批量导出多个用户的关注列表
  4. 差异对比:对比两次导出结果,发现新增/取消关注的UP主
  5. 分类统计:按UP主分区(如科技、游戏、美食)进行统计可视化
评论
分享