浏览器 Agent 卡在登录和验证码?用“人工接管 + 断点续跑”保住任务现场

Agent 已经填完十几项内容,文件也上传好了。就在最后提交前,页面突然跳回登录界面。

你重新登录,Agent 却像失忆一样从第一步开始重跑。更糟的是,它可能再次上传文件、重复发布内容,甚至把刚才已经生效的操作再提交一遍。

很多浏览器 Agent Demo 看起来很聪明:会打开网页、填写表单、点击按钮。但真正投入使用后,最容易让系统崩掉的,往往不是按钮没找到,而是任务进行到一半,网站突然要求“必须由本人处理”。

例如:

  • 登录状态过期;
  • 出现图形验证码、滑块或人机验证;
  • 要求输入短信验证码;
  • 需要扫码、App 授权或支付确认;
  • 敏感操作前弹出二次确认。

这类问题不应该通过“让 Agent 多试几次”解决。可靠的做法是:识别阻塞、安全暂停、保存现场、交给人工处理,然后从原任务状态继续。

一个成熟的浏览器 Agent,不是遇到所有页面都敢点,而是知道什么时候该停下来,把控制权还给人。

本文讨论的是合规的 Human-in-the-loop(人在回路)机制,不提供自动破解验证码、规避二次验证或绕过网站风控的方法

---

一、先别堆异常处理,把任务画成状态机

不少浏览器自动化项目一开始只有两种状态:

成功 / 失败

遇到登录过期就抛异常,遇到验证码就重试,连续失败后再把整个任务重新启动。

问题在于,登录过期并不代表任务失败。它更像开车经过收费站:车没坏,路线也没错,只是有一个节点需要人来确认身份。

因此,更合理的状态机应该是:

RUNNING

BLOCKED_DETECTED

PAUSING

WAITING_FOR_HUMAN

HUMAN_COMPLETED

VALIDATING

RESUMING

RUNNING / FAILED

其中最重要的不是 WAITING_FOR_HUMAN,而是前后的两个阶段:

  • 暂停前,是否完整保存了任务现场;
  • 恢复前,是否验证了页面和业务状态。

如果只是暂停浏览器,却没有保存“做到哪一步”,人工回来后 Agent 仍然不知道该从哪里继续。

三类常见人工阻塞

#### 1. 登录过期

常见信号包括:

  • 页面跳转到包含 loginsigninauth 的地址;
  • 出现“会话已失效”“请重新登录”等提示;
  • 业务接口返回 401
  • 页面原本存在的业务内容消失,转而出现登录表单。

需要注意,403 不一定代表登录过期,也可能是权限不足、访问策略限制或风控拦截,因此不能单独作为判断依据。

#### 2. 验证码或人机验证

包括图形验证码、滑块、行为验证、第三方人机确认组件等。

系统应识别并暂停,而不是尝试自动破解。频繁点击、刷新或重复提交,反而可能增加验证难度,甚至触发账号限制。

#### 3. 二次确认

例如:

  • 短信验证码;
  • 扫码确认;
  • 在手机 App 中授权;
  • 发布、删除前的敏感操作弹窗;
  • 支付确认。

这里必须区分两件事:用户完成确认,不等于业务动作已经完成。

用户扫码后,订单可能已经支付,也可能只是完成授权。如果 Agent 不检查结果,直接再次点击“支付”,就可能造成重复操作。

---

二、阻塞检测要靠多信号,不能只盯一个选择器

最小版本可以从 URL、页面文字和特定元素开始判断。

async function detectHumanBlocker(page) {

const url = page.url();

const loginExpired =

/login|signin|auth/i.test(url) ||

(await page.getByText(

/登录已过期|会话失效|请重新登录/

).count()) > 0;

const captcha =

(await page.locator(

'iframe[src="captcha"], [class="captcha"], [id*="captcha"]'

).count()) > 0;

const secondFactor =

(await page.getByText(

/短信验证码|扫码确认|二次验证|在手机上确认/

).count()) > 0;

if (loginExpired) return { type: "login_expired" };

if (captcha) return { type: "captcha" };

if (secondFactor) return { type: "second_factor" };

return null;

}

这段代码适合 Demo,但不能直接被视为生产方案。

网站改一次类名,[class*="captcha"] 就可能失效;某篇文章正文里出现“短信验证码”几个字,也可能造成误判。

生产环境应综合以下信号:

  • 当前 URL 和跳转历史;
  • 页面标题、提示文字和登录表单;
  • DOM 元素及其可见性;
  • 接口响应状态;
  • 页面关键业务区域是否消失;
  • 最近一次点击后发生了什么变化;
  • 多个信号的组合评分。

例如,只有“URL 进入登录页”“业务区域消失”“出现密码输入框”同时成立时,才将其判断为登录过期。

阻塞检测可以借助模型理解页面,但最终是否暂停,最好由可审计的确定性规则决定。
截图建议 1:Agent 正常填写后台表单的页面。 截图建议 2:提交前跳转到登录页,并标注“检测到会话失效”。 截图建议 3:出现验证码后,Agent 自动停止点击的界面。

所有截图都应隐去账号、手机号、订单号、Cookie、二维码和页面中的个人信息。

---

三、暂停不是截张图,而是完整保存现场

假设 Agent 正在后台发布内容,已经完成:

1. 进入发布页面;

2. 填写标题和正文;

3. 上传封面及附件;

4. 设置分类;

5. 准备点击发布。

此时登录失效。如果系统只保存一张截图,恢复时仍然不知道:

  • 草稿是否已经保存;
  • 上传是否完成;
  • 当前浏览器里有几个标签页;
  • 下一步原计划做什么;
  • “发布”按钮是否已经被点击过。

因此,暂停时至少要保存四类信息:

  • 任务上下文:目标、已完成步骤和下一步计划;
  • 浏览器上下文:URL、活动标签页、会话引用;
  • 阻塞上下文:阻塞类型、发现时间和页面证据;
  • 交接上下文:用户需要做什么、完成后如何通知系统。

可以用下面的数据结构保存 Checkpoint:

type Checkpoint = {

taskId: string;

status: "waiting_for_human";

blocker: {

type: "login_expired" | "captcha" | "second_factor";

detectedAt: string;

message: string;

};

browser: {

pageUrl: string;

activeTab: number;

sessionRef: string;

};

progress: {

completedSteps: string[];

nextStep: number;

nextAction: {

name: string;

risk: "low" | "reversible" | "irreversible";

};

};

expectedState: {

accountHint?: string;

pageType: string;

requiredElements: string[];

};

handoff: {

instruction: string;

screenshotRef?: string;

expiresAt: string;

};

};

一个实际保存的交接对象可能是:

{

"taskId": "publish-20250308-001",

"status": "waiting_for_human",

"blocker": {

"type": "login_expired",

"detectedAt": "2025-03-08T10:30:00+08:00",

"message": "登录状态已失效,请重新登录"

},

"browser": {

"pageUrl": "https://example.com/editor",

"activeTab": 2,

"sessionRef": "encrypted-session-reference"

},

"progress": {

"completedSteps": [

"填写标题与正文",

"上传封面",

"设置分类"

],

"nextStep": 4,

"nextAction": {

"name": "检查草稿并发布",

"risk": "irreversible"

}

},

"expectedState": {

"accountHint": "内容运营账号",

"pageType": "content_editor",

"requiredElements": ["标题输入框", "正文编辑区", "发布按钮"]

},

"handoff": {

"instruction": "请在当前浏览器窗口重新登录,完成后点击“继续任务”",

"screenshotRef": "artifacts/task-001/blocker.png",

"expiresAt": "2025-03-08T10:45:00+08:00"

}

}

这里的时间和任务编号仅用于展示数据格式,生产环境应根据实际任务生成。

暂停与通知逻辑

async function pauseForHuman(page, task, blocker) {

await task.lock();

const checkpoint = await buildCheckpoint(page, task, blocker);

await saveCheckpoint(checkpoint);

await notifyUser({

taskId: checkpoint.taskId,

title: "浏览器任务需要人工处理",

message: checkpoint.handoff.instruction,

screenshotRef: checkpoint.handoff.screenshotRef,

action: {

label: "继续任务",

url: /tasks/${checkpoint.taskId}/resume

}

});

return {

status: "waiting_for_human",

taskId: checkpoint.taskId

};

}

task.lock() 很重要。它用于阻止其他 Worker 接手同一个任务,否则可能出现一个执行器在等待人工,另一个执行器却仍在点击页面。 截图建议 4:人工接管通知,展示任务摘要、阻塞原因和“继续任务”按钮。

交接数据的安全边界

无论是日志、截图还是发送给模型的上下文,都不要包含:

  • 明文密码;
  • 短信验证码;
  • 完整 Cookie;
  • 可直接使用的登录令牌;
  • 完整银行卡或支付信息;
  • 与当前任务无关的账户数据。

浏览器会话应该保存为受控的加密引用,而不是把 Cookie 原样写进 Checkpoint。

截图也需要脱敏,并设置保存期限。任务结束或审计期届满后,应自动删除不再需要的截图和页面快照。

---

四、人工完成后,Agent 不能立刻继续点

现在,用户已经在原浏览器窗口完成登录,并点击了“继续任务”。

最危险的实现是:

await page.click("text=发布");

因为暂停期间可能发生很多变化:

  • 登录后被跳转到了首页;
  • 用户登录了另一个账号;
  • 草稿内容没有保留;
  • 验证完成后,原动作已自动提交;
  • 用户手动点击了发布;
  • 页面被切换到了其他标签页。

恢复前必须依次回答五个问题:

1. 阻塞是否真的已经解除?

2. 当前账号是否正确?

3. 页面是否仍在预期业务节点?

4. 暂停前的操作是否已经生效?

5. 下一步是否属于不可逆操作?

async function resumeTask(page, checkpoint) {

const stillBlocked = await detectHumanBlocker(page);

if (stillBlocked) {

return {

status: "waiting_for_human",

reason: "阻塞状态仍未解除"

};

}

const currentState = await inspectBusinessState(page);

if (!isExpectedAccount(currentState, checkpoint.expectedState)) {

return {

status: "needs_review",

reason: "当前登录账号与任务要求不一致"

};

}

if (!isCompatible(currentState, checkpoint.expectedState)) {

return {

status: "needs_review",

reason: "页面状态与暂停前预期不一致"

};

}

const previousActionCompleted =

await verifyPreviousAction(page, checkpoint);

if (previousActionCompleted) {

checkpoint.progress.nextStep += 1;

await saveCheckpoint(checkpoint);

}

if (

checkpoint.progress.nextAction.risk === "irreversible"

) {

return {

status: "waiting_for_confirmation",

reason: "即将执行提交、支付、删除或发布操作"

};

}

return executeFromCheckpoint(page, checkpoint);

}

让恢复操作具备幂等性

所谓幂等,可以简单理解为:同一步骤即使被重复触发,也不会产生第二份业务结果。

async function executeStepIdempotently(task, step) {

const operationKey = ${task.taskId}:${step.id};

const existing = await operationLog.find(operationKey);

if (existing?.status === "completed") {

return {

status: "skipped",

reason: "该步骤已经完成"

};

}

await operationLog.createIfAbsent({

operationKey,

status: "running"

});

const alreadyEffective = await step.verify();

if (alreadyEffective) {

await operationLog.markCompleted(operationKey);

return {

status: "completed",

source: "verified_existing_result"

};

}

const result = await step.execute();

await step.verifyOrThrow(result);

await operationLog.markCompleted(operationKey);

return {

status: "completed",

source: "new_execution"

};

}

关键原则是:先查结果,再决定是否重做。

---

五、三种阻塞,恢复方式并不相同

场景一:后台发布前登录过期

这是贯穿全文的案例。

Agent 已经填写内容并上传素材,点击发布前进入登录页。系统暂停并保留原浏览器会话,用户重新登录后点击继续。

此时 Agent 应该:

1. 返回暂停前的编辑页面;

2. 检查当前账号;

3. 检查标题、正文和素材是否仍然存在;

4. 查询草稿或发布记录;

5. 确认尚未发布;

6. 在再次获得人工确认后执行发布。

如果内容丢失,应进入人工复核,而不是自动从头填写。

场景二:企业系统要求短信验证

Agent 在企业后台提交申请时遇到短信验证,应暂停并提示用户在原页面完成输入。

用户处理后,系统需要等待页面稳定,再判断申请是否已经自动提交。如果已经出现申请编号或成功状态,就不能再次点击提交。

场景三:支付前二次确认

支付场景的自动化边界必须更严格。

Agent 可以准备订单、核对金额并停在确认页面,但应把支付确认交给用户。用户完成后,Agent只负责:

  • 查询订单状态;
  • 核验支付结果;
  • 保存业务凭证引用;
  • 在状态不明确时进入人工复核。

它不应自动获取短信验证码,也不应替用户确认支付。

---

六、完整流程图:三个安全出口不能自动放行

flowchart TD

A[Agent 正常执行] --> B{检测到人工阻塞?}

B -- 否 --> A

B -- 是 --> C[锁定任务并保存 Checkpoint]

C --> D[生成交接摘要与脱敏截图]

D --> E[通知用户在原会话中处理]

E --> F{是否在期限内完成?}

F -- 否 --> G[安全退出或取消任务]

F -- 是 --> H[恢复校验]

H --> I{页面状态是否一致?}

I -- 否 --> J[进入人工复核]

I -- 是 --> K{上一步是否已经生效?}

K -- 是 --> L[跳过已完成步骤]

K -- 否 --> M[准备执行下一步]

L --> N{下一步是否不可逆?}

M --> N

N -- 是 --> O[再次请求人工确认]

N -- 否 --> P[从 Checkpoint 继续执行]

O --> P

P --> Q[记录结果并更新任务状态]

这套流程有三个明确的安全出口:

  • 人工处理超时:安全退出,不能无限占用浏览器和任务锁;
  • 页面状态不一致:进入复核,不能猜测页面发生了什么;
  • 即将执行不可逆动作:再次确认,不能因为“恢复任务”而默认获得操作授权。
截图建议 5:恢复校验日志,展示账号检查、页面节点检查和结果校验。 截图建议 6:Agent 从提交前节点继续,并输出最终任务结果。

还可以增加一张前后对比图:

错误方案:

遇到阻塞 → 重启 Agent → 重复填写 → 可能重复提交

正确方案:

遇到阻塞 → 保存现场 → 人工处理 → 校验状态 → 断点续跑

---

七、从 Demo 到长期运行,还缺这份工程清单

要让这套机制长期运行,建议逐项检查:

  • 每个任务生成唯一 task_id
  • 每个关键步骤生成唯一操作标识;
  • 暂停、恢复和通知接口必须幂等;
  • 为人工处理设置可配置的超时时间;
  • 统一分类登录失效、验证码和二次确认;
  • 单独记录多标签页、弹窗及跨域登录的焦点状态;
  • 恢复前检查账号、页面、业务结果和下一步风险;
  • 对提交、发布、支付、删除增加人工确认;
  • 恢复失败后进入复核,而不是无限重试;
  • 日志、截图和页面快照均进行脱敏;
  • 不把密码、验证码或完整 Cookie 发送给大模型;
  • 遵守目标网站的服务条款和自动化政策。

最小可用架构可以保持简单:

浏览器执行器

阻塞检测器

任务状态存储

人工接管通知

用户在原会话中处理

恢复校验器

断点续跑执行器

如果暂时没有真实测试数据,不要用主观印象代替指标。可以先建立测试表格:

| 场景 | 是否正确暂停 | 是否保留现场 | 是否成功恢复 | 是否重复执行 | |---|---:|---:|---:|---:| | 登录过期 | 是/否 | 是/否 | 是/否 | 是/否 | | 图形验证码 | 是/否 | 是/否 | 是/否 | 是/否 | | 短信验证 | 是/否 | 是/否 | 是/否 | 是/否 | | 页面被用户切换 | 是/否 | 是/否 | 是/否 | 是/否 | | 用户登录错误账号 | 是/否 | 是/否 | 是/否 | 是/否 | | 原操作已自动提交 | 是/否 | 是/否 | 是/否 | 是/否 |

有条件时,再持续记录阻塞识别准确率、恢复成功率、重复提交率、人工处理耗时和超时率。没有实测,就不要填写看似漂亮的数字。

---

八、模型负责理解,执行器负责确定性动作

阻塞检测的底层规则应尽量确定性,但大模型可以辅助处理更难结构化的部分:

  • 将暂停前的执行历史压缩成交接摘要;
  • 根据脱敏后的页面信息说明用户需要做什么;
  • 对比暂停前后的页面状态;
  • 生成候选恢复计划;
  • 识别提交、支付、删除、发布等高风险动作。

如果准备把这套机制接入自己的浏览器 Agent,可以通过 api.884819.xyz 调用模型,让模型负责“状态理解和恢复规划”,浏览器执行器负责确定性的点击、查询与结果校验。

curl https://api.884819.xyz/v1/chat/completions \

-H "Authorization: Bearer $API_KEY" \

-H "Content-Type: application/json" \

-d '{

"model": "YOUR_MODEL",

"messages": [

{

"role": "system",

"content": "你是浏览器 Agent 的恢复规划器。不得尝试绕过验证码或二次验证,不得替用户执行支付确认。"

},

{

"role": "user",

"content": "请根据暂停前的任务状态与人工处理后的页面状态,生成安全的恢复步骤,并标记不可逆操作。"

}

]

}'

实际接口参数应以平台文档为准。API Key 应保存在服务端环境变量中,不要写进浏览器脚本,也不要把密码、短信验证码或完整 Cookie 发送给模型。

8848AI 使用用户名和密码即可注册,不需要邮箱验证;平台内置 AI 对话功能,注册后可以直接使用。国产模型如 Deepseek、千问等完全免费,没有月租和订阅,其他服务按量付费。

访问地址:api.884819.xyz

新用户注册即送体验token。

---

最后:检查你的 Agent 是否真的会“停”

现在可以用下面五个问题检查自己的系统:

1. 遇到登录、验证码和二次确认时,它会停止点击吗?

2. 暂停时,它保存的是完整任务状态,还是只有截图?

3. 用户处理后,它会检查账号和页面是否正确吗?

4. 它会先验证上一步结果,再决定是否重做吗?

5. 面对发布、支付和删除,它会重新请求确认吗?

如果其中任何一个答案是否定的,那么你的 Agent 可能“能跑”,但还谈不上可靠。

真正成熟的浏览器 Agent,不是永远不需要人,而是知道什么时候必须停、应该把什么交给人,以及人处理后怎样安全地继续。

但这里还有一个最容易出事故的问题:人工处理完以后,Agent 怎么确定“上一步到底有没有成功”?

下一篇,我们会专门拆解 《浏览器 Agent 最怕重复提交:用幂等键、结果校验和操作日志实现安全重试》,讲清楚页面刷新、网络超时和任务恢复后,如何避免多下单、多发布和多支付。

本文由8848AI原创,转载请注明出处。关注8848AI,带你从零开始学AI。

#浏览器Agent #AI教程 #自动化测试 #Playwright #人工智能 #8848AI #Agent开发 #人在回路