为什么 Proxy Request 耗尽重试次数
问题说明
POST /api/proxy/ 调用返回错误且无数据。错误信息简短且格式固定:
{
"error": "Download maxTry limit reached",
"total": 34.812,
"request": { "...": "..." }
}
无论所有出口节点都被封禁、所有出口节点均已失效,还是 FourA 几乎在每次尝试中都获取到了真实页面但被您自己的 validate 规则丢弃,该句子的表象完全一致。但这三种情况所需的修复方案截然相反。
解决方案:attemptReport
每个耗尽尝试次数后失败的 Proxy 调用,都会在错误信息旁附带一个 attemptReport 对象,包括以 "error": "Timeout" 结束的调用。无法解析的主机名会在执行任何尝试前直接失败,返回 status: 0 且不包含报告。它统计了各次尝试实际遇到的情况:
{
"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[] | 任务发送的浏览器配置档案,按首次使用顺序排列。default 表示请求按您的原始写法直接发出。 |
summary |
string | 根据计数值生成的一句话说明。可安全记录到日志或展示给用户。 |
数据解读
contentRejected 较高
页面已成功返回。您的 validate.data 规则未能匹配到它们。
这是您可以自行修复的问题,也是被其他所有指标掩盖的问题:请求在所有指标中看起来都是失败的,但 FourA 全程都在传输真实内容。在完全不使用 validate 的情况下通过 POST /api/single/ 获取一次页面,查看实际返回的内容,并针对它重写规则。
常见原因是将同一规则应用到了结构不同的页面集合上。例如,只存在于文章页而不存在于视频页的选择器,在命中视频页时会百分之百失败,并且持续产生全额费用。
statusRejected 较高
站点已响应,但您的 validate.status 规则判定不通过。如果这些状态码是 401、403、429 或 503,说明站点是在拒绝客户端,而不是页面不存在。请尝试:
- 更换浏览器配置档案(内部
request对象上的browser、os、version) - 如果内容受区域限制,请使用
exitCountries - 如果解除拒绝需要运行 JavaScript,请使用
POST /api/browser/
defense 较高
响应中识别到了 Bot 验证,vendors 会标明具体服务商。关于 FourA 目前支持绕过的内容以及仅报告的内容,请参见 站点验证。如果该服务商无法在此 endpoint 上绕过,请将调用迁移至 POST /api/browser/ 或 POST /api/auto/。
noResponse 较高
出口节点完全没有响应,因此未获取到任何关于目标的信息。请调高 maxTries,调高 timeout_ms,并检查该 URL 是否能从公网正常解析。
other 较高
站点返回了状态码,未识别到 Bot 验证,且该次尝试因其他原因失败,例如 validate.headers 规则。响应速度慢于您的 timeout_ms 的目标会统计在 noResponse 下。
浏览器配置档案轮换
当目标站点拒绝 FourA 发送的浏览器时,Proxy 会停止重试该浏览器,并尝试来自 public profile catalogue 的另一个系列。这不会消耗额外的尝试次数:轮换只会改变重试时发送的内容,绝不会影响是否进行重试。
Proxy 还会按站点记录在默认配置被拒绝后最近一次成功运行的系列。后续发往该站点的请求可以从首次尝试就直接使用该系列,一旦该站点也开始拒绝该系列,FourA 就会切换到下一个系列。手机浏览器仅对响应 JSON 的站点进行记忆。
通过 profilesTried 可以查看其发生过程。单个条目表示每次尝试都发送了相同的浏览器:default 表示原始请求,而目录 ID 则是 FourA 之前已为该站点学习并记录的系列。多个条目则意味着执行了轮换,且站点拒绝了其中的每一个,这与从未轮换过的情况完全不同。
在成功的 Proxy 响应中,仅当轮换选择了你未请求的浏览器时,才会出现 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 request 与 response 参考
- Site checks:供应商、
defense与重放 clearance - Request Outcomes:被拒绝 response 的分类与计费规则
- Common Issues:其他常见问题及解决方法
- Choosing the Right Endpoint:何时不适合使用 Proxy