文

用 AstrBot + NapCat 把大模型接入 QQ

用 AstrBot + NapCat 把大模型接入 QQ


注意:下面这套东西不要用自己的主号跑,有被封的风险。用小号。

1. 项目介绍

现在大模型的 API 很便宜,搭一个能上 QQ 的机器人成本不高,就想着自己做一个试试。

整体是三层:

QQ 客户端(手机)
    ↓ 扫码登录
协议端(NapCat / SnowLuma)   ← 登录 QQ,把协议翻译成 OneBot 标准接口
    ↓ WebSocket(OneBot v11)
AstrBot                       ← 接消息、调模型、管人格
    ↓ HTTP
大模型 API

说人话:

  • 协议端:让一个程序能以 QQ 号的身份登录,收发消息。没有它,AstrBot 拿不到 QQ 的消息。
  • AstrBot:拿到消息之后干什么——调模型、发回复。
  • QQ 客户端:用来跟机器人聊天、扫码登录。

协议端有两个选择,NapCat 和 SnowLuma,功能一样,选一个就行。


2. AstrBot

AstrBot 是一个支持插件、多平台的大模型机器人框架。

  • GitHub:https://github.com/AstrBotDevs/AstrBot
  • 文档:https://astrbot.app/

2.1 基础安装

Docker 一条命令:

docker run -d --name astrbot \
  --network host \
  -v /www/wwwroot/astrbot/data:/AstrBot/data \
  soulter/astrbot:latest

--network host 是为了省掉端口映射的麻烦,后面协议端连它的时候直接用 127.0.0.1。

起来之后浏览器打开 http://服务器IP:6185。

2026-10-31 修正:上面这句只在你把 6185 放行到公网的条件下才成立。后来我把这个做法改了——6185 和 6099 都不要对公网开,走 SSH 隧道进来:ssh -N -L 16185:127.0.0.1:6185 用户名@服务器IP,然后开 127.0.0.1:16185。理由在《把大肥鱼搬上腾讯云》第三节。

默认账号密码在容器日志里:

docker logs astrbot 2>&1 | grep -i password

登进去第一件事:改密码。

2.2 配置模型

AstrBot 里叫「服务提供商」。进「配置 → 服务提供商 → 新增」:

类型      OpenAI 兼容
API Base  https://api.deepseek.com/v1
API Key   你的 key
模型      deepseek-chat

保存之后在「默认模型」里选中它。

2.3 配置 QQ 平台

「配置 → 平台 → 新增」:

类型            aiocqhttp
名称            napcat
反向 WS 主机     0.0.0.0
反向 WS 端口     6199
Token           (留空,或者跟协议端填一致)

这里有个坑:6199 是 AstrBot 的 OneBot 反向 WS 端口,不是 6185。

6185 是给人看的 WebUI,6199 才是给协议端连的。

保存之后重启:

docker restart astrbot

3. NapCatQQ

NapCat 是基于 TypeScript 的 QQ 协议端,把 QQ 协议转成 OneBot 标准接口。

  • GitHub:https://github.com/NapNeko/NapCatQQ
  • 文档:https://napneko.github.io/

3.1 安装

docker run -d --name napcat \
  --network host \
  -e NAPCAT_GID=0 \
  -v /www/wwwroot/napcat/config:/app/napcat/config \
  -v /www/wwwroot/napcat/qq:/app/.config/QQ \
  mlikiowa/napcat-docker:latest

-e NAPCAT_GID=0 那句的意思是让容器里跑出来的文件属主跟着 root 走。但宿主机那两个挂载目录的属主也得对得上,否则 NapCat 读不到登录态,表现是「每次重启都让你重新扫码」——十个里有九个会当成风控,其实是权限。

两个挂载点很关键:

config  协议配置(OneBot 连接信息)
qq      登录态(不挂的话容器重启就要重新扫码)

3.2 扫码登录

docker logs napcat 2>&1 | tail -40

日志里会打印一个字符画的二维码,或者告诉你二维码图片存在哪:

二维码已保存到 /app/napcat/cache/qrcode.png

拷出来:

docker cp napcat:/app/napcat/cache/qrcode.png ./qrcode.png

用手机 QQ 扫,然后手机上点「确认登录」。

第一次登录可能触发设备验证(短信或者好友辅助),这个是正常的。

3.3 连接 AstrBot

NapCat 的 WebUI 在 http://服务器IP:6099。

网络配置 → 新建 → WebSocket 客户端:

名称        astrbot
URL         ws://127.0.0.1:6199/ws
重连间隔     30000
启用心跳     是

保存之后看 AstrBot 的日志:

docker logs -f astrbot

连上会打印类似这样:

[aiocqhttp] 1234567890/你的昵称: 你好

前面是 QQ 号,后面是昵称。能刷出这行就是通了。


4. SnowLuma

SnowLuma 是另一个 QQ 协议端。功能和 NapCat 一样——把 QQ 变成 OneBot v11,AstrBot 那头一行都不用改。

但后来我把它换成了默认。换的理由不是功能,是它走的路子不一样;而那个路子,恰好更抗踢。

4.1 它和 NapCat 差在哪

先把一个常见的误传说掉:两个都在跑真的 QQ。不是「一个用协议、一个用客户端」,两个都是 NTQQ 的客户端,一个不少。

NapCat 跑的是一个拆掉了显示层的 QQ。Shell 模式自己把 NTQQ 引擎拉起来(loadQQWrapper,启动时带 --no-sandbox),只是不渲染窗口。它拿消息走两条路:调 QQ 内部的 Node 模块,以及 Hook 原生包——登录时抓数据库密码用的就是后面这条。官网那句「天生无头、不依赖 Electron、内存 50~100MB」是真的,省掉的正是渲染和显示那一层。

SnowLuma 跑的是一个原样的 QQ。镜像里装了 Linux QQ,容器起来先把 QQ 拉起来,配 Xvfb 开出虚拟显示,再往 QQ 进程里注入 hook,读它已经解析好的内部数据包,翻成 OneBot v11。它不省任何一层。

区别就在这一句:NapCat 拆掉显示层去跑 QQ,SnowLuma 原样跑 QQ。

那为什么拆掉显示层会更容易掉线?我的理解是:风控判断的不是「你发了什么」,是「这个设备像不像一个正常人在用」。SnowLuma 那边,登录是在真 QQ 的界面里扫的,会话由原装客户端自己维持,渲染进程、显示层、心跳全是 QQ 自己的——它和一个坐在电脑前的人没有区别。NapCat 把这些拆了,只留引擎和消息通道。

我不敢说这就是它更稳的全部原因。账号本身的质量、常用地、发消息的节奏,影响可能都比协议端大得多。但在我这台机器上,换过去之后确实没再出现过那种「日志显示在线、消息发不出去」的状态。

吃内存这件事也能对上。SnowLuma 是真的在跑一个完整的 QQ:渲染进程、Xvfb、VNC 一个不少,单账号 300MB 起,多开能到 1.2G。NapCat 那 50~100MB 省掉的,省的就是这些。

还有一个顺带的差别,是扫码。NapCat 把二维码打进日志,你得 docker logs 里捞、或者 docker cp 出来再扫。SnowLuma 是 noVNC 网页远程桌面:你看到的是真的 QQ 登录窗口,鼠标点上去扫。

4.2 安装

docker run -d --name snowluma \
  --network host \
  --restart unless-stopped \
  -e TZ=Asia/Shanghai \
  -v /www/wwwroot/snowluma/data:/app/data \
  <镜像名>

镜像名以它 Releases 页面上写的为准。它的发行包分 full 和 lite:full 自带 Node 运行时,lite 要你机器上本来就有 Node 22 以上。Docker 镜像走的是 full 那一套,这点不用你操心。

别用 latest——协议端发版快,某次拉到坏版本,表现就是「某天开始老掉线」,而且你根本不会想到是镜像的问题。

它有四个端口,一个都别混:

  • 5099 —— WebUI。配 OneBot 连接、看日志、看账号状态
  • 6081 —— noVNC。网页版远程桌面,扫码登录在这里,不在 WebUI 里
  • 3000 / 3001 —— OneBot 的 HTTP / WS,默认正向

4.3 连接

这里和 NapCat 不是一回事,得说清楚:NapCat 的 WebUI 在 6099、反向连 AstrBot 的 6199;SnowLuma 的 WebUI 在 5099、正向 WS 在 3001。数字没一个对得上。

最省事的走法还是反向 WS:WebUI → 网络配置 → 新建 WebSocket 客户端,填 ws://127.0.0.1:6199/ws。AstrBot 那头不动。

配完去 http://服务器IP:6081 扫码。看到的是 QQ 的登录窗口,手机扫完还要在手机上点「确认登录」。登录态存在容器里,之后重启不用再扫——前提是 data 那个目录挂出来了。

反过来让 AstrBot 主动连 ws://127.0.0.1:3001/ 也能通,但要改 AstrBot 的平台配置。没必要。

注意:换协议端的时候先把旧的停掉(docker stop napcat),不然两个都连同一个 6199,AstrBot 会同时收到两份事件,表现为机器人回两遍。

4.4 换之前要知道的三件事

  • 它不认 NapCat 的登录态。napcat/qq 拷过去没用,必须重新扫码。换的那天,手机得在手边。
  • 它更吃内存。单账号常驻 300MB 到 1.2G。2G 的机器上换过去会很紧,4G 才舒服。
  • 它不是完全开源。源码可见,但核心 native 组件闭源,许可写着非商业。自己玩没问题,别拿去做生意。

5. 常见问题

5.1 AstrBot 一直显示重连中

先看协议端到底登录没:

docker logs napcat 2>&1 | grep -iE "登录|online|account"

协议端没登录成功的话,AstrBot 那头怎么配都连不上。先解决登录,再解决连接。

5.2 二维码扫了没反应

  • 二维码有效期大概两分钟,过期要重新取
  • 手机上要点「确认登录」,光扫不够
  • 日志里有「二维码已过期」就重新跑一遍流程

5.3 容器重启后要重新扫码

qq 那个目录没挂载,或者挂载路径写错了。

NapCat 的登录态在容器内的 /app/.config/QQ,务必挂出来。

5.4 消息发不出去,但日志显示在线

这个基本就是被风控了。

表现:

  • 登录成功,能收到消息
  • 发消息失败,或者发出去对方收不到
  • 过一段时间被踢下线

没有技术解法,只能换号,或者减少使用频率。

5.5 换协议端要重配吗

不用。 两个都走 OneBot v11,AstrBot 那头不用改。

只要:

  1. 停掉旧的(docker stop napcat)
  2. 起新的,连同一个 ws://127.0.0.1:6199/ws
  3. 重新扫码

5.6 改了配置,重启之后又变回去了

这个坑不报错,只会让你以为是自己记错了。

# 你以为改了配置
vim /www/wwwroot/astrbot/data/cmd_config.json
# 重启让它生效
docker restart astrbot
# 结果:被改回去了

AstrBot 启动时会把内存里那份配置写回 cmd_config.json。restart 是「停掉 → 启动」,启动那一刻它拿内存里的旧值覆盖你刚改的文件,而且不报错。

正确顺序是先停、再改、再起:

docker stop astrbot
vim /www/wwwroot/astrbot/data/cmd_config.json
docker start astrbot

stop 和 restart 的差别就在这。踩的时候是改 possibility_reply,写成 0.05,读出来是 0.02——查了半天以为是配置项名字写错了。


6. 几个实践建议

① 容器都用 --network host

省掉端口映射的麻烦。代价是要自己注意端口冲突:

6185   AstrBot WebUI
6199   AstrBot 的 OneBot WS(协议端连这个)
6099   NapCat WebUI
5099   SnowLuma WebUI
6081   SnowLuma 的 noVNC(扫码在这里)
3000   SnowLuma 的 OneBot HTTP API
3001   SnowLuma 的 OneBot WS

② 登录态一定要挂载

不挂的话每次重启都要扫码,而频繁扫码本身就会让风控更容易注意到你。

③ 别频繁重启协议端

重启 = 重新登录 = 一次新的设备握手。

稳定跑着比反复折腾安全。

④ 出问题先看协议端日志

大部分「连不上」的答案都在协议端那头,不在 AstrBot。


7. 参考


写于 2026 年 10 月

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注