💬

赛博小雅·弹幕采集器

Cyber Xiaoya Danmaku

抖音 / B站 / YouTube / 视频号四路弹幕与礼物,清洗、去重、连击合并、按优先级排队,再统一 POST 给你自己的数字人桥——它只搬运观众的声音,不负责发声和出画。

GitHub 仓库★ 50
🖥 WindowsPythonWebSocketprotobufCDPInnerTube📜 AGPL-3.0📅 发布 ✏️ 更新

做数字人直播,第一个卡住人的往往不是模型,是弹幕怎么进来。四个主流平台,四种完全不同的接入方式:抖音要签名、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站直播开放平台。抖音同理:采集服务是第三方项目,使用风险自负。

相关