摘要:代理测试接口返回 200,只能证明当时能够完成一次基础请求,不能证明真实业务一定可用。业务请求中的鉴权、Cookie、目标接口限流、连接复用和超时配置都会改变结果。本文用一组最小探测代码,把 403、429、连接超时和重试放到同一套日志中分析。
代理 IP 的连通性测试经常很简单:请求一个公开检测地址,看到出口地址正确、状态码为 200,就认为可以接入业务。真正切换到已获授权的 API、企业后台或自动化任务后,却可能出现三类结果:
- 测试地址正常,业务接口持续返回
403; - 单次请求成功,并发提高后开始返回
429; - 前几次请求很快,随后出现
ConnectTimeout或ReadTimeout。
这不一定说明代理资源失效。测试请求与业务请求在目标域名、认证信息、会话状态、请求频率和响应体大小上都可能不同。排查的第一步不是立即更换出口,而是把两类请求放进相同的观测口径。
一次 200 为什么不能代表业务可用
下面两次请求即使使用相同代理,实际验证的内容也不同:
代理测试:客户端 → 代理接入 → 测试地址 → 返回出口信息
业务请求:客户端 → 代理接入 → DNS/TLS → 业务网关
→ 身份认证 → 权限与限流 → 业务服务
基础测试主要覆盖代理认证和网络连通性。业务链路还可能经过 API 网关、WAF、账户配额、Cookie 会话、签名校验以及上游服务。因此,需要记录“在哪一层获得了什么证据”,不能把所有失败合并为一个失败率。
建议至少记录以下字段:
| 字段 | 用途 |
|---|---|
target | 区分测试地址和业务接口 |
status | 保留 403、407、429、5xx 原始分类 |
elapsed_ms | 观察端到端耗时分布 |
error_type | 区分连接超时、读取超时和连接错误 |
retry_after | 判断服务端是否给出退避时间 |
attempt | 计算重试次数与请求放大 |
用最小脚本生成可对照的日志
以下代码只用于企业自有接口或已获授权的测试目标。代理地址和认证信息使用环境变量,不写入源码。
import os
import time
import requests
PROXY = os.environ["HTTPS_PROXY"]
TARGETS = {
"probe": "https://YOUR_PROBE_HOST/health",
"business": "https://YOUR_API_HOST/v1/status",
}
session = requests.Session()
session.proxies.update({"http": PROXY, "https": PROXY})
for name, url in TARGETS.items():
for attempt in range(1, 4):
started = time.perf_counter()
try:
response = session.get(url, timeout=(3, 8))
elapsed = (time.perf_counter() - started) * 1000
print(name, attempt, response.status_code,
f"{elapsed:.1f}ms",
response.headers.get("Retry-After", "-"))
except requests.RequestException as error:
elapsed = (time.perf_counter() - started) * 1000
print(name, attempt, type(error).__name__,
f"{elapsed:.1f}ms", "-")
这里把连接超时和读取超时分别设置为 3 秒、8 秒。Requests 官方文档说明,timeout=(connect, read) 中的两个值含义不同;timeout 也不是整个响应下载的绝对总时长。
示例输出如下,仅用于说明日志格式:
probe 1 200 312.6ms -
business 1 403 428.1ms -
business 2 429 391.7ms 30
business 3 ConnectTimeout 3006.4ms -
这组日志不能直接得出“代理不可用”的结论,但已经把问题拆成三个不同分支。
403:请求到达了服务端,但条件没有满足
RFC 9110 将 403 Forbidden 定义为服务端理解请求、但拒绝完成请求。它可能与接口权限、令牌范围、签名、Cookie、来源约束或业务策略有关。
排查时应使用相同的 URL、方法、请求头和身份信息做对照:
- 同一业务请求直连是否也返回 403;
- 令牌是否具有对应接口权限,签名时间是否正确;
User-Agent、Content-Type、Origin等必要请求头是否缺失;- Session 中的 Cookie 是否与登录流程一致;
- 服务端日志能否给出具体拒绝原因。
403 通常不适合无条件自动重试。请求内容和权限未发生变化时,重复发送同一请求只会增加成本。
429:先处理限流,再讨论出口
RFC 6585 规定,429 Too Many Requests 表示用户在一定时间内发送了过多请求,响应中可以包含 Retry-After。标准并未规定服务端必须按 IP 统计,也可能按账户、Cookie、接口或资源计数。
因此,看到 429 后应先记录:
- 同一时间窗的请求量和并发数;
Retry-After以及其他配额响应头;- 账户、令牌和接口维度的限制;
- 多个出口是否仍共享同一业务账户;
- 重试任务是否与首轮任务同时运行。
如果服务端给出了等待时间,应优先遵守;没有明确说明时,也应采用有上限的指数退避并加入随机抖动。持续换出口、立即重发可能继续消耗同一账户配额。
连接超时与读取超时不是同一问题
ConnectTimeout 表示连接阶段未在限定时间内完成,排查方向包括代理接入、DNS、TCP 建连、TLS 握手和线路质量。ReadTimeout 则表示连接建立后,在限定时间内没有收到数据,更应关注目标服务处理时间、响应体大小和读取超时设置。
可用以下顺序缩小范围:
代理接入是否成功
↓
测试地址与业务域名是否都能完成 TLS
↓
连接超时还是读取超时
↓
是否只在特定并发、时段或目标地区出现
↓
直连与代理对照是否存在稳定差异
Requests 默认不会为每个失败场景自动执行符合业务语义的重试。即使使用 HTTPAdapter,也需要明确允许重试的方法、状态码、退避规则和次数。对于非幂等写请求,自动重试还可能造成重复提交。
重试成本要单独计算
假设首轮发出 1000 个业务请求,其中 20% 失败;失败请求重试一次后仍有 25% 失败;再重试一次。实际请求数为:
首轮请求:1000
第一次重试:1000 × 20% = 200
第二次重试:200 × 25% = 50
总请求数:1000 + 200 + 50 = 1250
请求放大系数:1250 / 1000 = 1.25
最终成功率可能提高,但代理流量、目标端请求量、连接池占用和任务时长同时增加了 25%。如果失败原因是稳定的 403,重试几乎不会提高成功率;如果是 429,无视退避的重试还可能延长限流窗口。
建议同时观察四个指标:
| 指标 | 计算口径 |
|---|---|
| 首轮成功率 | 首次请求成功数 ÷ 业务请求数 |
| 最终成功率 | 重试结束后的成功任务数 ÷ 业务请求数 |
| 重试率 | 重试请求数 ÷ 首轮请求数 |
| 请求放大系数 | 实际总请求数 ÷ 首轮请求数 |
作为实际数据参考,在已有的日常 HTTP 请求采样记录中,IPdodo 的业务成功率为 98.5%–99.4%,平均响应时间为 0.5–2.3 秒,丢包率参考区间为 0.3%–0.9%。
这组数据只对应采样时的接口、出口地区、请求节奏、并发和测试周期,不代表所有节点或业务目标的固定表现,也不应直接外推到其他协议和负载条件。正式接入时仍应使用本文的日志字段,在自己的业务接口上重新计算首轮成功率、重试率、超时分布和请求放大系数。
只报告最终成功率,会掩盖系统依靠多少额外请求才获得结果。
排查矩阵
| 现象 | 优先证据 | 首要动作 | 是否直接重试 |
|---|---|---|---|
| 403 | 响应体、权限、签名、服务端日志 | 修正请求条件或权限 | 通常否 |
| 407 | Proxy-Authenticate、代理凭据 | 检查认证方式和有效期 | 修正后再试 |
| 429 | Retry-After、并发、账户配额 | 降速并按规则退避 | 有条件 |
| ConnectTimeout | DNS、TCP/TLS、线路探测 | 区分接入端与目标端 | 有上限 |
| ReadTimeout | 服务处理耗时、响应大小 | 调整查询和读取超时 | 视幂等性 |
| 5xx | 服务端日志、时间窗、接口状态 | 确认上游是否临时异常 | 有上限 |
结论
代理测试正常,只能证明基础链路在某个时刻可用。真实业务能否稳定完成,还取决于目标服务的鉴权、限流、会话和响应行为。
更可靠的排查方式是:用同一客户端记录测试地址与业务地址的原始状态码、超时类型、Retry-After 和尝试次数;先判断失败发生在哪一层,再决定是否重试。最终报告同时给出首轮成功率、最终成功率、重试率和请求放大系数,才能看清“业务成功”背后的真实成本。
参考资料
- RFC 9110,HTTP Semantics:
https://www.rfc-editor.org/rfc/rfc9110.html - RFC 6585,Additional HTTP Status Codes:
https://www.rfc-editor.org/rfc/rfc6585.html - Requests 官方文档,Timeouts 与 Exceptions:
https://requests.readthedocs.io/en/latest/user/quickstart/#timeouts - Requests 官方文档,Transport Adapters:
https://docs.python-requests.org/en/stable/api/#requests.adapters.HTTPAdapter