必须用 WebSocket 连接 B站直播弹幕服务,先调用 HTTP 接口获取带 token 的 ws 地址,再发送二进制认证包;PHP 需借助 textalk/websocket 等库手动解析 TLV 协议,注意 token 时效性、心跳保活及风控限制。
PHP 调用 B站直播开放平台必须走 WebSocket,HTTP 接口只读不推
直接用
或
请求 B站弹幕接口会失败——官方没提供轮询式 HTTP 弹幕拉取接口。所有实时数据(弹幕、礼物、进入房间等)都得靠 WebSocket 长连接收发。PHP 本身不原生支持 WebSocket 客户端,得靠扩展或第三方库。
常见错误现象:
或空响应;
返回
;或者连上就断开,没任何数据。
必须用
或
协议连接,不能用
B站要求先通过 HTTP 接口
获取真实 WebSocket 地址和认证参数(
、
)
拿到地址后,要拼接
、
(固定为
)、
(如
)、
(
表示弹幕)和
(即
)作为 URL 查询参数
连接建立后,必须立刻发送认证包(
前需先发
),格式是二进制协议(Bilibili 自定义的 TLV 结构),不是 JSON
用
php
-websocket 库最省事,但要注意二进制协议解析
推荐用
(Composer 可装),它能处理连接和基础帧收发,但不自动解析 B站的二进制协议。你得自己拆包:每个包开头 4 字节是包总长度(含头),接着 2 字节是头部长度,2 字节是协议版本,2 字节是操作码(
是弹幕,
是礼物,
是心跳响应),最后是 JSON 数据体(需从
字段里提取)。
容易踩的坑:
直接报错,因为
是原始二进制流,得先用
截出有效载荷,再
;另外,B站可能返回多个包粘连在一起,一次
可能含多条消息,得按包长循环切分。
立即学习
“
PHP免费学习笔记(深入)
”;
安装:
别用
发认证包——必须构造二进制字符串:
(简化示意,实际更复杂)
操作码
对应弹幕,
是礼物,
是用户入场,别硬编码成
或
心跳间隔官方建议 30 秒,超时未发
包会被服务器断连
获取直播间弹幕与礼物数据前,务必处理好鉴权和防封策略
B站对非官方客户端有严格风控:单 IP 短时间连多个房间、频繁重连、无心跳、消息体格式错误,都会触发限流甚至封禁 IP。这不是“调不通”,而是“被拒绝”。
PHP 8.5.5
PHP 8.5.5 是 PHP 8.5 分支的维护更新版本。该版本延续了“小步快跑”的迭代逻辑,通过深度错误修复、底层性能微调以及安全加固,旨在为开发者提供一个更健壮、更高效的运行环境。该版本严格遵守语义化版本规范,不包含破坏性变更。
下载
真实使用场景中,多数人卡在 token 过期或房间号无效。注意:
接口返回的
有效期约 1–2 分钟,且绑定具体
和
(如果登录了),不能复用;若房间已下播,
会返回
(房间不存在)或
(状态异常)。
每次重连前,必须重新调用
拿新
和
不要用个人账号 Cookie 去请求
——除非你明确做了登录态维护(
+
),否则大概率
(未登录)
测试阶段用小号房间(在线人数
生产环境建议加随机延迟(500–2000ms)再发心跳,避免整齐划一的请求节奏
PHP 不适合长期运行弹幕服务,别硬扛高并发连接
一个 PHP-FPM 进程只能维持一个 WebSocket 连接,没法像 Node.js 或 Go 那样单进程管几千连接。如果你要监控 50 个直播间,就得启 50 个独立进程(比如用
),内存和 CPU 开销会迅速失控,且进程挂了没人拉起。
性能影响很实际:每条弹幕平均 300–800 字节,千人房每秒 10–50 条,单连接带宽不高,但 PHP 解析二进制 + JSON + 业务逻辑(比如写数据库、触发通知)容易成为瓶颈,尤其用
写日志时磁盘 I/O 会拖慢整个循环。
用
启子进程管理单个房间,主进程负责保活和日志汇总(但 Windows 不支持)
更稳妥的做法是把 PHP 当“协议翻译层”:WebSocket 收到数据后,用
转发给本地 Node.js/Python 服务处理,PHP 只做轻量胶水
别在
里用
轮询——用
或事件循环(如
)才靠谱
记录弹幕时,批量插入(
)比逐条
快 10 倍以上
真正难的不是连上,是稳住连接、正确解包、扛住风控、不崩进程。这些细节没对齐,代码写得再“完整”,跑半天就断了。
file_get_contentscurl403 Forbiddencurl{"code":-400,"message":"invalid request"}ws://wss://http://https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfotokenhost_listroom_idprotover2platformwebtype1keytokenHEARTBEATVERIFICATIONtextalk/websocket358bodyjson_decode($body, true)$bodysubstr($raw, $headerLen)json_decoderecv()composer require textalk/websocketjson_encodepack('N', $totalLen) . pack('n', 16) . pack('n', 1) . pack('n', 7) . pack('n', $roomId)351612HEARTBEATgetDanmuInfotokenroom_iduidgetDanmuInfocode=1code=400getDanmuInfotokenhostgetDanmuInfoSESSDATAbili_jct412nohup php danmu.php &file_put_contentspcntl_forkstream_socket_clientwhile(true)sleep(1)socket_selectreactphp/socketINSERT INTO ... VALUES (...),(...)INSERT