博客仅供学习研究特别声明
特别声明:本博客【2024 年最新基于 Python 实现抖音直播间逆向弹幕评论消息监听(仅供学习研究)】的所有源码和内容均仅供学习研究,严禁用于包括但不限于商业谋利、破坏系统、盗取个人信息等不良不法行为,违反此声明使用所产生的一切后果均由违反声明使用者承担。
博主有话说:博客的所有源码和内容均仅供学习研究,若是博客学习上有问题通过 CSDN 私信博主即可。完整数据帧解包 Protobuf 通过 CSDN 私信博主获取(仅供学习研究)。
特别注意:附加内容是通过 websocket-client 连接(待更新)
弹幕监听项目结构
douyin_proto.py 解析消息
main.py 主函数(监听 websocket 数据帧)
解析 websocket 数据帧(解包)
定义了一组用于解析二进制协议消息的 dataclass,通过 betterproto 库将协议消息映射为 Python 数据类。
HeadersList 类表示头部键值对,PushFrame 类封装了一个二进制有效载荷,Message 类包含消息的基本信息如方法、ID 和类型,Response 类表示响应消息,包含多个 Message 对象,User 类表示用户的基本信息(如 ID 和昵称),而 ChatMessage 类表示聊天消息,包含用户信息和消息内容。
douyin_proto.py
from dataclasses import dataclass
from typing import Dict, List
import betterproto
@dataclass
class HeadersList(betterproto.Message):
key: str = betterproto.string_field(1)
value: str = betterproto.string_field(2)
@dataclass
class PushFrame(betterproto.Message):
payload: bytes = betterproto.bytes_field(8)
@dataclass
class Message(betterproto.Message):
method: str = betterproto.string_field(1)
payload: bytes = betterproto.bytes_field(2)
msg_id: int = betterproto.int64_field(3)
msg_type: int = betterproto.int32_field(4)
@dataclass
class Response(betterproto.Message):
messages_list: List["Message"] = betterproto.message_field(1)
@dataclass
class User(betterproto.Message):
id: int = betterproto.uint64_field(1)
nick_name: str = betterproto.string_field(3)
@dataclass
class ChatMessage(betterproto.Message):
user: "User" = betterproto.message_field(2)
content: str = betterproto.string_field(3)
安装 pyppeteer 第三方库
pyppeteer 是一个 Python 库,提供了对 Puppeteer 的 Python 绑定,Puppeteer 是一个用于控制无头 Chrome 浏览器的 Node.js 库。pyppeteer 使得我们能够在 Python 中使用类似的功能来控制浏览器,进行自动化测试、抓取网页内容、截图、生成 PDF 等操作。
pyppeteer 安装方法
pip install pyppeteer
常见问题
在安装 pyppeteer 时,pyppeteer 会自动下载 Chrome 浏览器的版本(大约 100MB+)。但是博主发现 pyppeteer 自动下载 Chrome 浏览器会经常发生报错,所以我们可以手动下载谷歌浏览器,手动配置 executablePath
即可。
安装 chrome 浏览器
安装 chrome 浏览器(包含 chrome.exe 可执行文件)
chrome.exe:C:\Program Files\Google\Chrome\Application\chrome.exe
抖音直播间弹幕监听
使用 pyppeteer 库启动一个 Chrome 浏览器实例,打开指定的抖音直播间页面,并监听 WebSocket 数据帧。当接收到 WebSocket 帧时,脚本会解码并分析其中的消息内容,特别是聊天消息(WebcastChatMessage),然后提取消息发送者的昵称、UID(隐藏中间 8 位)和消息内容,并打印在控制台中。通过不断等待直到 stop_listening 为 True,脚本保持监听状态,直到满足停止条件后关闭浏览器。
import gzip
import base64
import asyncio
import traceback
from pyppeteer import launch
from douyin_proto import *
executable_path = r'C:\Program Files\Google\Chrome\Application\chrome.exe'
stop_listening = False
def hide_uid(uid: int):
uid_str = str(uid)
if len(uid_str) > 8:
masked_uid = uid_str[:4] + "********" + uid_str[-4:]
else:
masked_uid = uid_str
return masked_uid
def analysis(decoded_bytes):
try:
package = PushFrame().parse(decoded_bytes)
response = Response().parse(gzip.decompress(package.payload))
for msg in response.messages_list:
if msg.method == 'WebcastChatMessage':
ret = ChatMessage().parse(msg.payload)
user_name = ret.user.nick_name
user_id = ret.user.id
content = ret.content
masked_user_id = hide_uid(user_id)
print(f"【弹幕】[UID: {masked_user_id}]{user_name}: {content}")
except Exception as err:
print(f"Error: {err}")
traceback.print_exc(err)
async def listen_debugger():
browser = await launch(executablePath=executable_path, headless=False)
page = await browser.newPage()
session = await page.target.createCDPSession()
await session.send('Network.enable')
def on_request(request):
decoded_data = base64.b64decode(request['response']['payloadData'])
analysis(decoded_data)
session.on('Network.webSocketFrameReceived', on_request)
room_id = "【直播间ID】"
await page.goto(f'https://live.douyin.com/{room_id}')
task = asyncio.create_task(wait_for_condition(stop_listening))
await task
print("Condition met, closing browser...")
await browser.close()
async def wait_for_condition(stop_listening):
while not stop_listening:
await asyncio.sleep(1)
asyncio.run(listen_debugger())
弹幕监听:程序运行结果
设置 main.py 脚本 room_id = "777528469683"
执行运行 main.py 程序
python main.py
运行截图
自动打开浏览器(显示 Chrome 正受到自动测试软件的控制)
pycharm 控制台截图
注意:博主进行了 UID 数据隐藏(在非匿名的直播间可以获取用户的 UID 数据)
附加:消息事件监听
演示了弹幕消息类型的监听,事件上直播间互动消息还包括其他数据类型,这边只是简化提取了弹幕类型的数据。其他消息事件类型需要相应 proto 进行解包操作。(待更新·····)
附加:websocket-client 监听消息
通过 websocket-client 直接连接直播间地址同样能获取直播间的数据帧,同样进行数据帧的解包处理,可以获取解析真实数据。
signature 签名数据
附加:定位 new Websocket
通过开发者工具查询 websocket 连接启动器
定位【创建 new Websocket 连接】源码
需要创建 websocket 连接,通过查看源码,我们可以知道连接 websocket 需要的数据是通过 getSocketParams 返回的,所以需要定位【getSocketParams 函数】源码。
定位【getSocketParams 函数】源码
定位【创建 signature 函数】源码
附加:MD5 算法解析 X-MS-STUB
o = ",live_id=1,aid=6383,version_code=180800,webcast_sdk_version=1.0.14-beta.0,room_id=7444732087904635711,sub_room_id=,sub_channel_id=,did_rule=3,user_unique_id=7223996956878915084,device_platform=web,device_type=,ac=,identity=audience"
o.substring(1) = "live_id=1,aid=6383,version_code=180800,webcast_sdk_version=1.0.14-beta.0,room_id=7444732087904635711,sub_room_id=,sub_channel_id=,did_rule=3,user_unique_id=7223996956878915084,device_platform=web,device_type=,ac=,identity=audience"
MD5 验证
import hashlib
input_string = "live_id=1,aid=6383,version_code=180800,webcast_sdk_version=1.0.14-beta.0,room_id=7444732087904635711,sub_room_id=,sub_channel_id=,did_rule=3,user_unique_id=7223996956878915084,device_platform=web,device_type=,ac=,identity=audience"
md5_hash = hashlib.md5(input_string.encode('utf-8'))
md5_result = md5_hash.hexdigest()
print(md5_result)
MD5 验证 65b92687ae0c7cfd888e5a57b7ab99f2
附加:解析 signature 创建函数
选择在【新标签页打开】获取 webmssdk.es5.js 脚本地址
下载 webmssdk.es5.js 地址:https://lf-c-flwb.bytetos.com/obj/rc-client-security/c-webmssdk/1.0.0.53/webmssdk.es5.js
我们进行 webmssdk.es5.js 脚本处理,通过 ctrl + F 查询,定位注释删除 setTimeout 函数,绑定 create_sign 函数 => function _0x5c2014
注释删除 setTimeout 函数
setTimeout(function () {
var _0x36b72e = _0x5af634;
_0x4e6ec1(_0x5aac87[_0x36b72e(0x24f)]());
}, 0x2205 * 0x1 + 0x1d4 * -0x10 + -0x67 * 0x7);
绑定 create_sign 函数 => function _0x5c2014
function _0x5c2014(_0x1fa689) {
return w_0x5c3140('484e4f4a403f524300362d0a5f00233c0000000029b6a730000000630214000103001400020700001400030700011400041101031100031347000d11010311000313140001450023110103110004134700130211010011010311000413430114000145000607000214000102110101110002021100014303140005110005420003096b1e7e601e606766710c6b1e7e601e63726a7f7c7277200303030303030303030303030303030303030303030303030303030303030303', {
get 0x0() {
return _0x5dd467;
},
get 0x1() {
return _0x34c70a;
},
0x2: arguments,
0x3: _0x1fa689
}, this);
}
create_sign = _0x5c2014
添加 document、windows、naviagator
注意:需要手动进行添加 document、windows、naviagator(通过 node 本地环境执行没有 document、windows、naviagator)
var document = {}
var window = {}
var navigator = {'userAgent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'}
var create_sign
······
已调整 webmssdk.es5.js 源码
······
function get_sign(md5) {
let data = {
"X-MS-STUB": md5
}
return create_sign(data)["X-Bogus"];
}
console.log(get_sign('65b92687ae0c7cfd888e5a57b7ab99f2'))
node 执行获取签名
node webmssdk.es5_update.js