Asyncio client for Douyu live danmaku & room status / 斗鱼弹幕与房间状态 asyncio 客户端
Project description
aiodouyu
斗鱼弹幕与房间状态的 asyncio 客户端库 —— 零依赖、自动重连、干净停止。
English | 背景 | 安装 | 用法 | 房间信息 | 多房间 | 传输方式 | 类型化模型 | 录制与回放 | 协议说明 | 版本与稳定性 | 局限与路线图
为什么有这个库
Python 生态里的斗鱼弹幕库已全部停止维护:pydouyu(2022 年后无更新)、danmu(2017)、danmaku(已归档)。aiodouyu 是这个空缺的现代替代品:
- asyncio 优先:每个房间一个协程,不再是"每房间三个线程"
- 零运行时依赖:纯标准库 TCP 实现
- 自动重连:指数退避 + 抖动;空闲超时检测半开连接
- 干净停止:
close()立即中止,不遗留线程/任务(pydouyu 的停止会泄漏线程) - 完整消息流:rss(开播状态)、chatmsg(弹幕)、dgb(礼物)、uenter(进房)……全部透出
- 可测试:附带完整测试套件与假服务器测试基建
安装
pip install aiodouyu
要求 Python >= 3.10。
用法
异步迭代(推荐)
import asyncio
from aiodouyu import DanmakuClient
async def main():
async with DanmakuClient(room_id=9999) as client:
async for msg in client:
if msg["type"] == "chatmsg":
print(f'{msg.get("nn")}: {msg.get("txt")}')
elif msg["type"] == "rss":
# ivl=='0' 排除视频轮播:轮播房的 ss 也是 '1'
is_live = msg.get("ss") == "1" and msg.get("ivl") == "0"
print("开播" if is_live else "下播")
asyncio.run(main())
回调注册
client = DanmakuClient(room_id=9999)
@client.on("rss")
def on_status(msg):
print("直播状态变化:", msg)
@client.on("*") # 通配符匹配所有消息
async def on_any(msg): # 同步/异步回调均可
...
await client.run() # 运行直到 client.close()
只订阅关心的消息类型
# 只做开播提醒?只要 rss,其他消息在解码后直接丢弃
client = DanmakuClient(room_id=9999, types={"rss"})
感知断连窗口
from aiodouyu import EVENT_CONNECTED, EVENT_DISCONNECTED
client = DanmakuClient(room_id=9999, emit_connection_events=True)
async for msg in client:
if msg["type"] == EVENT_DISCONNECTED:
... # 断连期间可能错过状态变化,可在重连后主动校准
斗鱼只在状态变化时推送 rss。断连窗口内的变化不会补发, 建议消费方在收到
EVENT_CONNECTED后通过fetch_room()校准一次当前状态。
命令行冒烟测试
python -m aiodouyu 9999 --types rss,chatmsg --duration 30
python -m aiodouyu 9999 --info # 查询房间信息后退出
房间信息(HTTP)
web 模块提供房间信息拉取,同样零依赖。典型用途:重连后校准直播状态、
获取标题/主播名/分类/封面用于通知富化。
from aiodouyu import fetch_room, RoomNotFound
info = await fetch_room(9999)
print(info.title, info.owner, info.category) # 标题 / 主播名 / 分类
print(info.is_live) # 是否真实开播(视频轮播不算,见 is_loop)
print(info.started_at) # 本场开播 epoch 秒;未开播为 None
print(info.cover_url) # 直播间封面
两个数据源,source 参数选择:
| source | 端点 | 特点 |
|---|---|---|
"betard"(默认优先) |
www.douyu.com/betard/{rid} |
字段最全,videoLoop 可识别视频轮播 |
"open" |
open.douyucdn.cn/api/RoomApi/room/{rid} |
公开 API 更稳定,但无法识别轮播 |
"auto"(默认) |
先 betard,传输失败回退 open | 房间不存在(RoomNotFound)不回退 |
open 源无法判定视频轮播:其
is_loop为None,轮播房会被报告为开播。 做开播状态校准时建议显式source="betard"(失败则跳过本轮校准), 或在is_loop is None时自行决定是否信任is_live。
配合弹幕客户端做状态校准:
from aiodouyu import EVENT_CONNECTED, DanmakuClient, fetch_room
client = DanmakuClient(room_id=9999, types={"rss"}, emit_connection_events=True)
async for msg in client:
if msg["type"] == EVENT_CONNECTED:
info = await fetch_room(9999) # 断连窗口内的变化在这里补上
print("当前状态:", info.is_live)
elif msg["type"] == "rss":
print("状态变化:", msg.get("ss") == "1" and msg.get("ivl") == "0")
多房间(DanmakuHub)
监控 N 个主播的应用不必手写任务编排、异常隔离、聚合队列、优雅关停 这套样板——Hub 把它们上移进库:
from aiodouyu import DanmakuHub
hub = DanmakuHub(types={"rss"}, emit_connection_events=True)
await hub.add(9999) # 运行中可动态增删,幂等
await hub.add(288016)
async for room_id, msg in hub: # 统一聚合流,库负责打房间号
...
await hub.close() # 关停全部房间,不遗留任务
消费速度与背压:聚合队列有界(默认 1024)。overflow="block"
(默认)时慢消费者会反压到各房间的 TCP 接收——不丢消息,代价是消费
时效;只关心最新状态的场景用 overflow="drop_oldest" 丢旧保新。
单客户端场景同理:async for 不取消息时 TCP 缓冲会积压,长期不消费
可能被服务端判定僵尸连接断开(库会自动重连)。
类型化模型(可选)
不想记 nn/txt/gfid 这些协议字段名?models.parse() 把常见消息
转成带类型字段的 dataclass(可选层,默认行为不变;字段缺失/畸形得
None 永不抛异常,原始字典始终在 .raw):
from aiodouyu import models
async for msg in client:
parsed = models.parse(msg) # 未识别类型返回 None
match parsed:
case models.ChatMsg():
print(parsed.nickname, parsed.text)
case models.Gift():
print(parsed.nickname, "送出", parsed.gift_name, "x", parsed.count)
case models.RoomStatus():
print("开播" if parsed.is_live else "下播/轮播")
粉丝牌是三元组
badge_name/badge_level/badge_room_id——实测跨房 粉丝牌是常态(badge_room_id常 ≠ 当前房间),展示时请一起判断。
录制与回放
把真实消息流录成 JSONL,离线回放给完全相同的消费代码——测试夹具、 issue 复现(协议漂移报告请附 dump)、弹幕数据分析都用它:
python -m aiodouyu 9999 --record dump.jsonl --duration 60
--record录制完整消息流(--types只影响控制台打印), 语料才不会系统性缺字段。文件首行是版本化 header,其后每行{"ts": ..., "msg": {...}}。
from aiodouyu import replay
# 即刻回放(默认,测试主场景)
async for msg in replay("dump.jsonl"):
... # msg 与 DanmakuClient 产出完全同构
# 按原速回放,但单次空窗最多睡 1 秒(真实语料含 45s+ 心跳空窗)
async for msg in replay("dump.jsonl", speed=1.0, max_gap=1.0):
...
# types 过滤与连接伪事件豁免的语义与 DanmakuClient 一致
async for msg in replay("dump.jsonl", types={"rss"}):
...
传输方式(TCP / WebSocket)
默认走明文 TCP(danmuproxy.douyu.com:8601)。该端口常被企业防火墙
或部分云出口拦截,受限网络可切到网页端同款的 WebSocket 端点:
DanmakuClient(9999, transport="ws") # wss://danmuproxy.douyu.com:8506
DanmakuClient(9999, transport="auto") # 先 ws,失败回退 tcp
WebSocket 客户端是手写的最小 RFC 6455 实现(仅客户端角色、二进制帧、 零扩展协商),零依赖不变;弹幕协议在两种传输上完全一致,ping/pong 在传输层透明处理。
实测细节:斗鱼 wss 端点只提供
AES256-GCM-SHA384(RSA 密钥交换), 现代 OpenSSL 默认的 SECLEVEL=2 会直接拒绝握手。库内置的 TLS 上下文 只把密码套件安全级别降到 1,证书与主机名校验保持开启;需要自定 义时传ssl_context。
协议说明
连接 danmuproxy.douyu.com:8601(TCP),STT 序列化(@= 键值、/ 分隔、
@A/@S 转义),小端长度前缀成帧(发 689 / 收 690),loginreq + joingroup
握手,每 45 秒 mrkl 心跳。
注意:弹幕协议是斗鱼的非官方公开接口,端点与字段可能随时变更 (历史上已发生过一次域名迁移)。本库不隶属于斗鱼,请合理使用、避免滥用。
局限与路线图
当前局限:
- STT 嵌套结构(如 ranklist 的分组数据)不展开,值以原始字符串返回
web模块的 betard 数据源是网页端内部接口,字段可能随时变更 (auto模式会自动回退到公开 API)
路线图:核心能力已完备,后续按 issue 与实际需求演进。 欢迎 issue / PR(贡献指南见 CONTRIBUTING.md)。
版本与稳定性
本库处于 0.x 阶段,遵循如下承诺(自 0.2 起生效):
- 补丁版本(0.y.Z)永不破坏 API;破坏性变更只出现在次版本(0.Y.0)。
下游建议以
aiodouyu~=0.y.z钉住次版本 - 公共 API 面 = 包根与
packet/stt/web/replay各自的__all__、EVENT_*常量值及"连接伪事件不受 types 过滤"的行为、异常继承关系 (RoomNotFound ⊂ ApiError ⊂ AiodouyuError)、DanmakuClient/fetch_room/replay的关键字签名 - 不承诺:斗鱼协议的 dict 字段与服务端行为(协议漂移出适配性次版本, 不视为库的破坏);未来 typed models 的字段增补视为兼容
- 弃用流程:先在某个 0.Y 版本标记
DeprecationWarning,至少隔一个次 版本后才移除
下游示例:AstrBot 插件 astrbot_plugin_douyu_live 以
>=0.1.2,<0.2依赖本库。
English
Asyncio client for Douyu (斗鱼) live-stream danmaku and room-status events.
Zero runtime dependencies, automatic reconnection with exponential
backoff, idle-timeout detection for half-open connections, and clean
shutdown. Consume messages via async for or callback registration. A
zero-dependency web module (fetch_room) retrieves room metadata and live
status over HTTP for state calibration after reconnects, and replay()
replays JSONL dumps recorded with python -m aiodouyu <rid> --record as a
stream identical in shape to the live client — ideal for tests and issue
reproduction. Patch releases never break the API (see 版本与稳定性). The
barrage protocol is an unofficial Douyu interface and may change without
notice.
async with DanmakuClient(room_id=9999) as client:
async for msg in client:
print(msg)
info = await fetch_room(9999) # title / owner / category / is_live ...
许可证
致谢
协议实现参考了 pydouyu(MIT)与社区对 斗鱼弹幕协议的公开分析文章。
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aiodouyu-0.4.0.tar.gz.
File metadata
- Download URL: aiodouyu-0.4.0.tar.gz
- Upload date:
- Size: 56.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d6f9a080a994a70b10387f742f53815144a9bb43076ca83b7dc63b133ce100b0
|
|
| MD5 |
1fc1dd6eafeb885a109e9fa8cae557ec
|
|
| BLAKE2b-256 |
168dfb65ff5e6b20b3db73e03007b349f9d3db61ef0a6702062efb835a30bb49
|
Provenance
The following attestation bundles were made for aiodouyu-0.4.0.tar.gz:
Publisher:
release.yml on GEMILUXVII/aiodouyu
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aiodouyu-0.4.0.tar.gz -
Subject digest:
d6f9a080a994a70b10387f742f53815144a9bb43076ca83b7dc63b133ce100b0 - Sigstore transparency entry: 2256454914
- Sigstore integration time:
-
Permalink:
GEMILUXVII/aiodouyu@b7bda311a15bd94c4bf44ba8c02978e1df7dc0cf -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/GEMILUXVII
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b7bda311a15bd94c4bf44ba8c02978e1df7dc0cf -
Trigger Event:
push
-
Statement type:
File details
Details for the file aiodouyu-0.4.0-py3-none-any.whl.
File metadata
- Download URL: aiodouyu-0.4.0-py3-none-any.whl
- Upload date:
- Size: 41.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0e8821cc9a28056564cfad9544715974da799fefa75b5717a7c3131465c214e3
|
|
| MD5 |
87fbd595de5bc94a45e32cddb32d7cc9
|
|
| BLAKE2b-256 |
b991875fe5aeb02cc69c2b6bfdb6dd88f6b490873e386a6a78c1bffd0e6042bf
|
Provenance
The following attestation bundles were made for aiodouyu-0.4.0-py3-none-any.whl:
Publisher:
release.yml on GEMILUXVII/aiodouyu
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aiodouyu-0.4.0-py3-none-any.whl -
Subject digest:
0e8821cc9a28056564cfad9544715974da799fefa75b5717a7c3131465c214e3 - Sigstore transparency entry: 2256454925
- Sigstore integration time:
-
Permalink:
GEMILUXVII/aiodouyu@b7bda311a15bd94c4bf44ba8c02978e1df7dc0cf -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/GEMILUXVII
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b7bda311a15bd94c4bf44ba8c02978e1df7dc0cf -
Trigger Event:
push
-
Statement type: