爬取斗鱼弹幕大致分为以下几个主要步骤
代码地址:https://github.com/Recru1t000/douyuCrawler

  1. 连接websocket
  2. 发送登录请求、入组请求、发送心跳
  3. 接收并分析websocket发送过来的信息
  4. 将建立弹幕的数据库表
  5. .将信息写入数据库

一、连接websocket

斗鱼弹幕推送是通过websocket进行的消息推送。

Websocket简介:WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。依靠这种技术可以实现客户端和服务器端的长连接,双向实时通信。

那么想要获取斗鱼所推送的弹幕信息第一步就应该连接上斗鱼弹幕推送的websocket。

我所运用的是通过maven获取的Java-WebSocket的1.5.1版本进行的连接的。以下为maven中的设置。

<dependency><groupId>org.java-websocket</groupId><artifactId>Java-WebSocket</artifactId><version>1.5.1</version>
</dependency>

首先要新建一个WebSocketClient对象,需要赋予WebSocketClient一个新的URI对象,一个新的Draft对象,并且重写onOpen、onMessage、onClose、onError四种方法。

  • URI:统一资源标识符,是一类通用的资源标识符,URL实际上是URI的子集,URI是一个通用的概念,URI有两种形式来实现对资源的统一标识:URL和URN;也就是说我们输入只要在URI()对象中正常输入我们想要连接的url即可。斗鱼弹幕推送的url为wss://danmuproxy.douyu.com:8506
  • Draft:为websocket规范RFC6455,包中自带的为Draft_6455(),但是我在运行时发生了一些错误,详情请看https://blog.csdn.net/weixin_43659561/article/details/107160240。如果发生了一下错误请按此修改,新建自己的Draft的对象来解析收到的字节流。
  • onOpen方法:在建立连接时运行
  • onMessage方法:在收到消息时运行
  • onClose方法:在连接关闭时运行
  • onError方法:在发生错误时运行

接下来运行websocketclient.connect(),看到打开连接就表示我们已经成功连接到服务器。以下为java代码。

 WebSocketClient websocketclient = new WebSocketClient(new URI("wss://danmuproxy.douyu.com:8506/"
), new Draft_6455()) {@Overridepublic void onOpen(ServerHandshake serverHandshake) {System.out.println("打开连接");
}@Overridepublic void onMessage(String message) {System.out.println(message);
}@Overridepublic void onClose(int i, String s, boolean b) {System.out.println("连接关闭");
}@Overridepublic void onError(Exception e) {System.out.println("发生错误");
}};

二、发送登录请求、入组请求、发送心跳

在成功连接服务器后,我们却还不能收到服务器所推送的信息。这是因为我们还需要发送登录请求和入组请求,详情请看斗鱼开发协议https://open.douyu.com/source/api/63

斗鱼消息协议格式如下所示,其中字段说明如下:

  • 消息长度:4 字节小端整数,表示整条消息(包括自身)长度(字节数)。
  • 消息长度出现两遍,二者相同。
  • 消息类型:2 字节小端整数,表示消息类型。取值如下:
  • 689 客户端发送给弹幕服务器的文本格式数据
  • 690 弹幕服务器发送给客户端的文本格式数据。
  • 加密字段:暂时未用,默认为 0。保留字段:暂时未用,默认为 0。
  • 数据部分:斗鱼独创序列化文本数据,结尾必须为‘\0’。详细序列化、反序列化算法见下节。(所有协议内容均为 UTF-8 编码)

在发送登录请求和入组请求前,需要将发送信息变成符合斗鱼协议的格式。

  1. 首先添加两次4字节小端整数的消息长度,为要发送的数据内容的长度+9(头部结构为(4B消息长度、2B消息、1B加密字段、1B保留字段),尾部为(1B的’\0’))。
  2. 接下来将689转化为四字节的小端整数(因为加密字段和保留字段都为0,689转化后也为后两个字节也0,就不用特意去用0转化)。
  3. 前8B的消息头已经转化完成,下面只要将所发送的信息字节化即可,最后加上数据结尾的’\0’即可。 以下为进入房间号为123456的内容。

登录请求内容:type@=loginreq/roomid@=123456/
分组请求内容:type@=joingroup/rid@=123456/gid@=-9999/
下面为代码:

public byte[] login(String roomId) throws IOException {String message = "type@=loginreq/roomid@=123456/";return douyuRequestEncode(message);
}
//加入群组请求
public byte[] joinGroup(String roomId) throws IOException{String message ="type@=joingroup/rid@=123456/gid@=-9999/";return douyuRequestEncode(message);
}
//心跳
public byte[] heartBeat() throws IOException{String message = "type@=mrkl/";return douyuRequestEncode(message);
}//将传入的数据变成符合斗鱼协议要求的字节流返回public byte[] douyuRequestEncode(String message) throws IOException {int dataLen1 = message.length() + 9;//4 字节小端整数,表示整条消息(包括自身)长度(字节数)。int dataLen2 = message.length() + 9;//消息长度出现两遍,二者相同。int send = 689;//689 客户端发送给弹幕服务器的文本格式数据,暂时未用,默认为 0。保留字段:暂时未用,默认为 0。byte[] msgBytes= message.getBytes(StandardCharsets.UTF_8);int end = 0;byte[] endBytes = new byte[1];endBytes[0] = (byte) (end  & 0xFF);;//结尾必须为‘\0’。详细序列化、反序列化ByteArrayOutputStream bytes = new ByteArrayOutputStream();bytes.write(intToBytesLittle(dataLen1));bytes.write(intToBytesLittle(dataLen2));bytes.write(intToBytesLittle(send));bytes.write(msgBytes);bytes.write(endBytes);//返回byte[]return bytes.toByteArray();
}//将整形转化为4位小端字节流
public  byte[] intToBytesLittle(int value) {return new byte [] {(byte) (value & 0xFF),(byte) ((value >> 8) & 0xFF),(byte) ((value >> 16) & 0xFF),(byte) ((value >> 24) & 0xFF)};}

完成这步后,在websocket中发送完消息理论应该是可以收到信息的,但却出现了两个问题onMessage中的System.out.println(message);却没有打印出信息。并且在一段时间后连接就会自动断开。
第一个问题:因为我们接受到的斗鱼推送信息是以字节流方式存在的,所以直接用重写的onMessage(String message)方法是无法输出信息的,点开websocketClient的源码,我们看到public void onMessage( ByteBuffer bytes ) {//To overwrite}这样一条代码。我们应该在新建websocket时重写该方法,使我们能够正常输出String类型的消息。
第二个问题:想要保持斗鱼弹幕推送的长连接需要定时发送心跳,心跳的内容为:type@=mrkl/。斗鱼的要求是每隔45秒发送心跳。所以在创建连接时直接并发一个每隔45秒发送一次消息的线程。
基础可运行代码如下所示。

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.handshake.ServerHandshake;
import org.junit.Test;import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
public class test {@Testpublic void crawl() throws URISyntaxException {WebSocketClient websocketclient = new WebSocketClient(new URI("wss://danmuproxy.douyu.com:8506/"), new Draft_6455()) {@Overridepublic void onOpen(ServerHandshake handshakedata) {try {send(login());//发送登录请求send(joinGroup());//发送加入群组请求send(heartBeat());//发送心跳Thread heartBeatThread = new Thread(() -> {while (true){try {send(heartBeat());System.out.println("发送心跳");Thread.sleep(45000);} catch (IOException | InterruptedException e) {e.printStackTrace();}}});heartBeatThread.start();} catch (IOException e) {e.printStackTrace();}System.out.println("打开连接");}@Overridepublic void onMessage(String message) {}public void onMessage(ByteBuffer bytes){Charset charset = StandardCharsets.UTF_8;CharBuffer charBuffer = charset.decode(bytes);String s = charBuffer.toString();System.out.println(s);}@Overridepublic void onClose(int i, String s, boolean b) {System.out.println("连接关闭");}@Overridepublic void onError(Exception e) {System.out.println("发生错误");}};websocketclient.run();}public byte[] login() throws IOException {String message = "type@=loginreq/roomid@=5189167/";return douyuRequestEncode(message);}//加入群组请求public byte[] joinGroup() throws IOException{String message ="type@=joingroup/rid@=123456/gid@=-9999/";return douyuRequestEncode(message);}//心跳public byte[] heartBeat() throws IOException{String message = "type@=mrkl/";return douyuRequestEncode(message);}//将传入的数据变成符合斗鱼协议要求的字节流返回public byte[] douyuRequestEncode(String message) throws IOException {int dataLen1 = message.length() + 9;//4 字节小端整数,表示整条消息(包括自身)长度(字节数)。int dataLen2 = message.length() + 9;//消息长度出现两遍,二者相同。int send = 689;//689 客户端发送给弹幕服务器的文本格式数据,暂时未用,默认为 0。保留字段:暂时未用,默认为 0。byte[] msgBytes= message.getBytes(StandardCharsets.UTF_8);int end = 0;byte[] endBytes = new byte[1];endBytes[0] = (byte) (end  & 0xFF);;//结尾必须为‘\0’。详细序列化、反序列化ByteArrayOutputStream bytes = new ByteArrayOutputStream();bytes.write(intToBytesLittle(dataLen1));bytes.write(intToBytesLittle(dataLen2));bytes.write(intToBytesLittle(send));bytes.write(msgBytes);bytes.write(endBytes);//返回byte[]return bytes.toByteArray();}//将整形转化为4位小端字节流public  byte[] intToBytesLittle(int value) {return new byte [] {(byte) (value & 0xFF),(byte) ((value >> 8) & 0xFF),(byte) ((value >> 16) & 0xFF),(byte) ((value >> 24) & 0xFF)};}
}

三、未完待续

用java爬取斗鱼弹幕相关推荐

  1. python爬斗鱼直播_Python爬虫:利用API实时爬取斗鱼弹幕

    原标题:Python爬虫:利用API实时爬取斗鱼弹幕 这些天一直想做一个斗鱼爬取弹幕,但是一直考试时间不够,而且这个斗鱼的api接口虽然开放了但是我在github上没有找到可以完美实现连接.我看了好多 ...

  2. java爬取斗鱼:与虎牙对比

    看了下斗鱼的基础页面,感觉和虎牙的有点像,但是实际上确有点不同. 首先想要获取页数,看了斗鱼的html页面,发现却不显示页面. 这是因为斗鱼把界面藏在了js页面中,谷歌浏览器的右击检查 那么接下来就是 ...

  3. 实时爬取斗鱼直播时的弹幕消息

    最近想爬取实时消息,上网查了下,所以爬取斗鱼直播的弹幕消息做了下练习,这个开源的代码有很多,但是具体是怎么爬取到的还是要仔细地研究下.想爬取斗鱼的弹幕消息,按照常用的做法是打开网页用抓包工具抓包,但是 ...

  4. Java爬取B站弹幕 —— Python云图Wordcloud生成弹幕词云

    一 . Java爬取B站弹幕 弹幕的存储位置 如何通过B站视频AV号找到弹幕对应的xml文件号 首先爬取视频网页,将对应视频网页源码获得 就可以找到该视频的av号aid=8678034 还有弹幕序号, ...

  5. python 爬取直播弹幕视频_调用斗鱼API爬取直播间弹幕信息(用户昵称及弹幕内容)...

    调用斗鱼API爬取直播间弹幕信息(用户昵称及弹幕内容) 查看<斗鱼弹幕服务器第三方接入协议v1.4.1>,了解斗鱼API的使用方法,即如何连接斗鱼弹幕服务器.维持连接及获取弹幕信息 Pyt ...

  6. 使用selenium自动爬取斗鱼直播平台的所有房间信息

    使用selenium自动爬取斗鱼直播平台的所有房间信息 文章目录 使用selenium自动爬取斗鱼直播平台的所有房间信息 使用selenium实现动态页面模拟点击 什么是selenium? selen ...

  7. java爬取论坛信息_Java爬取校内论坛新帖

    Java爬取校内论坛新帖 为了保持消息灵通,博主没事会上上校内论坛看看新帖,作为爬虫爱好者,博主萌生了写个爬虫自动下载的想法. 嗯,这次就选Java. 第三方库准备 Jsoup Jsoup是一款比较好 ...

  8. 利用斗鱼api学习爬取斗鱼直播间信息

    python爬取斗鱼房间的弹幕 斗鱼弹幕服务api:https://open.douyu.com/source/api/63 基本步骤: 连接斗鱼api服务器 构造登录请求 进入房间并构造获取弹幕请求 ...

  9. Java爬取解析去哪儿景点信息

    前言:这两周在做 Web 课的大作业,顺便琢磨了一下如何使用 Java 从网上获取一些数据,现在写这篇博客记录一下. PS:这里仅限交流学习用,如利用代码进行恶意攻击他网站,和作者无关!!! Java ...

  10. 爬虫之selenium爬取斗鱼网站

    爬虫之selenium爬取斗鱼网站 示例代码: from selenium import webdriver import timeclass Douyu(object):def __init__(s ...

最新文章

  1. 留的住叫做幸福. 流逝的叫做遗憾
  2. Python中双冒号的作用[::]
  3. boost::iostreams::grep_filter用法的测试程序
  4. Anti-Forgery Request Recipes For ASP.NET MVC And AJAX 防伪验证,防伪请求
  5. Series和DataFrame、相关性及NaN处理
  6. python-虚拟环境的作用
  7. 201621123031 《Java程序设计》第13周学习总结
  8. TCP和UDP协议的特点和区别详解
  9. vscode 调试找不到对应的python模块(根本,简单,有效)
  10. STM32串口波特率计算问题和常用波特率
  11. Spring的配置项aspectj-autoproxy
  12. 指标梳理对成功实施BI项目有什么意义
  13. 在家远程控制(远程桌面)到公司的电脑
  14. Coursera | Introduction to Data Science in Python(University of Michigan)| Assignment2
  15. 自己写操作系统学习总结
  16. 【论文笔记】FC-EF,FC-Siam-conc,FC-Siam-diff:用于变化检测的全卷积孪生神经网络
  17. iTextSharp 纸张横向
  18. 游戏中的基本数学概念-矢量与点(转载)
  19. 性能指标TP50、TP90、TP99、TP999解读和学习
  20. Allegro文件转AD

热门文章

  1. vue项目获取视频封面展示在页面上
  2. 老徐和阿珍的故事:强引用、软引用、弱引用、虚引用,傻傻分不清楚
  3. 黑客红客骇客红客蓝客飞客是什么?有什么区别?(学习资料)
  4. Jieba中文分词 (二) ——词性标注与关键词提取
  5. Docsify支持Markdown多种流程图
  6. 解决vue报错:Avoided redundant navigation to current location
  7. 服务器搭建ftp共享文件夹,设置共享文件夹或者搭建FTP服务器。
  8. dropbear ssh服务移植到arm9开发板
  9. Django中使用163邮箱发送邮件
  10. Git-git命令:全局设置用户名邮箱配置