社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

Python利用GUI界面制作B站弹幕分析工具

初与久歌_4a05 • 6 年前 • 759 次点击  

学了一学期 大学计算机基础 的小白要开始做大作业了,从5个看起来都不太正经的题目里面选了个看起来顺眼的——简易B站弹幕分析工具。这个分析可不是做一个词云或者统计一下词频那么简单,要用GUI界面显示出来。分析的更有趣,界面做的更美观都是加分项。

题目 3:简易 B 站弹幕分析工具
加粗样式一、题目描述

网络爬虫是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。通过爬虫技
术,你甚至可以获取互联网上任何你想要的信息。
哔哩哔哩现为中国年轻世代高度聚集的文化社区和视频平台,而弹幕也是 B 站的一大
特色。本题意在使用爬虫技术对 B 站弹幕进行抓取,并利用相关工具对爬取的弹幕进行分
析。
二、基本实验要求

本题希望同学们充分发挥自己的自学能力和资料查找能力,利用 Python3 及其强大的
第三方库设计实现能够自动爬取指定视频的弹幕,并给出相关分析报告的 B 站弹幕分析软
件。
三、评分细则及加分项

  1. 必做部分
    (1)设计一个美观简洁的 GUI,使用户可以通过 GUI 操作软件。
    (2)用户输入视频 BV 号,你的程序需要根据 BV 号来爬取相应视频的弹幕,并在主窗
    口显示至少前 100 条弹幕的内容(如果弹幕数量足够多)。显然一个屏幕的长度不足以显示
    100 条弹幕的内容,你的程序可以通过加入滚动条或其他方式让用户浏览全部内容。
    (3)为了让用户对此时视频的弹幕有更直观的认识,你的程序需要进行相应的统计分
    析,包括但不限于:高能进度条(具体会在下文讲解)、弹幕数量 top10 的柱状图、弹幕类
    型的统计图、弹幕颜色的统计图等。你将数据刻画得越充分,你的分数也会有相应的提高。
    (4)显示中文的位置禁止出现乱码。
    (5)操作过程中禁止弹出第三方程序,禁止依赖命令行完成操作(即使用 os 库中的
    system 函数)。
  2. 选做部分
    (1)网络异常或其他原因导致爬取失败时应给出提示,而不是报错或闪退,对于 B 站
    没有版权、已失效或分 P 的视频,你的程序应该给出提示或有相应处理方式。
    (2)按日期爬取弹幕并进行上述分析。
    (3)分析用到的统计图可以做得非常美观,至少不是简单的使用默认参数生成。
    (4)根据一定规则进行分词,并生成一个词云(你可能需要一份停用词表)。
    (5)爬取的弹幕可以导出成 Excel 文件,生成的词云可以导出成 png 文件。
    (6)弹幕筛选功能:输入一个时间段,筛选出相应时间段出现的弹幕;或输入一串字
    符,筛选出包含这一串字符的弹幕;或输入一个数字,筛选出长度不大于这个数字的弹幕。
    增加任意一种筛选功能均可。
    (7)弹幕筛选功能支持简单的逻辑筛选和正则表达式筛选。
    (8)常见语言禁止显示乱码,包括但不限于:简中、繁中、英语、日文、韩文。
    7
    (9)以上选做部分不要求全部完成,也不必局限于给出的这些内容。
    四、实验指导
  3. 高能进度条
    将一个视频分成长度相等的许多小段,通常以视频长度的 1%作为每一小段的长度,
    统计每一小段时间出现的弹幕数量,并做成条形统计图,形式上可以尽量接近 b
    站原生的样式,当然这一点不做要求。
  4. 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
  5. 停用词表,如果你找不到停用词表,可以看看下面几个链接:
    https://blog.csdn.net/shijiebei2009/article/details/39696571
    https://github.com/goto456/stopwords
    当然你也可以使用自己的停用词表。
  6. 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('<d.*?>(.*?)</d>')   #处理弹幕文件
            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

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/74490