把一个「每天点一下」的网页任务做成可靠定时作业:探活、幂等、证据链

举报
yd_261373198 发表于 2026/10/23 09:48:23 2026/10/23
【摘要】 网页任务自动化的四条工程约束:端口探活替代进程名判断、用页面状态做幂等、以提交侧证据而非积分到账判成功、以及为什么登录态必须留给人。

把一个"每天点一下"的网页任务做成可靠定时作业:探活、幂等、证据链

## 结论先行

把网页操作自动化不难,把它做成**半年不用管**的定时作业才难。我维护一个每天跑一次的"网页任务"作业(登录态挂在真实浏览器里,用 Selenium 通过 marionette 接管),踩过的坑可以归纳成四条工程约束,按重要性排序:

1. **探活先于执行**:作业第一步永远是"目标还活着吗",而不是"开始干活"。环境不对时的失败必须快速、明确,而不是超时后报一个含义模糊的堆栈。
2. **每个动作都要幂等**:定时作业一定会在"已经做过"的状态下被重复触发(补跑、手动重跑、上次只成功一半),脚本必须能从页面状态反推"这件事是不是已经完成"。
3. **证据链比返回值重要**:脚本返回 0 只说明它自己没崩。真正能证明完成的是页面状态变化 + 落盘快照(前后数值、URL、按钮文案)。
4. **登录态是这个系统里唯一不可自动化的部分**:会话 Cookie 一旦服务端过期,任何"搬 Cookie"的方案都救不回来,必须把人放进回路,而且要把这一步做得足够快。

下面按这四条展开,都是实测中撞出来的。

## 一、探活:先判断环境,再决定动作

定时作业的第一次失败往往不是业务逻辑错,而是前置条件没满足:浏览器没起来、端口没监听、代理环境变量被带进子进程、上一次的残留进程占着会话。我现在的写法是:作业脚本的第一段只做三件事——端口连通性探测、必要时拉起依赖、把探测结果写进日志。

```bash
port_open() { python3 -c "
import socket,sys
s=socket.socket(); s.settimeout(1)
sys.exit(0 if s.connect_ex(('127.0.0.1',2829))==0 else 1)
"; }
port_open && { echo "BROWSER_OK"; exit 0; }
```

三个容易被忽略的细节:

- **别用 pgrep/grep 判断进程存活**。进程名会匹配到自己(`pgrep -f` 的命令行里就含关键字),导致"检测通过"但端口根本没开。用端口连一次是最便宜的真探活。
- **代理变量会污染子进程**。shell 里 export 的 `ALL_PROXY`/`HTTPS_PROXY` 会被浏览器和 curl 继承,内网/本机服务直接连不上。拉起依赖时显式 `env -u ALL_PROXY -u all_proxy -u HTTP_PROXY ...`。
- **休眠恢复后锁文件会撒谎**。profile 目录里的 `lock`/`.parentlock` 常指向仍然存活的旧 pid,先 kill 那个 pid 再删锁,否则新实例静默退出(退出码 0,端口不监听,最容易被误判成"程序有问题")。

## 二、幂等:让脚本能从状态反推"做没做过"

定时作业的重复触发是常态。我的每个动作函数在动手前都先读一次目标状态:

- 签到按钮文案是 `立即签到` 还是 `已签到`;
- 任务卡片按钮是 `去完成` 还是 `已完成`;
- 提交成功标志是容器 class 变化(如 `submit` → `submit-success`),而不是 URL 变化——很多站点提交后仍停在原 URL。

这里有个反直觉的经验:**不要把"积分到账"当成成功判据**。积分往往有几十分钟到几小时的延迟,用它判断会诱导脚本重复提交,而重复提交在论坛、建议、作品这类场景里是可见的污染(同一条回答出现两遍)。正确做法是:用**提交侧证据**(回读到自己的内容、`共 N 人回答`、`已提交` 状态)判定"已做",用**下一轮回查**判定"已到账"。

## 三、证据链:落盘快照 + 差异比对

我的作业每次运行都会写一个 JSON:

```json
{"ok": true, "ts": "2026-10-23 01:30:49", "click": "clicked",
 "before": {"points": 4720, "sign": "立即签到"},
 "after":  {"points": 4730, "sign": "已签到"}}
```

好处是排障时不用复现:积分从 4720 变 4730、按钮从"立即签到"变"已签到",就足以证明这一轮真的跑成了。反过来,`points: null` 是登录态失效的强信号——页面读不到数字,说明页面根本不是登录后的视图,这时候继续往下点只会得到一串无关报错。

另一条经验:**截图要和结构化字段一起存**。只存截图,人眼要一张张看;只存字段,出问题时不知道页面长什么样。两者都有,才能在一次失败后 30 秒内定位。

## 四、登录态:唯一该交给人的环节

这是整个系统里最容易被低估的一段。我试过的路径和结论:

- **复制 Cookie 重放**:对靠 CAS/SSO 的站点基本无效。浏览器里 `expiry=0` 的会话级 Cookie 由服务端会话支撑,把值搬到脚本里,服务端那张会话表早就过期了,请求会一直停在登录页(HTTP 200 但不跳转,看起来"请求成功")。
- **从会话恢复文件里挖 Cookie 再逐域注入**:技术上可行(Selenium 只允许给当前域设 Cookie,所以要按域逐个 `get` 再注入),但注入完仍是游客态——这说明丢的不是客户端 Cookie,而是服务端的会话状态。
- **强制重启浏览器来挂调试端口**:必然掉登录。这条最坑,因为它"看起来成功了"——端口通了,自动化跑起来了,只是所有后续动作都在游客态下静默失败。

所以正确的设计是:**把登录态验证前置成作业的第一个显式检查**,失败时不做任何"重试或绕过",而是把自动化窗口停在登录页,然后在汇报里写清三件事——已经试过哪几条恢复路径、页面给出的确切症状、需要人做的那一件 30 秒内能完成的事。把不确定性收敛成一次人工操作,比让脚本在游客态下跑完一整套流程再报"完成了 0 项"要诚实得多。

## 五、时区与调度

最后一个常被忽略的坑:cron 用的是系统时区。机器 `timedatectl` 是 UTC,想在北京时间 09:30 跑就得写 `30 1  *`。我在作业注释里固定写上两句换算,避免半年后自己看不懂为什么是 1 点。

## 小结

- 探活用端口,不用进程名;拉依赖前清代理变量、清残留锁。
- 幂等靠页面状态,不靠"脚本自己记得"。
- 成功判据用提交侧证据,到账用回查,中间的时间差不要用重复提交填。
- 登录态不可自动化时,明确交人,并把人工步骤压缩到 30 秒。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。