前言
关于这个小项目的由来。
最开始是想要利用b站的弹幕进行一些互动之类的。原本也有想过可以利用现有的弹幕姬做个插件来解决的,但无奈不会C#,所以只能自己研究b站的弹幕协议。
后来有写过一个C++版本的,不过有一些小问题,这在后文中会提到。
开码
一丶利用 POST 方式获取 B 站直播弹幕
参考:【python】b站直播弹幕获取
首先,随便打开一个b站的直播页面,按F12打开控制台,点进“网络(Network)”标签,刷新一下,然后审计一下里面的内容,可以找到“gethistory”这个文件里面就是我们要的弹幕了。
实际上,仔细观察便不难发现,请求 gethistory 的时候返回的是请求时最近的10条历史弹幕,不过根据这些就可以写出来一个简易的弹幕姬了。具体做法就是每隔一定的时间请求一次,然后与上次的请求做对比。不同的部分就是这段时间新发的弹幕了,这样就可以对弹幕进行一些操作了。
我们点进“headers”标签:
有了这些我们就可以开写一个弹幕姬了。
虽然headers很乱,不过实际上我们在请求弹幕的时候并不需要这么多headers,具体哪些headers是必要的可以用实验试出来,不过具体过程和结果我就直接略去了。最后的代码可以参考:
B站直播弹幕爬取
或我自己写的C++版本:
【笔记/学习】c++实现b站弹幕姬
(代码有点长而且不是本文的重点这里就不放了)
注:之前的时候获取弹幕的URL是:https://api.live.bilibili.com/ajax/msg
,不过我写这篇文再去复现的时候发现这个URL已经没了,经过观察发现变成了 https://api.live.bilibili.com/xlive/web-room/v1/dM/gethistory
截至写文时二者都能用。不过这些都不是重点了。
二丶利用 WebSocket 获取 B 站弹幕
前文利用 POST 的方式获取B站弹幕。这种方法虽然简单,但也有不方便的地方。我设的时间间隔为3s,但如果3s内发送的弹幕数量超过了10条,这种方法就会丢失一部分的弹幕,而如果简单的减小时间间隔,不仅会占用更多的网络资源,如果太过频繁的话还可能会被封IP。
而 HTTP 请求的这种缺陷也正好就是 WebSocket 的出现所为了解决的问题。事实上,我们在看B站直播的时候正是通过 WebSocket 的方式与服务器通信的。
让我们继续打开 F12 :
这个 sub
就是与弹幕服务器通信的 WebSocket 啦。
点进 Message
标签,会看见一大堆东西。不过我们并不需要自己去研究这个通信协议,在 Github 上已经有了B站的API可以直接使用。
弹幕WS协议
API文档使用 JavaScript 写的,不过这并不妨碍我们移植一个 Python 版本的。
由 API 可知,我们与服务器进行通信所发送的数据大多是 json 的数据,偶尔还会有 zlib 数据。所以我们自然需要导入这两个包。我们与服务器使用 WebSocket 进行通信,但原生 Python 并不能直接发送 WebSocket,我们自然也不可能使用 socket 去造轮子。不过好在已经有很多好用的 WebSocket 的库可供我们使用了。
目前常用的 WebSocket 库有: websocket-client
, websockets
, aiowebsocket
三个。其中 websocket-client
是同步的,因为我们在收弹幕的同时还得要发送心跳包才能不被服务器断开连接,使用异步io会方便一些。所以不用他。另外两个我也都试过。感觉上 aiowebsocket
更稳定一些,所以这里我们使用这个库。
安装:
pip install aiowebsocket
除了 aiowebsocket
要安装外,其他的库都是 python 自带的,直接导入就行了。自然不要忘了用了异步操作要加上 asyncio
库哦。
import asyncio
import zlib
from aiowebsocket.converses import AioWebSocket
import json
之后我们写好入口函数:
if __name__ == '__main__':
remote = 'wss://broadcastlv.chat.bilibili.com:2245/sub'
try:
asyncio.get_event_loop().run_until_complete(startup(remote))
except KeyboardInterrupt as exc:
pring('Quit.')
remote
自然就是API中弹幕服务器的地址了。然后是startup()
roomid = '5322'
data_raw='000000{headerLen}0010000100000007000000017b22726f6f6d6964223a{roomid}7d'
data_raw=data_raw.format(headerLen=hex(27+len(roomid))[2:], roomid=''.join(map(lambda x:hex(ord(x))[2:],list(roomid))))
async def startup(url)