
Ethan Collins
Pattern Recognition Specialist

202 挑战响应或 405 CAPTCHA 响应;在路由代理前,需同时检查状态码和 x-amzn-waf-action 请求头。capsolver-core 的代理适配器,提供现成的 LangChain 工具和面向浏览器的检测与填入方法。resolved、not_needed、review 或 denied 等小而类型化的结果。AWS WAF 挑战和 CAPTCHA 操作会改变正常的请求路径。根据 AWS WAF 操作文档,携带有效令牌的请求会继续到下一个规则。不携带有效令牌的请求可能会收到挑战响应。
对于挑战,AWS 文档记录了 x-amzn-waf-action: challenge 响应头和 HTTP 状态 202。对于 CAPTCHA,它记录了 x-amzn-waf-action: captcha 和状态 405。当客户端期望 HTML 时,AWS WAF 可能返回 JavaScript 间谍页。成功的交互会更新令牌并重新提交原始请求。
这种行为对代理很重要,因为通用 HTTP 客户端可能将响应解释为正常页面、临时服务器错误或空结果。语言模型不应猜测发生了哪种情况。主机应用应分类响应、检查授权,并通过受控恢复步骤路由工作流。
目标不是让挑战处理变得不可见。目标是使其明确、有限、可观测,并仅限于操作员拥有或获得测试权限的系统上的合法自动化。
生产设计有五个独立职责:
CapSolver 的 官方代理工具指南 描述了 capsolver-agent 作为 capsolver-core 的轻量适配器。核心包执行 solve、detect 和 solve_on_page 等操作;代理包提供框架友好的工具模式。其记录的 LangChain 路径通过 get_langchain_tools() 提供现成工具。
这种边界很有用。模型不需要原始凭证、令牌、浏览器对象或无限制的网络功能。它接收狭窄的工具合同,而确定性应用代码控制工具何时运行。
在编写代理代码之前,定义操作边界:
CAPSOLVER_API_KEY 和模型凭证的密钥存储;使用隔离的 Python 环境。以下安装命令遵循当前 CapSolver 代理指南:
python -m venv .venv
source .venv/bin/activate
pip install git+https://github.com/capsolver-ai/capsolver-core.git
pip install "capsolver-agent[langchain] @ git+https://github.com/capsolver-ai/capsolver-agent.git"
pip install langchain-openai langgraph playwright
playwright install chromium
将凭证存储在源代码控制之外:
export CAPSOLVER_API_KEY="set-this-in-your-secret-manager"
export OPENAI_API_KEY="set-this-in-your-secret-manager"
不要将真实密钥粘贴到提示、跟踪、笔记本、问题或检查点中。环境变量对本地示例方便;部署系统中更推荐使用托管密钥存储。
第一个确定性组件应分类响应。此示例使用 AWS 记录的状态和头组合:
from dataclasses import dataclass
from typing import Mapping, Literal
WafAction = Literal["challenge", "captcha", "none", "unknown"]
@dataclass(frozen=True)
class WafSignal:
action: WafAction
status_code: int
needs_review: bool = False
def classify_aws_waf_response(
status_code: int,
headers: Mapping[str, str],
) -> WafSignal:
normalized = {key.lower(): value.lower() for key, value in headers.items()}
action = normalized.get("x-amzn-waf-action", "")
if action == "challenge" and status_code == 202:
return WafSignal(action="challenge", status_code=status_code)
if action == "captcha" and status_code == 405:
return WafSignal(action="captcha", status_code=status_code)
if action in {"challenge", "captcha"}:
return WafSignal(
action="unknown",
status_code=status_code,
needs_review=True,
)
return WafSignal(action="none", status_code=status_code)
需要同时满足两个信号。单独的 202 可能是有效的应用响应,单独的 405 可能意味着端点不支持该 HTTP 方法。意外组合应转至审核而不是触发自动化恢复循环。
AWS 还指出,跨域运行的浏览器 JavaScript 无法读取 x-amzn-waf-action,因为该头无法通过 CORS 访问。在这种情况下,在浏览器自动化层分类网络响应,或使用同源集成。不要仅通过页面文本推断挑战。
领取您的 CapSolver 奖励代码
立即提升您的自动化预算!
在充值 CapSolver 账户时使用奖励代码 CAP26,每次充值可获得额外 5% 奖励 —— 无限制。
现在在您的 CapSolver 仪表板 中领取
挑战处理不应对模型能提及的每个 URL 都可用。在任何代理工具执行前检查目标:
from dataclasses import dataclass
from urllib.parse import urlparse
@dataclass(frozen=True)
class PolicyDecision:
allowed: bool
reason: str
ALLOWED_HOSTS = {"staging.example.com", "research.example.org"}
ALLOWED_PURPOSES = {"qa-validation", "authorized-research"}
def authorize_recovery(
url: str,
purpose: str,
attempts: int,
) -> PolicyDecision:
host = (urlparse(url).hostname or "").lower()
if host not in ALLOWED_HOSTS:
return PolicyDecision(False, "host-not-allowed")
if purpose not in ALLOWED_PURPOSES:
return PolicyDecision(False, "purpose-not-allowed")
if attempts >= 2:
return PolicyDecision(False, "retry-budget-exhausted")
return PolicyDecision(True, "authorized")
将此函数保留在语言模型之外。在生产中,从版本化配置中加载批准的主机和用途,拒绝重定向到其他主机,并仅记录非敏感决策元数据。
记录的代理包可以公开 LangChain 兼容工具。最小设置如下:
import os
from capsolver_agent.langchain import get_langchain_tools
capsolver_tools = get_langchain_tools(
api_key=os.environ["CAPSOLVER_API_KEY"],
)
具体的代理构建 API 可能会随着 LangChain 和 LangGraph 的版本而变化。将 CapSolver 工具获取保留在一个小的适配器模块中,固定测试过的依赖版本,并通过这些版本支持的代理构建器连接 capsolver_tools。
不要给每个代理每个工具。更安全的模式是在恢复子图或专用执行器中仅暴露挑战工具,该执行器在 authorize_recovery() 返回 allowed=True 后运行。
CapSolver 在高层次上记录了代理工具映射:
solve_captcha 调用核心 solve 功能;detect_captchas 调用核心 detect 功能;solve_on_page 调用核心 solve_on_page 功能;仅使用集成所需的最小工具。对于实时浏览器会话,面向浏览器的检测和填入工作流通常比让模型操作原始解决方案保留更多上下文。
AWS WAF 令牌是客户端会话的一部分。 AWS WAF 令牌文档 解释了挑战和 CAPTCHA 操作使用令牌来跟踪成功交互。在检测和重试之间更换浏览器或丢失其 cookies 会丢弃该状态。
不要将 Playwright Page 序列化到 LangChain 消息或图检查点中。将其存储在应用拥有的注册表中:
class BrowserRegistry:
def __init__(self) -> None:
self._pages: dict[str, object] = {}
def register(self, page_id: str, page: object) -> None:
self._pages[page_id] = page
def get(self, page_id: str) -> object:
if page_id not in self._pages:
raise KeyError("browser page is not registered")
return self._pages[page_id]
async def close(self, page_id: str) -> None:
page = self._pages.pop(page_id, None)
if page is not None:
await page.close()
代理状态应仅包含不透明的 page_id、当前 URL、用途、尝试次数和状态。排除 cookies、本地存储、解决方案令牌、API 密钥和原始 HTML。
使用小的结果类型,使模型无法重新解释低级响应:
from typing import Literal, TypedDict
RecoveryStatus = Literal[
"not_needed",
"authorized",
"resolved",
"retry",
"review",
"denied",
]
class RecoveryState(TypedDict, total=False):
request_id: str
purpose: str
current_url: str
page_id: str
attempts: int
waf_action: str
recovery_status: RecoveryStatus
error_code: str | None
final_assertion_passed: bool
def route_after_detection(state: RecoveryState) -> str:
if state.get("waf_action") not in {"challenge", "captcha"}:
return "continue"
if state.get("recovery_status") == "authorized":
return "recover"
if state.get("recovery_status") in {"denied", "review"}:
return "human_review"
return "authorize"
def route_after_recovery(state: RecoveryState) -> str:
status = state.get("recovery_status")
if status == "resolved":
return "verify"
if status == "retry":
return "authorize"
return "human_review"
恢复节点可以调用批准的 CapSolver 浏览器工具,但应仅返回状态和稳定错误代码。永远不要将原始工具响应放入下一个模型提示中。
挑战完成并不证明原始业务操作成功。在相同会话中重复执行预期的导航或请求,并验证受控应用信号:
async def verify_expected_page(page, expected_url_prefix: str) -> bool:
await page.wait_for_load_state("domcontentloaded")
if not page.url.startswith(expected_url_prefix):
return False
marker = page.get_by_test_id("authorized-content")
try:
await marker.wait_for(state="visible", timeout=15_000)
return True
except Exception:
return False
选择由您的应用控制的稳定标记:测试 ID、特定 API 响应或已知状态转换。避免使用广泛断言,如“页面包含文本”,因为错误页面可能包含类似文字。
如果验证失败,请勿立即调用求解器。重新分类当前响应,检查会话是否更改,强制执行重试预算,并将模糊情况发送给人类。
有限的工作流应区分至少以下情况:
| 条件 | 推荐路径 |
|---|---|
| 未检测到 AWS WAF 信号 | 继续正常工作流 |
| 在批准的主机上检测到已知信号 | 运行授权恢复节点 |
| 未知状态/头组合 | 人工审核 |
| 重定向到未批准的主机 | 拒绝 |
| 挑战工具超时 | 如果总预算允许,重试一次 |
| 恢复报告成功但页面断言失败 | 重新分类,然后审核 |
| 重试限制达到 | 停止并记录稳定错误代码 |
| 缺少凭证或浏览器会话 | 配置错误;不要让模型修复它 |
对瞬态传输错误使用指数退避,但不要使用无限制循环。重试计数器属于确定性状态,而不是模型内存。
记录事件如 waf_signal_detected、policy_allowed、recovery_started、recovery_finished 和 page_verified。包括请求 ID、主机、持续时间、尝试次数和错误代码。排除凭证、cookies、令牌、原始挑战负载和敏感页面内容。
Agent traces显示工作流的决策;AWS指标显示保护层的观察结果。AWS在其WAF指标参考中列出了Challenge和CAPTCHA活动的CloudWatch指标,包括请求、尝试、解决和有效令牌计数。
有用的运营问题包括:
使用内部请求ID而非凭证或令牌来关联系统。挑战流量的突然增加应触发诊断,而不是默认增加重试预算。
文本具有歧义且容易更改。应优先使用文档化的响应状态和头部、浏览器网络事件或应用自有信号。
新浏览器可能会丢失cookies和令牌状态。在检测、恢复、重试和验证过程中保持相同的已批准会话。
模型不需要它们。将敏感值保留在确定性适配器内部,并返回一个类型化状态。
始终重复预期操作并检查领域特定的断言。
强制执行主机名允许列表、用途检查、重定向检查、重试预算,并在模型外审查路由。
LangChain和LangGraph构建API会不断演进。固定通过测试的版本,将框架连接隔离在一个模块中,并在升级前重新运行集成测试。
使用自有的测试页面并覆盖以下情况:
在单元测试中模拟分类器、策略门和验证器。将带凭证的端到端测试保留给已批准的环境。测试日志同样重要:断言凭证、cookies和令牌应不存在。
CapSolver的入门指南记录了其任务生命周期和支持的CAPTCHA类别。选择任务路径时,请使用当前的第一方文档;不要根据旧代码片段或第三方帖子猜测字段。
可靠的AWS WAF LangChain集成是一个受控状态机,而不是单个“解决”提示。检测文档化的WAF信号,验证目标和用途,调用作用域狭窄的工具,保留相同的客户端会话,并在代理继续之前确认原始操作。
对于授权自动化,CapSolver 提供了将挑战处理连接到LangChain所需的代理和核心层,同时将策略、秘密和最终验证保留在应用代码中。
使用CapSolver的文档验证当前集成路径,然后在自有的或明确授权的测试环境中尝试CapSolver。充值时使用优惠码CAP26可获得配置的5%优惠。
Q: LangChain代理如何检测AWS WAF Challenge?
在确定性的HTTP或浏览器层中检查HTTP 202和x-amzn-waf-action: challenge的文档化组合。不要让语言模型从页面文本中推断条件。
Q: 哪种响应表示AWS WAF的CAPTCHA操作?
AWS文档中规定,当请求没有有效令牌时,使用HTTP 405和x-amzn-waf-action: captcha作为CAPTCHA响应。将状态/头部组合不匹配的情况视为未知,并路由至审核。
Q: CapSolver API密钥是否应传递给LangChain模型?
不。应从已批准的密钥存储中在主机应用或工具适配器中加载。模型不应看到密钥、cookies、WAF令牌或原始解决方案值。
Q: 代理在完成挑战后能否使用新浏览器?
应尽可能保持相同的浏览器上下文,因为AWS WAF令牌状态与客户端会话相关联。更换会话可能会丢弃重复请求所需的令牌状态。
Q: 成功的挑战工具结果是否足够继续?
不。重复预期操作并验证应用特定的成功断言。工具结果仅是中间状态。
Q: 代理应重试多少次?
根据工作流的风险和时间限制设置一个小的显式重试预算。示例中使用两次尝试作为应用策略,而非CapSolver或AWS的保证。
Q: 此工作流能否用于任何网站?
不能。仅用于您拥有或明确授权自动化的系统。在模型外部强制执行目标和用途检查,并将不确定情况路由至人工审核。