这个想法一开始很简单:让一个 QQ 小号替我盯着几个群,把消息收下来,免得每天爬楼。
真正动手之后才发现,从扫码登录到 WebSocket 握手,坑一个接一个,即便是AI辅助,前后还是折腾了很久。
现在链路终于跑通:NapCat 负责登录 QQ,采集器把群消息写进 SQLite,旁边还接了 AstrBot 做交互试验。把这套流程整理出来,方便以后重装,也给同样想折腾的人一个参考。
为安全起见,文中的服务器 IP、QQ 号和 Token 都做了脱敏。
正文
需求分析
需求本身并不复杂:一个真实 QQ 小号,7×24 在线,替我收集几个群的消息,最好还能在以后接上大模型做日报和简单交互。
限制条件也不少:
- 必须是真实 QQ,官方机器人的权限和场景都不太够用。
- 服务器是 4C 3.4G 的大陆机,已经装了 1Panel,尽量用 Docker 部署,别把系统环境搞乱。
- 管理面板和协议端口不能直接暴露公网。
- 采集要保持独立,后面接不接 AI、接哪个 AI,都不能影响消息落库。
- 以后可能扩展到多平台、多账号,结构上要留出余地。
选型上,NapCat 负责登录 QQ 并提供 OneBot v11 接口;采集器用 Python 写,直接连 NapCat 的 WebSocket;AstrBot 作为旁路,用来验证以后做交互机器人的可能性。三者互相独立,坏一个不至于全挂。
配置方案
整体架构
1 | QQ 群 / 好友 |
这里其实有两条并行链路:
- 采集器连接 NapCat 提供的 WebSocket 服务端,负责把消息写进数据库。
- AstrBot 反过来,由 NapCat 主动连出去,走反向 WebSocket。
- 停掉 AstrBot 不影响采集,停掉采集器也不影响 AstrBot。
环境与端口
| 项 | 值 |
|---|---|
| 服务器 | 1Panel / Ubuntu / 4C 3.4G |
| 公网 IP | 已脱敏 |
| NapCat 容器 | napcat,镜像 mlikiowa/napcat-docker:latest |
| NapCat 配置目录 | /opt/napcat/config |
| QQ 登录态 | /opt/napcat/QQ |
| 采集器目录 | /opt/qq-collector |
| 数据库 | /opt/qq-collector/data/messages.db |
| systemd 服务 | qq-collector.service |
| AstrBot 容器 | astrbot(1Panel 应用商店安装) |
| Docker 网络 | qq-net |
宿主机的 3001 端口已经被 1Panel 的 node 进程占用,所以 NapCat 容器端口改映射到 13001,容器内部仍然是 3001。这个坑后面还会反复出现,记住不要杀那个 node 进程。
Token 的存放位置:
- NapCat WS Token:
/opt/napcat/config/onebot11_<QQ号>.json的network.websocketServers[].token - 采集器 Token:
/opt/qq-collector/.env的ONEBOT_TOKEN - AstrBot Token:AstrBot 平台配置中的 token,要和 NapCat
websocketClients[].token一致
部署过程
NapCat
先建目录。容器内以 root 运行,所以这两个目录保持 root 所有即可:
1 | sudo mkdir -p /opt/napcat/config /opt/napcat/QQ |
Compose 文件如下,WebUI 和 WebSocket 都只绑定本机:
1 | services: |
启动后看一眼状态和日志,日志里会打印 WebUI Token:
1 | sudo docker ps --filter name=napcat |
WebUI 只监听本机,访问时走 SSH 隧道:
1 | ssh -N -o ServerAliveInterval=30 -o ServerAliveCountMax=3 \ |
浏览器打开 http://127.0.0.1:16099,输入日志里的 Token,然后用专用小号扫码登录。
登录态保存在 /opt/napcat/QQ,容器重建后一般不需要重新扫码,这个目录别删。
接下来在 WebUI 的「网络配置」里建一个 WebSocket 服务器,给采集器用:
| 项 | 值 |
|---|---|
| 名称 | collector |
| Host | 0.0.0.0 |
| Port | 3001 |
| Token | 自行生成,16 位以上 |
| 消息格式 | Array |
| 心跳间隔 | 30000 |
| 上报自身消息 | 关 |
| 强制推送事件 | 开 |
配置完可以先验一下握手:
1 | KEY=$(openssl rand -base64 16) |
期望看到 101 Switching Protocols,以及一条 lifecycle/connect 事件。
这里踩过一个很隐蔽的坑:Sec-WebSocket-Key 必须是合法值,手抄一个不合法的值会得到 400,响应里常见 Content-Length: 43。用 openssl rand -base64 16 生成就好。
采集器
先准备 Python 环境和依赖:
1 | sudo mkdir -p /opt/qq-collector/data |
安装时如果弹出 needrestart 的「Daemons using outdated libraries」,选 <Cancel>,不要顺手重启 docker.service 或 containerd,否则 1Panel 和所有容器都会一起重启。
采集器的主程序是 /opt/qq-collector/collector.py:
1 | #!/usr/bin/env python3 |
环境变量放在 /opt/qq-collector/.env:
1 | WS_URL=ws://127.0.0.1:13001 |
GROUPS 留空表示采集所有群,需要白名单时填逗号分隔的群号。文件权限记得收紧:
1 | chmod 600 /opt/qq-collector/.env |
用 systemd 常驻,文件是 /etc/systemd/system/qq-collector.service:
1 | [Unit] |
启用并观察:
1 | sudo systemctl daemon-reload |
验证分三步:日志出现 已连接 NapCat;主号私聊小号能看到 [私聊];群里发消息能看到 [群 <群号>] 昵称: 内容。最后查一下数据库:
1 | python3 - <<'PY' |
AstrBot(可选)
AstrBot 这一步只是验证以后能不能做交互机器人,不参与采集。
先把两个容器放进同一张 Docker 网络:
1 | sudo docker network create qq-net 2>/dev/null || true |
输出里应该同时有 napcat 和 astrbot。1Panel 重建容器后容器名可能变化,网络关系也可能丢,需要重新执行。
AstrBot 这边新建 OneBot v11 平台:
| 项 | 值 |
|---|---|
| 名称 | napcat |
| 启用 | 是 |
| 反向 WebSocket 主机 | 0.0.0.0 |
| 端口 | 6199 |
| Token | 与 NapCat 客户端一致 |
| 配置文件 | default |
保存后执行 sudo docker restart astrbot。
NapCat 这边新增一个 WebSocket 客户端:
| 项 | 值 |
|---|---|
| 名称 | astrbot |
| URL | ws://astrbot:6199/ws |
| Token | 与 AstrBot 平台一致 |
| 消息格式 | Array |
| 启用 | 是 |
注意别建错成「WebSocket 服务器」窗口,那个是采集器用的 collector。
验证看 AstrBot 日志:出现 aiocqhttp(OneBot v11) 适配器已连接。,再用主号私聊小号,日志里出现 [napcat(aiocqhttp)] 昵称/QQ号: 内容,就说明通了。没配模型时它不会回复,能收到消息就是成功。
这一步建议先保持“只收不回”。一旦配了模型,AstrBot 默认会回私聊和群里 @ 它的消息,必须先想好触发词和群白名单,否则很容易在采集群里乱说话。
日常运维
1 | # 容器与服务状态 |
几个不要碰的操作:
- 不要执行
systemctl restart docker,会连带重启 1Panel 和所有容器。 - 不要杀占用宿主 3001 的 node 进程,它属于 1Panel。
- 不要删除
/opt/napcat/QQ,否则要重新扫码。 - 1Panel 重建 NapCat 或 AstrBot 容器后,记得检查
qq-net是否还在。
常见问题整理:
| 现象 | 原因 | 处理 |
|---|---|---|
| 编排启动报端口被占用 | 宿主 3001 被 node 占用 | 宿主改映射 13001,容器内保持 3001 |
| WebUI 打不开 | 只绑了 127.0.0.1 |
走 SSH 隧道,不要改公网绑定 |
WS 握手 400,Content-Length: 43 |
Sec-WebSocket-Key 不合法 |
用 openssl rand -base64 16 生成 |
| WS 握手失败或立即断开 | Token 不一致 | 从配置中取 Token,URL 带 access_token |
NapCat 日志反复 403 |
AstrBot Token 不一致或未重载 | 对齐 Token 后 docker restart astrbot |
| AstrBot 没有「适配器已连接」 | 网络不通、客户端未启用、URL 错误 | 检查 qq-net、URL、启用状态 |
采集器 Permission denied |
目录属 root | sudo chown -R ubuntu:ubuntu /opt/qq-collector |
| 提取 Token 长度为 0 | glob 没匹配到配置文件 | 用 grep -l '3001' /opt/napcat/config/*.json 定位 |
| WebUI 显示离线但容器 Up | WebUI 会话断开,或隧道断了 | 刷新页面/重开隧道;采集器 active 就不影响 |
| 1Panel 重建后两边连不上 | 容器名变化或脱离 qq-net |
重新 docker network connect,URL 用实际容器名 |
安全与合规
- 只用专用小号,不集中、不高频加群,保持固定环境。
- 加群前告知群主采集范围、用途和保留期;群消息属于群友个人数据。
- NapCat WebUI 只绑定本机,通过 SSH 隧道访问。
- 6185、6199 在云安全组关闭入站放行。Docker 会绕过 ufw,以安全组为准。
- Token 不进聊天、不进截图。疑似泄露时,NapCat WS Token、采集器
.env、AstrBot Token 三处一起轮换。 - 大模型 API Key 只写在服务器
.env中并chmod 600,不要贴到聊天。 - 数据库设置保留期(例如 30 天)并限制访问,备份文件也按敏感数据处理。
后续计划
采集跑通只是第一步,后面还想做几件事:
- DeepSeek 日报:按天、按群读取 SQLite,过滤纯图片和空消息,控制每群上下文长度,调用
deepseek-chat生成 Markdown,输出到/opt/qq-collector/reports/,先手动跑通再加定时任务。 - 加群策略:一次加一两个群,隔天再继续,先观察账号状态;需要严格限定时把群号填进
GROUPS。 - 数据保留与备份:定期清理 N 天前的数据,用
sqlite3 .backup做一致性备份,再转移到独立目录。 - 交互规则:给 AstrBot 定触发词、群白名单和人格,避免它在采集群里抢话。
- 多平台适配:把采集和推送拆开,未来接 Telegram、Discord 或多账号时统一消息表,用
account_id区分来源。
结语
整个流程最耗时的不是写代码,而是几个看起来很小的配置:端口冲突、WebSocket Key 格式、Token 不一致导致的 403,每一个都能让人卡半天。
现在采集器已经常驻,NapCat 登录态也稳定,AstrBot 这条旁路验证完毕但暂时不配模型。下一步就是把日报做出来,让这个值守的小号真正开始产出,而不是只做一个安静的记录器。