为什么 Proxy Request 耗尽重试次数
问题说明
一次 POST /api/proxy/ 调用返回错误且无数据。错误信息简短且格式固定:
{
"error": "Download maxTry limit reached",
"total": 34.812,
"request": { "...": "..." }
}
无论是因为所有出口都被封禁、所有出口均失效,还是 FourA 几乎在每次尝试中都获取到了真实页面但被您自己的 validate 规则丢弃,该句子的表述都完全相同。这三种情况需要截然相反的修复方案。
解决方案:attemptReport
每个失败的 Proxy 响应都会在错误信息旁附带一个 attemptReport 对象。它统计了各次尝试实际遇到的情况:
{
"error": "Download maxTry limit reached",
"attemptReport": {
"total": 25,
"noResponse": 0,
"defense": 0,
"contentRejected": 25,
"statusRejected": 0,
"other": 0,
"vendors": [],
"profilesTried": ["default"],
"summary": "25 attempt(s): 25 returned HTTP 200 with no defense present and were rejected only by your validate.data - the page was fetched, your content rule did not match it"
},
"total": 34.812
}
error 字符串特意保持未修改,以便匹配它的客户端能继续正常工作。如果需要单行概述,请读取 attemptReport.summary;如果需要基于计数进行逻辑分支,请读取各项计数。
字段
| 字段 | 类型 | 统计内容 |
|---|---|---|
total |
integer | 已发起的尝试次数 |
noResponse |
integer | 出口节点未响应,因此从未连上目标站点 |
defense |
integer | 站点已响应,且在响应中识别到 Bot 拦截服务商 |
contentRejected |
integer | HTTP 200,无 Bot 拦截,仅被您的 validate.data 拒绝 |
statusRejected |
integer | 站点已响应,无 Bot 拦截,被您的 validate.status 拒绝 |
other |
integer | 已响应,且不属于上述任何情况 |
vendors |
string[] | 任务中检测到的所有 Bot 拦截服务商 |
profilesTried |
string[] | 任务发送的浏览器 Profile,按首次使用顺序排列。default 表示请求完全按照您的原始配置发出。 |
summary |
string | 根据统计计数生成的一句话说明。可安全用于记录日志或向用户展示。 |
解读数据
contentRejected 偏高
页面已成功返回,但您的 validate.data 规则未能匹配。
这是您可以自行修复的问题,也是被其他所有指标掩盖的问题:在所有指标看来请求全部失败,而 FourA 全程都在成功交付真实内容。在不使用任何 validate 的情况下通过 POST /api/single/ 获取一次页面,查看实际返回的内容,并针对返回内容重写规则。
常见原因是将同一套规则应用到了结构不同的页面集合上。例如,文章页存在的选择器在视频页不存在,每次请求到视频页都会失败,且会一直持续并产生完整计费。
statusRejected 偏高
站点已响应,但被您的 validate.status 规则拒绝。如果这些状态码是 401、403、429 或 503,说明站点是在拒绝客户端,而不是页面不存在。可以尝试:
- 更换其他浏览器 Profile(在内部
request对象上配置browser、os、version) - 如果内容有地域限制,使用
exitCountries - 如果拒绝需要 JavaScript 执行才能解除,使用
POST /api/browser/
defense 偏高
在响应中识别到了 Bot 拦截,vendors 会列出具体服务商。关于 FourA 目前可直接绕过与仅作检测报告的分类,请参阅 Anti-Bot Defenses。如果该服务商无法在此 endpoint 上绕过,请将调用迁移至 POST /api/browser/ 或 POST /api/auto/。
noResponse 偏高
出口节点完全未收到响应,因此无法获取目标的任何状态信息。请调大 maxTries,调大 timeout_ms,并检查该 URL 是否可以从公网正常解析。
other 偏高
目标已响应,但未命中上述任何分类。请比对 total_time 与您的 timeout_ms:目标响应耗时超出预算时会归入此类。
浏览器 Profile 轮换
当目标网站拒绝 FourA 发送的浏览器特征时,Proxy 会停止使用该特征,并尝试 公共 profile 目录中的其他系列。这不会消耗额外的尝试次数:轮换只会改变重试时发送的内容,而不会决定是否进行重试。
profilesTried 用于查看该过程的具体发生情况。仅包含一条记录表示每次发送的 request 均与定义完全一致。包含多条记录则表示执行了轮换,但网站拒绝了其中的每一次尝试,这与完全未轮换的情况截然不同。
在成功的 Proxy response 中,仅当轮换选择了非您指定的浏览器时,才会出现 profile 字段:
{
"status": 200,
"data": "<!doctype html>...",
"proxy": "A1B2C3",
"profile": "...",
"total": 4.108
}
该值是来自 GET /api/profiles 的目录 ID。缺失表示 request 完全按原样发出。存在则表示最终生效的浏览器并非你输入的浏览器,因此在后续调用中应将该 ID 作为 profile 传回,而不是重放失败的 ID。控制台 Playground 会通过 Carry 自动为你处理此操作。
在你的 request 中显式指定的 profile、browser、os 或 version 绝不会被覆盖。携带自定义 User-Agent 或 Cookie header 的 request 也不会被覆盖,因为 clearance 已与获取它时所用的签名绑定。
在代码中读取
import requests
API = "https://eu.api.foura.ai"
H = {"X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json"}
r = requests.post(f"{API}/api/proxy/", headers=H, json={
"maxTries": 5,
"request": {
"method": "GET",
"url": "https://example.com/product/42",
"validate": {"data": {"accept": ["Add to cart"]}},
},
}).json()
if "error" in r:
rep = r.get("attemptReport", {})
print(rep.get("summary", r["error"]))
if rep.get("contentRejected", 0) > rep.get("total", 0) / 2:
# The pages arrived. The validate rule is what threw them away.
raise SystemExit("validate.data did not match the real page")
if rep.get("defense", 0):
print("bot check met:", ", ".join(rep.get("vendors", [])))
相关内容
- API Endpoints:完整的 Proxy 请求与响应参考
- Anti-Bot Defenses:供应商、
defense以及重放验证通过凭证 - Request Outcomes:被拒绝响应的分类与计费机制
- Common Issues:其他常见问题及解决方案
- Choosing the Right Endpoint:何时不适合使用 Proxy