Python 定时任务遇到 POST 超时:先查状态,再决定是否重试
Python 定时任务遇到 POST 超时:先查状态,再决定是否重试
定时脚本最容易写错的一段,往往只有一行:请求失败后重新执行。对于查询操作,这通常只是多一次请求;对于会修改状态的操作,超时可能发生在服务端已经处理成功之后。客户端没有收到响应,并不等于服务端没有完成操作。
这次在华为云开发者成长中心做了一次签到脚本实测。初始积分为0,提交后查询签到状态和积分余额,确认今日已签到,积分增至10,连续签到1天。随后再次执行脚本,先查状态,返回今日已签到并跳过提交。这里分享的重点是这种状态核验方式,而不是依赖一条成功提示判断定时任务完成。
把结果未知当作一种独立状态
以“每天执行一次”的操作为例,客户端至少需要区分四种结果:尚未执行、已完成、明确失败、结果未知。网络超时或连接中断属于结果未知。直接把它归为失败并马上重试,可能重复创建订单、重复发消息或重复修改记录。
如果服务提供幂等键,应优先使用同一业务操作对应的稳定幂等键。服务端必须保证重复请求返回同一业务结果,客户端自行添加一个头字段并不会自动获得幂等性。接口没有幂等键但提供可靠的业务状态查询时,可以在提交前后分别查询,以缩小不确定性。
查询与提交使用不同重试策略
查询请求可以对网络异常、429和5xx做有限重试,并使用退避与随机抖动。认证失效、参数错误和接口结构变化需要单独处理,重复请求通常不会解决这些问题。
提交请求则谨慎处理。示例中的提交函数只调用一次;超时之后查询状态,等待短暂同步。如果仍未确认完成,退出并记录待核验结果,下一次运行先查询,不在同一轮里盲目重复提交。
下面是与具体产品接口无关的控制流程:
import time
def execute_once(query_state, submit):
before = query_state()
if before["done"]:
return {"status": "already_done"}
ambiguous = False
try:
submit()
except (TimeoutError, ConnectionError):
ambiguous = True
for attempt in range(4):
if attempt:
time.sleep(3)
after = query_state()
if after["done"]:
return {
"status": "done",
"recovered_after_timeout": ambiguous,
}
raise RuntimeError("Submission has not been confirmed")
query_state和submit由实际业务适配层实现,应验证HTTP状态码、业务错误码以及返回字段,而不是仅判断响应中是否出现success字符串。上面的异常类型也是示例;采用requests时需要捕获requests.Timeout与requests.ConnectionError。
登录有效和任务完成是两件事
本次实测前,浏览器关机后的登录状态已过期。脚本首先检查登录、获取本次会话的CSRF并读取账号信息;这些步骤通过后才查询签到状态。凭据文件缺失或过期时,返回独立退出码,不把定时器“运行过”记成业务成功。
会话Cookie有有效期,也可能受服务端风控或网络环境影响。把脚本放到另一台服务器之后,需要重新验证服务器上的请求,不能仅凭本机测试通过推断云端一定成功。凭据也不应出现在控制台输出、文章或代码仓中。
用可复现的测试覆盖失败路径
本地测试使用模拟响应验证了提交成功、今日已完成、登录过期、HTTP 200带业务错误、GET重试耗尽、POST超时后查询到成功、提交返回成功但状态始终未变化等路径。模拟测试证明控制流程按设计运行;实际签到和积分变化则来自真实接口及页面核对,两类证据需要分别记录。
对于定时运行,还应加进程锁,避免人工运行和定时器同时触发。日志至少记录执行时间、业务状态、核验结果、前后余额和退出码,不记录认证头或Cookie。余额差额只是观察值:如果同时发生其他积分活动,不能把全部变化都归因于这次操作。
这次实测得到什么
本次真实签到核验结果是:状态变为已签到、积分0到10、连续签到1天;重复运行直接跳过。服务器配置已准备,但还没有部署,因此本文没有服务器长期运行或登录续期的验证结论。
这个小任务的经验可以复用到更多批处理:请求送达、业务完成和结果被客户端确认,是三个不同阶段。把查询、提交和核验拆开,遇到网络波动时才能给出可信的运行结果。
- 点赞
- 收藏
- 关注作者
评论(0)