摘要:用 NapCat 把 QQ 接进自动化系统,掉线二十来次后,我把根因、排查流程和加固方案全整理出来了。核心结论:八成不是网络问题,也不是腾讯风控,而是你自己的守护脚本在背后捅刀子。更离谱的是——文章刚写完,我又被打脸了,于是又爆肝一个通宵,才有了文末的「终极重构版」。
标签:
NapCatOneBotQQ机器人运维踩坑Windows适用环境:Windows 11 + NapCat Shell 无头模式 + QQ 9.9.33-52230
关于作者:我是一名在校大学生,平时喜欢折腾 QQ 机器人、自动化脚本这类东西。这篇文章是纯个人踩坑记录,不是教程,也不是什么权威结论——就是把我自己掉线二十来次攒下的经验摊开给你看。如果对你有用,欢迎评论区聊聊你踩过的坑;如果有说得不对的地方,也请不吝指正,我还在学。
先说结论:如果你也在用 NapCat 把 QQ 接进自动化系统,然后发现它老是莫名其妙掉线——八成不是网络问题,也不是腾讯风控,而是你自己的守护脚本在背后捅刀子。
这篇文章是我自己踩坑的记录。环境是 Windows 11,NapCat 走 Shell 无头模式,QQ 版本 9.9.33-52230。前后掉线二十来次,每一次都留了日志和进程快照,下面写的每一条根因,我都能翻出对应的证据。
一、先记住一句话:别信 online 字段
⚠️ 作者打脸预警(更新):写完这篇文章的第二天,我就被打脸了!事实证明,我在这篇文章前半部分深信不疑的某个「核心判断逻辑」,其实是个天坑,而真正的内鬼竟然是我自己的……
为了保留真实的排坑全过程,前八章原文我不做修改——强烈建议大家当作「错题本」看,并一定要读到文末的**「终极重构版」**,那里有真正的答案和彻底断根的自愈方案。
我一开始排查,最蠢的做法就是去看 get_status 返回的 online。
看到 online:true 就以为没事了,结果消息发出去全是失败。后来才明白:HTTP 适配器活着,不代表 QQ 登录着。 这俩是两码事。
真正靠谱的判据顺序是:
日志 > 端口 > API 字段
get_status 那个 online 字段,我愿称之为「假在线」——它只告诉你 Node 进程还喘着气,至于 QQ 协议层(NTQQ Core)是不是已经断线重连失败,它压根不知道。我遇到过好几次「端口通着、online:true、但发消息全败」的情况,我管这叫脑死亡。
那看什么?看三个端口:
| 端口 | 干嘛的 | 我的判据 |
|---|---|---|
| 3000 | HTTP API | 不通 = 服务根本没起来 |
| 3001 | WebSocket | 不通 = 事件通道断了 |
| 6099 | WebUI | 通 = 进程活着(但绝不代表登录着) |
这张表是我用血换来的。
尤其是最后一行请死死记住:6099 端口通,只能说明 Node 进程没死,别的什么都说明不了!
二、掉线分几种?我总结了一张「对号入座」表
掉线不是一种病,得先分类。我现在的习惯是:先查端口,再对号入座。
| 你看到的现象 | 真实类型 | 怎么办 |
|---|---|---|
| 6099 有,3000/3001 没有 | 卡在登录 | 看日志,多半是登录态失效 |
三端口齐全 + online=false | 注入丢失 | 重启注入就好,不用扫码 |
三端口齐全 + 重启后还是 online=false | 真·登录态失效 | 认命,扫码吧 |
三端口全无 + 没有 node.exe | 进程崩了/没启动 | 重新拉起来 |
三端口齐全 + online=true 但发消息全败 | 脑死亡 | 让看门狗 L7 探测去触发重启 |
这张表里,第二行是我踩了最久的坑。「三端口齐全 + online=false」不等于要扫码——它只是注入链路断了,登录态(数据目录)还在,重新注入就能复用旧登录态,免扫码自愈。
我当初把「online=false 就必须扫码」写进了自己的笔记,后来被实测打脸,又灰溜溜地改了回来。结论下太早,是要还的。
三、几个把我折磨到凌晨的坑
坑 1:登录态失效(这个最高频,也最没脾气)
症状很典型:6099 有 / 3000·3001 无,日志里躺着这么几行:
正在快速登录 <你的QQ号>
[error] 快速登录错误:登录态已失效,请重新登录!
请扫描下面的二维码
原因就是腾讯风控把登录态吊销了——换 IP、换设备、协议异常,或者账号被判定成「外挂」,都会触发。
处理顺序我建议这样:
- 先试免扫码重启(
-q快速登录,复用旧登录态); - 重启后还是回退二维码 → 那就是真被吊销了,必须扫码;
- 扫码走 WebUI(
http://127.0.0.1:6099/webui?token=<你的token>),别用控制台那个二维码——它有效期只有 2 分钟,人根本来不及扫。
⚠️ 这里有个死循环陷阱,我差点把号玩没:如果 -q 快速登录失败,NapCat 会直接退出(Exit Code > 0)。这时候如果你的守护脚本傻乎乎地再拉起来、再 -q、再失败……就会陷入「频繁尝试无效登录」的死循环,极易触发腾讯风控,账号被临时冻结。
所以守护脚本必须做失败熔断:连续 3 次快速登录失败,就停下来告警,等人来扫码。别让它自己在那儿死磕。
坑 2:注入版和 Shell 版在抢 Token(这个最隐蔽)
这个坑我找了好久。现象是 Shell 模式反复掉线,-q 免扫码也失效。
根因是:桌面注入版 QQ 和 Shell 无头模式,在抢同一个账号的 Token。 桌面 QQ 一登录,服务端就把 Shell 的登录态吊销了,Shell 回退二维码,卡死。
我最后顺着进程树挖出来的元凶链是这样的:
开机自启项(注册表 Run)
→ 掉线看门狗脚本
→ launcher-user.bat(注入版)
→ NapCatWinBootMain.exe 注入 QQ.exe
→ 抢 Token
→ Shell 被踢
说白了,就是我自己挂的开机自启看门狗,偷偷把注入版又拉起来了,然后它把我的 Shell 顶掉了。凶手是我自己。
处理办法:停掉注入版看门狗 → taskkill /T /F 连根拔起注入版进程树 → 物理隔离注入器(改名保留,方便回滚):
NapCatWinBootMain.exe→.disabledNapCatWinBootHook.dll→.disabledlauncher-user.bat/launcher-win10-user.bat→.disabled
然后把看门狗改成 Shell 版(node.exe ./index.js -q <QQ号>)。
坑 3:看门狗用旧逻辑重启,把 -q 弄丢了
这个坑最气人,因为它纯粹是我自己懒。
现象:掉线后自动重启了,但重启完卡在二维码。日志里明晃晃写着:
没有 -q 指令指定快速登录,将使用二维码登录方式
原因就一句话:看门狗进程是「改代码之前」启动的旧进程。
我明明已经把看门狗代码改成带 -q 了,但跑在内存里的那个进程还是老的。这就是那句老话——「代码改了 ≠ 进程改了」。
查法很简单,看运行中进程的实际命令行:
Get-CimInstance Win32_Process -Filter "Name='pythonw.exe'" | Select ProcessId,CommandLine
然后对比一下:看门狗进程的启动时间,是不是晚于你改代码的时间?如果早于,那就是它了——杀掉旧进程,重启看门狗。
坑 4:桌面注入版结构性崩溃(历史遗留,现在已经被 Shell 模式根治了)
这个是我最早期的噩梦。QQ 反复崩溃重启,主控脚本报 WinError 10061。
证据在 %APPDATA%\QQ\Crashpad\log\global\bugly_log\ 里——每次 QQ 重启,都对应一条 bugly 崩溃上报。
原因:NapCat 桌面注入用的是 Inline Hook 去定位 QQNT 内部函数。QQ 版本一变,内存偏移就错位,Hook 打到错误地址,直接 EXCEPTION_ACCESS_VIOLATION (0xC0000005),QQ 崩 → Crashpad 自动重启 → 再崩 → 死亡循环。
这个坑的教训是:别在「寄生」这个结构上修修补补。 后来我直接改用 Shell 无头模式,不依赖桌面 QQ 进程,从结构上把这类掉线消灭了。
坑 5:WebUI 二维码僵死
现象:WebUI 扫码一直提示「二维码过期」。
证据:cache\qrcode.png 的时间戳停滞超过 2 分钟(正常应该每 1~2 分钟自动刷新一次)。
我当时的处理是:果断放弃 WebUI 扫码,改走免扫码重启。 别跟一个僵死的二维码较劲。
坑 6:NapCat Shell 版本太旧
现象:被腾讯判定为「外挂」,强制下线。
原因:Shell 版本旧(比如 9.9.31-49738),Hook 内存偏移错位,被风控。
处理:升级 NapCat Shell 到官方推荐版本(我升到了 v4.18.30),QQ 版本保持官方推荐值不动。
⚠️ 这里我犯过一个想当然的错:一开始以为「QQ 9.9.33 太新,NapCat 不兼容」,差点去降级 QQ。结果一查官方文档——NapCat v4.18.30 推荐的 QQ 就是 9.9.33-52230,问题根本不在 QQ,而在 Shell 太旧。先查官方推荐版本,别自己脑补。
坑 7:残留端口假死和文件锁
现象:注入版崩溃后直接启动 Shell 版,报 EADDRINUSE(端口占用)或者 LevelDB/SQLite locked。
原因:注入版崩溃后,残留的不只是进程,还有:
- 网络端口的 TIME_WAIT 状态(默认保留 120 秒);
- 本地数据库的文件锁(
.lock)。
处理:启动 node.exe 之前,加两步清理:
- 断开端口死锁:检测并强制释放 3000 / 3001 / 6099;
- 清理缓存锁:删掉
D:\NapCat\config\session\或对应账号目录下的.lock临时锁文件(只删锁文件,核心配置别动),防止 Shell 接管时死锁崩溃。
四、我现在的排查流程
被坑多了,我现在的排查肌肉记忆就这几步,按顺序来:
先扫一眼端口(Get-NetTCPConnection -State Listen -LocalPort 3000,3001,6099),对上号;再摸一下进程看有没有带 -q(Get-CimInstance Win32_Process -Filter "Name='node.exe'");最后直接去 Tail 日志——切记必须查 boot_out.log 的最后 500 行,不然报错早被淹没了。如果是脑死亡,就补一发 Invoke-RestMethod http://127.0.0.1:3000/get_status 探活,必须 online=true 且 good=true 才算健康。
至于 qrcode.png 的时间戳,顺手看一眼就行:停滞超过 2 分钟,基本就是二维码僵死了。
最后按分类处理:注入丢失就重启注入(免扫码);登录态失效先试免扫码重启,失败再 WebUI 扫码;看门狗是旧进程就杀掉重启;脑死亡就触发看门狗重启。
那个「Tail 500」是有讲究的:NapCat 冷启动特别话痨,15 秒能刷 200~300 行,你 Tail 50 行的话,关键报错早被淹没了。我第一次就是 Tail 太少,盯着屏幕找了半天没找到,白折腾。
五、常用命令(我直接存成快捷方式了)
# 查端口
Get-NetTCPConnection -State Listen -LocalPort 3000,3001,6099
# 查进程命令行
Get-CimInstance Win32_Process -Filter "Name='node.exe'" | Select ProcessId,CommandLine
# 查登录状态(L7 健康探测)
Invoke-RestMethod http://127.0.0.1:3000/get_status
# 精确杀进程(按命令行匹配,避免误杀私人 QQ)
Get-CimInstance Win32_Process | Where-Object {
($_.Name -eq 'QQ.exe' -or $_.Name -eq 'QQEX.exe' -or $_.Name -eq 'NapCatWinBootMain.exe') -and
$_.CommandLine -match 'NapCat'
} | Invoke-CimMethod -MethodName Terminate
# 杀进程树(Stop-Process 杀不掉 Chromium 子进程)
taskkill /T /F /PID <PID>
# 启动 Shell 版
cd /d "D:\NapCat" && node.exe ./index.js -q <QQ号>
六、血泪避坑清单
这些每一条我都真金白银踩过,按重要性排:
get_status的online字段不可信——必须交叉验证端口和日志。6099 通≠ 登录着——WebUI 活着只说明进程没死。- 杀进程必须
taskkill /T /F——Stop-Process杀不掉 Chromium 子进程,孤儿进程会死握着.lock不放。 - 端口等待别只写
-State Listen——Listen 瞬间消失,但 TIME_WAIT 默认保留 120 秒,照样EADDRINUSE。 - Tail 500,不是 50——NapCat 冷启动 15 秒刷 200~300 行。
- 「代码改了 ≠ 进程改了」——改完代码必须重启进程,并核对启动时间。
- 控制台二维码有效期只有 2 分钟——用 WebUI 扫码。
- 协议层别乱改 iPad/MacOS——会触发高危风控;真要改,改 Watch(platform=2)。
- 强烈建议用独立小号——别拿主力号冒险,被牵连封禁哭都来不及。
- 伴随现象 ≠ 因果——时间接近不代表因果。我曾经把「热更新」误判成掉线真凶,后来深挖
updater.log才发现 hotUpdate 每次都返回「无需更新」,curVersion从 8/19 到 9/30 一直是9.9.33-52230,压根没变过。时间接近,纯属巧合。 taskkill /IM QQ.exe /F会误杀——CMD 原生taskkill匹配不了命令行,同机要是有私人 QQ 或别的机器人,会被一起干掉。必须用 PowerShellGet-CimInstance按 CommandLine 精确狙击。- 裸跑
node index.js是运维大忌——终端被误关、RDP 注销,进程直接挂。得守护进程化。 -q快速登录失败会死循环——盲目重试触发风控,必须做失败熔断 + 告警。
七、我最后是怎么加固的
光会排查不够,得让它别再掉。我做了这几件事:
- 用 Shell 无头模式,不用桌面注入版(结构性根治)。
- 看门狗必须带
-q,而且改完代码必须重启看门狗进程。 - 物理隔离注入器(改名
.disabled),防止被误拉起。 - 独立小号专供机器人,平台保持默认 PC 端。
- 养号期别频繁重启——等登录态稳定了再启用守护脚本。
- 校园网 23:30 断网——提前切手机热点,避免频繁换 IP 触发风控。
- 守护进程化——放弃
.bat裸跑,用 NSSM 或 PM2 把node.exe封装成 Windows 后台服务,勾上自动重启,杜绝控制台黑框误触和 RDP 注销掉线。 - 失败熔断 + 二维码告警推流——连续 3 次快速登录失败就停止自动重启;检测到
qrcode.png更新时,通过 ServerChan / PushPlus / 飞书钉钉 Webhook 把二维码推到手机,而不是让机器人默默「半死」。
几个具体的加固细节
精确杀进程:CMD 原生 taskkill /IM QQ.exe /F 匹配不了命令行,同机有私人 QQ 或别的机器人时,直接 kill 会严重误杀。必须用 PowerShell / WMI 按命令行精确狙击:
Get-CimInstance Win32_Process | Where-Object {
($_.Name -eq 'QQ.exe' -or $_.Name -eq 'QQEX.exe' -or $_.Name -eq 'NapCatWinBootMain.exe') -and
$_.CommandLine -match 'NapCat'
} | Invoke-CimMethod -MethodName Terminate
守护进程化:cd /d D:\NapCat && node.exe ./index.js -q <QQ号> 直接在 CMD 裸跑是大忌——终端被误关、RDP 会话注销,进程直接挂;环境变量缺 Node,启动失败。改用 NSSM 或 PM2 封装成 Windows 后台服务:
- NSSM 配置:
Path→ 绝对路径的node.exeArguments→index.js -q <QQ号>AppDirectory→D:\NapCat- 勾选自动重启
防 -q 死循环:当 qrcode.png 刚生成(卡在等扫码),说明 Token/Ticket 已经失效或被挤下线。这时候盲目 node index.js -q → 快速登录失败 → 退出 → 守护脚本再拉起 → 死循环 → 触发风控 → 账号被冻结。加固:
- 启动后状态监控:别只等 15 秒看端口,必须检查日志里有没有
Token expired或Waiting for QR code scan; - 失败熔断:连续 3 次快速登录失败 → 停止自动重启,转人工;
- 二维码告警推流:检测到
qrcode.png更新 → 通过 ServerChan / PushPlus / 飞书钉钉 Webhook 推到手机。
L7 应用层健康探测:「端口存活探测」太浅——Node 进程活着、3000 端口在监听,但 QQ 协议层可能已经断线重连失败(脑死亡),端口通着但发消息全败。升级成 L7 探测:看门狗发 HTTP GET http://127.0.0.1:3000/get_status,解析 JSON,必须同时满足 "online": true 且 "good": true 才算健康;连续 3 次 false 就触发重启。
# 看门狗 L7 探测示例
$r = Invoke-RestMethod http://127.0.0.1:3000/get_status
if ($r.online -and $r.good) { "HEALTHY" } else { "UNHEALTHY" }
八、一个我到现在也没完全搞明白的事
最后留个尾巴,也算诚实交代:6099 端口有时候要等好几秒才通,具体为啥我到现在也没彻底搞懂。我怀疑是 WebUI 初始化慢,但没去翻源码。反正我在启动脚本里加了个 sleep 5 就绕过去了,能用就行,先不管了。
如果你知道原因,欢迎在评论区告诉我。
【更新:防打脸终极重构版】
上面这八章,是我「以为自己搞明白了」的时候写的。下面这一章,是我被打脸之后,又爆肝一个通宵的成果。真正的答案在这里。
九、打脸来得太快:文章刚写完,QQ 又掉了
说来你可能不信。
这篇文章的初稿,是我熬了半个月、掉线二十来次之后,一个字一个字敲出来的。敲完最后一个句号的时候,我甚至有点小得意——「总算把这破事整明白了」。
然后,打脸就来了。
就在我按下「保存」之后没多久,QQ 又又又掉线了。
哎,我真服了。
那一刻我盯着屏幕,脑子里只有一个念头:我写的那些「加固方案」,到底加了个寂寞?
没办法,只能又爆肝一个通宵。但这一次,我没有再「头痛医头」,而是把整个自愈链路从头到尾重写了一遍。
9.1 破案了:不是腾讯太强,是我自己的看门狗太蠢
我之前的看门狗,是靠扫日志关键字判断「是不是掉线了」的:日志里出现 KickedOffLine、登录态失效 这些词,就认为掉线了,然后重启。
听起来没毛病,对吧?
问题出在:日志是「历史累积」的。 一条 KickedOffLine 记录,会一直躺在日志文件里。看门狗每次扫日志尾部,都会扫到这条「旧伤疤」,于是误以为又掉线了,然后疯狂重启——重启完其实是在线的,但它还是觉得「没恢复」,继续重启……
这就是我这次掉线的真相:不是 QQ 掉了,是我的看门狗「以为」QQ 掉了。
回头看,这次掉线的根因,其实特别讽刺:不是腾讯太强,是我自己的看门狗太蠢。 它拿着「历史日志」当「实时状态」,自己吓自己,然后疯狂重启,把好好的号折腾到掉线。
这让我想起原文里我自己写的那句话——「八成不是网络问题,也不是腾讯风控,而是你自己的守护脚本在背后捅刀子。」 结果这次,捅刀子的还是我自己的脚本。只不过上次是「丢了 -q」,这次是「误判掉线」。
9.2 判据换血:不再猜,直接问 API
想通这一点之后,我把判断逻辑彻底换了:不再猜,直接问。
NapCat 提供了 HTTP 接口 get_status,返回里有个 online 字段。我让看门狗直接调这个接口:
- 返回
True→ 真在线,啥也不用做; - 返回
False→ 真掉线,触发自愈; - 返回
None(接口都调不通)→ 进程可能挂了,另作处理。
日志关键字?降级成「辅助线索」,只用来参考,不再作为判断依据。
⚠️ 这里我要修正一下原文第一节的说法。原文我说「别信 online 字段」,那是在讲「端口通 ≠ 登录着」这个场景——那时候我混淆了「HTTP 适配器活着」和「QQ 登录着」。但这次我搞明白了:
get_status的online字段,恰恰是判断 QQ 协议层是否真的登录的最可靠信号。真正不可信的是「端口通」和「日志关键字」。准确的说法应该是:
- 端口通 → 只说明进程活着(不可信);
- 日志关键字 → 有历史残留,会误判(不可信);
get_status.online→ 直接反映协议层登录状态(可信)。
9.3 自愈与熔断:杀干净再拉,拉不起就停
判断准了,自愈逻辑也得跟上。我现在的流程是:
- 没进程 → 直接拉起;
- 有进程但端口不通 → 拉起;
- 端口通 +
online=true→ 健康,清空失败计数; - 端口通 +
online=false→ 精确杀掉 NapCat 进程 → 带-q重拉 → 等 20 秒 → 再用 API 复核; - 端口通 + API 调不通 → 先观察,不急着动手;
- 连续失败 ≥ 3 次 → 熔断,停止自动重启,弹窗喊人。
第 4 步是核心。之前我「重拉」的时候,没有先把旧进程杀干净,导致新旧进程抢端口、抢登录态,越搞越乱。现在改成「先杀干净,再重拉」,一次就成。
至于第 6 步的熔断,道理很简单:如果 -q 快速登录真的失效了(Token 被吊销),那再怎么重拉都是白搭,反而会因为「频繁尝试无效登录」被腾讯盯上。这时候停下来,比死磕更安全。
9.4 细节防风控:作息断开与行为伪装
最后是两个「锦上添花」的细节。
作息断开:我让看门狗在凌晨 2 点到 7 点之间「装死」——这个时段就算检测到掉线,也不自动重启。为啥?因为凌晨是风控的高发期,也是我睡觉的时间。万一这个时段掉线了,看门狗疯狂重启,我人又不在,很容易把号玩没。与其瞎折腾,不如安静等天亮。
行为伪装:我给回传消息加了打字延迟和频率限制。
- 打字延迟:发消息前,按字数模拟「打字时间」(基础 1 秒 + 每字 0.03 秒,上限 3 秒,再加 ±20% 抖动);
- 频率限制:每分钟最多发 10 条,超了就排队等。
这俩改动,是为了让机器人的行为更像真人,降低被风控标记的概率。虽然不能保证 100% 有效,但「像人一点」总比「机器一样秒回」要安全。
9.5 最后说一句:关于「和 AI 一起完成」
这次重构,我把排查出的核心逻辑和 API 机制喂给了 AI,让它当我的「结对编程搭子」。不得不说,只要你把「必须先杀旧进程」「连续 3 次必须熔断」这些人类踩坑得来的业务逻辑定义清楚,AI 就能帮你快速把代码敲出来,省下不少复测时间。
但我想说的是:真正值钱的,从来不是那几行代码,而是你踩坑换来的「业务逻辑」。 AI 能帮你写代码,但替不了你熬夜看日志。
所以这篇文章真正的结论是:掉线排查,本质上是一场「和自己的脚本斗智斗勇」的过程。 腾讯的风控是外因,但真正让你反复掉线的,往往是你自己写的那些「聪明」逻辑。
附录 / 彩蛋:一个至今没搞明白的玄学问题
6099 端口有时候要等好几秒才通,具体为啥我到现在也没彻底搞懂。我怀疑是 WebUI 初始化慢,但没去翻源码。反正我在启动脚本里加了个 sleep 5 就绕过去了,能用就行,先不管了。
有没有大佬知道原因?求在评论区解答。