为什么 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", [])))
更新于: 2026年9月30日