python爬虫----简单的抓取斗鱼弹幕

来源:互联网 发布:大数据目前发展情况 编辑:程序博客网 时间:2024/05/01 05:47

近几年来直播越来越火,看直播也成为了人们生活的娱乐项目

个人也是比较喜欢看直播,看着主播的搞笑的操作和弹幕不时会开怀大笑。


于是就想能不能把弹幕抓取下来,带着这个问题我就点开了一个直播间。按照以前学过的方法好像根本没有办法弄到弹幕那一块,

于是赶紧去网上查,发现网上有不少人已经做了。呃......怎么说,大概了解了获取弹幕的原理,就是通过socket向斗鱼弹幕服务器发送请求,

然后服务器会返回数据给你,但是他们的具体操作不是太明白。没办法只好继续搜索,终于看到了一个知乎用户的专栏里的一篇文章,

@Ehco的   从零开始写Python爬虫 --- 爬虫应用: 利用斗鱼Api抓取弹幕  地址:https://zhuanlan.zhihu.com/p/28164017,

因为斗鱼现在已经开放了弹幕服务器接入协议,我也看了其他人也提到了这个,但具体怎么用这个协议还是不明白。

通过读了Ehco他的文章后才一点点的弄清楚。


因为之前完全不知道怎么弄,所以代码写的和原作者十分类似。

但我在注释中写了一些自己的理解,如果觉得不清楚可以去看原作者的文章。

也十分感谢原作者分享了这么好的文章。





好吧,下面就来说说我的理解



首先是斗鱼协议

《斗鱼弹幕服务器第三方接入协议v1.4.1》:

http://dev-bbs.douyutv.com/forum.php?mod=viewthread&tid=115&extra=page%3D1

《斗鱼第三方开放平台API文档v2.0》:

http://dev-bbs.douyutv.com/forum.php?mod=viewthread&tid=108&extra=page%3D1


这里我们只需要看第一个就行了




然后再看协议的内容

首先是协议头,这个最重要了,每个请求前面都要带这个



这部分的代码:

def send_request_msg(msgstr):        msg = msgstr.encode('utf-8')#协议规定所有协议内容均为 UTF-8 编码    data_lenth = len(msg) + 8    #data_lenth表示整个协议头的长度(消息长度),包括数据部分和头部,len(msg)就是数据部分,8就是头部的长度    code = 689    #根据协议消息类型字段用689    msghead = int.to_bytes(data_lenth,4,'little') + int.to_bytes(data_lenth,4,'little') + int.to_bytes(code,4,'little')    #msghead是按照斗鱼第三方协议构造的协议头    #前2段表示的是消息长度,最后一个是消息类型    #这里还有个值得注意的一点,发送给服务器的类型和服务器返回的类型都是bytes,因此要把数据信息通过int.to_bytes()变成bytes            client.send(msghead)#发送协议头    client.send(msg)#发送消息请求




接着就是获取弹幕的部分了


获取弹幕的步骤是这样的:

先要向服务器发送登录请求消息,再发送入组消息用于加入房间,

在获取弹幕的过程中还要发送心跳消息来维持和服务器的连接。




获取弹幕的代码:

def get_danmu(roomid):    denglu_msg = 'type@=loginreq/roomid@={}/\0'.format(roomid)#登录请求消息,最后面的'\0',是协议规定在数据部分结尾必须是'\0'    send_request_msg(denglu_msg)    join_room_msg = 'type@=joingroup/rid@={}/gid@=-9999/\0'.format(roomid)#加入房间分组消息    send_request_msg(join_room_msg)    while True:                data = client.recv(1024)        #这个data就是服务器向客户端发送的消息        #具体的信息可以看斗鱼弹幕第三方接入协议        danmu_username = re.findall(user_id,data)        dammu_content = re.findall(danmu,data)        #print(data)        if not data:            break        else:            for i in range(0,len(danmu_username)):                try:                    print('[{}]:{}'.format(danmu_username[i].decode('utf-8'),dammu_content[i].decode('utf-8')))                                #返回的数据是bytes型,所以要用decode方法来解码                except:                    continuedef keeplive():    #维持与后台的心跳    #关于心跳消息,协议中有详细的解释    while True:        live_msg = 'type@=keeplive/tick@=' + str(int(time.time())) + '/\0'        send_request_msg(live_msg)        time.sleep(15)



主体部分大概就是这么多了,接下来怎么使程序运作起来的问题了。

这其中涉及到了一些知识,主要是那几个模块的使用,还有因为发送给服务器信息和服务器返回的信息都是bytes,

因此还有一些python关于bytes类型的资料


我把我在查资料时觉得不错的资料列一下吧


socket模块的介绍:http://blog.csdn.net/rebelqsp/article/details/22109925

python3 bytes与str的区别:http://www.ituring.com.cn/article/1116

关于int.to_bytes函数的官方文档:https://translate.google.com/translate?depth=1&hl=zh-CN&prev=search&rurl=translate.google.com.hk&sl=en&sp=nmt4&u=https://docs.python.org/3/library/stdtypes.html#int.to_bytes

signal模块的介绍:http://blog.sina.com.cn/s/blog_c2839d2a0102x3j1.html

还有就是多进程的模块,也就是multiprocessing模块,网上都写的差不多,大家可以自己去搜搜。



最后一点很关键的一点,在程序写完后让它运行,发现在python自带的IDLE中没反应,就好像是好几个函数没用一样。

后面才发现要找到你写的代码的那个文件,也就是带.py的文件。点开它,弹幕就会出来了,是在windos下的cmd里才会显示出来。

经过测试

显示出的弹幕,在弹幕量大的时候好像显示得不完全,有些用户的弹幕获取不到?(不知道是不是我看错了没有),不过一般的弹幕量还是可以获取到的。




完整代码:

import reimport socketimport signalimport multiprocessingimport timeclient = socket.socket(socket.AF_INET,socket.SOCK_STREAM)port = 8602 # 端口8601、8602、12601、12602这几个端口号都是,但有时候有号些获取不到弹幕的信息,每个都试下总有一个可以host = socket.gethostbyname('openbarrage.douyutv.com')client.connect((host,port))danmu = re.compile(b'txt@=(.+?)/')user_id = re.compile(b'nn@=(.+?)/')def send_request_msg(msgstr):        msg = msgstr.encode('utf-8')#协议规定所有协议内容均为 UTF-8 编码    data_lenth = len(msg) + 8    #data_lenth表示整个协议头的长度(消息长度),包括数据部分和头部,len(msg)就是数据部分,8就是头部的长度    code = 689    #根据协议消息类型字段用689    msghead = int.to_bytes(data_lenth,4,'little') + int.to_bytes(data_lenth,4,'little') + int.to_bytes(code,4,'little')    #msghead是按照斗鱼第三方协议构造的协议头    #前2段表示的是消息长度,最后一个是消息类型    #这里还有个值得注意的一点,发送给服务器的类型和服务器返回的类型都是bytes,因此要把数据信息通过int.to_bytes()变成bytes            client.send(msghead)#发送协议头    client.send(msg)#发送消息请求def get_danmu(roomid):    denglu_msg = 'type@=loginreq/roomid@={}/\0'.format(roomid)#登录请求消息,最后面的'\0',是协议规定在数据部分结尾必须是'\0'    send_request_msg(denglu_msg)    join_room_msg = 'type@=joingroup/rid@={}/gid@=-9999/\0'.format(roomid)#加入房间分组消息    send_request_msg(join_room_msg)    while True:                data = client.recv(1024)        #这个data就是服务器向客户端发送的消息        #具体的信息可以看斗鱼弹幕第三方接入协议        danmu_username = re.findall(user_id,data)        dammu_content = re.findall(danmu,data)        #print(data)        if not data:            break        else:            for i in range(0,len(danmu_username)):                try:                    print('[{}]:{}'.format(danmu_username[i].decode('utf-8'),dammu_content[i].decode('utf-8')))                                #返回的数据是bytes型,所以要用decode方法来解码                except:                    continuedef keeplive():    #维持与后台的心跳    #关于心跳消息,协议中有详细的解释    while True:        live_msg = 'type@=keeplive/tick@=' + str(int(time.time())) + '/\0'        send_request_msg(live_msg)        time.sleep(15)def logout():    out_msg = 'type@=logout/'    send_request_msg(out_msg)    print('已退出服务器!')def signal_handler(signal,frame):    #捕捉ctrl + c的信号,即signal.SIGINT    p1.terminate()#结束进程    p2.terminate()#结束进程    logout()if __name__ == '__main__':        roomid = 74751#房间号,主播开播才能获取到信息        signal.signal(signal.SIGINT,signal_handler)    p1 = multiprocessing.Process(target = get_danmu,args = (roomid,))    p2 = multiprocessing.Process(target = keeplive)    p1.start()    p2.start()





写这么个东西没我想的那么容易,也用了不少的时间。不过也还是弄出来了,

可能写的也不是很好吧,但每次写一个新东西也是一次新的挑战,

也在其中学到了不少的东西。可能我的理解也表达不是那么清楚,可能也有一些错误,如果有发现错误的地方欢迎各位指正


原创粉丝点击