使用Bv号生成弹幕词云

编程 · 2024-05-02 · 33 人浏览
使用Bv号生成弹幕词云

最近学了点py的爬虫。还没有到框架,只是request库加bs4解析的程度。
不知道是不是我不熟悉bs库的问题,我总感觉正则表达式比bs4好用😀。
就是有的时候bs的findall方法反而找不到目标,但是正则表达式解析一下就有结果。
难道findall不应该“人如其名”一样直接返回全部文档的匹配项吗?
还是bs解析成文档树导致标签藏得太深了,实在是找不到...

Anyway

总之想爬点什么

我必须把篮球和鸡结合起来,bushi)是弹幕和词云。
弹幕做成的词云,很有意思,能直接看出观众的关注点和视频内容,乃至某种态度。
比如某Chubbyemu的医学视频的弹幕词云:

上面最大的两个词是该频道固有的猜人名环节贡献的最多词组。而这个故事发生在一个吃了腐败土豆的囚犯身上,病情跟肉毒有关
这是某财经视频的弹幕词云:

最近日元贬值,聊的最多的是美元,以及日本。
这是日本人在上海旅游的视频:

这是海军生日的视频:

不管到哪都能看见“哈哈哈”和“啊啊啊”,看出来b友真的喜欢表达自己激动和喜悦。

所以要怎么做才能获得这么炫酷有趣的词云?

起先我是想去通过bv获取页面进行一个弹幕的寻找。
但是很遗憾太复杂,没有找到门道。
后来了解到,b站的弹幕依靠cid进行加载,而有一个现成的api可以通过cid来获取弹幕信息。

b站存在历史弹幕这种东西,所以新弹幕会存在弹幕池里,而旧弹幕就会从池底抽走加入历史弹幕。
所以原则上,这个api查询的弹幕是不全的,较新的弹幕。

所以cid在哪里?

其实它就藏在网页界面的源码里,进行一个“"cid":”的搜索就能找到。
对网页使用一个正则表达式的匹配,可以找到一个cid的列表。
有很多个cid,但是第一个一定是本视频的cid。
由此,我们完成了BV->cid的阶段。

用cid怎么获取弹幕?

通过这个api接口,我们能访问到存放弹幕的xml文件:
https://comment.bilibili.com/
正确的访问url应该是"接口地址+cid+.xml"
通过获取的xml,可以利用bs4进行弹幕的提取。
弹幕的xml文件中,所有的弹幕字符串都是由<d>标签标记的。
因此直接findall<d>标签就可以获取弹幕列表。
这个阶段,BV->cid->弹幕str

构造词云

可以使用WordCloud库进行词云的创建。
如果直接WordCloud().generate(弹幕str)也是可以直接出现词云图片的,只是所有的中文都变成了框。
所以一定要找一个中文字体,WordCloud可以定义字体,需要在font_path参数设置一下。
wordcloud.to_file(file_path)可以直接保存到指定位置。
如果想直接展示,可以借助pyplot:

    plt.imshow(word_cloud,interpolation='bilinear')
    plt.axis("off")
    plt.show()

这时的生成还没有和词频挂钩。
要想提到的越多词越大,就需要jieba来分词以及获取词频,然后转换成字典

freq = jieba.analyse.extract_tags(jieci_str,150,withWeight = True)  
        # 提取文件中的关键词,topK表示提取的数量,withWeight=True表示会返回关键词的权重。
        #print(freq)
        freq = {i[0]: i[1] for i in freq}  # 字典

然后wordcloud使用generate_from_frequencies(jieci_freq)来使用词频生成词云。
于此就完成了BV->CID->danmu_str->分词与词频->词云的流程。

源码如下:

import time
import jieba.analyse
import requests as r
import re
import os
from bs4 import BeautifulSoup as bs
import jieba
from wordcloud import WordCloud
import matplotlib.pyplot as plt




#获取查询弹幕要用的cid
def get_cid(url):
    try:
        text = request_html(url)
        cid = re.findall(r'(?<=/)\d{10}',text)[0]
        return cid
    except Exception as e:
        print("error in getting cid:",e)
        return ""
    
#通用方法,获取html文本
def request_html(url) -> str:
    header = {
        "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0"
    }
    try:
        res = r.get(url,headers = header)
        res.raise_for_status()
        res.encoding = res.apparent_encoding
        soup = bs(res.text,features="lxml")
        return soup.prettify()
    except Exception as e:
        print("访问失败",e)
        return ""
    
#获取弹幕文本
def get_comment_texts(url):
    commen_url = "https://comment.bilibili.com/"
    text_list = []
    try:
        cid = get_cid(url)
        comment_text = request_html(commen_url+cid+".xml")
        comment_soup = bs(comment_text,features="xml")
        d_list = comment_soup.find_all("d")
        for d in d_list:
            text_list.append(d.text.strip())
        return text_list
    except Exception as e:
        print("error in getting comment texts",e)
        return ""
    
#获取弹幕解词和权重
def get_jieci_list(comment_list):
    jieci_list = []
    try:
        for comment in comment_list:
            #设定自定义字典,防止自定义关键词被切分
            jieba.load_userdict("./customDict.txt")
            jieci_list.append(comment.strip())
        jieci_str = str(jieci_list)
        jieci_str = jieci_str.replace("'","")
        jieci_str = jieci_str.replace(","," ")
        # 提取关键词和权重
        freq = jieba.analyse.extract_tags(jieci_str,150,withWeight = True)  
        # 提取文件中的关键词,topK表示提取的数量,withWeight=True表示会返回关键词的权重。
        #print(freq)
        freq = {i[0]: i[1] for i in freq}  # 字典
        return freq
    except Exception as e:
        print("解词失败",e)

#展示和下载词云
def get_wordcloud(jieci_freq,bv):
    word_cloud = WordCloud(font_path="./Font.ttf",width=800,height=800,background_color="#ffffff").generate_from_frequencies(jieci_freq)
    plt.imshow(word_cloud,interpolation='bilinear')
    plt.axis("off")
    plt.show()
    pic_index = str(time.time())
    file_name = "./Pics/CommentCloud_"+bv+"_"+pic_index+".jpg"
    if not os.path.exists("./Pics"):
        print("新建Pics目录")
        os.mkdir("./Pics")
    print("保存词云图片到",os.path.abspath(file_name))
    word_cloud.to_file(file_name)
    
#根据BV号获取词云
def bv_2_wordcloud(bv):
    bili_url = "https://www.bilibili.com/video/"
    comment_list = get_comment_texts(bili_url+bv)
    if comment_list:
        jieci_freq= get_jieci_list(comment_list)
        get_wordcloud(jieci_freq,bv)
    else:
        print("获取失败!")
    

if __name__ == "__main__":
    bv_2_wordcloud("BV16m421j7kF")
python 词云
Theme Jasmine by Kent Liao