自动化脚本的 9 种"静默失败":不报错、不崩溃、就是不干活
脚本跑完退出码 0,日志干干净净,一件事也没干成。
这种失败最难查,因为它不留下任何线索。下面 9 种是我实际踩过的,每一种都给出现象、真因、一条自检命令、修法。
1. 浏览器随父进程一起被回收
现象:脚本里启动的 Chrome,等脚本跑完就消失了;下次再连接 CDP 端口直接ECONNREFUSED。
真因:浏览器进程是命令行会话的子进程,会话结束即被清理。用Start-Process、schtasks、WMI 启动也躲不掉。
自检:
curl-s--noproxy'*'--max-time3http://127.0.0.1:9333/json/version# 空输出 = 已经没了修法:启动和操作必须写在同一条命令里:
cmd //c start"""C:/Program Files/Google/Chrome/Application/chrome.exe"\--remote-debugging-port=9333--user-data-dir="C:\...\profile"--no-first-run\;sleep14;nodeyour-script.js2. curl 探测本地端口走了代理
现象:明明服务在跑,curl就是超时或返回代理的错误页。
真因:环境里设了http_proxy,本地请求也被送到代理去了。
自检:
env|grep-iproxy修法:探测本地端口一律加--noproxy '*'。这条我记了很久才形成肌肉记忆。
3. 运行时版本被静默升级
现象:昨天还好好的脚本,今天报No such file or directory。
真因:托管运行时被自动升级,路径里的版本目录变了(比如 Node22.22.2-3→22.22.2-6)。写死路径的任务全部失效。
自检:
lsC:/Users/<你>/.workbuddy/binaries/node/versions/修法:脚本里先探测再取用,别写死版本号:
N=$(ls-d/path/to/versions/*/|tail-1)/node.exe4. 定时任务到期自动停跑
现象:任务一直在列表里,状态还是 ACTIVE,但就是没执行记录。
真因:任务设了validUntil,过期后静默停跑,状态字段不更新。
自检:创建/修改任务时检查有效期字段;定期核对"最近一次执行时间"。
修法:要么不设validUntil,要么设一个远超预期的日期,并在任务描述里写明"到期前续期"。
5. 后台标签页里鼠标键盘全部静默失效
现象:坐标算对了、事件也发了,页面上毫无反应;同样的代码在前台标签就正常。
真因:标签处于后台时visibilityState === 'hidden',浏览器会抑制输入事件。
自检:
document.visibilityState// 'hidden' 就是它修法:操作前先切前台:
awaitsend('Page.bringToFront');顺带一提:后台标签里window.screenX / screenY也会返回 0,拿它算坐标就是错的(详见滑块验证码那篇)。
6. 灌入长文本时换行被吞掉
现象:脚本把 Markdown 塞进编辑器,结果所有换行消失,全文糊成一坨。
真因:Input.insertText对换行符处理不可靠,富文本编辑器(CodeMirror / Tiptap / Draft.js)尤其明显。
修法:走剪贴板 +Ctrl+V:
importpyperclip,time pyperclip.copy(markdown_text)# 聚焦编辑器后pyautogui.hotkey('ctrl','v')7. 二维码过期了但页面上没写"过期"
现象:脚本一直等扫码,等满十几分钟超时退出;页面上那个码其实早就失效了。
真因:过期态的视觉表现是"模糊的码 + 中间一个刷新圈",innerText里不含"过期"两个字,按文字判断永远判断不出来。
修法:要么定时刷新二维码(点二维码框中心),要么看图——截图后人工确认。别指望文本匹配。
8. 接口返回 200,业务其实是失败的
现象:抓包看状态码全 200,事情没办成。
真因:业务错误码藏在响应体里。比如签到接口:
{"code":10001,"msg":"今天已签到,请明天再来"}HTTP 状态是 200,code才是真相。再比如状态查询接口返回 200 但字段全是零值——那不代表"真的为零",可能只是它查的是另一套维度。
修法:断言要打到响应体字段,不能只看 HTTP 状态。而且要先搞清楚哪个字段才是权威口径。
9. 状态查询接口和实际操作接口口径不一致
这是我踩得最狠的一条。
同一个签到功能,查询接口说today_checked_in: false、streak_days: 0,看起来就是"没签到、连签断了"。但实际调操作接口,返回的是"今天已签到,请明天再来"。
哪个是真的?操作接口的回执是真的。查询接口在这个上下文里返回的是另一套维度的数据(赛季维度,赛季未开启时字段全零)。
教训:判断"做没做成",永远以操作接口的回执为准,不要用查询接口的状态字段去推断。
附:一条命令的自检清单
把上面最容易犯的几条串起来,开工前跑一次:
#!/usr/bin/env bash# 自检:端口 / 代理 / 运行时路径 / 标签前台PORT=${1:-9333}echo"[1] CDP 端口"curl-s--noproxy'*'--max-time3"http://127.0.0.1:${PORT}/json/version"\&&echo" OK"||echo" ✗ 浏览器没起来(可能被回收)"echo"[2] 代理环境"ifenv|grep-qi'http_proxy';thenecho" ⚠ 存在代理,本地请求请加 --noproxy '*'"elseecho" OK"fiecho"[3] Node 运行时"ls-d"$HOME/.workbuddy/binaries/node/versions/"*/2>/dev/null|tail-1echo"[4] Python venv"ls-d"$HOME/.workbuddy/binaries/python/envs/"*/2>/dev/null|tail-1跑完这四项,能挡掉一半的"脚本没反应"。
总结
这些坑的共同点:失败路径上没有报错。
所以写自动化脚本时,别只看退出码。关键动作后面要跟断言:
- 点完按钮 → 断言 URL 变了 / 出现了成功文案
- 提交表单 → 断言列表里多了一条
- 调用接口 → 断言响应体里的业务码
- 上传文件 → 断言页面上出现了新资源的 URL
没有断言的自动化,等于没有自动化。它只是在安静地假装成功。