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

學了一學期 大學計算機基礎 的小白要開始做大作業了,從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

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374