使用 Python 爬取 Bilibili 弹幕数据并导出 Excel

使用 Python 爬取 Bilibili 弹幕数据并导出 Excel

前言

Bilibili 作为国内知名的视频弹幕网站,其弹幕数据具有很高的分析价值。本文将介绍如何使用 Python 爬取 Bilibili 视频弹幕数据,并通过 Protocol Buffers 解析数据,最终导出到 Excel 文件。

一、项目概述

本项目实现了一个 Bilibili 弹幕爬虫工具,主要功能包括:

  1. 通过 Bilibili API 获取视频弹幕数据
  2. 使用 Protocol Buffers 解析二进制弹幕数据
  3. 数据清洗和格式化处理
  4. 导出到 Excel 文件并支持增量更新

二、技术要点

2.1 Protocol Buffers 简介

Bilibili 的弹幕 API 返回的是 Protocol Buffers(protobuf)格式的二进制数据,而不是常见的 JSON 格式。Protocol Buffers 是 Google 开发的一种数据序列化工具,具有体积小、解析快等优点。

我们需要:

  1. 定义 .proto 文件来描述数据结构
  2. 使用 protoc 编译器生成 Python 代码
  3. 使用生成的代码解析二进制数据

2.2 Bilibili 弹幕 API

Bilibili 的弹幕 API 端点:

复制代码
https://api.bilibili.com/x/v2/dm/wbi/web/seg.so

这个 API 需要以下参数:

  • type: 弹幕类型
  • oid: 视频 OID
  • pid: 视频 PID
  • segment_index: 分段索引
  • w_ridwts: 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 []

关键点解析:

  1. 发送请求 :使用 requests.get() 获取二进制数据
  2. 解析 Protobuf
    • 创建 Feed 对象
    • 使用 ParseFromString() 解析二进制数据
    • 使用 MessageToDict() 转换为字典格式
  3. 数据格式化:提取所需字段并清洗数据
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

  1. 打开浏览器,访问 Bilibili 并登录
  2. 按 F12 打开开发者工具
  3. 切换到 Network 标签
  4. 刷新页面,找到任意请求
  5. 在 Request Headers 中复制 Cookie 字段
  6. 将 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 解析。需要使用:

  1. 定义 .proto 文件:描述数据结构
  2. 生成 Python 代码 :使用 protoc 编译器
  3. 解析数据 :使用生成的 Feed
python 复制代码
info = Feed()
info.ParseFromString(response.content)  # 解析二进制数据
_data = MessageToDict(info)  # 转换为字典

6.2 数据去重和合并

支持增量更新,如果 Excel 文件已存在,会:

  1. 读取已有数据
  2. 合并新数据
  3. 根据弹幕ID去重
  4. 保存合并后的数据

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 弹幕数据,关键技术点包括:

  1. ✅ Protocol Buffers 数据解析
  2. ✅ Bilibili API 调用
  3. ✅ 数据处理和清洗
  4. ✅ Excel 文件操作
  5. ✅ 数据去重和合并

通过本项目,你可以:

  • 学习 Protocol Buffers 的使用
  • 了解 Bilibili 弹幕 API 的工作机制
  • 掌握数据爬取和处理的基本流程

十、参考资源


注意事项:

  • 本教程仅供学习交流使用
  • 请遵守 Bilibili 的服务条款
  • 不要对服务器造成过大压力
  • 请勿将爬取的数据用于商业用途

完整代码: 已上传至 GitHub,欢迎 Star 和 Fork!


如果本文对你有帮助,欢迎点赞、收藏、评论!

相关推荐
wtsolutions2 小时前
Understanding Excel Data Formats - What Excel to JSON Supports
ui·json·excel
Arms2062 小时前
python时区库学习
开发语言·python·学习
短剑重铸之日2 小时前
《7天学会Redis》特别篇: Redis分布式锁
java·redis·分布式·后端·缓存·redission·看门狗机制
与光同尘 大道至简2 小时前
ESP32 小智 AI 机器人入门教程从原理到实现(自己云端部署)
人工智能·python·单片机·机器人·github·人机交互·visual studio
清水白石0082 小时前
深入 Python 对象模型:PyObject 与 PyVarObject 全解析
开发语言·python
ぁず2 小时前
excel想生成一列随机数并删除公式保留值
excel
独自破碎E2 小时前
说说Java中的反射机制
java·开发语言
tjjucheng2 小时前
小程序定制开发服务商推荐
python
囊中之锥.2 小时前
《从零到实战:基于 PyTorch 的手写数字识别完整流程解析》
人工智能·pytorch·python