
简介
之前爬取过bilibili的短评:萧萧:简单爬虫:爬一爬哔哩哔哩热门番剧《刀剑神域》短评(python_007),后来有人问bilibili视频的弹幕能否爬取?这次正好在学习文本分析,就以弹幕为素材来练练手吧。
平常看b站视频比较少,在选择视频的时候纠结了一小会儿,最后在排行榜中选择了央视的综艺视频《国家宝藏》(地址:国家宝藏 第二季)。有些小惊讶,这类有点小严肃的纪录片性质的文化传播类节目竟然这么多弹幕,这跟我印象中爱看吃鸡直播和番剧的b站二次元用户画像有很大出入呀。b站的用户又是怎么评价《国家宝藏》的呢?这是本文需要寻找的答案。

内容大纲
- 爬取弹幕
- 清洗数据和分词
- 词云图展示
一、爬取弹幕
弹幕是什么?
弹幕(barrage)指的是在观看网络视频时弹出的评论性字幕、表情等。来源于军事用于中密集的炮火射击,因过于密集就像一张幕布一样。
日本NICONICO动画网站是最先使用弹幕视频系统的,之后国内的A站(AcFun) 和B站(bilibili)先后引进了弹幕视频。弹幕在观看时给观众一种实时互动的错觉。
弹幕的原理是什么?
弹幕其实是视频view的上面叠加了另一个完全透明的弹幕view,因为是透明的,所以覆盖在视频上的弹幕view不会影响到视频的正常观看。当有人发送弹幕时,将消息绘制到弹幕的view上面就可以了。
这里的view可以理解成窗口的一个视图层,一个窗口由多个视图层叠加。而用户最终发送的弹幕其实都是一个XML文件。
B站的视频弹幕也是存放在一个个XML文件里,找到了视频对应的XML文件也就找到了弹幕内容。我们发现B站的每个视频都有对应的cid,而弹幕的XML文件地址是http://comment.bilibili.com/(cid).xml 。
那么,如何找到cid呢?
步骤如下:
(1)打开chrome浏览器
(2)按f12 或右键“检查”
(3)播放视频-找到‘heartbeat’请求
(4)在该请求的From Data中就找到cid值

找到《国家宝藏 第二季:第4集 四川博物院-天府之国生欢喜之美》视频的cid值:69349202
弹幕所在的XML文件地址:https://comment.bilibili.com/69349202.xml
在浏览器中输入这个地址,弹幕的内容就出现了。

下一步,通过python的requests和bs4库抓取内容并保存到本地。
from bs4 import BeautifulSoup
import requests
import pandas as pd
# 发起xml请求
url = 'https://comment.bilibili.com/69349202.xml' #弹幕地址
html = requests.get(url).content #发起请求并获得网页内容
html_data = str(html, 'utf-8') #对网页进行‘utf-8’解码
# 解析xml并提取弹幕内容
soup = BeautifulSoup(html_data,'lxml')
results = soup.find_all('d') #找到所有的‘d'标签
comments = [x.text for x in results] #提取每个’d'标签的text内容,即弹幕文字
# 保存结果
comments_dict = {'comments':comments[1:]}
df = pd.DataFrame(comments_dict)
df.to_csv('bilibili.csv', encoding='utf-8')
二、清洗数据和分词
在进行文本分析前,通常需要将数据进行以下几个步骤的清洗:
- 去数字
- 去空格
- 去停用词
最后将数据整理成词频字典格式:{词:频次}
import jieba
# 导入文件
df = pd.read_csv('bilibili.csv', encoding='utf-8')
df.dropna(inplace=True)
comment_list = df.comments.values.tolist() # comments一列转化为list
# 分词和数据清洗(去数字/去空格/去停用词)
segment = []
for line in comment_list:
try:
segs = jieba.cut_for_search(line) # 用jieba对每条弹幕内容进行分词
segs = [v for v in segs if not str(v).isdigit()] # 去数字
segs = list(filter(lambda x: x.strip(), segs)) # 去左右空格
for seg in segs:
if len(seg)>1 and seg!='rn':
segment.append(seg)
except:
print(line)
continue
# 将分词结果存放到新的数据框
words_df = pd.DataFrame({'segment':segment})
# 加载停用词
stopwords = pd.read_table('stopwords.txt',encoding='utf-8',quoting=3,index_col=False,
sep='t',names=['stopwords'])
# 分组统计词频
words_grouped = words_df.groupby(['segment']).segment.count()
# 对词频进行降序排序
words_grouped_sorted = words_grouped.sort_values(ascending=False).reset_index(name='计数')
# 去除停用词
words_count = words_grouped_sorted[~words_grouped_sorted.segment.isin(stopwords.stopwords)]
# 整理成{词:频}格式
frequency = {x[0]:x[1] for x in words_count[:500].values}
结果如下:

三、词云图展示
通过两种方式来展示词云图:
- 第一种:直接将上述的分词结果进行展示。
- 第二种:通过jieba.posseg提取每个词语的词性,并将形容词单独提取出来再词云图展示。
我觉得,将弹幕中的形容词提取出来更能反映B站用户对视频内容的感情色彩。所以通过两种方式的词云图来做个对比。
图1、对所有分词的词云图展示

从图1我们可以看到:
- 本期的内容是四川博物馆,所以“四川、成都、自贡“这些地理名词成了热议对象。
- 用户提到较多的词有“哈哈哈,啊啊啊,谢谢“表明用户在观看时很激动,也能说明用户对视频内容很感兴趣;
- 另外,提到比较多的词还有“丞相、中国、历史、国家、石室、民族、老师、诸葛、口音、郑恺“等,这些都与视频内容相关,说明视频内容被用户较深入地讨论与交流,也从一个方面说明《国家宝藏》被B站用户所喜欢。
图2、提取形容词分词的词云展示

图2的形容词则更直观得将B站用户对《国家宝藏》节目的喜爱程度表达出来:
- “好、骄傲、快乐、不错、亲切、漂亮、厉害、好看、辛苦、尊重、优秀、很棒...“这些正面的形容词霸占了词云图的绝大部分位置,可见B站用户对《国家宝藏》节目的接受度非常高。
词云图源代码:
# 图1
import jieba
from wordcloud import WordCloud, ImageColorGenerator
from PIL import Image
import matplotlib.pyplot as plt
wc = WordCloud(font_path='simhei.ttf',max_font_size=60)
wc.generate_from_frequencies(frequency)
mask = np.array(Image.open('timg.jpeg'))
image_colors = ImageColorGenerator(mask)
wc.recolor(color_func=image_colors)
plt.figure(figsize=(14,8))
plt.imshow(wc)
plt.axis('off')
plt.show()
#图2
import jieba.posseg as pseg
comm = df.comments.values.tolist()
words_list = []
for i in comm:
words = pseg.cut(i)
for m in words:
words_list.append((m.word, m.flag))
words_list = pd.DataFrame(words_list, columns=['word','type'])
grouped = words_list.groupby(['type','word']).word.count()
words_grouped_list = grouped.reset_index(name="count")
df_a_count = words_grouped_list[words_grouped_list['type'].isin(['a'])].sort_values(by='count',ascending=False)
tag_list = {i[1]:i[2] for i in df_a_count.values}
wc1 = WordCloud(font_path='simhei.ttf',max_font_size=100)
wc1.generate_from_frequencies(tag_list)
plt.figure(figsize=(14,8))
mask = np.array(Image.open('timg.jpeg'))
image_colors = ImageColorGenerator(mask)
wc1.recolor(color_func=image_colors)
plt.imshow(wc1)
plt.axis('off')
plt.show()
完整源码、词云图中使用的背景图、停用词、字体文件等请参考:
链接:https://pan.baidu.com/s/1Z3UUmr41TSegxm7cLbOuDA 密码:w713