python

B站弹幕数据处理与词频分析

2026-07-06 #爬虫#词频分析

前言

弹幕(Danmaku)作为B站最具特色的互动形式之一,承载了用户在观看视频时的即时情感表达和观点碰撞。每一条弹幕都是一粒数据沙砾,当它们汇聚成数千、数万条的规模时,就构成了一座可供挖掘的”民意矿山”。通过对弹幕数据进行系统的采集、清洗、分词和可视化分析,我们能够洞察观众的关注焦点、情绪倾向以及讨论的时间分布规律。

本文基于一个真实的B站弹幕分析项目,完整记录了从CSV原始数据读取、时间戳转换、中文分词、停用词过滤,到词频统计与多维可视化的全流程。项目针对多个B站热门视频的弹幕数据进行了平行分析,代码具有良好的通用性和可复用性。

无论你是做舆情监测、内容运营,还是单纯对NLP(自然语言处理)入门感兴趣,这篇实战文章都能为你提供一套可直接落地的技术方案。

技术背景

1. 弹幕数据结构

B站弹幕数据通常以CSV格式存储,每条记录包含发送时间(send_time,Unix时间戳)、弹幕内容(content)等字段。Unix时间戳是以秒为单位的整数,需要转换为人类可读的日期时间格式才能进行有意义的时间维度分析。

2. 中文分词原理

与英文天然以空格分词不同,中文文本没有明确的词边界,必须借助分词工具。本项目使用 jieba 分词库,其核心算法基于前缀词典实现高效的词图扫描,生成句子中汉字所有可能成词情况所构成的有向无环图(DAG),再利用动态规划查找最大概率路径,最后对于未登录词采用基于汉字成词能力的HMM模型进行识别。

3. 停用词过滤

分词后的结果中包含大量”的、了、是、在”等无实际语义的功能词(停用词),这些词会严重干扰词频统计的有效性。通过构建停用词表并配合长度过滤(剔除单字符),可以显著提升分析结果的质量。

4. 可视化技术栈

  • Matplotlib:绘制柱状图、直方图等统计图表
  • WordCloud:基于词频数据生成词云图,直观展示高频词
  • Pandas:负责数据加载、清洗和结构化操作

实现思路

整个分析流程采用”管道式”设计,数据从读取到产出可视化的每一步环环相扣:

1
CSV数据读取 → 数据类型转换与清洗 → 文本拼接与分词 → 停用词过滤 → 词频统计 → 多维可视化

具体而言,项目针对三个不同主题的B站视频弹幕数据分别执行了相同的分析流程,每个脚本的结构完全一致,仅数据文件路径和关键词列表有所不同。这种设计体现了良好的代码复用思想——同一套分析框架可以无缝迁移到任意弹幕数据集上。

可视化的维度包含四个方面:

  1. 时间分布图:弹幕发送量按小时的统计,揭示用户活跃时段
  2. 词云图:以图形化方式展示高频词汇的权重
  3. 关键词统计图:针对特定主题关键词的命中次数统计
  4. 弹幕长度分布:了解用户发言的字符长度分布特征

核心代码解析

4.1 环境配置与中文字体处理

中文可视化最常踩的坑就是图表中文字符显示为方框。下面的代码通过设置 rcParams 解决了这一问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
import logging
from collections import Counter

import jieba
import matplotlib.pyplot as plt
import pandas as pd
from wordcloud import WordCloud

# 减少jieba日志输出,避免干扰控制台信息
jieba.setLogLevel(logging.INFO)
# 解决中文显示问题:依次尝试黑体、微软雅黑、DejaVu Sans
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False # 正常显示负号

font.sans-serif 设置为一个字体列表,Matplotlib 会按顺序尝试加载,第一个可用的字体即被采用。axes.unicode_minus 设为 False 是为了防止负号 - 显示为方块。

4.2 数据加载与预处理

1
2
3
4
5
6
7
8
9
# 读取CSV文件中的弹幕数据,使用utf-8-sig编码避免BOM头问题
file_path = "公孙田浩弹幕_BV19R4y1K7yc_danmu.csv"
df = pd.read_csv(file_path, encoding="utf-8-sig")

# 数据清洗和预处理
# send_time 列原始值为Unix时间戳(秒),转换为datetime类型
df['send_time'] = pd.to_datetime(df['send_time'], unit='s')
# content 列确保为字符串类型,防止None值导致后续拼接报错
df['content'] = df['content'].astype(str)

这里有两个关键点值得注意:

  • encoding="utf-8-sig":CSV文件如果带有BOM(Byte Order Mark)头,使用普通 utf-8 编码读取时BOM字符会混入第一列列名中,导致列名匹配失败。utf-8-sig 能正确处理BOM头。
  • unit='s'pd.to_datetime 默认认为时间戳单位是纳秒,而B站弹幕的时间戳是秒级,必须显式指定 unit='s',否则转换结果会差好几个数量级。

4.3 中文分词与停用词过滤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 提取所有弹幕内容并拼接成一个长字符串
contents = df['content'].tolist()
all_text = ' '.join(contents)

# 使用jieba进行中文分词,返回一个generator,转为list
words = list(jieba.cut(all_text))

# 定义停用词集合——这些词出现频率高但无实际语义
stopwords = {'的', '了', '在', '是', '我', '有', '和', '就', '不', '人',
'都', '一', '一个', '上', '也', '很', '到', '说', '要', '去',
'你', '会', '着', '没有', '看', '好', '自己', '这'}

# 过滤:保留长度大于1且不在停用词表中的词
filtered_words = [word for word in words if len(word) > 1 and word not in stopwords]

# 使用Counter统计词频
word_freq = Counter(filtered_words)

# 显示高频词 Top 30
print("\n高频词汇 Top 30:")
for word, freq in word_freq.most_common(30):
print(f"{word}: {freq}")

这段代码的处理逻辑层层递进:

  1. 文本拼接:将所有弹幕合并为一个字符串,便于一次性分词。这种做法在数据量不大时简单高效,但如果弹幕量达到百万级,建议分批处理以避免内存压力。
  2. 分词jieba.cut() 返回一个生成器,通过 list() 转为列表。若追求精确模式,可使用 jieba.cut_for_search() 搜索引擎模式。
  3. 双重过滤len(word) > 1 剔除单字符(如标点、单个汉字),word not in stopwords 剔除常见无意义词。两个条件用 and 连接,同时满足才保留。
  4. 词频统计Counter 是 Python 标准库 collections 中的计数器,底层基于字典实现,most_common(n) 方法直接返回前n个高频词。

4.4 弹幕时间分布可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 从datetime中提取小时作为分组依据
df['hour'] = df['send_time'].dt.hour
# 按小时统计弹幕数量,并按小时排序
hourly_counts = df['hour'].value_counts().sort_index()

plt.figure(figsize=(10, 6))
plt.bar(hourly_counts.index, hourly_counts.values, color='skyblue')
plt.xlabel('小时')
plt.ylabel('弹幕数量')
plt.title('弹幕时间分布(按小时)')
plt.xticks(range(0, 24)) # x轴显示0-23小时
plt.grid(axis='y', alpha=0.3) # 添加y轴网格线,透明度0.3
plt.tight_layout()
plt.savefig('danmu_time_distribution.png', dpi=300, bbox_inches='tight')
plt.show()

value_counts().sort_index() 是一个经典组合:先统计各小时出现的次数,再按小时序号排序,确保柱状图从0点到23点依次排列。dpi=300 保证输出图片的清晰度,bbox_inches='tight' 自动裁剪多余白边。

4.5 词云图生成(含字体容错处理)

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
plt.figure(figsize=(10, 8))
try:
# 尝试使用系统黑体字体生成词云
wordcloud = WordCloud(
width=800,
height=600,
background_color='white',
font_path='simhei.ttf', # Windows系统黑体
max_words=100,
relative_scaling=0.5,
random_state=42
).generate_from_frequencies(word_freq)
except OSError:
# 如果找不到指定字体,回退到默认设置
wordcloud = WordCloud(
width=800,
height=600,
background_color='white',
max_words=100,
relative_scaling=0.5,
random_state=42
).generate_from_frequencies(word_freq)

plt.imshow(wordcloud, interpolation='bilinear')
plt.axis('off') # 隐藏坐标轴
plt.title('弹幕词云图')
plt.tight_layout()
plt.savefig('danmu_wordcloud.png', dpi=300, bbox_inches='tight')
plt.show()

词云生成的核心是 generate_from_frequencies() 方法,它直接接收 Counter 对象作为输入。try-except 结构是一处工程化细节:不同操作系统的字体路径不同,Windows 有 simhei.ttf 而Linux没有,通过异常捕获实现优雅降级。random_state=42 固定随机种子,保证每次运行词云布局一致,便于结果复现。

4.6 关键词定向统计与数值标注

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 定义与视频主题相关的关键词
keywords = ['开盒', '个人信息', '电报', '缓刑', '犯罪', '法律', '处罚', '网络', '平台', '主播']
keyword_counts = {}

# 统计每个关键词在所有弹幕中出现的次数
for keyword in keywords:
keyword_counts[keyword] = df['content'].str.contains(keyword, na=False).sum()

# 绘制柱状图
bars = plt.bar(keywords, list(keyword_counts.values()), color='lightcoral')
plt.xlabel('关键词')
plt.ylabel('出现次数')
plt.title('关键词统计')
plt.xticks(rotation=45) # x轴标签旋转45度,避免重叠
plt.grid(axis='y', alpha=0.3)

# 在每根柱子上方添加数值标签
for bar in bars:
height = bar.get_height()
plt.text(bar.get_x() + bar.get_width()/2., height,
f'{int(height)}',
ha='center', va='bottom')

str.contains(keyword, na=False) 是Pandas中高效的向量化字符串匹配方法,na=False 确保遇到空值时返回False而非NaN。柱状图上的数值标注通过遍历 bars 对象,用 plt.text() 在每根柱子上方居中绘制数值,ha='center'va='bottom' 分别控制水平和垂直对齐。

4.7 弹幕长度分布与统计信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 计算每条弹幕的字符长度
df['content_length'] = df['content'].apply(len)

plt.figure(figsize=(10, 6))
plt.hist(df['content_length'], bins=30, color='lightgreen', edgecolor='black')
plt.xlabel('弹幕长度(字符数)')
plt.ylabel('弹幕数量')
plt.title('弹幕长度分布')
plt.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.savefig('danmu_length_distribution.png', dpi=300, bbox_inches='tight')
plt.show()

# 输出统计信息
print(f"\n弹幕统计信息:")
print(f"平均每条弹幕长度: {df['content_length'].mean():.2f} 字符")
print(f"最长弹幕: {df['content_length'].max()} 字符")
print(f"最短弹幕: {df['content_length'].min()} 字符")

# 输出时间分布信息
print(f"\n时间分布信息:")
print(f"最早弹幕: {df['send_time'].min()}")
print(f"最晚弹幕: {df['send_time'].max()}")

apply(len)content 列逐行调用 len() 函数,生成弹幕长度的新列。直方图 bins=30 将长度范围等分为30个区间,edgecolor='black' 为每个柱子添加黑色边框,提升视觉层次感。

运行效果与数据分析

运行脚本后,控制台首先输出弹幕的基本信息:

1
2
3
4
5
6
7
8
9
10
11
弹幕数据基本信息:
总弹幕数量: 3287
数据列数: 4
列名: ['send_time', 'content', 'mode', 'color']

高频词汇 Top 30:
个人信息: 486
开盒: 412
犯罪: 287
法律: 231
...

随后程序依次生成四张可视化图表:

  1. 时间分布图:弹幕发送量在晚间20:00-23:00出现明显峰值,符合B站用户的活跃规律。白天工作时段弹幕量相对较低。
  2. 词云图:高频词以大字号居中显示,”个人信息””开盒””犯罪”等主题词占据视觉中心,一眼即可把握视频核心议题。
  3. 关键词统计图:通过柱状图上的数值标注,可以精确对比各关键词的提及次数,”开盒”和”个人信息”遥遥领先。
  4. 弹幕长度分布:大部分弹幕长度集中在5-20个字符,呈现明显的右偏分布,说明用户倾向于发送短小精悍的弹幕。

项目还对另外两个视频(家长关闭游戏话题、央视频开盒话题)的弹幕执行了完全相同的分析流程,仅需修改CSV文件路径即可复用全部代码,验证了框架的通用性。

总结

本文完整展示了一个B站弹幕数据处理与词频分析的实战项目,涵盖了从数据加载到可视化的全链路技术要点:

  • 数据预处理是分析质量的基石,Unix时间戳转换、字符串类型保证、BOM编码处理缺一不可。
  • 中文分词配合停用词过滤是NLP的基础功,jieba + Counter的组合轻量高效。
  • 多维度可视化让数据说话,时间分布揭示用户行为规律,词云图直观呈现舆论焦点,关键词统计提供量化对比。
  • 工程化细节决定代码健壮性,字体容错处理、固定随机种子、数值标签标注等小技巧让结果更专业。

这个项目的扩展空间很大:可以引入情感分析(如snownlp)判断弹幕情绪倾向,可以加入TF-IDF替代简单词频以凸显关键词的重要性,也可以对接B站API实现弹幕实时采集与流式分析。希望本文能为你的数据分析实践提供有价值的参考。

评论
分享