用java爬取斗鱼弹幕

5 篇文章 0 订阅
2 篇文章 0 订阅

爬取斗鱼弹幕大致分为以下几个主要步骤
代码地址: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()) {
    @Override
    public void onOpen(ServerHandshake serverHandshake) {
System.out.println("打开连接");
}
    @Override
    public void onMessage(String message) {
System.out.println(message);
}
    @Override
    public void onClose(int i, String s, boolean b) {
System.out.println("连接关闭");
}
    @Override
    public 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 {
    @Test
    public void crawl() throws URISyntaxException {
        WebSocketClient websocketclient = new WebSocketClient(new URI("wss://danmuproxy.douyu.com:8506/"
        ), new Draft_6455()) {
            @Override
            public 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("打开连接");
            }

            @Override
            public 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);
            }
            @Override
            public void onClose(int i, String s, boolean b) {
                System.out.println("连接关闭");
            }
            @Override
            public 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)
        };
    }
}

三、未完待续

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值