python爬虫-斗鱼弹幕(asyncore仅供学习参考用)

python编程快速上手(持续更新中…)

python爬虫热点项目(Flask )


asyncore 模块

介绍

这个模块为异步socket的服务器客户端通信提供简单的接口。该模块提供了异步socket服务客户端和服务器的基础架构。相比python原生的socket?api,asyncore具备有很大的优势,asyncore对原生的socket进行封装,提供非常简洁优秀的接口,利用asyncore覆写相关需要处理的接口方法,就可以完成一个socket的网络编程,从而不需要处理复杂的socket网络状况以及多线程处理等等

实现流程

在这里插入图片描述

网络助手启动TCP服务器

在这里插入图片描述

客户端 Socket 开发基本使用

在这里插入图片描述
1. 定义类并且继承自 asyncore.dispatcher

class SocketClient(asyncore.dispatcher):

2. 实现类中的回调函数代码

def init(self,host,port):
# 调用父类方法
asyncore.dispatcher.init(self)
# 创建 Socket 对象
self.create_socket()
# 连接服务器
address = (host,port)
self.connect(address)
pass

3. 实现 handle_connect 回调函数

def handle_connect(self):
print(“连接成功”)

4. 实现 writable 回调函数

def writable(self):
return False

5.实现 handle_write 回调函数

def handle_write(self):
# 内部实现对服务器发送数据的代码
# 调用 send 方法发送数据,参数是字节数据
self.send(‘hello world\n’.encode(‘utf-8’))

6.实现 readable 回调函数

def readable(self):
return True

7. 实现 handle_read 回调函数
def handle_read(self):
# 主动接收数据,参数是需要接收数据的长度
result = self.recv(1024)
print(result)

8.实现 handle_error 回调函数

def handle_error(self):
# 编写处理错误方法
t,e,trace = sys.exc_info()
self.close()

9.实现 handle_close 回调函数

def handle_close(self):
print(“连接关闭”)
self.close()

创建对象并且执行 asyncore.loop 进入运行循环

if name == ‘main’:
client = SocketClient(‘127.0.0.1’,9000)
# 开始启动运行循环
asyncore.loop(timeout=5)

测试效果

在这里插入图片描述

斗鱼弹幕(仅供学习参考用)

斗鱼弹幕开发流程

openbarrage.douyutv.com ,该链接已无法访问,现接口已使用token验证
在这里插入图片描述

数据发送和接收流程

在这里插入图片描述

数据包讲解

在这里插入图片描述
在这里插入图片描述

发送数据包

在这里插入图片描述

构建接收数据包队列
存放接收的数据包对象

self.recv_queue = Queue()

读取回调函数中读取数据
读取长度
读取长度,二进制数据

data_length_data = self.recv(4)

通过二进制获取length 具体数据

data_length = int.from_bytes(data_length_data,byteorder=‘little’,signed=False)

读取内容
通过数据包的长度获取数据

data = self.recv(data_length)

构建数据包对象
数据包构造函数中解析二进制来构建数据包对象

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’)

通过二进制数据构建数据包对象

dp = DataPacket(data_bytes=data)

把数据包放入接收数据包容器中

self.recv_queue.put(dp)

构建处理线程专门处理接收数据包容器中数据
构建一个专门处理接收数据包容器中的数据包的线程

self.callback_thread = threading.Thread(target=self.do_callback)
self.callback_thread.setDaemon(True)
self.callback_thread.start()

实现回调函数处理接收的数据包

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

实现外部传入回调函数

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

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

2.外部传入自定义回调函数

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=10)

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

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

数据内容序列化与反序列化

1.键 key 和值 value 直接采用‘@=’分割
2.数组采用‘/’分割
3.如果 key 或者 value 中含有字符‘/’,则使用‘@S’转义
4.如果 key 或者 value 中含有字符‘@’,使用‘@A’转义

例子
多个键值对数据:key1@=value1/key2@=value2/key3@=value3/数组数据:value1/value2/value3/

登录

参看斗鱼弹幕文档

加入弹幕分组

参看斗鱼弹幕文档,-9999 为海量弹幕

心跳机制

作用是让服务器解决假死连接问题,客户端必须每隔45秒发送一次请求,否则就会被主动断开。

实现发送心跳函数
构建心跳数据包
把数据包添加到发送数据包容器队列中

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

代码实现

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import asyncore
import sys
import threading
import time

from queue import Queue

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):
        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: 
        '''
        return 4 + 2 + 1 + 1 + len(self.content.encode('utf-8')) + 1

    def get_bytes(self):
        '''
        通过数据包转换成 二进制数据类型
        :return: 
        '''
        data = bytes()
        # 构建 4 个字节的消息长度数据
        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_runing = 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):
        '''
        开启心跳
        :return: 
        '''
        self.ping_runing = True

    def stop_ping(self):
        '''
        结束心跳
        :return: 
        '''
        self.ping_runing = False

    def do_ping(self):
        '''
        执行心跳
        :return: 
        '''
        while True:
            if self.ping_runing:
                self.send_heart_data_packet()
                time.sleep(40)



    def do_callback(self):
        '''
        专门负责处理接收数据包容器中的数据
        :return: 
        '''
        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(client,dp):
    '''
    自定义回调函数
    :param dp: 数据包对象
    :return: 
    '''

    resp_data = decode_to_dict(dp.content)
    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"]))
    pass




if __name__ == '__main__':
    client = DouyuClient('openbarrage.douyutv.com',8601,callback=data_callback)

    client.login_room_id(10092762)

    asyncore.loop(timeout=10)

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值