做数字人直播,第一个卡住人的往往不是模型,是弹幕怎么进来。四个主流平台,四种完全不同的接入方式:抖音要签名、B站是明文 WS 加 protobuf、YouTube 有一堆配额规则、视频号干脆没有接口。
这个项目只做一件事:把观众的声音搬进来。采集、清洗、排队、转发到一个统一接口,剩下的(说话、动作、出画)交给数字人那边——也就是配套的赛博小雅 · 直播版。
数据流
四条来源(抖音 / B站 / YouTube / 视频号)各自采集,统一汇进 reader.py:清洗、去重、连击合并、按优先级排队,再 POST /api/danmaku 给数字人桥(:8795)。不接数字人时也可以在本机自己念(语音桥 / 系统 SAPI)。
转出去的事件只有一种形状:
{
"nick": "观众名",
"content": "弹幕内容",
"platform": "抖音",
"text": "念出来的文本"
}
礼物额外带 kind:"gift"、gift_item、gift_num、gift_value。只要你的程序能收这个 JSON,就能接它——不绑死数字人项目。
四个平台,四种接法
| 平台 | 接入方式 | 需要 key? | 主要难点 |
|---|---|---|---|
| B站 | 官方 web 弹幕 WS(wss://<host>/sub) | 不需要(Cookie 可选) | 协议手写、WBI 签名、protobuf |
| 抖音 | 本地 douyinLive 采集服务 + 本机 WS | 需要抖音 Cookie(给采集服务) | 网页要签名,只能靠采集服务 |
| YouTube | 网页 InnerTube /live_chat 长轮询 | 不需要 | 官方 API 配额扛不住 |
| 视频号 | CDP 调试浏览器读中控台 DOM | 扫码登录 | 没有公开接口 |
攻克了哪些技术
① B站:整条协议手写,不引第三方库
链路是:room_init 解析真实房间号 → WBI 签名 + bili_ticket 取 getDanmuInfo → 连 wss → op=7 鉴权 → op=2 心跳 → op=5 收命令流(protover=2,zlib 压缩)。
两个坑值得单独说:
- token 和 uid 必须同源。
getDanmuInfo下发的 token,要和鉴权包里写的 uid 对得上:登录态用nav接口拿到的mid,游客态必须写 0,否则握手直接失败。 INTERACT_WORD_V2/SEND_GIFT_V2的 body 是 base64 的 protobuf。为了不引 protobuf 运行时,项目里写了一个迷你 varint 解析器,按字段号手工取昵称与消息类型——够用、零依赖、也不会因为协议小改就整条链挂掉。
另外,游客模式先访问一次 www.bilibili.com 领 buvid3,能显著降低 -352 风控的概率。
② YouTube:绕开官方 API 的配额
官方 Data API 的 liveChatMessages.list 每次记账 5 个配额单位,一天一万的额度,一整天轮询分分钟打光。所以走网页自己用的 InnerTube:
GET /live_chat?v=<videoId>&is_popout=1—— 必须走这个独立页,watch 页里聊天是懒加载的,挖不到liveChatRenderer;- 起始 token 在
liveChatRenderer.continuations[0].reloadContinuationData.continuation,顺带还能抓到INNERTUBE_API_KEY; - 之后循环
POST /youtubei/v1/live_chat/get_live_chat,下一轮 token 在continuationContents.liveChatContinuation.continuations[0]; - 直播结束 / 聊天被关时
continuations会消失,所以外层要重新 seed——主播下播再开播也能自动接上。
支持填 videoId、观看页链接、/live/ 链接、youtu.be 短链、Studio 后台链接,也支持填频道 handle 自动找当前正在直播的那一场。
③ 视频号:没有接口,就读 DOM
视频号直播没有公开弹幕接口,但「视频号助手」的直播中控台会把评论实时渲染出来。于是用一个带调试端口的独立浏览器(默认 9223),通过 CDP 每秒读一次评论列表,增量提取新增条目。
这个做法有个意外的好处:浏览器最小化、被别的窗口遮挡都不影响——读的是 DOM,不是屏幕像素,所以比「截图 + OCR」稳得多。
④ 抖音:网页要签名,交给采集服务
抖音弹幕不能直接从网页读,社区通行做法是先跑一个本地采集服务(Go 写的 douyinLive,自带 ws + http,默认 1089 端口),本项目连它的 WebSocket。
实测有个坑:「下播再开播」之后采集服务不会自己重新握手,事件就断了。所以客户端会检测这种情况并自动重启采集服务,而不是干等着。
⑤ 事件清洗:读起来像个人,而不是像日志
观众看到的是「阿糖说你好呀」,而不是「用户 abcd1234 发送:你好呀」。清洗层做了这些:
- 昵称缩写:纯中文取前 2 个汉字,数字 ID 取前 4 位,英文取第一个单词,中英混排从第一个汉字起取 2 个可读字符,纯符号(emoji、希腊字母)返回空——由泛称兜底;
- 10 秒窗口去重、关键词黑名单、50 字截断;
- 连击礼物按
groupId聚合:repeatEnd==1或超时收口,数量取最大值、单价取最大值不累加——否则「×100 连击」会被念一百遍。
⑥ 双优先级队列 + 朗读节奏
热闹的直播间里,逐条追播既吵又永远在念过时的弹幕。所以做了两层:
| 层 | 规则 |
|---|---|
| 优先级 | 弹幕 / 礼物 / 关注 / 舰长 / 醒目留言 = 高优先,立即读;进场 / 点赞 = 低优先,高优先清空才读,且最短间隔 10 秒 |
| 节奏 | 读完一条至少隔 gap_seconds(默认 5 秒);等待期间积压的普通弹幕只读最新一条,礼物 / 关注 / 舰长 / SC 一条都不丢 |
规则细节很重要:如果「只读最新」把礼物也合并掉,观众就会觉得主播没谢他——所以合并只作用于普通弹幕。
⑦ 热加载 + 一个不装库的托盘
config.json有文件监听,改完 2 秒内生效,不用重启;- 托盘是独立的 PowerShell + WinForms 小程序,只读写
config.json:音量档位、朗读哪些事件、朗读间隔,勾选一下写回文件,朗读器那边热加载收下。不需要 IPC,也不需要任何第三方依赖; - 两边的状态每 3 秒同步一次:在控制台敲
vol 0.3,托盘里的勾选和音量也会跟着变。
和数字人怎么对接
config.json 里把出口设成数字人,弹幕就不再本地朗读,而是转发给桥:
{
"tts": {
"output": "dh",
"dh_url": "http://127.0.0.1:8795",
"dh_min_interval_ms": 800
}
}
output 三个取值:dh(转发给数字人,默认)、bridge(本机 TTS 朗读)、both(两边都响,一般不用,会合唱)。
数字人那边收到后按「主播模式」处理:先复读弹幕(字幕带【弹幕】前缀)→ 同时把内容交给 LLM → 复读完再念回答。没连线时静默不转发,连上自动恢复。
快速开始
python -m venv .venv
.venv\Scripts\pip install requests websockets
copy config.example.json config.json # 填房间号
.venv\Scripts\python.exe reader.py --print-only # 先只打印验证
.venv\Scripts\python.exe reader.py # 正式跑(带托盘)
抖音还需要一个采集服务;B站只要房间号就能收(想要完整事件和昵称请配 Cookie);视频号要先双击 启动调试浏览器.cmd 扫码进中控台。
常见问题
Q:B站昵称变成「天***」,事件也少了?
游客态就这样。用 配置B站Cookie.cmd 把整串 Cookie 贴进去(含 SESSDATA),昵称完整、事件齐全。注意 Cookie 等于登录凭证,别发给任何人。
Q:YouTube 一直超时?
先确认本机能直连 youtube.com;这个采集走网页接口,网络不通时只能等它重试,没有任何 key 能绕过去。
Q:为什么同时只能开一个朗读器?
两个实例会让每条弹幕读两遍。启动脚本做成幂等的,已经在跑就跳过;另外直接点 ✕ 关窗口时 Python 不走 finally,会留下采集服务占着 1089,脚本会先清理孤儿进程。
Q:.cmd 里为什么不能写中文?
这台机器代码页是 65001,.cmd 会按控制台代码页解码,中文写进去会吞字节导致命令失效。所以外壳是纯 ASCII,中文提示统一交给带 BOM 的 .ps1 打印。
Q:能只采集、不朗读吗?
可以。--print-only 只打印事件,也可以把 output 设成 dh 转发给数字人,本机一声不出。
合规提示
B站这条用的是非官方 web 接口(官方文档库 2026 年初已因律师函关停),仅供个人直播间自用,请勿公开分发或商用;要做合规生产接入,应评估 B站直播开放平台。抖音同理:采集服务是第三方项目,使用风险自负。
相关
- 主仓库(数字人本体):赛博小雅 · 直播版
- 项目:cyber-xiaoya/cyber-xiaoya-danmaku · AGPL-3.0