使用 Python 爬取 Bilibili 弹幕数据并导出 Excel
前言
Bilibili 作为国内知名的视频弹幕网站,其弹幕数据具有很高的分析价值。本文将介绍如何使用 Python 爬取 Bilibili 视频弹幕数据,并通过 Protocol Buffers 解析数据,最终导出到 Excel 文件。
一、项目概述
本项目实现了一个 Bilibili 弹幕爬虫工具,主要功能包括:
- 通过 Bilibili API 获取视频弹幕数据
- 使用 Protocol Buffers 解析二进制弹幕数据
- 数据清洗和格式化处理
- 导出到 Excel 文件并支持增量更新
二、技术要点
2.1 Protocol Buffers 简介
Bilibili 的弹幕 API 返回的是 Protocol Buffers(protobuf)格式的二进制数据,而不是常见的 JSON 格式。Protocol Buffers 是 Google 开发的一种数据序列化工具,具有体积小、解析快等优点。
我们需要:
- 定义
.proto文件来描述数据结构 - 使用
protoc编译器生成 Python 代码 - 使用生成的代码解析二进制数据
2.2 Bilibili 弹幕 API
Bilibili 的弹幕 API 端点:
https://api.bilibili.com/x/v2/dm/wbi/web/seg.so
这个 API 需要以下参数:
type: 弹幕类型oid: 视频 OIDpid: 视频 PIDsegment_index: 分段索引w_rid和wts: Web 签名参数- 其他参数...
由于视频弹幕数据量较大,Bilibili 将其分成多个分段存储,每个分段有独立的 segment_index。
三、项目实现
3.1 Protocol Buffer 定义文件
首先,我们需要定义 feed.proto 文件来描述弹幕数据结构:
protobuf
syntax = 'proto3';
message Feed {
message Message{
int32 id = 1;
int32 progress = 2;
int32 mode = 3;
int32 fontsize = 4;
int32 color = 5;
string midHash = 6;
string content = 7;
int32 ctime = 8;
int32 weight = 9;
string idStr = 13;
}
repeated Message message = 1;
}
3.2 生成 Python 代码
使用 protoc 编译器生成 Python 代码:
bash
protoc --python_out=. feed.proto
这会生成 feed_pb2.py 文件,包含了用于解析 protobuf 数据的 Python 类。
3.3 主程序实现
3.3.1 导入依赖库
python
import json
import requests
import pandas as pd
import os
import re
from feed_pb2 import Feed
from google.protobuf.json_format import MessageToDict
3.3.2 数据清洗函数
Excel 对某些特殊字符不兼容,需要清理:
python
def clean_text(text):
"""清理文本中的特殊字符"""
if not isinstance(text, str):
return text
# 移除Excel不支持的字符
text = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]', '', text)
return text
3.3.3 弹幕获取函数
核心功能:通过 API 获取弹幕数据并解析:
python
def fetch_danmu(params, cookies, headers):
"""获取单个分段的弹幕数据"""
try:
response = requests.get('https://api.bilibili.com/x/v2/dm/wbi/web/seg.so',
params=params, cookies=cookies, headers=headers)
if response.status_code != 200:
print(f'请求失败,状态码:{response.status_code}')
return []
# 使用 Protocol Buffers 解析数据
info = Feed()
info.ParseFromString(response.content)
_data = MessageToDict(info, preserving_proto_field_name=True)
messages = _data.get("message") or []
# 格式化数据
danmu_list = []
for message in messages:
danmu_list.append({
'弹幕ID': clean_text(message.get('idStr')),
'发送时间': message.get('ctime'),
'弹幕内容': clean_text(message.get('content')),
'弹幕模式': message.get('mode'),
'字体大小': message.get('fontsize'),
'颜色': message.get('color'),
'用户Hash': clean_text(message.get('midHash')),
'进度': message.get('progress'),
'权重': message.get('weight')
})
return danmu_list
except Exception as e:
print(f'获取分段 {params["segment_index"]} 的弹幕时出错:{str(e)}')
return []
关键点解析:
- 发送请求 :使用
requests.get()获取二进制数据 - 解析 Protobuf :
- 创建
Feed对象 - 使用
ParseFromString()解析二进制数据 - 使用
MessageToDict()转换为字典格式
- 创建
- 数据格式化:提取所需字段并清洗数据
3.3.4 主函数
主函数负责组织整个爬取流程:
python
def start_requests():
# 1. 配置 Cookies 和 Headers
cookies = {...}
headers = {...}
# 2. 定义所有分段的参数
segments_params = [
{
'type': '1',
'oid': '26637832007',
'pid': '113276373500554',
'segment_index': '1',
# ... 其他参数
},
# ... 更多分段
]
# 3. 遍历所有分段获取弹幕
all_danmu = []
for params in segments_params:
danmu_list = fetch_danmu(params, cookies, headers)
all_danmu.extend(danmu_list)
# 4. 数据处理和导出
new_df = pd.DataFrame(all_danmu)
excel_file = 'bilibili_danmu.xlsx'
# 5. 合并已有数据并去重
if os.path.exists(excel_file):
existing_df = pd.read_excel(excel_file)
combined_df = pd.concat([existing_df, new_df], ignore_index=True)
combined_df = combined_df.drop_duplicates(subset=['弹幕ID'], keep='last')
else:
combined_df = new_df
# 6. 保存到 Excel
combined_df.to_excel(excel_file, index=False, engine='openpyxl')
四、使用步骤
4.1 安装依赖
bash
pip install requests pandas openpyxl protobuf
4.2 获取 Cookies
- 打开浏览器,访问 Bilibili 并登录
- 按 F12 打开开发者工具
- 切换到 Network 标签
- 刷新页面,找到任意请求
- 在 Request Headers 中复制 Cookie 字段
- 将 Cookie 字符串解析为字典格式
4.3 获取视频参数
需要获取以下参数:
- oid:视频 OID,可通过视频页面的网络请求获取
- pid:视频 PID,同样通过网络请求获取
- segment_index:分段索引,通常从 1 开始递增
4.4 运行程序
bash
python main.py
五、数据字段说明
导出的 Excel 文件包含以下字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
| 弹幕ID | String | 弹幕的唯一标识符 |
| 发送时间 | Integer | Unix 时间戳 |
| 弹幕内容 | String | 弹幕的文本内容 |
| 弹幕模式 | Integer | 滚动、顶部、底部等模式 |
| 字体大小 | Integer | 弹幕字体大小 |
| 颜色 | Integer | 颜色值(RGB 格式) |
| 用户Hash | String | 用户哈希值(已脱敏) |
| 进度 | Integer | 弹幕在视频中的时间位置(毫秒) |
| 权重 | Integer | 弹幕权重 |
六、技术难点解析
6.1 Protocol Buffers 解析
Bilibili 返回的是 protobuf 格式的二进制数据,不能直接用 JSON 解析。需要使用:
- 定义 .proto 文件:描述数据结构
- 生成 Python 代码 :使用
protoc编译器 - 解析数据 :使用生成的
Feed类
python
info = Feed()
info.ParseFromString(response.content) # 解析二进制数据
_data = MessageToDict(info) # 转换为字典
6.2 数据去重和合并
支持增量更新,如果 Excel 文件已存在,会:
- 读取已有数据
- 合并新数据
- 根据弹幕ID去重
- 保存合并后的数据
6.3 文本清洗
Excel 对某些控制字符不兼容,需要清理:
python
text = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]', '', text)
七、优化建议
7.1 添加重试机制
网络请求可能失败,可以添加重试:
python
import time
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
def get_session_with_retry():
session = requests.Session()
retry = Retry(total=3, backoff_factor=0.3)
adapter = HTTPAdapter(max_retries=retry)
session.mount('http://', adapter)
session.mount('https://', adapter)
return session
7.2 添加进度条
使用 tqdm 显示爬取进度:
python
from tqdm import tqdm
for params in tqdm(segments_params, desc='爬取进度'):
danmu_list = fetch_danmu(params, cookies, headers)
all_danmu.extend(danmu_list)
7.3 多线程爬取
对于多个分段,可以使用多线程加速:
python
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=5) as executor:
futures = [executor.submit(fetch_danmu, params, cookies, headers)
for params in segments_params]
for future in futures:
all_danmu.extend(future.result())
八、常见问题
Q1: Cookies 过期怎么办?
A: Cookies 有时效性,过期后需要重新获取。可以将 Cookies 保存到配置文件,定期更新。
Q2: 如何确定分段数量?
A: 可以通过测试不同的 segment_index 值,直到返回空数据为止。
Q3: 数据爬取不完整?
A: 检查所有分段是否都已包含在 segments_params 列表中,某些视频可能有更多分段。
九、总结
本文介绍了如何使用 Python 爬取 Bilibili 弹幕数据,关键技术点包括:
- ✅ Protocol Buffers 数据解析
- ✅ Bilibili API 调用
- ✅ 数据处理和清洗
- ✅ Excel 文件操作
- ✅ 数据去重和合并
通过本项目,你可以:
- 学习 Protocol Buffers 的使用
- 了解 Bilibili 弹幕 API 的工作机制
- 掌握数据爬取和处理的基本流程
十、参考资源
注意事项:
- 本教程仅供学习交流使用
- 请遵守 Bilibili 的服务条款
- 不要对服务器造成过大压力
- 请勿将爬取的数据用于商业用途
完整代码: 已上传至 GitHub,欢迎 Star 和 Fork!
如果本文对你有帮助,欢迎点赞、收藏、评论!