一个基于 WebSocket 的抖音直播弹幕抓取工具。
它做的事很简单:
- 连接抖音直播间消息流
- 解析弹幕 / 礼物 / 点赞 / 进场等消息
- 再通过你本地启动的 WebSocket 服务把消息转发给你的客户端
适合两种用法:
- 直接当成一个本地 WebSocket 服务用
- 当成 Go 库集成到你自己的项目里
- 实时接收直播间消息
- 支持单进程监听多个直播间
- 支持弹幕、礼物、点赞、进场、关注等常见消息
- 支持可选 Cookie,适配部分需要登录态的场景
- 可作为独立服务运行,也可作为 Go 库使用
- 内置断线重连和基础保活逻辑
这个项目主要是直播间消息抓取 / 转发,不是录播工具。
它不负责:
- 下载 flv / m3u8 视频流
- 录制直播画面
- 保存回放
如果你要的是录播,应该看录制类项目;如果你要的是实时弹幕、礼物、互动消息,这个项目更合适。
- 打开 Releases
- 下载对应平台的程序
- 运行程序
./douyinLive程序启动后会在本地启动一个 WebSocket 服务,默认端口是 1088。
然后你的客户端连接:
ws://127.0.0.1:1088/ws/直播间标识
例如:
ws://127.0.0.1:1088/ws/516466932480
git clone https://github.com/jwwsjlm/douyinLive.git
cd douyinLive
go build -o douyinLive ./cmd/main
./douyinLivedocker run --rm -p 1088:1088 ghcr.io/jwwsjlm/douyinlive:latest程序启动后,对外提供的 WebSocket 地址仍然是:
ws://127.0.0.1:1088/ws/直播间标识
如果你需要固定版本,也可以直接拉指定 tag:
docker run --rm -p 1088:1088 ghcr.io/jwwsjlm/douyinlive:v2.0.3如果你希望加载自定义配置,先在宿主机准备一个 config.yaml,再把它挂载到容器中的 /app/config.yaml,并通过 --config 显式传入:
docker run --rm -p 1088:1088 \
-v $(pwd)/config.yaml:/app/config.yaml:ro \
ghcr.io/jwwsjlm/douyinlive:latest --config /app/config.yaml说明:
-v $(pwd)/config.yaml:/app/config.yaml:ro:把宿主机当前目录下的config.yaml挂载到容器内:ro:只读挂载,避免容器误改宿主机配置--config /app/config.yaml:显式指定程序读取这个配置文件
如果你希望容器长期后台运行,不要使用 --rm,建议改成:
docker run -d \
--name douyinlive \
--restart unless-stopped \
-p 1088:1088 \
-v $(pwd)/config.yaml:/app/config.yaml:ro \
ghcr.io/jwwsjlm/douyinlive:latest --config /app/config.yaml这样即使容器被删除或重建,宿主机上的 config.yaml 仍然保留,达到配置持久化的效果。
如果你后续不只想挂一个配置文件,也可以直接挂整个目录:
mkdir -p ./data
cp config.example.yaml ./data/config.yaml
docker run -d \
--name douyinlive \
--restart unless-stopped \
-p 1088:1088 \
-v $(pwd)/data:/app \
ghcr.io/jwwsjlm/douyinlive:latest --config /app/config.yaml这种方式更适合统一管理容器运行时使用到的文件。
项目已自带两个 compose 示例文件:
compose.yaml:挂载单个config.yamlcompose.data.yaml:挂载整个data目录
先准备配置文件:
cp config.example.yaml config.yaml然后直接启动:
docker compose up -d
docker compose logs -f
docker compose downcompose.yaml 内容如下:
services:
douyinlive:
image: ghcr.io/jwwsjlm/douyinlive:latest
container_name: douyinlive
restart: unless-stopped
ports:
- "1088:1088"
volumes:
- ./config.yaml:/app/config.yaml:ro
command: ["--config", "/app/config.yaml"]如果你想把配置统一收纳到目录里,先执行:
mkdir -p data
cp config.example.yaml data/config.yaml然后用下面命令启动:
docker compose -f compose.data.yaml up -d
docker compose -f compose.data.yaml logs -f
docker compose -f compose.data.yaml downcompose.data.yaml 内容如下:
services:
douyinlive:
image: ghcr.io/jwwsjlm/douyinlive:latest
container_name: douyinlive
restart: unless-stopped
ports:
- "1088:1088"
volumes:
- ./data:/app
command: ["--config", "/app/config.yaml"]此时你只需要保证宿主机存在:
./data/config.yaml
docker logs -f douyinlive
docker ps
docker stop douyinlive
docker rm -f douyinlive很多人第一次用会卡在这里。
这个程序启动时不需要在命令行传直播间号。
直播间标识是通过 WebSocket 路径传进去的:
ws://127.0.0.1:1088/ws/直播间标识
也就是说:
- 程序只负责启动本地服务
- 你连接哪个房间,是由
/ws/后面的内容决定的
一般就是你访问下面这个地址时,后面的那段:
https://live.douyin.com/xxxxx
这里的 xxxxx 就是你应该传给 /ws/ 的内容。
例如:
https://live.douyin.com/516466932480- 则连接:
ws://127.0.0.1:1088/ws/516466932480
- 则连接:
如果你传的是无效标识,服务端会关闭这个连接。
如果直播间暂时未开播:
- 本地 WebSocket 连接会保留
- 服务端会先返回一条“直播间未开播”的状态通知
- 然后按配置的时间间隔持续推送未开播状态
- 一旦检测到开播,就自动切回正常消息流
./douyinLive默认行为:
- 读取同目录下的
config.yaml(如果存在) - 如果没有配置文件,就使用默认值
- 默认端口:
1088
./douyinLive --port 1088./douyinLive --config ./config.yaml./douyinLive --unknown你可以创建一个 config.yaml 放在程序同目录下。
示例:
port: "1088"
unknown: false
monitor:
poll_interval: "15s"
notify_interval: "30s"
cookie:
douyin: ""
rooms:
# "516466932480": "ttwid=...; sessionid=..."项目里也自带了一个示例文件:
config.example.yaml
本地 WebSocket 服务端口。
默认值:
port: "1088"是否打印未知消息类型。
默认值:
unknown: false未开播时,服务端检查“是否已经开播”的时间间隔。
默认值:
monitor:
poll_interval: "15s"未开播时,服务端向本地 WebSocket 客户端重复推送状态通知的时间间隔。
默认值:
monitor:
notify_interval: "30s"客户端会收到类似:
{"type":"system","event":"live_status","live":false,"room_id":"516466932480","message":"直播间未开播","retry_interval_seconds":30}抖音默认 Cookie,可选。
没有单独配置某个直播间的 Cookie 时,会优先回退到这里。再往后才是自动获取的逻辑。
cookie:
douyin: "ttwid=...; sessionid=..."按直播间 ID 单独配置 Cookie,可选。
如果你要同时监听多个直播间,而且它们对应不同账号、不同登录态,就可以在这里分别配置。没有配置到的直播间,会自动回退使用 cookie.douyin。
cookie:
douyin: "默认 Cookie"
rooms:
"516466932480": "直播间 516466932480 专用 Cookie"
"123456789": "直播间 123456789 专用 Cookie"
"888888888": "直播间 888888888 专用 Cookie"一个更完整的例子:
port: "1088"
unknown: false
monitor:
poll_interval: "15s"
notify_interval: "30s"
cookie:
douyin: "默认 Cookie"
rooms:
"516466932480": "room A 的 Cookie"
"123456789": "room B 的 Cookie"Cookie 优先级:
WebSocket 临时 Cookie > 直播间 Cookie(cookie.rooms) > 默认 Cookie(cookie.douyin) > 自动获取
WebSocket 临时 Cookie 仅建议临时调试使用:
ws://127.0.0.1:1088/ws/直播间ID?cookie_b64=BASE64URL_COOKIE
也支持直接传 URL 编码后的 Cookie:
ws://127.0.0.1:1088/ws/直播间ID?cookie=URL_ENCODED_COOKIE
不是所有场景都必须填 Cookie。
你可以先不填,直接跑。
如果出现下面这些情况,再考虑补 Cookie:
- 某些直播间拿不到消息
- 请求被限制
- 页面返回结果异常
- 需要更稳定的登录态
- 浏览器打开:
https://live.douyin.com - 登录抖音
- 按
F12 - 打开
Network - 随便点一个请求
- 复制请求头里的
Cookie
然后填到:
cookie:
douyin: "你的完整 Cookie"你也可以直接把 douyinLive 作为 Go 库集成到你自己的项目中。
go get github.com/jwwsjlm/douyinLive/v2package main
import (
"log"
douyinlive "github.com/jwwsjlm/douyinLive/v2"
"github.com/jwwsjlm/douyinLive/v2/generated/new_douyin"
)
func main() {
// 直播间ID,从 https://live.douyin.com/xxxx 获取
roomID := "516466932480"
// 可选 Cookie,如果需要登录态可以传入,留空表示不使用
cookie := ""
// 创建实例
dl, err := douyinlive.NewDouyinLive(roomID, log.Default(), cookie)
if err != nil {
log.Fatalf("创建失败: %v", err)
return
}
// 订阅事件,所有抖音消息都会通过这个回调推送过来
dl.Subscribe(func(msg *new_douyin.Webcast_Im_Message) {
// 根据 msg.Method 判断消息类型,然后反序列化处理
log.Printf("收到消息 method=%s payload_len=%d\n", msg.Method, len(msg.Payload))
// 你可以在这里根据不同消息类型做相应处理
})
// 启动监听,会阻塞直到连接关闭
dl.Start()
}package main
import (
"log"
douyinlive "github.com/jwwsjlm/douyinLive/v2"
"github.com/jwwsjlm/douyinLive/v2/generated/new_douyin"
"github.com/jwwsjlm/douyinLive/v2/generated/douyin"
"google.golang.org/protobuf/proto"
)
func main() {
roomID := "516466932480"
dl, err := douyinlive.NewDouyinLive(roomID, log.Default(), "")
if err != nil {
log.Fatal(err)
}
dl.Subscribe(func(msg *new_douyin.Webcast_Im_Message) {
switch msg.Method {
case "WebcastChatMessage":
chat := &douyin.WebcastChatMessage{}
if err := proto.Unmarshal(msg.Payload, chat); err != nil {
log.Println(err)
return
}
// chat.Content 就是弹幕内容
// chat.User 就是发送用户信息
if chat.GetContent() != "" && chat.GetUser() != nil {
log.Printf("弹幕 [%s]: %s\n", chat.User.GetNickname(), chat.GetContent())
}
case "WebcastGiftMessage":
gift := &douyin.WebcastGiftMessage{}
if err := proto.Unmarshal(msg.Payload, gift); err != nil {
log.Println(err)
return
}
log.Printf("礼物: %s 赠送了 %s x%d\n",
gift.GetUser().GetNickname(),
gift.GetGift().GetName(),
gift.GetGiftCount(),
)
case "WebcastLikeMessage":
like := &douyin.WebcastLikeMessage{}
if err := proto.Unmarshal(msg.Payload, like); err != nil {
log.Println(err)
return
}
log.Printf("%s 点赞了直播间\n", like.GetUser().GetNickname())
}
})
dl.Start()
}更多消息类型可以参考 generated/douyin 包下的 protobuf 生成代码。
如果你直接运行独立服务,你的客户端只需要连本地 WebSocket 服务即可。
const ws = new WebSocket('ws://127.0.0.1:1088/ws/516466932480');
ws.onopen = () => {
console.log('已连接');
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('收到消息:', data);
if (data.event === 'live_status') {
if (data.live) {
console.log('状态通知: 直播间已开播');
} else if (data.ended) {
console.log(`状态通知: ${data.message},后续会继续按 ${data.retry_interval_seconds} 秒轮询`);
} else {
console.log(`状态通知: ${data.message},${data.retry_interval_seconds} 秒后重试`);
}
return;
}
switch (data.method) {
case 'WebcastChatMessage':
console.log(`弹幕: ${data.user.nickname} - ${data.content}`);
break;
case 'WebcastGiftMessage':
console.log(`礼物: ${data.user.nickname} 赠送了 ${data.gift.name}`);
break;
case 'WebcastLikeMessage':
console.log(`${data.user.nickname} 点赞了直播间`);
break;
default:
break;
}
};
ws.onclose = () => {
console.log('连接关闭');
};
ws.onerror = (err) => {
console.error('WebSocket 错误:', err);
};
// 可选:给本地服务发 ping,服务会回 pong
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send('ping');
}
}, 30000);服务端会把解析后的 protobuf 消息转成 JSON 文本发给你。
不同消息类型字段不完全一样,但都会包含对应消息内容。
另外会额外补一个字段:
livename:直播间名称
如果直播间还没开播,则会返回系统状态消息,例如:
{"type":"system","event":"live_status","live":false,"room_id":"516466932480","message":"直播间未开播","retry_interval_seconds":30}检测到开播时,也会先返回一条状态消息:
{"type":"system","event":"live_status","live":true,"room_id":"516466932480","message":"直播间已开播"}如果直播过程中下播,也会先返回一条状态消息:
{"type":"system","event":"live_status","live":false,"room_id":"516466932480","message":"直播间已下播","ended":true,"retry_interval_seconds":30}douyinLive/
├── cmd/main/ # 可执行程序入口
│ ├── main.go # 主程序
│ ├── app.go # HTTP / WebSocket 服务
│ ├── room.go # 房间与客户端管理
│ ├── config.go # 配置读取
│ └── WsHandler.go # WebSocket 事件处理
├── douyin.go # 核心抓取逻辑,对外库接口
├── sign/ # 签名与 Cookie 相关逻辑
├── jsScript/ # 签名脚本
├── protobuf/ # protobuf 定义
├── generated/ # 生成后的 protobuf 代码
├── utils/ # 工具函数
├── config.example.yaml # 配置示例
└── README.md
如果你需要:
- 获取抖音直播间实时弹幕
- 做自己的弹幕大屏
- 做直播互动统计
- 做礼物 / 点赞 / 关注监听
- 把抖音消息接进自己的系统
这个项目就比较合适。
本项目参考过这些项目和资料:
感谢原作者们的公开分享。
如果这个项目对你有帮助,欢迎点个 Star。