asyncore斗鱼弹幕抓取

本文详细介绍使用asyncore模块抓取斗鱼直播平台弹幕数据的完整流程,包括数据包的封装与解包、登录与心跳机制实现,以及自定义回调函数处理数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

斗鱼弹幕抓取

斗鱼api网上开放的。
在这里插入图片描述

数据发送和接收流程:

先发送数据长度,在发送数据,接收数据就是先接收长度,后接收数据。
在这里插入图片描述

对数据包封装:
对数据包进行对象化封装,对数据的封装方便以后使用,实现对象和二进制数据之间的转换。

  • 通过参数构建数据包对象
class DataPacket():
    """封装数据包"""
    def __init__(self,type=DATA_PACKET_TYPE_SEND,content=""): #默认是发送数据包,正文内容content
        #数据包的类型
        self.type=type
        #数据部分内容
        self.content=content
        #加密标记
        self.encrypt_flag=0
        #保留字段
        self.preserve_flag=0
  • 获取数据包长度的方法
    看上面图,4个字节表示数据的消息长度,2个字节表示消息类型,1个字节加密字段,1个字节保留字段,再加上数据部分的长度len(self.content.encode(“utf-8”)),
    还要加上’\0’,
    在这里插入图片描述
    总长度:4+2+1+1+len(self.content.encode(“utf-8”))+1。
 def get_length(self):
        """获取数据包的长度,为以后发送数据包做准备"""
        

        return 4+2+1+1+len(self.content.encode("utf-8"))+1
  • 实现获取二进制数据的方法
    def get_bytes(self):
        """通过数据包转换成二进制数据"""
        data=bytes()
        #构建四个字节的消息长度
        data_packet_length=self.get_length()
        #to_bytes 把一个整形数据转换成二进制数据
        # 第一个参数 表示需要转换的二进制数据占几个字节
        #byteorder 第二个参数描述字节序
        #signed 设置是否有符号
        
        #处理消息长度
        data+=data_packet_length.to_bytes(4,byteorder="little",signed=False)
        #处理消息类型
        data+=self.type.to_bytes(2,byteorder="little",signed=False)
        #处理加密字段
        data+=self.encrypt_flag.to_bytes(1,byteorder="little",signed=False)
        #处理保留字段
        data+=self.preserve_flag.to_bytes(1,byteorder="little",signed=False)
           #处理保留字段
        data+=self.preserve_flag.to_bytes(1,byteorder="little",signed=False)

        #处理数据内容
        data+=self.content.encode("utf-8")

        #添加\0数据
        data+=b'\0'

实现发送数据包:
在这里插入图片描述
1.构建发送数据包队列容器

#构建发送数据包的队列容器
        self.send_queue=Queue()

2.实现回调函数,判断容器中有数据就发送没数据就不发送

    def  writable(self):
        return self.send_queue.qsize()>0

    def handle_write(self):
        #从发送数据包队列中获取数据包对象
        dp=self.send_queue.get()
        #获取数据包长度,并且发送给服务器
        dp_length=dp.get_length()
        dp_length_data=dp_length.to_bytes(4,byteorder="little",signed=False)
        self.send()
        #发送数据包二进制数据
        self.send(dp.get_bytes())
        #数据发送完成
        self.send_queue.task_done()

3.实现登录函数
在这里插入图片描述

    def login_room_id(self,room_id):
        """登录服务器"""
        #构建登录数据包

        content=" type @= loginreq/roomid @={}/".format(room_id)
        login_dp=DataPacket(DATA_PACKET_TYPE_SEND,content=content)

        #把数据包添加到发送数据包容器中
        self.send_queue.put(login_dp)

实现接收数据包:
在这里插入图片描述
1.构建数据包队列

 #存放接收数据包对象
        self.recv_queue=Queue()
        

2.读取回调函数中的读取数据

    def handle_read(self):
        # 读取长度,二进制数据
        data_length_data = self.recv(4)
        # 通过二进制获取 length具体数据
        data_length = int.from_bytes(data_length_data, byteorder='little', signed=False)
        # 通过数据包的长度获取数据
        data = self.recv(data_length)
        # 通过二进制数据构建数据包对象
        dp = DataPacket(data_bytes=data)
        # 把数据包放入接收数据包容器中
        self.recv_queue.put(dp)

3.构建处理数据包的线程

 #构建一个专门处理接收数据包容器中的数据包线程
        self.callback_thread=threading.Thread(target=self.do_callback)
        self.callback_thread.setDaemon(True)
        self.callback_thread.start()

然后封装do_callback函数。

    def  do_callback(self):
        """专门负责处理接收数据包容器中的数据"""
        while True:
            #从接收数据包容器中获取数据包
            dp=self.recv_queue.get()
            #对数据进行处理
            print(dp.content)
        pass

实现外部传入回调函数
通过外部指定回调函数实现自定义数据处理
·添加指定函数callback
构造函数中添加参数

def __init__(self, host, port,callback=None):
#定义外部传入的自定义回调函数
        
        self.callback=callback

外部传入自定义回调函数

def data_callback(dp):
    """
    自定义回调函数
    :param dp: 数据包对象
    :return:
    """
    print("data_callback:",dp.content)
    pass
    
 if __name__ == '__main__':
		 client = DouyuClient('openbarrage.douyutv.com', 8601,callback=data_callback)
		 client.login_room_id(4494106)
		 asyncore.loop(timeout=20)

·在处理接收数据包的线程中调用回调函数

    def  do_callback(self):
        """专门负责处理接收数据包容器中的数据"""
        while True:
            #从接收数据包容器中获取数据包
            dp=self.recv_queue.get()
            #对数据进行处理
            if  self.callback is not None:
                self.callback(dp)
            self.recv_queue.task_done()
        pass

数据内容序列化与反序列化
1.键key和 值value 直接采用’@=‘分割
2.数组采用 ‘/’ 分割
3.如果key或者value总含有字符’/’,则使用’@S’转义
4.如果key或者value总含有字符’@’,则使用’@A’转义

def  encode_content(content):
    """
    序列化函数
    :param content:  需要序列化的内容
    :return:
    """
    if isinstance(content,str):
        return content.replace(r'@',r'@A').replace(r'/',r'@S')
    elif isinstance(content,dict):
        return r'/'.join(["{}@={}".format(encode_content(k),encode_content(v)) for k,v in content.items()])+r'/'
    elif isinstance(content,list):
        return r'/'.join(([encode_content(data) for data in content]))+r'/'
    return ""
data=['a',"b"]
print(encode_content(data))

实现反序列化

def decode_to_str(content):
    """
    反序列化字符串
    :param content:字符串数据
    :return:

    """
    if isinstance(content,str):
        return content.replace(r'@S',r'/').replace('@A',r'@')
    return ""
def decode_to_dict(content):
    """
    反序列化字典数据
    :param content:被序列化的字符串
    :return:
    """
    ret_dict=dict()
    if isinstance(content,str):
        item_strings=content.split(r'/')
        for item_string in item_strings:
            k_v_list=item_string.split(r'@=')
            if k_v_list is not None and len(k_v_list)>1:
                k=k_v_list[0]
                v=k_v_list[1]
                ret_dict[decode_to_str(k)]=decode_to_str(v)
    return ret_dict

def  decode_to_list(content):
    """
    反序列化列表数据
    :param content:被序列化的字符串
    :return:
    """
    ret_list=[]
    if isinstance(content,str):
        items =content.split(r'/')
        for idx,item in enumerate(items):
            if idx<len(items)-1:
                ret_list.append(decode_to_str(item))
    return ret_list
#测试
  # data={"a":"b",
    #       "c":"x@A"}
    data=["a","b/c","c"]
    encode_data=encode_content(data)
    # print(decode_to_dict(encode_data))
    print(encode_data)
    print(decode_to_list(encode_data))

实现心跳机制
在这里插入图片描述
作用是让服务器解决假死连接问题,客户端必须每隔45秒发送一次请求,否则会被主动断开连接。
实现发送心跳函数
构建心跳数据包
把数据包添加到发送数据包队列容器中

    def  send_heart_data_packet(self):
        """发送心跳机制"""
        send_data={
            'type':'mrkl'
        }
        content=encode_content(send_data)
        dp=DataPacket(type=DATA_PACKET_TYPE_SEND,content=content)
        self.send_queue.put(dp)

构建心跳线程
构建心跳线程
添加触发机制
添加暂停机制

 def  send_heart_data_packet(self):
        """发送心跳机制"""
        send_data={
            'type':'mrkl'
        }
        content=encode_content(send_data)
        dp=DataPacket(type=DATA_PACKET_TYPE_SEND,content=content)
        self.send_queue.put(dp)
    def start_ping(self):
        """开启心跳"""
        self.ping_running=True
    def stop_ping(self):
        """关闭心跳"""
        self.ping_running=False
    def  do_ping(self):
        """触发心跳"""
        while True:
            if self.ping_running:
                self.send_heart_data_packet()
                time.sleep(45)

全部代码:

import asyncore
import sys
from queue import Queue
import threading
import time
DATA_PACKET_TYPE_SEND = 689
DATA_PACKET_TYPE_RECV = 690

def  encode_content(content):
    """
    序列化函数
    :param content:  需要序列化的内容
    :return:
    """
    if isinstance(content,str):
        return content.replace(r'@',r'@A').replace(r'/',r'@S')
    elif isinstance(content,dict):
        return r'/'.join(["{}@={}".format(encode_content(k),encode_content(v)) for k,v in content.items()])+r'/'
    elif isinstance(content,list):
        return r'/'.join(([encode_content(data) for data in content]))+r'/'
    return ""

def decode_to_str(content):
    """
    反序列化字符串
    :param content:字符串数据
    :return:

    """
    if isinstance(content,str):
        return content.replace(r'@S',r'/').replace('@A',r'@')
    return ""
def decode_to_dict(content):
    """
    反序列化字典数据
    :param content:被序列化的字符串
    :return:
    """
    ret_dict=dict()
    if isinstance(content,str):
        item_strings=content.split(r'/')
        for item_string in item_strings:
            k_v_list=item_string.split(r'@=')
            if k_v_list is not None and len(k_v_list)>1:
                k=k_v_list[0]
                v=k_v_list[1]
                ret_dict[decode_to_str(k)]=decode_to_str(v)
    return ret_dict

def  decode_to_list(content):
    """
    反序列化列表数据
    :param content:被序列化的字符串
    :return:
    """
    ret_list=[]
    if isinstance(content,str):
        items =content.split(r'/')
        for idx,item in enumerate(items):
            if idx<len(items)-1:
                ret_list.append(decode_to_str(item))
    return ret_list
class DataPacket():
    """封装数据包"""

    def __init__(self, type=DATA_PACKET_TYPE_SEND, content="", data_bytes=None):  # 默认是发送数据包,正文内容content
        if data_bytes is None:

            # 数据包的类型
            self.type = type
            # 数据部分内容
            self.content = content
            # 加密标记
            self.encrypt_flag = 0
            # 保留字段
            self.preserve_flag = 0
        else:
            self.type =int.from_bytes(data_bytes[4:6],byteorder="little",signed=False)
            self.encrypt_flag=int.from_bytes(data_bytes[6:7],byteorder="little",signed=False)
            self.preserve_flag=int.from_bytes(data_bytes[7:8],byteorder="little",signed=False)
            #构建数据部分
            self.content=str(data_bytes[8:-1],encoding="utf-8")
    def get_length(self):
        """获取数据包的长度,为以后发送数据包做准备"""

        return 4 + 2 + 1 + 1 + len(self.content.encode("utf-8")) + 1

    def get_bytes(self):
        """通过数据包转换成二进制数据"""
        data = bytes()
        # 构建四个字节的消息长度
        data_packet_length = self.get_length()
        # to_bytes 把一个整形数据转换成二进制数据
        # 第一个参数 表示需要转换的二进制数据占几个字节
        # byteorder 第二个参数描述字节序
        # signed 设置是否有符号

        # 处理消息长度
        data += data_packet_length.to_bytes(4, byteorder="little", signed=False)
        # 处理消息类型
        data += self.type.to_bytes(2, byteorder="little", signed=False)
        # 处理加密字段
        data += self.encrypt_flag.to_bytes(1, byteorder="little", signed=False)
        # 处理保留字段
        data += self.preserve_flag.to_bytes(1, byteorder="little", signed=False)

        # 处理数据内容
        data += self.content.encode("utf-8")

        # 添加\0数据
        data += b'\0'
        return data


class DouyuClient(asyncore.dispatcher):
    def __init__(self, host, port,callback=None):
        # 构建发送数据包的队列容器
        # 存放了数据包对象
        self.send_queue = Queue()

        # 存放接收数据包对象
        self.recv_queue = Queue()
        #定义外部传入的自定义回调函数
        self.callback=callback

        asyncore.dispatcher.__init__(self)
        self.create_socket()
        address = (host, port)
        self.connect(address)
        #构建一个专门处理接收数据包容器中的数据包线程
        self.callback_thread=threading.Thread(target=self.do_callback)
        self.callback_thread.setDaemon(True)
        self.callback_thread.start()
        #构建心跳线程
        self.heart_thread=threading.Thread(target=self.do_ping)
        self.heart_thread.setDaemon(True)
        self.ping_running=False

        pass

    def handle_connect(self):
        print("连接成功")
        self.start_ping()

    def writable(self):
        return self.send_queue.qsize() > 0

    def handle_write(self):
        # 从发送数据包队列中获取数据包对象
        dp = self.send_queue.get()
        # 获取数据包长度,并且发送给服务器
        dp_length = dp.get_length()

        dp_length_data = dp_length.to_bytes(4, byteorder="little", signed=False)

        self.send(dp_length_data)
        # 发送数据包二进制数据
        self.send(dp.get_bytes())
        # 数据发送完成
        self.send_queue.task_done()
        pass

    def readable(self):
        return True

    def handle_read(self):
        # 读取长度,二进制数据
        data_length_data = self.recv(4)
        # 通过二进制获取 length具体数据
        data_length = int.from_bytes(data_length_data, byteorder='little', signed=False)
        # 通过数据包的长度获取数据
        data = self.recv(data_length)
        # 通过二进制数据构建数据包对象
        dp = DataPacket(data_bytes=data)
        # 把数据包放入接收数据包容器中
        self.recv_queue.put(dp)

    def handle_error(self):
        t, e, trace = sys.exc_info()
        print(e)
        self.close()

    def handle_close(self):
        self.stop_ping()
        print("连接关闭")
        self.close()

    def login_room_id(self, room_id):
        """登录服务器"""
        self.room_id=room_id
        send_data={
            'type':"loginreq",
            "roomid":str(room_id)
        }
        # 构建登录数据包
        content = encode_content(send_data)
        login_dp = DataPacket(DATA_PACKET_TYPE_SEND, content=content)

        # 把数据包添加到发送数据包容器中
        self.send_queue.put(login_dp)
    def join_room_group(self):
        """
        加入弹幕分组
        :return:
        """
        send_data={
            'type':'joingroup',
            'rid':str(self.room_id),
            'gid':'-9999'
        }
        content=encode_content(send_data)
        dp=DataPacket(type=DATA_PACKET_TYPE_SEND,content=content)
        self.send_queue.put(dp)
        pass
    def  send_heart_data_packet(self):
        """发送心跳机制"""
        send_data={
            'type':'mrkl'
        }
        content=encode_content(send_data)
        dp=DataPacket(type=DATA_PACKET_TYPE_SEND,content=content)
        self.send_queue.put(dp)
    def start_ping(self):
        """开启心跳"""
        self.ping_running=True
    def stop_ping(self):
        """关闭心跳"""
        self.ping_running=False
    def  do_ping(self):
        """触发心跳"""
        while True:
            if self.ping_running:
                self.send_heart_data_packet()
                time.sleep(45)


    def  do_callback(self):
        """专门负责处理接收数据包容器中的数据"""
        while True:
            #从接收数据包容器中获取数据包
            dp=self.recv_queue.get()
            #对数据进行处理
            if  self.callback is not None:
                self.callback(self,dp)
            self.recv_queue.task_done()
        pass
def data_callback(dp):
    """
    自定义回调函数
    :param dp: 数据包对象
    :return:
    """
    resp_data=decode_to_dict(dp.content)
    print(resp_data )
    if resp_data["type"]=="loginres":
        #调用加入分组请求
        print("登录成功",resp_data)
        client.join_room_group()
    elif resp_data['type'] =="chatmsg":
        print("{}:{}".format(resp_data["nn"],resp_data["txt"]))
    elif resp_data['type']=="onlinegift":
        print("暴击鱼丸")
    elif resp_data['type']=="uenter":
        print("{}进入房间".format(resp_data['nn']))


    print("data_callback:",dp.content)
    pass

if __name__ == '__main__':
    client = DouyuClient('openbarrage.douyutv.com', 8601,callback=data_callback)
    client.login_room_id(4494106)
    asyncore.loop(timeout=20)

这就是通过asyncore异步抓取弹幕,代码代码是几个月前写的,现在就是端口号变了,连接被拒绝没现在不能连接,但是可以了解asyncore模块的全部流程,在后面我们重新发表斗鱼弹幕抓取selenium抓取的相关代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值