先说结论:在自动化里,遇到验证码/二次验证/账号锁定就自动重试,是把"一次验证"升级成"账号被挂起"的最经典路径。
这不是道德问题,是概率问题。风控看的是行为模式:一个正常用户遇到验证码会停顿、会换网络、会手动输入;一个脚本遇到验证码会在 30 秒内重复请求三次,每次的间隔精确到毫秒。## 错误写法长什么样pythonfor attempt in range(3): page.goto(url) if page.locator("#captcha").count(): time.sleep(2) continue # ← 问题在这一行 break````continue` 看起来无害。但它表达的是"这是可重试的临时故障",而验证码**不是**临时故障——它是一个**需要人介入的终止条件**。## 我的写法:把"需要人"建模成正常分支在状态枚举上直接给闸门一个判定:python@propertydef is_gate(self) -> bool: return self in (SessionState.NEEDS_2FA, SessionState.NEEDS_CAPTCHA, SessionState.BLOCKED)然后**重试动作单独做一个枚举,"交给人"是一等公民**:pythonclass RetryAction(str, Enum): NONE = “none” # 当前步原样重试 RELOGIN = “relogin” # 重新走登录 RESTART_ENV = “restart_env” # 关掉浏览器重开一套干净环境 ESCALATE_HUMAN = “escalate_human” # 交给人,停止自动操作关键在于 `ESCALATE_HUMAN` **和 `NONE` 一样是正常返回值**,而不是一个 throw 出去的异常。为什么这点重要?因为异常会被外层的兜底 `except` 吃掉——而兜底逻辑通常就是"重试"。**把"需要人"写成异常,等于把它交给了最不该处理它的那一层。**## 判定顺序也得配合闸门必须在登录标记**之前**判定。原因很反直觉:**验证码和 OTP 是以浮层形式出现在已经登录的页面之上的。**python # ---- 硬闸门(优先级最高) ---- if Marker.ACCOUNT_LOCKED in probe.markers: return Verdict(SessionState.BLOCKED, 0.95, …) if Marker.CAPTCHA in probe.markers: return Verdict(SessionState.NEEDS_CAPTCHA, 0.95, …) if Marker.OTP_INPUT in probe.markers: return Verdict(SessionState.NEEDS_2FA, 0.95, …) # ---- 登录态(排在闸门之后) ---- if probe.has(Marker.SELLER_NAV): return Verdict(SessionState.LOGGED_IN, …)顺序反过来会怎样?页面上的导航菜单还在(因为**底层确实还是已登录状态**),于是判成 `LOGGED_IN`,闸门被完全漏掉。脚本高高兴兴地继续采集,然后在一个永远不会前进的页面上超时。**这个 bug 在本地 mock 上是复现不出来的**,只有真实站点才会暴露。## 还有一个语义坑:HTTP 码别一律当"页面坏了"python# 这些 HTTP 码在亚马逊后台语境的含义是"对方暂时不想理你",不是"页面坏了"。# 当成网络错误处理:退避重开环境,而不是硬刷。_TRANSIENT_HTTP = frozenset({0, 408, 429, 500, 502, 503, 504})429(限流)和 503 的正确反应是**退避 + 重开干净环境**,而不是"选择器变了,我去改代码"或者"再刷一次"。把限流误判成结构变化,会让你去修一个根本没坏的东西。## 判定结果要带上置信度和证据判定结果不是布尔值,而是带 **confidence** 和 **evidence** 的结构:python@dataclass(frozen=True, slots=True)class Verdict: state: SessionState confidence: float evidence: tuple[str, …] flags: frozenset[Flag] = frozenset()信息不足时退化到 URL 推断,并且**把置信度明确压低**:python # ---- 退化到 URL 推断(置信度明确压低,别假装确定) ---- path = probe.url.split(“?”, 1)[0] if “/ap/signin” in path: return Verdict(SessionState.LOGGED_OUT, 0.6, (“URL 落在 /ap/signin”,), …) flags.add(Flag.PARTIAL_RENDER) return Verdict(SessionState.UNKNOWN, 0.3, (“未命中任何标记”,), …)```一个能报出"我不确定(conf=0.30,原因是没命中任何标记)“的系统,比一个永远返回布尔值的系统好 debug一个数量级——因为前者把排查线索留下来了。## 交付物要能证明这件事这类设计最容易出现的退步是"代码里写了闸门,但没人测它”。我的做法是把闸门判成"转人工"这件事写成端到端用例:demo命令的最后一步就是验证验证码闸门被判成"转人工"。166 项测试里包含真开 Chromium 打本地 mock 后台的端到端——闸门行为有回归保护,不是靠注释承诺。## 完整代码项目开源在这里,测试可以自己跑:https://github.com/Amouren7/amz-harnessPython + Playwright,只做只读采集。README 开头写了 2026-03-04 起亚马逊新协议下哪些能做、哪些不能做——尤其是"多店铺代管"这类形态已经被明确盯上,做之前值得先看一眼。
对着验证码重试,等于告诉风控“我是脚本“——自动化里最贵的一行代码
张小明
前端开发工程师
XLPagerTabStrip Swift 2 到 Swift 3 迁移指南:6.0+ 全部 API 命名变化与源码级验证
UI组件移动开发 【免费下载链接】XLPagerTabStrip Android PagerTabStrip for iOS. 项目地址: https://gitcode.com/gh_mirrors/xl/XLPagerTabStrip 点击查看 免费下载 本文面向正在把基于 XLPagerTabStrip 的 iOS 项目从 Swift 2 升级到 Swift 3 的开发者…
CTF信息收集实战:Whois查询从入门到渗透测试应用
CTF 的 Misc 和 Web 方向里,Whois 这种题目属于典型的“看着简单,动手才知道水有多深”的题型。你可能以为它就是查一下域名注册信息,然后把结果里藏着的 flag 复制出来提交,实际上这道题能延伸出不少信息收集的思路。这篇就按我自…
STM32霍尔传感器直流电机闭环调速实战:从测速到PID
简介:一份基于霍尔传感器与STM32的直流电机调速系统课程设计报告,适合自动化、电气类本科生及嵌入式初学者参考,用于完成电力拖动类课程设计或理解直流电机闭环调速实现思路。资源为PDF格式,共1个文件,压缩包大小约762…
tfsec 检查项详解:aws-cloudtrail-enable-log-validation —— 为 CloudTrail 日志开启文件校验,防止日志数据被篡改
应用安全SASTIaC 【免费下载链接】tfsec Tfsec is now part of Trivy 项目地址: https://gitcode.com/gh_mirrors/tf/tfsec 点击查看 免费下载 本篇技术指南聚焦 tfsec 内置检查项 aws-cloudtrail-enable-log-validation(旧编号 AWS064)&…
2026网易企业邮箱代理商推荐哪家,企业邮箱选购要点分享
摘要:企业邮箱是对外商务沟通与内部文件流转的核心枢纽。在选型阶段,许多企业常因产品版本、服务商资质及实际体验等问题陷入纠结。本文结合网易企业邮箱的产品特性,系统梳理通用选购标准,深度剖析版本差异与行业适用场景,并分享甄别优质服务商的实用思路,帮助企业结合自身规模…
从codex转战workbuddy使用一周的感受
初体验:有点"费钱"我先用它修了一个简单的 bug,顺手选了当时口碑最好的国产编程模型 GLM-5.3——毕竟早有耳闻,GLM 算是国内 AI 编程模型的"第一梯队"。几分钟就修完了,我检查了一遍,没毛病。下班…