对某音直播间弹幕相关数据采集

3,950 阅读5分钟

声明:

本人在此郑重声明,本文章中的所有内容仅供学习交流使用,已对抓包内容、敏感网址和数据接口做了脱敏处理。请勿将这些内容用于商业和非法用途,否则将承担所有法律后果。如果本文章内容侵犯了您的权益,请立即联系我,我将及时予以删除!

目标地址

某音东方甄选直播间

开始逆向分析

  1. 浏览器打开网页版抖音直播间,F12进入控制台
  2. 分析弹幕数据: 弹幕这种数据它的交互方式是这样,客户端可以发送弹幕数据,服务器端也可以推送所有人的弹幕数据(浏览器也不需要数据刷新,不需要向服务器重新发起请求),像我们通常用的http请求,只能做到客户端向服务器发送一次请求后,浏览器把相应数据给客户端; 我们可以看到抖音这里的ws这一块,数据一直在实时变动,但是地址只有一直是一个,这里也就是我们的弹幕数据,也就是说,弹幕是在同一个websocket连接下进行数据传输的
  3. 这里先简单的介绍下websocket WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它可以在客户端和服务器之间建立一种实时的双向通信机制。WebSocket 协议使得客户端和服务器之间可以持续地交换数据,而无需进行任何轮询或定时拉取。

相对于传统的 HTTP 协议,WebSocket 协议的优势在于:

实时性:WebSocket 协议建立的连接是全双工的,服务器可以主动向客户端发送数据,而客户端也可以随时向服务器发送数据,因此可以实现实时通信。

轻量级:WebSocket 协议的头部信息相对较小,因此数据传输的开销比较小。

节省带宽:由于 WebSocket 可以通过单个连接传输多个消息,因此相对于 HTTP 协议而言,可以节省带宽。

更好的跨域支持:WebSocket 协议支持跨域通信,因此可以更好地满足现代 Web 应用程序的需要。

WebSocket 协议支持多种编程语言和平台,可以用于构建实时聊天室、在线游戏、在线教育等需要实时通信的应用程序。

  1. 抓取弹幕的数据到本地主要的思路如下:

弹幕在浏览器显示的数据是在后台发送给浏览器的,可以看到这些数据是二进制数据,红的代表后台返回,绿色的代表浏览器请求后台;

既然后台是二进制的数据,js必须解析得到明文才能展示浏览器给用户,所以我们就需要找到解密二进制为明文的这个地方

  1. 寻找接收后台数据的位置

js与后台websocket通信,常规代码如下:

const socket = new WebSocket('ws://localhost:8080');   // 创建 WebSocket 实例

socket.addEventListener('open'function (event) {
  socket.send('Hello Server!');  // 在连接建立后发送消息
});

socket.addEventListener('message'function (event) {
  console.log('Message from server:', event.data);  // 处理接收到的消息
});

socket.addEventListener('close'function (event) {
  console.log('Connection closed');   // 处理连接关闭事件
});

我们可以搜关键字new WebSocketopen, message等这里的; 我们搜索下new WebSocket,只有2处,都点进入打上断点,刷新页面 断点成功断住,看了一眼抖音这块代码,与上面的js websocket通讯模板是极其相似

      class m {
            constructor(e) {
                const t = u(e);
                "undefined" != typeof WebSocket && (this.socket = new WebSocket(t),
                this.socket.binaryType = "arraybuffer")
            }
            onError(e) {
                this.socket.addEventListener("error", e)
            }
            onMessage(e) {
                this.socket.addEventListener("message", e)
            }
            onOpen(e) {
                this.socket.addEventListener("open", e)
            }
            onClose(e) {
                this.socket.addEventListener("close", e)
            }
        }

onMessage(e)就是接收到后台数据,然后进行解析处理,因此我们重点看这个里面的e 6. 寻找解密后台返回数据的位置 在onMessage(e)方法内打上断点,F8继续执行断点,进入onMessage中,光标放到参数e,它也是一个方法,点击进去它的实现位置 进入到onMessage方法中,里面调用是bindClientMessage

this.client.onMessage((e=>{
   this.bindClientMessage(e, i)
}

在点击进入到bindClientMessage方法中 代码如下:

bindClientMessage(e, t)
{
    var i;
    if (this.client) {
        const o = c.PushFrame.deserializeBinary(e.data)
            , a = r.Response.deserializeBinary(function (e) {
            for (const t of Object.values(e.getHeadersList()))
                if ("compress_type" === t.getKey() && "gzip" === t.getValue())
                    return !0;
            return !1
        }(o) ? (0,
            n.ec)(o.getPayload()) : o.getPayload_asU8());
        this.setHeaderListToObject(null !== (i = o.getHeadersList()) && void 0 !== i ? i : []);
        if (a.getNeedAck()) {
            let e = a.getInternalExt()
                , t = a.getCursor();
            (o.getHeadersList() || []).forEach((i => {
                    "im-internal_ext" === (null == i ? void 0 : i.getKey()) && (e = null == i ? void 0 : i.getValue()),
                    "im-cursor" === (null == i ? void 0 : i.getKey()) && (t = null == i ? void 0 : i.getValue())
                }
            )),
                this.internalExt = e,
                this.cursor = t;
            const i = new c.PushFrame;
            i.setPayloadType("ack"),
                i.setPayload(function (e) {
                    const t = [];
                    for (const i of e) {
                        const e = i.charCodeAt(0);
                        e < 128 ? t.push(e) : e < 2048 ? (t.push(192 + (e >> 6)),
                            t.push(128 + (63 & e))) : e < 65536 && (t.push(224 + (e >> 12)),
                            t.push(128 + (e >> 6 & 63)),
                            t.push(128 + (63 & e)))
                    }
                    return Uint8Array.from(t)
                }(e)),
                i.setLogid(o.getLogid()),
                this.client.socket.send(i.serializeBinary())
        }
        if ("msg" === o.getPayloadType() && (this.info("fetchSocketServer socket response: ", (() => a.toObject())),
            this.emit(a)),
        "close" === o.getPayloadType())
            return t(new Error("close by payloadtype"))
    }
}
}

我们看到代码的这一行,有很明显的介绍fetchSocketServer socket response: 这个中文解释就是获取到socket服务器的相应 打印a.toObject()里面的payload就是后台返回的数据,不过是加密的,说明代码往后走就会有解析数据payload的代码; 这里的emit(a)方法断点进入看下

emit(e){
    const t = e.getMessagesList();
    t.length && t.forEach((e => {
            const t = e.getMethod()
                , a = "RoomMessage" === t ? t : e.getMsgId();
            this.messageIdsForDistinct.has(a) || (this.messageIdsForDistinct.add(a),
                this.runAllEvents(t, e))
        }
    ))
}

再继续跟进去this.runAllEvents(t, e))

runAllEvents(e, t)
{
    var a;
    for (const [r, i] of this.eventsMap.entries()) {
        const a = this.messageModules[r];
        if (i && a && this.isCorrectEventName(r, e)) {
            const o = t.getPayload_asU8()
                , s = a.deserializeBinary(o);
            return this.info(`emit Message Type: ${e} ${r}`),
                this.info("emit Message Payload:", (() => s.toObject())),
                void i.forEach((e => {
                        e(s, t, o)
                    }
                ))
        }
    }
    const o = null !== (a = this.messageNotUseCache.get(e)) && void 0 !== a ? a : [];
    o.length > r.ej && o.shift(),
        o.push(t),
        this.messageNotUseCache.set(e, o)
}

s.toObject()这里就是获取到明文数据的地方

这里在多说一点:抖音返回websocket返回的数据类型有很多,可以到method="WebcastGiftMessage" 其实还有很多很多,这里我列举了一些:

'WebcastMemberMessage': 代表谁进入了直播间
'WebcastLikeMessage': 代表为主播点赞了
'WebcastRoomStatsMessage': 代表当前房间的状态人数
'WebcastChatMessage': 代表当前与主播的互动消息
'WebcastRoomMessage''WebcastGiftMessage': 代表谁送了礼物
'WebcastUpdateFanTicketMessage': XXX
"WebcastLiveShoppingMessage": XXX
WebcastLiveEcomGeneralMessage
WebcastProductChangeMessage
       

我们想要什么数据的话,可以跟进method来判断,然后在获取对应的数据

  1. 开始注入我们的代码:

浏览器启用本地替换,注入我们代码后,保存并替换,刷新浏览器,让我们注入的代码生效

  1. 启动我们本地服务器websocket服务端进行接收转发的明文数据

到这里可以看到东方甄选直播间的弹幕数据被我们劫持下来了,当然你也可以获取其他数据