因为平时无聊的时候喜欢看下斗鱼之类的直播平台,所以顺便会思考下斗鱼是怎么让获取弹幕的。这里记录下过程。
抓包分析
了解到斗鱼有个专门的弹幕服务器,http://danmu.douyutv.com, 端口的话,8601,8602,12601,12602都是弹幕端口。
一开始我没完全看懂这个包内容具体的意思,参考了下知乎的答案后才明白。
这个内容分为4部分。
- 第一部分用黑色框住的,是后3个部分内容的总字节长度。这里是5b,即后3个部分内容的字节长度为91,另外第一部分自己会占4个字节,所以这个包的data长度为95,即橙色框部分。
- 第二部分用红色框住的,斗鱼的规则是这个内容和第一部分一样。占4个字节。
- 第三部分用绿色框住的,斗鱼的规则是向服务器发送的包,这部分固定为0xb1,0x02,0x00,0x00 这4个字节,从服务器发回来的包固定为0xb2,0x02,0x00,0x00 这4个字节
- 最后一部分就是发送的具体内容了。
抓包得知,如果需要连上斗鱼的弹幕服务器,需要让弹幕服务器发送2个包,
- 一种是上图的loginreq包。
“type@=loginreq/username@=auto_KRLJbE8mZM/password@=1234567890123456/roomid@=/“
这个账号密码随便填都行,roomid即房间号了,如果房间号是昵称的话,需要转换成id。 - 另一种是joingroup包。
“type@=joingroup/rid@=/gid@=/“
因为每个房间因为人数原因,人数多房间的会将弹幕分流,分成几个group。这里需要选择进入哪个group,我最后实验看不同的group的弹幕其实是一样的,所以它是为了把用户均衡下,分到不同的group,避免都连上同一个而负载很高。
然后就可以获得弹幕服务器发回来的弹幕了,如下图:
抓取弹幕
然后开始来抓取弹幕,我这里用Java来抓取,这里截取部分主要流程代码。
先获得和弹幕服务器的socket连接,我这里把地址和端口写到配置文件了。1
socket = new Socket(Config.getAddress(),Integer.parseInt(Config.getPort()));
然后向服务器发送那2个包。1
2sendMessage("type@=loginreq/username@=auto_KRLJbE8mZM/password@=1234567890123456/roomid@=" + roomId + "/");
sendMessage("type@=joingroup/rid@=" + roomId + "/gid@=" + groupId + "/");
这里roomId如果是昵称的话,需要转成id,我是去房间通过feedback_report_button这个id来得到的,也有其他方式。1
2
3
4
5
6
7String html = httpClient.doGet("http://www.douyutv.com/" + roomName);
Document doc = Jsoup.parse(html);
Element element = doc.select("#feedback_report_button").first();
String href = element.attr("href");
if (href != null) {
return href.split("=")[1];
}
gid的获取,我参考的github的一个项目。
- 首先去对应的斗鱼房间抓取页面,取到server_config的字符串
- 进行URL解码,得到一组ip, port
- 向其中任意的一个ip,port发送loginreq包,这个loginreq包和之前的不一样。
- 这个loginreq包中,devid是随机UUID,需要替换掉’-‘,字母全大写
rt是时间戳,这里必须是以秒为单位
ver是版本号
vk我不懂是什么意思,我参考的那个项目是这么做的: rt + 一串固定字符串 + devid的MD5编码 - 发送loginreq后会收到type@=setmsgroup包,里面就有gid
1 | public void sendReq() throws IOException { |
然后就可以从socket的inputstream来获取弹幕了,接着对获取的字符串进行解析,我这里只解析了弹幕,没有解析礼物那些。为了方便,我直接用log4j来存储弹幕了。1
2
3
4
5
6
7
8
9public void parseResponse(String response) {
String REGEX ="type@=chatmessage/.*/content@=(.*)/snick@=(.*?)/";
Pattern pattern = Pattern.compile(REGEX);
Matcher matcher = pattern.matcher(response);
if (matcher.find()) {
logger.info(matcher.group(2) + ": " + matcher.group(1));
}
}
然后,还需要不停的向服务器发送心跳包,否则服务器过段时间会关闭我们的连接。我这里每隔60秒发送一个心跳包。1
sendMessage("type@=keeplive/tick@=70/");
最后,贴下获取的弹幕图:
我抓取弹幕的过程大概就是这样。
代码地址: 代码参考