# 为什么 Proxy Request 耗尽重试次数

> URL: https://foura.ai/zh/docs/troubleshooting/proxy-attempt-report
> Updated: 2026-09-30
> Index: https://foura.ai/llms.txt

## 问题说明

`POST /api/proxy/` 调用返回错误且无数据。错误信息简短且格式固定：

```json
{
  "error": "Download maxTry limit reached",
  "total": 34.812,
  "request": { "...": "..." }
}
```

无论所有出口节点都被封禁、所有出口节点均已失效，还是 FourA 几乎在每次尝试中都获取到了真实页面但被您自己的 `validate` 规则丢弃，该句子的表象完全一致。但这三种情况所需的修复方案截然相反。

## 解决方案：`attemptReport`

每个耗尽尝试次数后失败的 Proxy 调用，都会在错误信息旁附带一个 `attemptReport` 对象，包括以 `"error": "Timeout"` 结束的调用。无法解析的主机名会在执行任何尝试前直接失败，返回 `status: 0` 且不包含报告。它统计了各次尝试实际遇到的情况：

```json
{
  "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 目前支持绕过的内容以及仅报告的内容，请参见 [站点验证](https://foura.ai/docs/api/site-checks)。如果该服务商无法在此 endpoint 上绕过，请将调用迁移至 `POST /api/browser/` 或 `POST /api/auto/`。

### `noResponse` 较高

出口节点完全没有响应，因此未获取到任何关于目标的信息。请调高 `maxTries`，调高 `timeout_ms`，并检查该 URL 是否能从公网正常解析。

### `other` 较高

站点返回了状态码，未识别到 Bot 验证，且该次尝试因其他原因失败，例如 `validate.headers` 规则。响应速度慢于您的 `timeout_ms` 的目标会统计在 `noResponse` 下。

## 浏览器配置档案轮换

当目标站点拒绝 FourA 发送的浏览器时，Proxy 会停止重试该浏览器，并尝试来自 [public profile catalogue](https://foura.ai/docs/api/endpoints) 的另一个系列。这不会消耗额外的尝试次数：轮换只会改变重试时发送的内容，绝不会影响是否进行重试。

Proxy 还会按站点记录在默认配置被拒绝后最近一次成功运行的系列。后续发往该站点的请求可以从首次尝试就直接使用该系列，一旦该站点也开始拒绝该系列，FourA 就会切换到下一个系列。手机浏览器仅对响应 JSON 的站点进行记忆。

通过 `profilesTried` 可以查看其发生过程。单个条目表示每次尝试都发送了相同的浏览器：`default` 表示原始请求，而目录 ID 则是 FourA 之前已为该站点学习并记录的系列。多个条目则意味着执行了轮换，且站点拒绝了其中的每一个，这与从未轮换过的情况完全不同。

在**成功**的 Proxy 响应中，仅当轮换选择了你未请求的浏览器时，才会出现 `profile` 字段：

```json
{
  "status": 200,
  "data": "<!doctype html>...",
  "proxy": "A1B2C3",
  "profile": "...",
  "total": 4.108
}
```

该值是来自 `GET /api/profiles` 的目录 ID。缺失表示 request 完全按原样发出。存在则表示成功生效的浏览器并非您所指定的浏览器，因此在后续调用中应将该 ID 作为 `profile` 传回，而不是重放失败的 ID。控制台 [Playground](https://foura.ai/docs/dashboard/playground) 会通过 **Carry** 自动处理此操作。

在 request 中明确指定的 `profile`、`browser`、`os` 或 `version` 绝不会被覆盖。携带您自定义 `User-Agent` 或 `Cookie` header 的 request 也不会被覆盖，因为 clearance 已与获取它的签名绑定。

## 在代码中读取

```python
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](https://foura.ai/docs/api/endpoints)：完整的 Proxy request 与 response 参考
- [Site checks](https://foura.ai/docs/api/site-checks)：供应商、`defense` 与重放 clearance
- [Request Outcomes](https://foura.ai/docs/api/outcomes)：被拒绝 response 的分类与计费规则
- [Common Issues](https://foura.ai/docs/troubleshooting/common-issues)：其他常见问题及解决方法
- [Choosing the Right Endpoint](https://foura.ai/docs/guides/choosing-task-type)：何时不适合使用 Proxy
