ckdk's blog

抓取斗鱼弹幕

2016/06/15

之前喜欢在斗鱼看直播,后来在知乎上看到大神们爬取斗鱼TV弹幕,因为自己正在学python,所以也跃跃欲试。后来斗鱼在3月份发布了新的弹幕协议,那么今天就尝试用python获取斗鱼TV的房间弹幕。

新版协议地址:《斗鱼弹幕服务器第三方接入协议》

斗鱼TV弹幕采用了TCP协议传输。所以我们需要导入socket库,顺便贴出其余用到的库。

1
2
3
4
5
import socket   #TCP连接
import sys #用于退出程序
import struct #处理二进制数据
import time #时间和时间戳
import threading #多线程

然后开始连接到斗鱼的弹幕服务器, 初学python,代码略渣。。。

创建socket

1
2
3
4
5
6
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print '创建socket成功!'
except socket.error,e:
print '创建socket失败!'
sys.exit()

连接到远程服务器

1
2
3
4
5
try:
s.connect((get_host_ip('openbarrage.douyutv.com'), 8601))
print '已连接到弹幕服务器'
except socket.error,e:
print e

发送登录请求

1
2
3
4
5
6
7
8
9
10
message = 'type@=loginreq/roomid@='+str(ROOMID)+'/0'
try:
s.send(getbyte(message))
reply = s.recv(4096)
if 'loginres' in reply:
print '登录成功'
else:
print '登录失败'
except socket.error,e:
print '登录失败'

加入房间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
message = 'type@=joingroup/rid@='+str(ROOMID)+'/gid@=-9999/0'
try:
s.send(getbyte(message))
s.settimeout(TIMEOUT)
reply = s.recv(4096)
if 'type@=error' in reply:
print '加入房间'+str(ROOMID)+'失败,请检查房间是否在线!'
else:
t1 = threading.Thread(target=send_heartbeat, args=(s,))
t2 = threading.Thread(target=recv_sock, args=(s,))
t1.start()
t2.start()
print '加入房间'+str(ROOMID)+'成功'
print('开始读取弹幕信息:')
print('------------------------------------------------')
except socket.error,e:
print '加入房间'+str(ROOMID)+'失败!'

这里需要注意的是,发送数据的时候,需要根据斗鱼消息协议加入协议头,否则收不到回复。
斗鱼消息协议格式如上所示,其中字段说明如下:

1
2
3
4
5
6
7
消息长度:4 字节小端整数,表示整条消息(包括自身)长度(字节数)。
消息长度出现两遍,二者相同。
消息类型:2 字节小端整数,表示消息类型。取值如下:
689 客户端发送给弹幕服务器的文本格式数据
690 弹幕服务器发送给客户端的文本格式数据。
加密字段:暂时未用,默讣为 0。
保留字段:暂时未用,默讣为 0。

根据此协议写一个方法来给发送的数据部分加入协议头

1
2
3
4
5
6
7
8
def getbyte(data):
#4字节消息长度(包括自身,出现两次)
a = struct.pack('i', len(data)+8)
#2字节消息类型 689/690
b = struct.pack('h', 689)
#1字节的加密字段和一字节的保留字段 未有 默认0
c = struct.pack('b', 0)
return a*2+b+c*2+data

另外在上述第四步,我们开启了两个线程,一个用于接收弹幕,一个用于向斗鱼服务器发送心跳包,因为斗鱼为了减少不正常的连接浪费资源,所有需要连接向服务器发送心跳来保持连接。斗鱼的心跳间隔目前设置为45秒。所以我们需要每小于45秒就往服务器发送心跳包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 发送心跳包
def send_heartbeat(c_sock):
while True:
time.sleep(10)
msg = 'type@=keeplive/tick@='+get_timestamp(get_datetime())+'/'
c_sock.send(getbyte(message))
time.sleep(30)

#接受socket返回
def recv_sock(c_sock):
while True:
reply = c_sock.recv(4096)
get_str(reply)
time.sleep(0.5)

其中get_str()方法,是用来格式化收到的消息的,我写的拙劣,就不贴出来了。另外本人初学python,写的代码难免有不规范和不足之处,见谅!这样获取斗鱼TV弹幕的功能就基本实现了。

result

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
电竞小可爱小仙女 : 小宇宙爆发吧!                    
大旭我套你个猴 : 哈哈哈啊啊
旭哥哥人家还想要 : 给爸爸成儿子!!!
听闻杨卓璇 : 碎税睡
伊甸园之东 : 左边不慌,还想加仓
天虐老贼单身狗 : 。。
浪费青春c : 50块 巨款
模凡 : 卖无色、
旧城以西是离愁丶 : 成成宝宝
宝哥我想中两次奖 : 哈哈已经是碎了不用再垫了
海外散仙历飞雨 : 卖无色
猫九命阿 : 提前舒服
怎能忘了西遊 : 已经是碎了
苞米地穿西装 : 墨迹必碎
改名为套猴 : 看看你那个怂样
装B王丶MaeKMO : 墨迹必碎
九日九日梦想秀 : 成成成成成成成
Piubull : 。。
有很多人很多人很多 : 赞助必碎
我不是任怡旭 : 右边我都梭哈了
旭的吊大粗又黑 : 墨迹必碎
99999改名字一定会中奖 : 一定不要成
丨尚安哥哥丨 : 上16化粪池蝶泳
Smile丶杨先森 : 碎
泪寒i28861 : 理财
炫迈牌套套XXXXL号 : 人格担保
莪愿一人 : 无色换成矛盾卖了就行了
暴打小佳佳 : 墨迹必碎
HAC丶无聊抽抽 : 左边的拉了
凌昕i : 成功我吃屎 记住我的ID 3
恭喜我这个逼中奖了 : 给爸爸碎
一点点兮欢 : 墨迹已经废了
何必灬老友 : ?????
斯亻独憔悴 : 没有秘药
401丶Torres : 基本上碎了,虽然我压的左边
在妓院的和尚 : 卖无色啊
A余咏记 : 有人要上天台了
CATALOG
  1. 1. 创建socket
  2. 2. 连接到远程服务器
  3. 3. 发送登录请求
  4. 4. 加入房间
  5. 5. result