最近在看廖雪峰老师的python教程,于是乎找点事干练练手。
在网上找到一份《斗鱼弹幕服务器第三方接入协议v1.6.2》,有了api就方便多了,不需要抓包分析。
##协议组成
从文档内可以找到协议头的组成
新建一个DataPackage类来包装信息
class DataPackage(object):
tag = ('type','roomid','tick','rid','gid')
def __init__(self, code, **kw):
self.dict = {}
self.reqcode = code
self.dict.update(kw)
def __getattr__(self, item):
if item in self.dict:
return self.dict[item]
其中dict来保存数据。
可以看到2个消息长度是8byte,消息类型、加密字段、保留字段分别是2byte、1byte、1byte,那么加起来就是12byte
def pack(self):
data = ''
for k, v in self.dict.items():
data = data + k + '@=' + v + '/'
data = data.encode('utf-8') + b'\0'
data_length = len(data) + 12
header = struct.pack(', data_length) + struct.pack(', data_length) + struct.pack(',self.reqcode) \
+ struct.pack(', 0) + struct.pack(', 0)
#print(len(header))输出:12
#后面查找错误用
#print('send:' + self.__str__())
return header + data
由于采用小端编码,所以可以改成
header = struct.pack('
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('openbarrage.douyutv.com', 8601))
def main(roomid):
login(s, roomid)
while True:
d = s.recv(1024)
if d:
package = DataPackage(690)
#解析数据
package.form(d)
#处理信息
handle_msg(s, package)
else:
break
def login(s, roomid):
data = DataPackage(689, type='loginreq', roomid=roomid).pack()
s.send(data)
data = DataPackage(689, type='joingroup', rid=roomid, gid='-9999').pack()
s.send(data)
在发送’loginreq’之后,服务器并没有按照协议返回数据,害我还以为哪里出了问题。其实只要再发送’joingroup’就可以拿到成功信息了。
##处理信息
正好练习一下正则表达式
attr = re.compile(rb'(\w+)@=(.*?)/')
def form(self, data):
attrs = DataPackage.attr.findall(data)
for a in attrs:
self.dict[a[0].decode('utf-8')] = a[1].decode('utf-8')
接着处理解析好的数据
def handle_msg(s, data_package):
#后面查找错误用
#print('receive:' + data_package.__str__())
typ = data_package.type
if typ == 'chatmsg':
print("[%s]%s"%(data_package.nn,data_package.txt))
'data_package.nn’是用户昵称,'data_package.txt’则是弹幕内容
##发送心跳包
每40s发送一次心跳包,防止掉线
def keep_alive():
data = DataPackage(689, type='mrkl').pack()
s.send(data)
time.sleep(40)
if __name__ == '__main__':
p1 = multiprocessing.Process(target=main, args=('30191',))
p2 = multiprocessing.Process(target=keep_alive)
p1.start()
p2.start()
试着把信息打印出来
class DataPackage(object):
def __str__(self):
s = ''
for k, v in self.dict.items():
s = s + k + '=' + v + ';'
return s
输出如下:
send:type=loginreq;roomid=30191;
send:type=joingroup;rid=30191;gid=-9999;
send:type=mrkl;
receive:type=loginres;userid=0;roomgroup=0;pg=0;sessionid=0;username=;nickname=;live_stat=0;is_illegal=0;ill_ct=;ill_ts=0;now=0;ps=0;es=0;it=0;its=0;npv=0;best_dlev=0;cur_lev=0;nrc=3758096520;ih=0;sid=70143;sahf=0;sceneid=0;
receive:type=pingreq;tick=1533054218508;
等等!pingreq是什么鬼!文档里面没有这个东西!
那…我只好试试用’pingres’回应,输出如下:
send:type=loginreq;roomid=30191;
send:type=joingroup;rid=30191;gid=-9999;
send:type=pingres;tick=1533055273438;
send:type=mrkl;
receive:type=error;code=51;
emm…
弄了很久都没有解决这个问题,网上也搜索不到对’pingreq’的处理,但我发现rieuse的代码里是这样写的
data_length = len(data) + 8
遂跟着修改代码,成功取到弹幕
header的长度确实是12byte,到底是哪里出了问题呢?
找了很久,在知乎上找到一名匿名用户的回答(部分截取如下)
那不应该是+12么???等我再搜一下,以后补上
##题外话
我也尝试过抓包。chrome的开发者工具抓不到,只好用wireshark。结果
完整代码:https://github.com/Chgtaxihe/DouyuDanmu
第一次写博客,不当之处还望指正。
发送心跳包那段代码需要加个循环,不然进程跑完就关掉了。
感谢@a710463885指出