把脚本从「本地跑通」改到「每天自动跑」:5 个运维坑
把脚本从"本地跑通"变成"每天自动跑",是两件难度不同的事。前者只要逻辑对,后者要面对一堆环境与时间的问题。这篇记录我把它挂上定时任务后踩到的 5 个坑。
一、时区:任务跑在了"昨天"
本机测试一切正常,挂到服务器上却总在错误的时间执行——因为 cron 用的是系统时区,而容器/CI 环境默认常常是 UTC。北京时间 08:00 对应 UTC 00:00,如果你按"本地时间"写 0 8 * * *,实际会在北京时间 16:00 才跑。
更隐蔽的是跨天判断。业务里常有"今天是否已签到"这种逻辑,如果脚本用 UTC 算"今天",那么北京时间 00:00–08:00 之间执行时,它算出来的"今天"还是前一天,于是重复执行或直接跳过。
处理方式只有一条:所有时间判断显式带上业务时区,不要依赖 new Date() 的本地语义;日志里也把时区打出来,方便对账。
二、登录态会过期,而脚本不会自己发现
浏览器 cookie 有生命周期,隔一天就失效是常态。第一版脚本的表现是:打开页面、找不到按钮、抛异常退出——看起来"失败"了,但报错信息是"元素不存在",掩盖了真正原因(未登录)。
改成显式的前置探测:
if 页面出现 "登录/注册":
判定为 未登录 → 触发登录流程(或明确抛出"需要人工登录")
关键是把"未登录"当成一种正常状态来处理,而不是让它在后续步骤里以各种离奇的报错形式暴露。
三、并发重入:同一个任务跑了两份
定时任务最容易忽略的是上一次还没跑完。我的文章发布步骤有 3~5 分钟耗时,如果间隔设得太短,或者手动触发了一次,就会出现两份脚本同时操作同一个账号——重复提交、状态互相覆盖。
两个措施:
- 文件锁/单例:脚本启动时创建锁文件,退出时删除;已存在则直接退出(而不是排队)。
- 幂等键:提交类操作带上时间戳或业务 id,服务端据此去重。
锁文件还要处理残留锁:进程被 kill 时锁不会自动消失,所以锁里要写 pid 和时间,超过阈值视为失效。
四、"成功"的退出码骗了自己
第一版我犯的错是:脚本最后一行只要有执行完就 exit 0。结果某天某一步静默失败,监控显示"昨天运行成功",实际上什么都没做。
正确做法是用业务断言决定退出码:
签到后余额 = 前值 + 10 ? 否则 exit 1
文章提交后 URL 含 /processcomplete ? 否则 exit 1
只有"业务数字符合预期"才算成功。这样监控的绿灯才有意义,也才能在失败当天就被发现,而不是等到积分对不上才回头翻日志。
五、日志:按天切分,每步一行
长期运行的脚本,日志会迅速膨胀,也最容易变成"出事时找不到当时那段"。我现在的约定:
- 按天一个文件,保留 14 天;
- 一行一步,包含:步骤名、结果、证据(读到的业务数字/终态文案);
- 失败时额外记录:当前 URL、关键元素状态、截图路径。
形如:
[2026-11-01 23:31] 签到 → 成功(余额 1020→1030)
[2026-11-01 23:33] DevSpace → 启动(环境 63%)
[2026-11-01 23:36] 文章 → 成功(1853 字,跳转 ?type=blog)
不做复杂的结构化,但这三样必须有:什么时候、做了什么、凭什么算成功。
六、还差一个"人工介入点"的设计
长期跑的任务一定会遇到脚本处理不了的事:验证码、人脸核验、页面改版。这时候正确的行为是停下来并交接,而不是死循环重试——后者除了制造噪音,还可能触发风控。
我的做法是:把这类步骤标记为"需人工",脚本执行到这里就退出并输出待办清单(哪一步、为什么、要人做什么)。人处理完,脚本下次运行时通过状态探测自动跳过已完成步骤,从断点继续。
小结
让脚本"每天都能跑"的,不是更聪明的选择器,而是这五件枯燥的事:显式时区、登录态探测、单例锁、业务断言退出码、可回溯日志,再加一个"需要人时果断停"的设计。
这些都属于"第一次做的时候觉得多余,第二次事故之后觉得必须"的类型。如果你正准备把某个脚本挂上定时任务,建议先把这五条补齐再上。
- 点赞
- 收藏
- 关注作者
评论(0)