最近发现词云图挺有意思的,正好在家闲来无事,就尝试做了一下B站视频弹幕的词云,顺便重拾一下爬虫技能。
寻找请求
首先用Chrome随便打开一个视频开始抓包,猜测弹幕信息是通过Ajax请求得到的,选中XHR,很轻松就可以找到弹幕请求的url。
https://api.bilibili.com/x/v1/dm/list.so?oid=152796906
打开之后发现是一个XML格式的文件,弹幕信息包含在d标签的text里。d标签p属性中的信息猜测可能是弹幕出现的时间、弹幕类型、弹幕发送时间戳、经加密的用户信息、弹幕在数据库里的ID等
但仔细观察发现这个XML里只有500条弹幕信息,这500条弹幕正是用户最新发布的500条弹幕,也就是视频旁边弹幕下拉框列表里最新的500条弹幕这里多出的49条弹幕是在我打开网页到截图这段时间里多出的弹幕,但在XML里仍只有500条
之后我尝试查看历史弹幕,找到历史弹幕的请求url(以2020-02-19的弹幕为例)
https://api.bilibili.com/x/v2/dm/history?type=1&oid=152796906&date=2020-02-19
可以看到这两个url差异很大,打开之后发现XML里仍然只有500条弹幕信息,最重要的是这些历史弹幕的获取需要用户登录,如果未登录返回的结果是这个样子
{"code":-101,"message":"账号未登录","ttl":1}
也就是说要请求历史弹幕需要带上cookie………那还是算了吧,就先拿500条弹幕玩一玩。
请求的url里只有一个参数oid,这个参数应该可以在主页面里找到(值得注意的是这个oid值在主页面的response里的标签为cid,真的坑……)
找到oid值之后我们的任务基本上已经完成了50%,接下来我们就可以写代码了。
python获取视频弹幕信息
1、请求视频页面
AV是av号,PAGE是视频集数,默认为1,headers里除了UA随便加了几个
def get_html():
url = "https://www.bilibili.com/video/av%s?%s" % (AV, PAGE)
headers = {
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Host": "www.bilibili.com",
"Referer": "https://search.bilibili.com",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36",
}
response = requests.get(url=url, headers=headers)
try:
if response.status_code == 200:
return response.text
else:
print('请求页面出错', response.url)
except RequestException:
print('请求页面出错', response.url)
return None
2、获取oid/cid
用正则得到oid/cid,顺便获取一下视频的发布时间和标题
def get_info(html):
pattern = re.compile(r'"cid":(\d+),"page":%s' % PAGE)
pattern1 = re.compile(r'"title":"(.*?)","pubdate":(\d+)')
cid = re.search(pattern, html).group(1)
title, pubdate_u = re.search(pattern1, html).groups()
pubdate = time.strftime("%Y-%m-%d%H:%M:%S", time.localtime(int(pubdate_u))) # 时间戳转str
return cid, title, pubdate
3、获取弹幕信息
这里为了方便就用了Xpath,顺便提取了第一条弹幕的发送时间
def get_dm(cid):
url = 'https://api.bilibili.com/x/v1/dm/list.so?oid=%s' % cid
headers = {
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site",
"Host": "api.bilibili.com",
"Origin": "https://www.bilibili.com",
"Referer": "https://www.bilibili.com/video/av%s" % AV,
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36",
}
response = requests.get(url=url, headers=headers)
try:
if response.status_code == 200:
html = etree.HTML(response.content)
dm = html.xpath('//d/text()')
s_utime = html.xpath('//d[1]/@p')[0].split(',')[4] # 第一条弹幕的发送时间
s_time = time.strftime("%Y-%m-%d%H:%M:%S", time.localtime(int(s_utime)))
return dm, s_time
else:
print('获取弹幕出错', response.url)
except RequestException:
print('获取弹幕出错', response.url)
return None
4、词云图制作
使用jieba分词和wordcloud
def ciyun(dm):
result = jieba.analyse.extract_tags(dm, withWeight=True)
wc = wordcloud.WordCloud(
font_path=r'C:/Windows/Fonts/simhei.ttf',
# background_color='White',
width=720, height=480,
max_font_size=150,
)
wc.generate_from_frequencies(dict(result))
wc.to_file('cy.png')
5、mian函数
def main():
html =get_html()
if html:
cid, title, pubdate = get_info(html)
if cid:
dm, s_time = get_dm(cid)
if dm:
print('*' * 50)
print('视频名称:%s,发布日期%s' % (title, pubdate))
print('从%s起,共获取到%d条弹幕' % (s_time, len(dm)))
dm = ''.join(dm) # 把所有弹幕连在一起
ciyun(dm)
if __name__ == '__main__':
main()
6、运行结果
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\pty666\AppData\Local\Temp\jieba.cache
**************************************************
视频名称:【 钉 钉 本 钉 ,在 线 求 饶 】,发布日期2020-02-16 20:24:22
从2020-02-21 13:51:45起,共获取到500条弹幕
Loading model cost 0.913 seconds.
Prefix dict has been built successfully.
Process finished with exit code 0矩形的词云图确实有点丑……
其他视频词云图
在我看了其他视频后发现,其实不同视频的弹幕存储量是不同的,有的是500,有的是1000,有的甚至可以达到6000、8000,形式也有所不同。以8000为例,这些弹幕并不是一天的,而是从选中日期开始向前数8000条弹幕。
最后放两个视频的词云图,猜一猜是哪两个视频 (狗头) 。