三、wss连接B站弹幕

环境 ws4py+

pip install ws4py
from ws4py.client.threadedclient import WebSocketClient

一、websocket协议

  1. 先与wss://broadcastlv.chat.bilibili.com/sub建立连接
  2. 发送登录包
    {
    "uid": 0表示未登录,否则为用户ID,
    "roomid": 房间ID,
    "protover": 1,
    "platform": "web",
    "clientver": "1.4.0"
    }
  3. 每隔一段时间发送心跳包(30秒)
    Python只有延迟功能threading.Timer(delay,fun)
    需要自定义一个不断循环的定时器
  4. 接收响应
    响应由头部和数据组成


    图片.png

    图片.png
  5. 解析响应得到数据
    这里有个细节 b站可能一次返回了好几帧数据
    先解析操作码再由操作码解析数据段
  6. 把数据交给主程序去处理
    需要自定义一个事件类
    用来实现事件的注册on和分发功能emit
    二、工具层 utils.py
  7. 定时器类
    可取消
  8. 事件类
    注册事件 (可重复)
    分发事件
    取消事件
class Timer():
    def __init__(self,delay,fun):
        self.delay,self.f=delay,fun
        self.t=threading.Timer(self.delay,self.fun)
        self.t.start()
    def fun(self):
        if self.f:self.f()
        self.t=threading.Timer(self.delay,self.fun)
        self.t.start()
    def cancel(self):
        self.t.cancel()
        print("threading cancel")

class Event():
    def __init__(self):
        self.map=[]
        self.keys=[]
    def index(self,k):
        i=-1
        for key in self.keys:
            i+=1
            if key==k:return i
        return -1
    def on(self,key,fun):
        i=self.index(key)
        if i==-1:
            self.map.append({"key":key,"funs":[fun]})
            self.keys.append(key)
        else:
            self.map[i]["funs"].append(fun)
    def emit(self,key,data=None):
        i=self.index(key)
        if i==-1:
            print("no regist event:"+str(key))
            return
        for f in self.map[i]["funs"]:f(data)
    def rm(self,key,fun):
        i=self.index(key)
        if i==-1:
            print("no regist event:"+str(key))
            return
        funs=self.map[i]["funs"]
        for j in range(len(funs)):
            if funs[j]==fun:funs[j]=None
        self.map[i]["funs"]=list(filter(None,funs))

三、服务层 DanmuWS.py
opened(self) 连接建立后父类会自动调用
closed(self,code,reason) 连接关闭后父类会自动调用
需要进行断线重连
received_message(self,message) 接收到数据时会自动调用
在这里解析数据并分发给主程序处理
send(self,data)父类发送数据的方法
sendLoginPacket 发送登录包
sendHeartBeatPacket 发送心跳包
bind 绑定主程序处理数据和事件的函数

import threading
import json
import struct
from ws4py.client.threadedclient import WebSocketClient
from utils import Event,Timer
event=Event()
class DanmuWebSocket(WebSocketClient):
    def __init__(self,info,serveraddress='wss://broadcastlv.chat.bilibili.com/sub'):
        self.serveraddress=serveraddress
        WebSocketClient.__init__(self,serveraddress)
        DanmuWebSocket.event=event
        DanmuWebSocket.headerLength=16
        self.Info=info
    def opened(self):
        self.sendLoginPacket(self.Info['uid'],self.Info['roomid'],self.Info['protover'],self.Info['platform'],self.Info['clientver'])
        self.sendHeartBeatPacket();
        self.heartBeatHandler = Timer(20,self.sendHeartBeatPacket)
        print("opened")
    def delay_close(self):
        dws=DanmuWebSocket(self.Info,self.serveraddress)
        event.emit('reconnect',dws);
    def closed(self, code, reason=None):
        print("Closed", code, reason)
        if hasattr(self,"heartBeatHandler"):self.heartBeatHandler.cancel();
        if code == 1000: return
        threading.Timer(5,self.delay_close).start()
        print("Closed", code, reason)
    def received_message(self, message):
        position,length=0,len(message.data)-1
        while position<length:
            header_pack=struct.unpack(">IHHII",message.data[position:position+16])
            length_pack=header_pack[0]
            operation=header_pack[3]
            if operation==3:
                num=header_pack[1]+position
                num=struct.unpack(">I",message.data[num:num+4])[0]
                event.emit('heartbeat',num)
            elif operation==5:
                data=json.loads(message.data[position+16:position+length_pack])
                event.emit('cmd',data)
                #print("recv:"+data["cmd"])
            else:
                event.emit('login');
            position+=length_pack
    def sendData(self,data, protover = 1, operation = 2, sequence = 1):
        if type(data)==dict:
            data=json.dumps(data).encode()
        elif type(data)==str:
            data=data.encode()
        header=struct.pack(">IHHII",DanmuWebSocket.headerLength+len(data),DanmuWebSocket.headerLength,protover,operation,sequence)
        self.send(header+data)
    def sendLoginPacket(self,uid, roomid, protover = 1, platform = 'web', clientver = '1.4.6'):
        # Uint(4byte) + 00 10 + 00 01 + 00 00 00 07 + 00 00 00 01 + Data 登录数据包
        data = {
            'uid': int(uid),
            'roomid': int(roomid),
            'protover': protover,
            'platform': platform,
            'clientver': clientver
        }
        print("sendLoginPacket")
        data=json.dumps(data)
        data=data.replace(' ','')
        self.sendData(data.encode(),1,7,1)
    def sendHeartBeatPacket(self):
        # Uint(4byte) + 00 10 + 00 01 + 00 00 00 02 + 00 00 00 01 + Data 心跳数据包
        self.sendData(b'[object Object]', 1, 2, 1);
    def bind(self,onreconnect=None,onlogin=None,onheartbeat=None,oncmd=None,onreceive =None):
        if "cmd" in event.keys:return
        if hasattr(onreconnect,"__call__"):event.on("reconnect",onreconnect)
        if hasattr(onlogin,"__call__"):event.on("login",onlogin)
        if hasattr(onheartbeat,"__call__"):event.on("heartbeat",onheartbeat)
        if hasattr(oncmd,"__call__"):event.on("cmd",oncmd)
        if hasattr(onreceive,"__call__"):event.on("receive",onreceive)

四、测试代码
利用前面写的二维码登录代码
登录上Bilibili获取个人uid
oncmd 处理弹幕数据
onlogin 连接成功时的处理函数
onreconnect 断线重连时调用更新ws
onheartbeat 处理服务器发送的直播间人气值

from server import Login
headers={
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0',
    'Accept': 'application/json, text/plain, */*',
    'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
    'Accept-Encoding': 'gzip, deflate, br',
    'Referer': 'https://live.bilibili.com/',
    'Origin': 'https://live.bilibili.com',
    'Connection': 'keep-alive'
    }
s=session(headers,'cookie.txt')
login=Login(s)
while not login.isLogin():
    login.get_vdcode()
    login.loop_vdcode()
info={
  "uid": login.info['uid'],
  "roomid": 7603080,
  "protover": 1,
  "platform": "web",
  "clientver": "1.4.0"
}
from DanmuWS import DanmuWebSocket
def oncmd(data):
    cmd=data["cmd"]
    if cmd=="SYS_MSG":
        print(data)
    elif cmd=="SPECIAL_GIFT":
        print(data)
    else:
        print(data)
def onlogin(data):
    print("login success")
def onreconnect(dws):
    global ws
    ws=dws
def onheartbeat(num):
    print(num)
try:
    ws = DanmuWebSocket(info,'wss://broadcastlv.chat.bilibili.com/sub')
    ws.connect()
    ws.bind(None,onlogin,onheartbeat,oncmd)
    ws.run_forever()
except:
    ws.close()
图片.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容