最近学了点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")