Websocket逆向与B站直播间弹幕爬取实战
介绍
- WebSocket是HTML5下一种新的协议(websocket协议本质上是一个基于tcp的协议)
- 它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯的目的
- Websocket是一个持久化的协议
- websocket约定了一个通信的规范,通过一个握手的机制,客户端和服务器之间能建立一个类似tcp的连接,从而方便它们之间的通信
- 在websocket出现之前,web交互一般是基于http协议的短连接或者长连接
- websocket是一种全新的协议,不属于http无状态协议,协议名为"ws"
- 和HTTP都是基于tcp的,都是可靠性传输协议,都是应用层协议
- WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息,但HTTP是单向的
- WebSocket是需要浏览器和服务器握手进行建立连接的,而http是浏览器发起向服务器的连接,服务器预先并不知道这个连接
JS实现
js websocket主要有以下几个方法:
//连接发生错误的回调方法
websocket.onerror = function () {
setMessageInnerHTML("WebSocket连接发生错误");
};
//连接成功建立的回调方法
websocket.onopen = function () {
setMessageInnerHTML("WebSocket连接成功");
}
//接收到消息的回调方法 接服务器信息的方法
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function () {
setMessageInnerHTML("WebSocket连接关闭");
}
当然这些都是浏览器环境
实战
逆向
此次目标为B站,随便找个直播间,来爬一下直播间的弹幕
首先找到给出WSS信息的API,这个是B站的
其实B站还有一个API能够拿到弹幕,详见 Bilibili直播API,我估计是为了填补WSS连接前的空档期
同时也可以在WS
这看到建立的连接:
当然数据是加密的,通过发起程序或者搜索关键字找到代码处:
最终给我一顿找找到这里:
这个要说明一下就是WS建立可能会因为你调试导致失败,比如我这个就导致直播推流失败,但是弹幕仍然有效,因此并不影响
失败的会自动重新建立几次连接,记得关注URL适当放行几次,这个得自己把握,同时每次数据都会变,不像HTTP每次请求对应一次数据,像B站这个,点开就一个二进制数据选项,每次进每次数据不一样,但有一点可以确定的是数据加密过
在上下不断寻找后,最终找到可能的解密处:
其实现在可以有两种办法,一是植入代码,浏览器保持运行,本地一个客户端同时接受,即做一个转发,另一个种那就是本地客户端直接接受了,但是那种可能还需要处理心跳问题,这里直接试试不用逆向解密方法的植入方式。
植入代码
植入方式最大好处就是浏览器环境,难以被反爬了吧
准备好js代码:
!function(){
const ws = new WebSocket('ws://127.0.0.1:7788');
window.wsHook = ws;
ws.onopen = function(e){
ws.send('CONNECTED')
}
ws.onmessage = function(e){
ws.send(JSON.stringify(n.body))
}
}();
然后选择替换(或者覆盖),然后对对应的JS文件右键-保存以覆盖(记住选择未格式化的!),直接在浏览器中打开已保存的代码进行编辑,在这段解密后的代码后面添加上面的代码即可
当然这不够优雅,可以考虑以下代码:
在文件头部加入
!function(){
const ws = new WebSocket('ws://127.0.0.1:7788');
window.wsHook = ws;
ws.onopen = function(e){
ws.send('CONNECTED')
}
}();
然后在对应地方加入以下代码:
window.wsHook.send(JSON.stringify(n.body));
现在确保开发者工具开启且启用覆盖,刷新一下页面,如果你恰好看着控制台,你会看到连接到127.0.0.1:7788
失败的消息,这是因为还没有客户端来接收
再用一下下面抄来的Python代码(懒):
import asyncio
# encoding: utf-8
import asyncio
import websockets
async def echo(websocket):
# 使用WebSocket在客户端和服务器之间建立全双工双向连接后,就可以在连接打开时调用send()方法。
message = 'hello world'
# 发送数据
await websocket.send(message)
return True
async def recv_msg(websocket):
while 1:
# 接收数据
recv_text = await websocket.recv()
print(recv_text)
async def main_logic(websocket, path):
await echo(websocket)
await recv_msg(websocket)
start_server = websockets.serve(main_logic, '127.0.0.1', 7788)
loop = asyncio.get_event_loop()
loop.run_until_complete(start_server)
loop.run_forever()
开RUN,可以看到直播间的消息已经都摆在你面前了
这时候是存入MongoDB还是现场就开始分析全看个人,仅仅插入一段代码,不用破解任何方法,正常看着直播就把弹幕爬了,这还扣啥代码,要什么自行车。
思路拓展
当遇到一个极难的网站,即使找到了解密方法,也很难扣的时候,是否可以使用这种办法转发解密后的数据给本地?这样虽然还是需要配合自动化但却省去了用BeautifulSoup等工具对网页的解析,自动化只需要做到能帮助翻页等功能即可。