近几年来直播越来越火,看直播也成为了人们生活的娱乐项目
个人也是比较喜欢看直播,看着主播的搞笑的操作和弹幕不时会开怀大笑。
于是就想能不能把弹幕抓取下来,带着这个问题我就点开了一个直播间。按照以前学过的方法好像根本没有办法弄到弹幕那一块,
于是赶紧去网上查,发现网上有不少人已经做了。呃......怎么说,大概了解了获取弹幕的原理,就是通过socket向斗鱼弹幕服务器发送请求,
然后服务器会返回数据给你,但是他们的具体操作不是太明白。没办法只好继续搜索,终于看到了一个知乎用户的专栏里的一篇文章,
@Ehco的 从零开始写Python爬虫 --- 爬虫应用: 利用斗鱼Api抓取弹幕 地址:/p/28164017,
因为斗鱼现在已经开放了弹幕服务器接入协议,我也看了其他人也提到了这个,但具体怎么用这个协议还是不明白。
通过读了Ehco他的文章后才一点点的弄清楚。
因为之前完全不知道怎么弄,所以代码写的和原作者十分类似。
但我在注释中写了一些自己的理解,如果觉得不清楚可以去看原作者的文章。
也十分感谢原作者分享了这么好的文章。
好吧,下面就来说说我的理解
首先是斗鱼协议
《斗鱼弹幕服务器第三方接入协议v1.4.1》:
http://dev-/forum.php?mod=viewthread&tid=115&extra=page%3D1
《斗鱼第三方开放平台API文档v2.0》:
http://dev-/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#根据协议消息类型字段用689msghead = 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()变成bytesclient.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:breakelse: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模块的介绍:/rebelqsp/article/details/22109925
python3 bytes与str的区别:/article/1116
关于int.to_bytes函数的官方文档:/translate?depth=1&hl=zh-CN&prev=search&rurl=.hk&sl=en&sp=nmt4&u=/3/library/stdtypes.html#int.to_bytes
signal模块的介绍:/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('')client.connect((host,port))danmu = pile(b'txt@=(.+?)/')user_id = pile(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#根据协议消息类型字段用689msghead = 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()变成bytesclient.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:breakelse: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.SIGINTp1.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()
写这么个东西没我想的那么容易,也用了不少的时间。不过也还是弄出来了,
可能写的也不是很好吧,但每次写一个新东西也是一次新的挑战,
也在其中学到了不少的东西。可能我的理解也表达不是那么清楚,可能也有一些错误,如果有发现错误的地方欢迎各位指正