Home  >  Article  >  Backend Development  >  如何获取斗鱼直播间的弹幕信息?

如何获取斗鱼直播间的弹幕信息?

WBOY
WBOYOriginal
2016-06-06 16:11:056402browse

如何获取如图红色框中的弹幕信息呢?想做一些基于弹幕信息的二次开发。


如何获取斗鱼直播间的弹幕信息?

回复内容:

在参考顶楼的回答和评论后,正确获取到了弹幕,来回报下社会,代码已经放在Github上了! douyu/danmu.cc at master · fishioon/douyu · GitHub
代码是C写的,本来用的python,感觉C更好用(在处理数据的时候)。

比较喜欢看yyf的dota2直播,但是最近yyf经常吹B,说有网友弹幕问“枫哥枫哥,你的XX怎么这么厉害啊”,水友们纷纷表示不信,所以就想着把弹幕爬下来,同时还有那些说赢了直播吃翔的,我通通都要记录下来!!!

回答正题,那我们该如何弄清楚这个协议呢?
弹幕属于实时消息,第一反应应该用websocket实现,打开chrome,F12,websocket中竟然找不到!那估计是通过Flash实现了。前端不熟,找了半天也没找到JS实现代码,只好祭出wireshark来分析了。
看了顶楼的提示,在wireshark中查看端口为8601的数据包,如下图所示。
如何获取斗鱼直播间的弹幕信息?
前三个数据包就是TCP的三次握手啦,复习下;接着我们看第四个数据包,如下图所示
如何获取斗鱼直播间的弹幕信息?一般来说通信协议设计“内容长度”+“内容”,我们来看tcp数据包内容,前四个字节为0x59 = 5*16+9 = 89,再看整个数据包长度为93,正好符合长度+内容,差不多我们可以确定通信协议如下:
struct {
int len; //数据包长度
int code; //经抓包发现该字段一直与len字段一致
int magic; //不知道啥意思,发现请求都是0x2b1, 返回都是0x2b2
char content[0]; //消息具体内容
}
从图中的内容中可以看到:
1. 登录弹幕服务器: "type@=loginreq/username@%s=/password@=%s/roomid@=%d/ct@=2/",输入自己的账号密码(经测试发现,账号密码随便填都能成功),要看的房间id,如yyf的房间id为52428
通过socket发送上面的内容后,你回收到这样的一条数据,格式与发送格式一样:
“type@=loginres/userid@=0/roomgroup@=0/pg@=0/sessionid@=0/username@=/nickname@=/live_stat@=0/is_illegal@=0/npv@=0/best_dlev@=0/cur_lev@=0/”,没发现有啥有用的数据。
2. 收到上面的消息后,这时候要加入一个组,格式如下:
"type@=joingroup/rid@=%d/gid@=%d/",rid就是房间id,要注意的问题来了:
gid应该是group id,登录不同房间该id都不一样,每次我都是抓包来查看该id是多少,有知情人告诉我吗?(一楼评论中也提到这个问题)
发送上面的消息后,我们就可以安心的接收数据了,然后从数据中提取我们想要的就可以了,其中很多数据都不懂啥意思。
最后我们看下yyf房间的弹幕哈!(节奏带的飞起)
如何获取斗鱼直播间的弹幕信息? Update 20160214 : 更新Python和Ruby客户端(请大家不要问我为什么情人节这一天为什么闲着没事更新代码)
Update 20160220 : 更新Python客户端,增加直播视频的Live获取,以及Mac平台下面的Mplayer的视频播放.代码均放在Github上面. GitHub - twocucao/danmu.fm: douyutv danmu 斗鱼TV 弹幕助手

由于zhihu没有法子贴动态图,那只好移步到我的博客一看了.(看博客之前记得点赞╮(╯_╰)╭)

Python程序员如何优雅的看斗鱼TV

===================优雅的看斗鱼TV的分割线==================================

-1.如果不想看长文,直接使用.则在安装好Python3或者Ruby2.0以上版本.

#安装Python客户端
pip3 install danmu.fm

# 比如主播的直播间
danmu.fm http://www.douyutv.com/16789
#或者
danmu.fm 16789
#安装Ruby客户端
gem install danmu
#使用
danmu douyu [room_id/url]
#比如
danmu douyu qiuri
danmu douyu http://www.douyutv.com/13861
[多图预警]

Update:2016.1.16
总结在博客里:抓取斗鱼直播弹幕
Github 项目地址:brucezz/DouyuCrawler
欢迎去提各种 issue & PR (代码水平不高)...


/********************************************************************************************/
Update:2016.1.14
最近几天会把 Java 项目发布到 Github 上面,大家提提意见 :)

/********************************************************************************************/
之前看到这个问题,感觉挺好玩的,就研究了下。
照着前面的思路,抓包分析。其他几位的回答,基本能抓到弹幕数据了,但是其中一个参数gid,需要手动抓包得到。我也查看了浏览器加载的js文件,没看到相关线索。最后看到排名第一的回答评论里有人说可以通过请求页面里server_config的ip。
然后我就顺着这条线索探索下去了。
具体过程如下:

先获取到直播页面中的server_config相关字段,发现是经过urlencode的。
如何获取斗鱼直播间的弹幕信息?然后进行urldecode,得到json数据,再格式化一下,
UrlEncode编码/UrlDecode解码
JSON在线编辑
就变成这样:
[{"ip":"119.90.49.107","port":"8035"},{"ip":"119.90.49.102","port":"8008"},{"ip":"119.90.49.110","port":"8050"},{"ip":"119.90.49.104","port":"8020"},{"ip":"119.90.49.107","port":"8034"},{"ip":"119.90.49.92","port":"8059"},{"ip":"119.90.49.95","port":"8071"},{"ip":"119.90.49.101","port":"8001"},{"ip":"119.90.49.93","port":"8063"},{"ip":"119.90.49.91","port":"8053"}]
##################20160325更新##########################
最近斗鱼修改了弹幕格式,大概长这个样子
b'\x81\x00\x00\x00\x81\x00\x00\x00\xb2\x02\x00\x00type@=chatmsg/rid@=9401/uid@=12635840/nn@=\xe8\xb6\x85\xe8\xb6\x8a\xe7\xa5\x9e\xe7\x9a\x84boy/txt@=\xe6\x89\x93\xe8\x84\xb8\xe5\x90\xa7/cid@=20e4e3fe427e410c67c5010000000000/level@=5/\x00'
优雅的获取弹幕和看弹幕:

如何获取斗鱼直播间的弹幕信息?

随便抓了100个房间大概半小时。
优雅的抓出那些说“屎尿屁”的“精神周星驰”:
(那么大家都发现了,疯狂说这些一般都是20级以下的人。斗鱼要到20级大概是充值3000-4000左右好像。那么你们明白什么了吗)
如何获取斗鱼直播间的弹幕信息? 如何获取斗鱼直播间的弹幕信息? 如何获取斗鱼直播间的弹幕信息? 如何获取斗鱼直播间的弹幕信息?

最后还有更好玩的(大家来找壕,看看壕都喜欢去什么房间):
如何获取斗鱼直播间的弹幕信息?






















当然编程的方式也要优雅:Python

至于方法,最高赞已经很干净清晰了的解答了,我这里做小小补充。

其实不需要那么多工序,用socket直接3个命令就可以了:

下面伪代码:

client_danmu = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#在客户端开启心跳维护
client_danmu.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) 


#官方弹幕服务器'openbarrage.douyutv.com'
SERVER = socket.gethostbyname('openbarrage.douyutv.com')
PORT = 8601

client_danmu.connect((SERVER,PORT))
client_danmu.settimeout(2)    #记得设置延迟,不然卡着recv

#这里发的要处理下发送内容,用最高赞的方法获取封包格式就好了
封包1 = 'type@=loginreq/username@=visitor1234567/password@=/roomid@=房间号/'
#登陆弹幕服务器
client_danmu.send(封包1)

封包2 =  'type@=joingroup/rid@=房间号/gid@=-9999/'
#加入弹幕组,这里要注意,gid就是弹幕组号。-9999是斗鱼开放的第三方获取弹幕的组别。是能获取所有弹幕的。因为有些主播动辄几十万人,那弹幕量如果都发给所有用户,那卡死用户事小,斗鱼自己流量事大啊。所以一般都是分组发送,所以你平时在浏览器看的并不一定是所有弹幕。用-9999这个组别号是可以获取所有的
client_danmu.send(封包2)

#这里开始就是用while循环获取弹幕了,记得大概每30秒发一个心跳包过去。
封包3 =  'type@=mrkl/'
client_danmu.send(封包3)
那我说说?
是这样的,斗鱼的弹幕信息有个自己的服务器
danmu.douyutv.com 可以抓包看到,他通过TCP连接到斗鱼弹幕服务器,然后获取的信息,那么想要对它进行二次开发只需要模拟用户连接并登入弹幕服务器即可。
 弹幕连接 OnConn
(program):1  弹幕 UserLogin [type@=loginreq/username@=xxxx/password@=1234567890123456/roomid@=58718/]
(program):1 弹幕 网络数据 [type@=loginres/userid@=0/roomgroup@=67108896/pg@=0/sessionid@=6/username@=/nickname@=/is_signined@=432003648/signin_count@=32756/s@=% ô/live_stat@=125387584/npv@=32756/]
(program):1 弹幕登录成功
VM160:1 弹幕分组 [type@=joingroup/rid@=58718/gid@=0/]
2VM171:1 FMPNetStream连接状态:NetStream.Buffer.Full
VM175:1 弹幕 网络数据 [type@=userenter/rid@=58718/gid@=0/userinfo@=id@A=4295897@Sname@A=auto_DJUziSYiJo@Snick@A=芙苏不语丶怪力乱神@Srg@A=1@Spg@A=1@Srt@A=1416480352@Sbg@A=0@Sweight@A=12800@Sstrength@A=51800@Scps_id@A=0@Sps@A=1@Sver@A=0@Sm_deserve_lev@A=0@S/]
VM176:1 新用户进入信息: [id@=4295897/name@=auto_DJUziSYiJo/nick@=芙苏不语丶怪力乱神/rg@=1/bg@=0/pg@=1/rt@=1416480352/weight@=12800/strength@=51800/cps_id@=0/]
我记录了下抓取弹幕的流程,可以参考下。
地址: 斗鱼弹幕抓取
代码:github.com/ndrlslz/Douy 既然没人发nodejs版,那我就发一个吧
var net = require('net');
var uuid = require('node-uuid');
var md5 = require('md5');
var request = require('request');

var HOST = 'danmu.douyutv.com';
var PORT = 8602;

function send(socket, payload)
{	
	var data = new Buffer(4 + 4 + 4 + payload.length + 1)
	data.writeInt32LE(4 + 4 + payload.length + 1, 0); //length
	data.writeInt32LE(4 + 4 + payload.length + 1, 4); //code
	data.writeInt32LE(0x000002b1, 8); //magic
	data.write(payload, 12); //payload
	data.writeInt8(0, 4 + 4 + 4 + payload.length); //end of string
	socket.write(data)
}

function login(socket, roomid, user, password)
{
	var req = 'type@=loginreq/username@=' + user + '/password@=' + password + '/roomid@=' + roomid;
	send(socket, req);
}

function getGroupServer(roomid, callback)
{
	request({uri:'http://www.douyutv.com/' + roomid}, function(err, resp, body) {
		var server_config = JSON.parse(body.match(/room_args = (.*?)\}\;/g)[0].replace('room_args = ', '').replace(';', ''));
		server_config = JSON.parse(unescape(server_config['server_config']));
		callback(server_config[0].ip, server_config[0].port);
	});
}

function getGroupId(roomid, callback)
{
	var rt = new Date().now;
	var devid = uuid.v4().replace(/-/g, '');
	var vk = md5(rt + '7oE9nPEG9xXV69phU31FYCLUagKeYtsF' + devid)
	var req = 'type@=loginreq/username@=/password@=/roomid@=' + 
		roomid + '/ct@=0/vk@=' + vk + '/devid@=' + 
		devid + '/rt@=' + rt + '/ver=@20150929/';
	
	getGroupServer(roomid, function(server, port) {
		console.log('group server: ' + server + ':' + port);
		var socket = net.connect(port, server, function() {
			send(socket, req);
		});
		
		socket.on('data', function(data) {
			if (data.indexOf('type@=setmsggroup') >= 0) {
				var gid = data.toString().match(/gid@=(.*?)\//g)[0].replace('gid@=', '');
				gid = gid.substring(0, gid.length - 1);
				socket.destroy();
				callback(gid);
			}
		});	
	});
}

function monitorRoom(roomid)
{
	var socket = net.connect(PORT, HOST, function() {
		login(socket, 'visitor1234567', '1234567890123456');
	});
	
	setInterval(function() {
		send(socket, 'type@=keeplive/tick@=70/'); //send keep alive message repeatly
	}, 50000);
	
	socket.on('data', function(data) {
		//data is a Buffer here
		if (data.indexOf('type@=loginres') >= 0) {
			getGroupId(roomid, function(gid) {
				console.log('gid of room[' + roomid +'] is ' + gid)
				send(socket, 'type@=joingroup/rid@=' + roomid + '/gid@=' + gid + '/');
			});
		} else if (data.indexOf('type@=chatmessage') >= 0) {
			var msg = data.toString();
			var snick = msg.match(/snick@=(.*?)\//g)[0].replace('snick@=', '');
			var content = msg.match(/content@=(.*?)\//g)[0].replace('content@=', '');
			
			snick = snick.substring(0, snick.length - 1);
			content = content.substring(0, content.length - 1);
			console.log(snick + ': ' + content);// 弹幕
		} else if (data.indexOf('type@=userenter') >= 0 ||
			data.indexOf('type@=keeplive') >= 0 ||
			data.indexOf('type@=dgn/gfid@=131') >= 0 ||
			data.indexOf('type@=blackres') >= 0 ||
			data.indexOf('type@=dgn/gfid@=129') >= 0 || 
			data.indexOf('type@=upgrade') >= 0 ||
			data.indexOf('type@=ranklist') >= 0 ||
			data.indexOf('type@=onlinegift') >= 0) {
			//没用的消息
		} else if (data.indexOf('type@=spbc') >= 0) {
			var drid = data.toString().match(/drid@=(.*?)\//g)[0].replace('drid@=', '');
			drid = drid.substring(0, drid.length - 1);
			
			console.log('rocket! room id:' + drid);
		} else {
			console.log(data.toString());	//在这里显示其它类型的消息
		}
	});
}

monitorRoom('');
以前我们是做视频聊天站的。2014年8月-9月我们一个月的时间抄了一个斗鱼,没错是整个站。开始的时候由于内容上的高度重合,比如蜡笔小新、RM等,房间也没有人气,我将斗鱼某房间的弹幕扒下来转播到对应内容的房间中,并过滤掉一些关键字,足以以假乱真。虽然平台最终半死不活,且『技术本身并不可耻』,然而还是匿了。

技术核心我看大家都没答到一点,就是反编译flash播放器,因为所有与socket相关的实现都在flash里,反编译出源码后的逻辑、协议,一目了然,然后用python实现出来,很简单,不上码了。

这问题应该是纯技术交流吧,我这么想。 斗鱼已经开放第三方接口了,获取弹幕的流程变得简单了许多:
《斗鱼弹幕服务器第三方接入协议v1.4.1》发布信息 斗鱼开发者论坛
以下为大概流程
第三方接入弹幕服务器:
IP 地址:openbarrage.douyutv.com 端口:8601
1.客户端向服务器端发送登录请求,格式为;
type@=loginreq/roomid@=****/
Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn