从零开始写Python爬虫 --- 爬虫应用: 利用斗鱼Api抓取弹幕

从零开始写Python爬虫 --- 爬虫应用: 利用斗鱼Api抓取弹幕

七月末的南京简直开启了「微波炉」模式,白天要学车的我,晚上自然选择宅在家看直播,看着狗贼叔叔满屏幕的弹幕,我就想着能不能把弹幕爬下来呢?说干就干

结果的展示:

这里只抓到弹幕内容和发送用户
并输出在终端上,有兴趣的小伙伴
可以在这个基础上接着开发,
搜集弹幕做做数据分析也是很ok的啊!

下面是展示图:




资料的搜集

面向Google编程的我,第一件事当然是键入关键词:「Python 弹幕」

吃惊的是,网上已经有了炒鸡完善的弹幕第三方库:「DanMU」

使用起来也是炒鸡简单,十几行代码就能轻松获取直播间的弹幕了,

有兴趣的同学可以去搜索看看。

本着练手和不折腾会死的态度,我还是想尝试自己写一个版本出来,

然后就找到了 斗鱼居然开放了Api,

这样的话,只要稍微处理一下,就能愉快的获取想要的信息了。

斗鱼Api接口文档和接入协议



仔细观察文档之后,我发现只要自己实现一下协议头,

就能接入弹幕服务器了,

接着构造弹幕请求,

就能实时的获取每一条弹幕了。

请求头的构造

先看文档的要求:




简而言之呢:

请求一共分为三个部分:长度,头部,数据部
分别按照文档的要求构造就行,
需要注意的是,获取和返回的类型是都是 Bytes

代码:

def send_req_msg(msgstr):
    '''构造并发送符合斗鱼api的请求'''
    msg = msgstr.encode('utf8')
    data_length = len(msg) + 8
    code = 689
    # 构造协议头
    msgHead = int.to_bytes(data_length, 4, 'little') \
        + int.to_bytes(data_length, 4, 'little') + \
        int.to_bytes(code, 4, 'little')
    client.send(msgHead)
    sent = 0
    while sent < len(msg):
        tn = client.send(msg[sent:])
        sent = sent + tn

获取弹幕

这里的部分也是按照文档要求写就成
首先 发送登录请求
接着 每隔固定时间发送【心跳请求】防止断线

def DM_start(roomid):
    # 构造登录授权请求
    msg = 'type@=loginreq/roomid@={}/\0'.format(roomid)
    send_req_msg(msg)
    # 构造获取弹幕消息请求
    msg_more = 'type@=joingroup/rid@={}/gid@=-9999/\0'.format(roomid)
    send_req_msg(msg_more)

    while True:
        # 服务端返回的数据
        data = client.recv(1024)
        # 通过re模块找发送弹幕的用户名和内容
        danmu_username = username_re.findall(data)
        danmu_content = danmu_re.findall(data)
        if not data:
            break
        else:
            for i in range(0, len(danmu_content)):
                try:
                    # 输出信息
                    print('[{}]:{}'.format(danmu_username[0].decode(
                        'utf8'), danmu_content[0].decode(encoding='utf8')))
                except:
                    continue

def keeplive():
    '''
    保持心跳,15秒心跳请求一次
     '''
    while True:
        msg = 'type@=keeplive/tick@=' + str(int(time.time())) + '/\0'
        send_req_msg(msg)
        print('发送心跳包')
        time.sleep(15)

tricky 的部分

上面的内容,说起来都不是很难,
但是想要完整的实现需求,
这里需要的知识还是比较多的:

  • socket相关
  • 正则表达式相关
  • signal相关
  • 多线程、多进程相关

比如我想要实现捕捉「ctrl+c」的信号,
好在我们退出程序的时候,能够正确的处理
这时候就要用到signal相关的知识
说起来,在今天之前,我完全不知道还可以这样用。

总之越是学到后面,
越是会觉得自己的知识储备不足,
Python 作为一门十分强大和容易上手的语言,
能够帮助我们迅速的实现需求,
但是不要认为他单单只能写爬虫哦,

完整的代码

有详细的注释哦:

'''
利用斗鱼弹幕 api
尝试抓取斗鱼tv指定房间的弹幕
'''

import multiprocessing
import socket
import time
import re
import signal

# 构造socket连接,和斗鱼api服务器相连接
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host = socket.gethostbyname("openbarrage.douyutv.com")
port = 8601
client.connect((host, port))

# 弹幕查询正则表达式
danmu_re = re.compile(b'txt@=(.+?)/cid@')
username_re = re.compile(b'nn@=(.+?)/txt@')


def send_req_msg(msgstr):
    '''构造并发送符合斗鱼api的请求'''

    msg = msgstr.encode('utf-8')
    data_length = len(msg) + 8
    code = 689
    # 构造协议头
    msgHead = int.to_bytes(data_length, 4, 'little') \
        + int.to_bytes(data_length, 4, 'little') + \
        int.to_bytes(code, 4, 'little')
    client.send(msgHead)
    sent = 0
    while sent < len(msg):
        tn = client.send(msg[sent:])
        sent = sent + tn


def DM_start(roomid):
    # 构造登录授权请求
    msg = 'type@=loginreq/roomid@={}/\0'.format(roomid)
    send_req_msg(msg)
    # 构造获取弹幕消息请求
    msg_more = 'type@=joingroup/rid@={}/gid@=-9999/\0'.format(roomid)
    send_req_msg(msg_more)

    while True:
        # 服务端返回的数据
        data = client.recv(1024)
        # 通过re模块找发送弹幕的用户名和内容
        danmu_username = username_re.findall(data)
        danmu_content = danmu_re.findall(data)
        if not data:
            break
        else:
            for i in range(0, len(danmu_content)):
                try:
                    # 输出信息
                    print('[{}]:{}'.format(danmu_username[0].decode(
                        'utf8'), danmu_content[0].decode(encoding='utf8')))
                except:
                    continue


def keeplive():
    '''
    保持心跳,15秒心跳请求一次
     '''
    while True:
        msg = 'type@=keeplive/tick@=' + str(int(time.time())) + '/\0'
        send_req_msg(msg)
        print('发送心跳包')
        time.sleep(15)


def logout():
    '''
    与斗鱼服务器断开连接
    关闭线程
    '''
    msg = 'type@=logout/'
    send_req_msg(msg)
    print('已经退出服务器')


def signal_handler(signal, frame):
    '''
    捕捉 ctrl+c的信号 即 signal.SIGINT
    触发hander:
    登出斗鱼服务器
    关闭进程
    '''
    p1.terminate()
    p2.terminate()
    logout()
    print('Bye')


if __name__ == '__main__':
    #room_id = input('请输入房间ID: ')

    # 狗贼的房间号
    room_id = 208114
    # 开启signal捕捉
    signal.signal(signal.SIGINT, signal_handler)

    # 开启弹幕和心跳进程
    p1 = multiprocessing.Process(target=DM_start, args=(room_id,))
    p2 = multiprocessing.Process(target=keeplive)
    p1.start()
    p2.start()


每天的学习记录都会 同步更新到:
微信公众号: findyourownway

知乎专栏:zhuanlan.zhihu.com/Ehco

blog :
www.ehcoblog.ml

Github:
github.com/Ehco1996/Pyt

发布于 2017-07-28

文章被以下专栏收录