[JAVA版本] 最新websocket获取B站直播弹幕

您所在的位置:网站首页 当前弹幕列表 [JAVA版本] 最新websocket获取B站直播弹幕

[JAVA版本] 最新websocket获取B站直播弹幕

2024-07-17 22:37| 来源: 网络整理| 查看: 265

文章目录 一、教程1、相关依赖2、获取Cookie3、自定义GET方法,让每次请求都带上cookie3、定义获取弹幕服务器信息 和 使用4、定义websocket监听类,处理监听到的事件4.1先新建几个常量,后面方便使用。4.2定义WebsocketListener 监听类定义 封包 / 解包 方法定义WebsocketListener 封包方法定义 WebsocketListener 创建鉴权包方法定义 WebsocketListener 创建心跳包方法定义 WebsocketListener 的解包方法 4.3 实现WebsocketListener 的 onOpen方法。4.4 实现WebsocketListener 的 onMessage方法,接收服务器消息 二、我的代码实现1、创建BiliRequest.java2、创建WebsocketListener.java 监听器3、使用

一、教程

如果只想要代码实现,直接看第二部分。

1、相关依赖

fastjson2用于解析JSON字符串,可自行替换成别的框架。 hutool-core用于解压zip数据,可自行替换成别的框架。

com.alibaba.fastjson2 fastjson2 2.0.40 cn.hutool hutool-core 5.8.21 2、获取Cookie

2023年9月B站如果不登录,获取到的弹幕消息是经过脱敏的,获取不到用户名和用户ID。 获取方式: 电脑浏览器登录B站,按F12去网络请求里把B站Cookie值全部复制出来。

3、自定义GET方法,让每次请求都带上cookie private String get(String url) throws IOException, NoSuchAlgorithmException, InvalidKeyException { String bodyStr = JSONObject.toJSONString(dataMap); HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection(); con.setRequestMethod(method.code); /**----------设置请求头------------------------------------------------------------------**/ con.setRequestProperty("User-Agent", "Mozilla/5.0"); con.setRequestProperty("Accept", "application/json");application/json。 con.setRequestProperty("Content-Type", "application/json"); con.setRequestProperty("Cookie", cookie); // 获取响应结果 try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8))){ // 返回响应结果 return bufferedReader.lines().collect(Collectors.joining("\n")); } } 3、定义获取弹幕服务器信息 和 使用

该方法可以获得弹幕服务器信息和检验你是否登录的token。

public JSONObject getDanmuInfoData(int roomid) throws IOException, NoSuchAlgorithmException, InvalidKeyException { //获取直播间真实ID ,因为存在短ID String result = get("https://api.live.bilibili.com/room/v1/Room/room_init?id="+roomid); roomid = JSONObject.parseObject(result).getJSONObject("data").getIntValue("room_id");; //获取弹幕服务信息 result = get("https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo?type=0&id="+roomid); return JSONObject.parseObject(getDanmuInfo(result )).getJSONObject("data"); }

使用:

//获取弹幕服务器信息 JSONObject danmuInfoData = getDanmuInfoData(直播间ID); //获取完整弹幕信息的Token String token = danmuInfoData.getString("token"); //服务器节点列表 JSONArray hostList = danmuInfoData.getJSONArray("host_list"); //选一个服务器节点 JSONObject host = hostList.getJSONObject(0); //弹幕服务器地址 String wsUrl = String.format("ws://%s:%s/sub", host.getString("host"), host.getString("ws_port")); 4、定义websocket监听类,处理监听到的事件 4.1先新建几个常量,后面方便使用。 public interface Opt{ short HEARTBEAT = 2;// 客户端发送的心跳包(30秒发送一次) short HEARTBEAT_REPLY = 3;// 服务器收到心跳包的回复 人气值,数据不是JSON,是4字节整数 short SEND_SMS_REPLY = 5;// 服务器推送的弹幕消息包 short AUTH = 7;//客户端发送的鉴权包(客户端发送的第一个包) short AUTH_REPLY = 8;//服务器收到鉴权包后的回复 } public interface Version{ short NORMAL = 0;//Body实际发送的数据——普通JSON数据 short ZIP = 2; //Body中是经过压缩后的数据,请使用zlib解压,然后按照Proto协议去解析。 } 4.2定义WebsocketListener 监听类

定义变量cookie、roomid、token,需要用他们生成鉴权包。

@ClientEndpoint public class WebsocketListener { private String cookie; private int roomid; private String token; public WebsocketListener(String cookie, int roomid, String token) { this.cookie = cookie; this.roomid = roomid; this.token = token; } private Session session; @OnOpen public void onOpen(Session session) throws IOException { } @OnMessage public void onMessage(ByteBuffer byteBuffer) { } @OnClose public void onClose(Session session, CloseReason closeReason) { System.out.println("服务器断开: " + closeReason); } @OnError public void onError(Session session, Throwable t) { t.printStackTrace(); } 定义 封包 / 解包 方法

Websocket发送和接收时使用。

定义WebsocketListener 封包方法 public static byte[] pack(String jsonStr, @NonNull short code) throws IOException { byte[] contentBytes = new byte[0]; if(Opt.AUTH == code){ contentBytes = jsonStr.getBytes(); } try(ByteArrayOutputStream data = new ByteArrayOutputStream(); DataOutputStream stream = new DataOutputStream(data)){ stream.writeInt(contentBytes.length + 16);//封包总大小 stream.writeShort(16);//头部长度 header的长度,固定为16 stream.writeShort(Version.NORMAL); stream.writeInt(code);//操作码(封包类型) stream.writeInt(1);//保留字段,可以忽略。 if(Opt.AUTH == code){ stream.writeBytes(jsonStr); } return data.toByteArray(); } } 定义 WebsocketListener 创建鉴权包方法

用于鉴权。 这里就需要用到变量cookie、roomid、token。

public byte[] generateAuthPack(String cookie,int roomid, String token) throws IOException { JSONObject jo = new JSONObject(); Arrays.stream(cookie.split(";")).forEach(c ->{ if(c.trim().startsWith("DedeUserID=")){ jo.put("uid", Long.valueOf(c.split("=")[1])); }else if(c.trim().startsWith("buvid3=")){ jo.put("buvid", c.split("=")[1]); } }); jo.put("roomid", roomid); jo.put("protover", 1); jo.put("platform", "web"); jo.put("type", 2); jo.put("key", token); return pack(jo.toString(), Opt.AUTH); } 定义 WebsocketListener 创建心跳包方法

用于维持服务连接。

public static byte[] generateHeartBeatPack() throws IOException { return pack(null, Opt.HEARTBEAT); } 定义 WebsocketListener 的解包方法

用于解析服务器返回的消息。 在方法内解析完消息后,可以在 //todo处自定义方法 或 处理器去处理弹幕消息;

public static void unpack(ByteBuffer byteBuffer){ int packageLen = byteBuffer.getInt(); short headLength = byteBuffer.getShort(); short protVer = byteBuffer.getShort(); int optCode = byteBuffer.getInt(); int sequence = byteBuffer.getInt(); if(Opt.HEARTBEAT_REPLY == optCode){ System.out.println("这是服务器心跳回复"); } byte[] contentBytes = new byte[packageLen - headLength]; byteBuffer.get(contentBytes); //如果是zip包就进行解包 if(Version.ZIP == protVer){ unpack(ByteBuffer.wrap(ZipUtil.unZlib(contentBytes))); return; } String content = new String(contentBytes, StandardCharsets.UTF_8); if(Opt.AUTH_REPLY == optCode){ //返回{"code":0}表示成功 System.out.println("这是鉴权回复:"+content); } //真正的弹幕消息 if(Opt.SEND_SMS_REPLY == optCode){ System.out.println("真正的弹幕消息:"+content); //todo 自定义处理 } //只存在ZIP包解压时才有的情况 //如果byteBuffer游标 小于 byteBuffer大小,那就证明还有数据 if(byteBuffer.position() { try { ByteBuffer heartBeatPack = ByteBuffer.wrap(generateHeartBeatPack()); remote.sendBinary(heartBeatPack); } catch (IOException e) { throw new RuntimeException(e); } }, 0, 30, TimeUnit.SECONDS); } 4.4 实现WebsocketListener 的 onMessage方法,接收服务器消息

需要调用上面的解包方法。

@OnMessage public void onMessage(ByteBuffer byteBuffer) { //解包 unpack(byteBuffer); } 二、我的代码实现 1、创建BiliRequest.java import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import jakarta.websocket.ContainerProvider; import jakarta.websocket.DeploymentException; import jakarta.websocket.WebSocketContainer; import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Map; import java.util.stream.Collectors; public class BiliRequest { private String cookie; public BiliRequest(String cookie) { this.cookie = cookie; } /** * 获取直播间ID ,因为存在短ID */ private String getReadRoomId(int roomid) throws IOException, NoSuchAlgorithmException, InvalidKeyException { return get("https://api.live.bilibili.com/room/v1/Room/room_init?id="+roomid); } /**获得弹幕服务地址信息**/ private String getDanmuInfo(int roomid) throws IOException, NoSuchAlgorithmException, InvalidKeyException { return get("https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo?type=0&id="+roomid); } public JSONObject getDanmuInfoData(int roomid) throws IOException, NoSuchAlgorithmException, InvalidKeyException { JSONObject readRoomId = JSONObject.parseObject(getReadRoomId(roomid)); roomid = readRoomId.getJSONObject("data").getIntValue("room_id"); return JSONObject.parseObject(getDanmuInfo(roomid)).getJSONObject("data"); } enum Method{ GET("GET"),POST("POST"); public String code; Method(String code) { this.code = code; } } public String get(String url) throws IOException, NoSuchAlgorithmException, InvalidKeyException { return request(Method.GET,url,null); } private String request(Method method,String url, Map dataMap) throws IOException, NoSuchAlgorithmException, InvalidKeyException { String bodyStr = JSONObject.toJSONString(dataMap); HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection(); con.setRequestMethod(method.code); /**----------设置请求头------------------------------------------------------------------**/ con.setRequestProperty("User-Agent", "Mozilla/5.0"); con.setRequestProperty("Accept", "application/json"); con.setRequestProperty("Content-Type", "application/json"); con.setRequestProperty("Cookie", cookie); // 发送 POST 请求 if(Method.POST == method && null != dataMap && !dataMap.isEmpty()){ con.setDoOutput(true); try(DataOutputStream wr = new DataOutputStream(con.getOutputStream())) { wr.writeBytes(bodyStr); wr.flush(); } } // 获取响应结果 try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8))){ // 返回响应结果 return bufferedReader.lines().collect(Collectors.joining("\n")); } } } 2、创建WebsocketListener.java 监听器 import cn.hutool.core.util.ZipUtil; import com.alibaba.fastjson2.JSONObject; import jakarta.websocket.*; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @ClientEndpoint public class WebsocketListener { private String cookie; private int roomid; private String token; public WebsocketListener(String cookie, int roomid, String token) { this.cookie = cookie; this.roomid = roomid; this.token = token; } private Session session; @OnOpen public void onOpen(Session session) throws IOException { this.session = session; RemoteEndpoint.Async remote = session.getAsyncRemote(); //鉴权协议包 ByteBuffer authPack = ByteBuffer.wrap(generateAuthPack()); remote.sendBinary(authPack); //每30秒发送心跳包 ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); executorService.scheduleAtFixedRate(() -> { try { ByteBuffer heartBeatPack = ByteBuffer.wrap(generateHeartBeatPack()); remote.sendBinary(heartBeatPack); } catch (IOException e) { throw new RuntimeException(e); } }, 0, 30, TimeUnit.SECONDS); } @OnMessage public void onMessage(ByteBuffer byteBuffer) { //解包 unpack(byteBuffer); } @OnClose public void onClose(Session session, CloseReason closeReason) { System.out.println("断开连接: " + closeReason); } @OnError public void onError(Session session, Throwable t) { t.printStackTrace(); } public interface Opt{ short HEARTBEAT = 2;// 客户端发送的心跳包(30秒发送一次) short HEARTBEAT_REPLY = 3;// 服务器收到心跳包的回复 人气值,数据不是JSON,是4字节整数 short SEND_SMS_REPLY = 5;// 服务器推送的弹幕消息包 short AUTH = 7;//客户端发送的鉴权包(客户端发送的第一个包) short AUTH_REPLY = 8;//服务器收到鉴权包后的回复 } public interface Version{ short NORMAL = 0;//Body实际发送的数据——普通JSON数据 short ZIP = 2; //Body中是经过压缩后的数据,请使用zlib解压,然后按照Proto协议去解析。 } /** * 封包 * @param jsonStr 数据 * @param code 协议包类型 * @return * @throws IOException */ public static byte[] pack(String jsonStr, short code) throws IOException { byte[] contentBytes = new byte[0]; if(Opt.AUTH == code){ contentBytes = jsonStr.getBytes(); } try(ByteArrayOutputStream data = new ByteArrayOutputStream(); DataOutputStream stream = new DataOutputStream(data)){ stream.writeInt(contentBytes.length + 16);//封包总大小 stream.writeShort(16);//头部长度 header的长度,固定为16 stream.writeShort(Version.NORMAL); stream.writeInt(code);//操作码(封包类型) stream.writeInt(1);//sequence,可以取常数1 .保留字段,可以忽略。 if(Opt.AUTH == code){ stream.writeBytes(jsonStr); } return data.toByteArray(); } } /** * 生成认证包 * @return */ public byte[] generateAuthPack(String jsonStr) throws IOException { return pack(jsonStr, Opt.AUTH); }/** * 生成认证包-用于非官方开放API * @return */ public byte[] generateAuthPack() throws IOException { JSONObject jo = new JSONObject(); Arrays.stream(cookie.split(";")).forEach(c ->{ if(c.trim().startsWith("DedeUserID=")){ jo.put("uid", c.split("=")[1]); }else if(c.trim().startsWith("buvid3=")){ jo.put("buvid", c.split("=")[1]); } }); jo.put("roomid", String.valueOf(roomid)); jo.put("protover", Version.NORMAL); jo.put("platform", "web"); jo.put("type", 2); jo.put("key", token); return pack(jo.toString(), Opt.AUTH); } /** * 生成心跳包 * @return */ public static byte[] generateHeartBeatPack() throws IOException { return pack(null, Opt.HEARTBEAT); } /** * 解包 * @param byteBuffer * @return */ public static void unpack(ByteBuffer byteBuffer){ int packageLen = byteBuffer.getInt(); short headLength = byteBuffer.getShort(); short protVer = byteBuffer.getShort(); int optCode = byteBuffer.getInt(); int sequence = byteBuffer.getInt(); if(Opt.HEARTBEAT_REPLY == optCode){ System.out.println("这是服务器心跳回复"); } byte[] contentBytes = new byte[packageLen - headLength]; byteBuffer.get(contentBytes); //如果是zip包就进行解包 if(Version.ZIP == protVer){ unpack(ByteBuffer.wrap(ZipUtil.unZlib(contentBytes))); return; } String content = new String(contentBytes, StandardCharsets.UTF_8); if(Opt.AUTH_REPLY == optCode){ //返回{"code":0}表示成功 System.out.println("这是鉴权回复:"+content); } //真正的弹幕消息 if(Opt.SEND_SMS_REPLY == optCode){ System.out.println("真正的弹幕消息:"+content); // todo 自定义处理 } //只存在ZIP包解压时才有的情况 //如果byteBuffer游标 小于 byteBuffer大小,那就证明还有数据 if(byteBuffer.position()


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3