学了一学期 大学计算机基础 的小白要开始做大作业了,从5个看起来都不太正经的题目里面选了个看起来顺眼的——简易B站弹幕分析工具。这个分析可不是做一个词云或者统计一下词频那么简单,要用GUI界面显示出来。分析的更有趣,界面做的更美观都是加分项。
题目 3:简易 B 站弹幕分析工具
加粗样式一、题目描述
网络爬虫是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。通过爬虫技
术,你甚至可以获取互联网上任何你想要的信息。
哔哩哔哩现为中国年轻世代高度聚集的文化社区和视频平台,而弹幕也是 B 站的一大
特色。本题意在使用爬虫技术对 B 站弹幕进行抓取,并利用相关工具对爬取的弹幕进行分
析。
二、基本实验要求
本题希望同学们充分发挥自己的自学能力和资料查找能力,利用 Python3 及其强大的
第三方库设计实现能够自动爬取指定视频的弹幕,并给出相关分析报告的 B 站弹幕分析软
件。
三、评分细则及加分项
- 必做部分
(1)设计一个美观简洁的 GUI,使用户可以通过 GUI 操作软件。
(2)用户输入视频 BV 号,你的程序需要根据 BV 号来爬取相应视频的弹幕,并在主窗
口显示至少前 100 条弹幕的内容(如果弹幕数量足够多)。显然一个屏幕的长度不足以显示
100 条弹幕的内容,你的程序可以通过加入滚动条或其他方式让用户浏览全部内容。
(3)为了让用户对此时视频的弹幕有更直观的认识,你的程序需要进行相应的统计分
析,包括但不限于:高能进度条(具体会在下文讲解)、弹幕数量 top10 的柱状图、弹幕类
型的统计图、弹幕颜色的统计图等。你将数据刻画得越充分,你的分数也会有相应的提高。
(4)显示中文的位置禁止出现乱码。
(5)操作过程中禁止弹出第三方程序,禁止依赖命令行完成操作(即使用 os 库中的
system 函数)。 - 选做部分
(1)网络异常或其他原因导致爬取失败时应给出提示,而不是报错或闪退,对于 B 站
没有版权、已失效或分 P 的视频,你的程序应该给出提示或有相应处理方式。
(2)按日期爬取弹幕并进行上述分析。
(3)分析用到的统计图可以做得非常美观,至少不是简单的使用默认参数生成。
(4)根据一定规则进行分词,并生成一个词云(你可能需要一份停用词表)。
(5)爬取的弹幕可以导出成 Excel 文件,生成的词云可以导出成 png 文件。
(6)弹幕筛选功能:输入一个时间段,筛选出相应时间段出现的弹幕;或输入一串字
符,筛选出包含这一串字符的弹幕;或输入一个数字,筛选出长度不大于这个数字的弹幕。
增加任意一种筛选功能均可。
(7)弹幕筛选功能支持简单的逻辑筛选和正则表达式筛选。
(8)常见语言禁止显示乱码,包括但不限于:简中、繁中、英语、日文、韩文。
7
(9)以上选做部分不要求全部完成,也不必局限于给出的这些内容。
四、实验指导 - 高能进度条
将一个视频分成长度相等的许多小段,通常以视频长度的 1%作为每一小段的长度,
统计每一小段时间出现的弹幕数量,并做成条形统计图,形式上可以尽量接近 b
站原生的样式,当然这一点不做要求。 - API
你可以利用哔哩哔哩提供的 API 轻松获取视频的弹幕信息。由于每个视频有独一无
二的 cid,因此你需要先通过另一个 API 获取到对应 BV 号的 cid。
视频 cid API:http://api.bilibili.com/x/web-interface/view?bvid={BV 号}
或:http://api.bilibili.com/x/web-interface/view?avid={AV 号}
使用指南:
https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/video/info.md
data 对象中的 cid 的内容即为对应视频的 cid
弹幕 API:http://api.bilibili.com/x/v1/dm/list.so?oid={cid}
使用指南:
https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/danmaku/danmaku.
md - 停用词表,如果你找不到停用词表,可以看看下面几个链接:
https://blog.csdn.net/shijiebei2009/article/details/39696571
https://github.com/goto456/stopwords
当然你也可以使用自己的停用词表。 - PyQt5 | Tkinter:GUI 开发库
Tkinter 是课程内的内容。PyQt5 是一个被广泛使用的 GUI 库,功能比 Tkinter 更强大。
非官方中文教程:
PyQt5:https://maicss.gitbooks.io/pyqt5/content/hello_world.html
Tkinter:https://www.runoob.com/python/python-gui-tkinter.html
NumPy |Pandas:强大的分析结构化数据的工具集
对于一般的二进制文件当然可以用 open()函数直接实现,但是对于 json、csv 等需
要二次处理的文件,或者是 xls 等非二进制文件,建议使用 Pandas 进行处理,同样
pandas 也可以写入这些文本,在数据处理时 Pandas 的 dataframe 也能使整个工作流
更简化。在处理更大量的数据和多维数组时,利用 NumPy 能更高效和简便地处理
数据。
Pandas 官方中文网站:https://www.pypandas.cn/
NumPy 官方中文网站:https://www.numpy.org.cn/
Matplotlib:基础的绘图工具停用词表
这个不做过多的说明,课程内的内容。
官方中文网站:https://www.matplotlib.org.cn/
其他常用的绘图库还有:ggplot,pycharts
8
BeautifulSoup4|requests|urllib:配合使用的强大爬虫工具
通常情况下爬取静态网站,使用 requests 库中 get()函数可以轻松获取网页源代码,
之后使用 BeautifulSoup4 中的 a = BeautifulSoup (r.content,"lxml")语句将网页源代码
进行读取,并通过 find()方法找到有用的信息。另外,如果网站上有音频、视频等
非文本信息,可以使用 urllib 库进行爬取。
非官方中文教程:
BeautifulSoup4:https://beautifulsoup.readthedocs.io/zh_CN/v4.4.0/
Requests:https://www.liaoxuefeng.com/wiki/1016959663602400/1183249464292448
Urllib:https://www.liaoxuefeng.com/wiki/1016959663602400/1019223241745024
当时基本上属于无处下手的状态,只好硬着头皮学了点tkinter库的知识,顺带复习了一点matplotlib库的画图技能,然后在网上找爬虫的资料慢慢来。 其实主要是把大任务分成几个小任务,比如先做出一个GUI页面,然后先爬取B站弹幕,之后逐个分析B站弹幕的特征~~
#大作业 b站弹幕分析工具
from imageio import imread #加载图片
import requests #发出请求
import csv #文件格式
import re #正则表达式筛选
import jieba #中文分词
import json
import urllib3
from urllib import request
from PIL import Image,ImageTk #呈现png,jpg图片
import wordcloud #绘制词云
import tkinter as tk
from tkinter import Button
from tkinter import messagebox
import matplotlib.pyplot as plt #绘图
import matplotlib as mpl
from bs4 import BeautifulSoup as BS #解析
mpl.rcParams['font.sans-serif'] = ['STKaiti'] #正常显示中文
main=tk.Tk() #建立主窗体
main.title('B站弹幕爬取界面')
main.geometry('1000x600')
label=tk.Label(main,text='请输入bv号')
label.grid(row=0,column=0)
entry=tk.Entry(main)
entry.grid(row=0,column=1)
Button(main,text='分析',command=lambda:menu0()).grid(row=0,column=2)
def menu0():
bv=entry.get() #获得输入内容
try:
if bv!='': #利用bv号获得cid,顺便获取duration和pic
url='http://api.bilibili.com/x/web-interface/view?bvid='+bv
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36"
}
urllib3.disable_warnings() #从urllib3中消除警告
response = requests.get(url,headers=headers)
content = json.loads(response.text)
# 获取到的是str字符串 需要解析成json数据
# print(response.content.decode('utf-8'))
statue_code = content.get('code')
#print(statue_code)
if statue_code == 0:
data=content['data']['pic']
name='fengmian.jpg'
request.urlretrieve(data,filename=name)
cid=content['data']['cid']
duration=content['data']['duration']
else:
print('该bv号不存在')
#利用cid获取并分析弹幕文件
url1='http://api.bilibili.com/x/v1/dm/list.so?oid='+str(cid)
response1 = requests.get(url1,headers=headers)
danmu_html = response1.content.decode('utf-8')
soup = BS(danmu_html, 'lxml') #解析
all_d = soup.select('d')
time,leixing,color=[],[],[]
for d in all_d:
biao=d['p'].split(',')
#把d标签中P的各个属性分离开
yanse=hex(int(biao[3]))[2:]
while len(yanse)<6: #处理颜色
yanse='0'+yanse
yanse1='#'+yanse
#print(yanse1)
color.append(yanse1)
time.append(int(eval(biao[0]))) #处理时间
leixing.append(int(biao[1])) #处理类型
res = re.compile('(.*?)') #处理弹幕文件
danmu = re.findall(res,danmu_html)
for i in danmu: #将弹幕按行写入csv文件
with open('b站弹幕.csv','a',newline='',encoding='utf-8-sig') as file:
writer = csv.writer(file)
danmu = []
danmu.append(i)
writer.writerow(danmu)
f = open('b站弹幕.csv',encoding='utf-8')
txt = f.read()
f.close() #打开文件,在文本框插入所有弹幕
text=tk.Text(main,height=30)
text.grid(row=1,column=1)
text.insert('insert',txt)
scrollbar = tk.Scrollbar() #关联文本框和滚动条
scrollbar.grid(row=1, column=1, sticky=tk.N+tk.S)
text['yscrollcommand'] = scrollbar.set
scrollbar['command'] = text.yview
def menu1():
count=[]
for i in range(duration):
count.append(time.count(i)) #统计每秒弹幕条数
plt.plot(range(duration),count,'b-',linewidth=2.5,label='弹幕密度')
plt.xlabel('时间')
plt.ylabel('弹幕条数')
plt.legend()
plt.title('高能进度条') #绘制折线图
plt.savefig('gaoneng.png',dpi=100)
#填充
plt.fill_between(x=range(duration),y1=0,y2=count,facecolor='blue', alpha=0.5)
plt.show()
top0=tk.Toplevel()
top0.title('高能进度条')
top0.geometry('600x400')
global img_png1 #显示图片
img = Image.open('gaoneng.png')
img_png1 = ImageTk.PhotoImage(img)
label =tk.Label(top0, image = img_png1)
label.pack()
def menu2():
txt_list = jieba.lcut(txt) #精确分词
string = ' '.join((txt_list)) #连接成字符串
#这里需要一张本地图片,设置成mask参数
mk = imread('C:/Users/lenovo/Pictures/20200505101029364.png')
#这里需要一份停用词表
f1=open('F:/stopwords.txt',encoding='utf-8')
txtt=f1.read()
f1.close()
w = wordcloud.WordCloud(max_font_size=50,
background_color='white',
font_path='C:/Windows/SIMLI.TTF',
mask=mk,
scale=2,
stopwords={txtt},
collocations=False,
contour_width=5,
contour_color='red')
w.generate(string) #生成词云
w.to_file('axwordcloud.png')
global img_png2
top=tk.Toplevel()
top.title('词云图')
top.geometry('800x600')
img = Image.open('axwordcloud.png')
img_png2 = ImageTk.PhotoImage(img)
label =tk.Label(top, image = img_png2)
label.pack()
def menu3():
global img_png3
dic,txt1={},[]
f = open('b站弹幕.csv',encoding='utf-8')
txt= f.readlines() #这个方法是形成一个长列表
f.close()
for line in txt:
danm='' #删除一些无关信息
stop=',./,。?、‘“;;!! ·~`^&*()@#$%[]{}'
line=line[:-1] #去掉换行符\n
for item in line:
if item not in stop:
danm+=item
txt1.append(danm)
for i in range(len(txt1)):
num=0
for j in txt1:
if j==txt1[i]:
num+=1
dic[txt1[i]]=num #统计弹幕出现次数
#字典排序
dic1=sorted(dic.items(),key=lambda x:x[1],reverse=True)
x,y=[],[]
for i in range(10):
x.append(dic1[i][0])
y.append(dic1[i][1])
#绘制水平柱状图
bar=plt.barh(range(10),y,height=0.5,color='rgb')
for rect in bar: #显示数字
w = rect.get_width()
plt.text(w, rect.get_y()+rect.get_height()/2, '%d' %
int(w), ha='left', va='center')
plt.yticks(range(10),labels=x) #导入标签
plt.xlabel('弹幕数量')
plt.ylabel('弹幕排名')
plt.savefig('danmutop10.png',dpi=100)
plt.show()
top1=tk.Toplevel()
top1.title('弹幕数量top10柱状图')
top1.geometry('700x500')
img = Image.open('danmutop10.png')
img_png3 = ImageTk.PhotoImage(img)
label =tk.Label(top1, image = img_png3)
label.pack()
def menu4():
global img_png4
x=['滚动弹幕','滚动弹幕','滚动弹幕','底端弹幕','顶端弹幕','逆向弹幕','精准定位','高级弹幕']
y=[0 for i in range(8)]
for i in leixing:
y[i-1]+=1 #弹幕类型
plt.bar(range(1,9),y,color='rgb',tick_label=x)
for i in range(1,9):
plt.text(i,y[i-1],'%d'%y[i-1],ha='center',va='bottom')
plt.savefig('leixing.png',dpi=100)
plt.show()
top3=tk.Toplevel()
top3.title('弹幕类型')
top3.geometry('700x500')
img = Image.open('leixing.png')
img_png4 = ImageTk.PhotoImage(img)
label =tk.Label(top3, image = img_png4)
label.pack()
def menu5():
dic={}
global img_png5
for i in color:
if i not in dic.keys():
dic[i]=1 #字典的键代表弹幕颜色,值代表出现次数
dic[i]+=1
dic1 = dict(sorted(dic.items(), key=lambda x: x[1],reverse=True))
size=dic1.values()
color1=dic1.keys()
plt.style.use('Solarize_Light2') #设置背景颜色
plt.pie(size,colors=color1, #autopct='%1.1f%%',
startangle=90,counterclock=False)
plt.savefig('color.png')
plt.show()
top4=tk.Toplevel()
top4.title('弹幕颜色')
top4.geometry('600x400')
img = Image.open('color.png')
img_png5 = ImageTk.PhotoImage(img)
label =tk.Label(top4, image = img_png5)
label.pack()
def menu6():
global img_png6
top2=tk.Toplevel()
top2.title('封面图片') #封面图片
top2.geometry('1000x800')
img = Image.open('fengmian.jpg')
img_png6 = ImageTk.PhotoImage(img)
label =tk.Label(top2, image = img_png6)
label.pack()
Button(main,text='高能进度条',command=menu1).grid(row=1,column=2)
Button(main,text='生成词云图',command=menu2).grid(row=2,column=0)
Button(main,text='弹幕数量top10柱状图',command=menu3).grid(row=2,column=2)
Button(main,text='弹幕类型统计图',command=menu4).grid(row=2,column=3)
Button(main,text='弹幕颜色统计图',command=menu5).grid(row=3,column=2)
Button(main,text='弹幕封面图片',command=menu6).grid(row=3,column=3)
else:
messagebox.showinfo(message='请输入bv号')
except:
messagebox.showerror(title='爬取失败',message='bv号错误或网络异常')
main.mainloop()
最终效果:
完整内容以及代码分析在下方的链接中
https://download.csdn.net/download/weixin_46530492/12789116