# 代理基准测试应该测量什么

Source: https://ipvolt.com/zh/blog/what-a-proxy-benchmark-should-measure
Markdown: https://ipvolt.com/zh/blog/what-a-proxy-benchmark-should-measure.md
Language: zh-CN

[ipvolt 首页](https://ipvolt.com/zh.md) / [博客](https://ipvolt.com/zh/blog.md) / 代理基准测试应该测量什么

分析
发布于: 2026-09-11
更新于: 2026-09-11
作者： ipvolt team
阅读约 2 分钟

使用内容成功率、响应时间分布和会话行为来比较代理，并提供一个基于 curl 的 Python 测试脚本，用于记录失败类别并计算成功率与延迟结果。

使用应用层成功率、响应时间分布、连接建立和会话行为来比较代理。在收集结果之前，先为每个目标站点定义什么是成功。下面的示例包括一次实测的直连基线，以及一个检查响应标记并计算成功率和延迟的小型测试脚本。我们尚未发布可比较的住宅代理与数据中心代理结果。

## 在测量速度之前先定义成功

当失败触发重试时，较低的响应时间可能掩盖每个成功请求的高成本。除了耗时，还要统计完成的工作量，并把所有重试流量计入成本估算。

成功需要针对每个目标站点分别定义：预期的状态码、必需的内容以及可接受的重定向结果。包含 CAPTCHA 质询的 HTTP 200 响应可能不符合该定义。缺失字段或不完整的下载同样可能不符合。简单的文本标记适合作为入门检查；生产工作负载可能需要解析后的记录、模式验证或完整的多步骤流程。

分别记录成功的响应和每一类失败，然后调查原因。超时可能发生在路径上的多个环节。单凭 403 无法识别是哪个中间节点或源站实施了限制。响应时间和状态码都不足以归责。

## 记录首字节时间和总时间

curl 报告的时间节点从传输开始计时。首字节时间（TTFB）告诉你响应何时开始；总时间包括接收响应体。两者都要记录，因为首字节很快，之后仍可能是缓慢或不完整的下载。

2026 年 9 月 11 日，从赫尔辛基的一台 VPS 使用 curl 8.18 对 `https://example.com/` 连续发出三次直连请求，得到以下数值。未使用代理。下面的标签明确了各值的累计含义：

```text
http=200 tcp_complete=0.009780s tls_complete=0.023224s ttfb=0.044821s total=0.044983s
http=200 tcp_complete=0.008289s tls_complete=0.023851s ttfb=0.039902s total=0.039966s
http=200 tcp_complete=0.008149s tls_complete=0.031935s ttfb=0.061614s total=0.061688s
```

这是一个实测的首字节约 40–62 ms 的基线，而不是未来请求的下限。三次观测无法确立稳定的分布。请在临近代理比较的时间，在同一台机器和同一目标站点上重新采集基线，并保留运行日期、客户端版本和配置。

下面这条有界命令为一次新的直连请求收集相同的字段：

```sh
curl --disable --silent --show-error --noproxy '*' \
  --connect-timeout 5 --max-time 20 --retry 0 \
  --output /dev/null \
  --write-out 'http=%{http_code} tcp_complete=%{time_connect}s tls_complete=%{time_appconnect}s ttfb=%{time_starttransfer}s total=%{time_total}s exit=%{exitcode}\n' \
  'https://example.com/'
```

要通过 HTTP(S) 代理发出同样的请求，请使用 curl 8.3 或更高版本。把 `PROXY_URL` 设为供应商的、不含凭据的网关，包括其支持的协议和端口。让密钥管理器在环境中提供 `PROXY_USERNAME` 和 `PROXY_PASSWORD`。此示例遵循 [curl 设置指南](/zh/guides/curl-proxy-setup)：凭据由 curl 自行导入，因此展开后的密码不会成为 shell 参数。不要开启 shell 跟踪。

```sh
: "${PROXY_URL:?Set a credential-free HTTP(S) gateway URL}"
curl --disable --silent --show-error --noproxy '' \
  --proxy "$PROXY_URL" --proxy-basic \
  --variable %PROXY_USERNAME --variable %PROXY_PASSWORD \
  --expand-proxy-user '{{PROXY_USERNAME}}:{{PROXY_PASSWORD}}' \
  --connect-timeout 5 --max-time 20 --retry 0 \
  --output /dev/null \
  --write-out 'http=%{http_code} tunnel=%{http_connect} tcp_complete=%{time_connect}s tls_complete=%{time_appconnect}s ttfb=%{time_starttransfer}s total=%{time_total}s exit=%{exitcode}\n' \
  'https://example.com/'
```

`--disable` 放在最前面以忽略个人 curl 配置。直连命令的 `--noproxy '*'` 绕过继承的代理；代理命令的空绕过列表则防止 `NO_PROXY` 把目标站点排除在外。这些命令每个 curl 进程使用一个新连接，不跟随重定向，也不重试。由于命令有意省略了 `--fail`，HTTP 错误时 curl 退出码仍可能为零；请单独检查 HTTP 状态。路由选择参见 [curl 手册](https://curl.se/docs/manpage.html#--noproxy)，截止时间参见[超时指南](/zh/guides/proxy-timeout-troubleshooting)。

## 理解连接时序包含了什么

对于经 HTTP 代理访问的 HTTPS 目标站点，curl 先建立到代理的连接，请求 CONNECT 隧道，然后通过该隧道与目标站点协商 TLS。[RFC 9110](https://www.rfc-editor.org/rfc/rfc9110#name-connect) 定义了 CONNECT。

`time_connect` 与 `time_appconnect` 之间的间隔包含隧道建立和 TLS 建立。减去相应的直连间隔并不能隔离出 CONNECT 开销：路由和握手条件也会变化。HTTPS 代理还会增加一次 TLS 握手。静态 CDN 对象并不能消除这些差异。

利用这些时间节点定位延迟出现在哪里，然后借助受控端点或经过适当插桩的客户端和代理日志进行调查。只对同一次简单传输中已完成的节点做减法。失败的请求、复用的连接和重定向需要单独解读；零值可能意味着某个阶段尚未到达。

`%{http_connect}` 报告 HTTP CONNECT 响应码，与传输的 HTTP 状态分开。`--proxy-header` 向代理发送请求头；它不报告隧道状态。优先使用选定的状态和时序字段，而不是可能包含身份验证数据的详细跟踪。这些解读遵循 curl 的[时序字段文档](https://curl.se/docs/manpage.html#-w)。

## 对照承诺的策略检查轮换

出口 IP 是目标站点观察到的公网地址。查询供应商支持的诊断端点，或你自己控制的、能报告调用方地址的端点。[ipify](https://www.ipify.org/) 是一个公开选项；在自动化请求之前请查看其当前的使用指引。

在把结果归约为唯一地址计数之前，先保留带时间戳的结果序列。对每次尝试，记录所选地区、会话设置、传输结果以及经过验证的 IPv4 或 IPv6 地址。把失败的请求和无效的响应体排除在地址计数之外，同时单独报告这些失败。在 Python 中，`ipaddress.ip_address(value.strip())` 可以验证纯文本的地址响应。

把该序列与供应商的轮换策略进行比较。轮换可能按请求、按连接或按时间间隔发生，池也可能重用某个地址。除非产品明确承诺，否则十个请求并不要求十个不同的出口。粘性会话旨在在其文档说明的时长内保留同一出口；在把重新分配视为故障之前，先检查其过期和断开行为。

使用你的工作负载所需的国家或地区以及会话设置。在相关的会话窗口内重复测试。[轮换代理与粘性代理指南](/zh/guides/rotating-vs-sticky-proxies)解释了应评估哪些行为。下面的内容基准测试不测量轮换。

## 选择样本量和观测窗口

十个请求可以发现基本故障，但对延迟分布几乎提供不了什么证据。缓慢的请求可能拖住队列；单独的平均值可能掩盖缓慢的离群值。

我们建议的起点是每个目标站点、每种配置 200 个请求，分布在至少一小时内。这是一个测试设计上的选择，不是通用的可靠性阈值。小样本的第 95 百分位取决于极少数观测。请报告样本量、失败次数和观测窗口，然后在与工作负载相关的不同时间和条件下重复。

比较供应商时，在同一窗口内交错发送它们的请求。这能减少目标站点负载变化造成的差异，但不保证条件完全相同。保持请求内容、客户端设置和成功标准一致。连接复用和生产环境的并发应与下面这个顺序执行、每次新建进程的示例分开测试。

## 一个带汇总的小型内容基准测试

将其保存为 `proxy-bench.py`，并运行 `python3 proxy-bench.py`。它需要 Python 3 和 curl 8.3+。它使用与上面相同的私有代理环境变量。直连运行时设置 `BENCH_MODE=direct`，否则保持为 `proxy`。每次运行都会创建一个新的结果目录并打印其路径。

默认目标站点是一个小型示例页面。要进行实际比较，请把 `BENCH_URL` 设为一个获准的 HTTPS 端点，并把 `BENCH_MARKER` 替换为有意义的预期字符串。不要使用包含凭据或敏感参数的 URL。在引用应用层成功率之前，先根据你的真实响应格式调整内容检查。

```python
import collections
import datetime
import json
import math
import os
from pathlib import Path
import statistics
import subprocess
import tempfile
import time

url = os.environ.get("BENCH_URL", "https://example.com/")
marker = os.environ.get("BENCH_MARKER", "<h1>Example Domain</h1>").encode()
mode = os.environ.get("BENCH_MODE", "proxy")
if not url.startswith("https://") or not marker or mode not in {"direct", "proxy"}:
    raise SystemExit("Use an HTTPS URL, a nonempty marker and direct or proxy mode")
route = ["--noproxy", "*"]
if mode == "proxy":
    route = ["--noproxy", "", "--proxy", os.environ["PROXY_URL"], "--proxy-basic",
             "--variable", "%PROXY_USERNAME", "--variable", "%PROXY_PASSWORD",
             "--expand-proxy-user", "{{PROXY_USERNAME}}:{{PROXY_PASSWORD}}"]
fields = ["http", "tunnel", "tcp_complete", "tls_complete", "ttfb", "total"]
write_out = "%{http_code} %{http_connect} %{time_connect} %{time_appconnect} %{time_starttransfer} %{time_total}"
count, window = 200, 3600.0
directory = Path(tempfile.mkdtemp(prefix="proxy-bench-", dir="."))
rows = []
started = time.monotonic()
with (directory / "results.jsonl").open("x") as log:
    for i in range(count):
        due = started + i * window / (count - 1)
        time.sleep(max(0, due - time.monotonic()))
        timestamp = datetime.datetime.now(datetime.timezone.utc).isoformat()
        with tempfile.TemporaryDirectory() as scratch:
            body = Path(scratch) / "body"
            result = subprocess.run(
                ["curl", "--disable", "--silent", "--connect-timeout", "5",
                 "--max-time", "20", "--retry", "0", *route,
                 "--output", str(body), "--write-out", write_out, "--url", url],
                capture_output=True, text=True)
            values = result.stdout.split()
            timings = dict(zip(fields, values)) if len(values) == len(fields) else {}
            if result.returncode:
                outcome = f"curl_{result.returncode}"
            elif not timings:
                outcome = "missing_measurements"
            elif timings["http"] != "200":
                outcome = "http_" + timings["http"]
            elif not body.exists() or marker not in body.read_bytes():
                outcome = "content_mismatch"
            else:
                outcome = "success"
        row = dict(attempt=i + 1, timestamp=timestamp, mode=mode,
                   exit_code=result.returncode, outcome=outcome, **timings)
        rows.append(row)
        log.write(json.dumps(row) + "\n")
        log.flush()

successful = [row for row in rows if row["outcome"] == "success"]
summary = {"attempts": len(rows), "successes": len(successful),
           "success_rate": len(successful) / len(rows),
           "outcomes": dict(collections.Counter(row["outcome"] for row in rows))}
for metric in ("ttfb", "total"):
    samples = sorted(float(row[metric]) for row in successful)
    summary[metric] = ({"median_seconds": statistics.median(samples),
                        "p95_seconds": samples[math.ceil(0.95 * len(samples)) - 1]}
                       if samples else None)
(directory / "summary.json").write_text(json.dumps(summary, indent=2) + "\n")
print(directory)
print(json.dumps(summary, indent=2))
```

第一个请求立即开始；第 200 个请求安排在第一个请求之后 3,600 秒。请求保持顺序执行。如果某个请求耗时超过间隔，后续的开始时间会顺延，整个运行也会变长。实际的开始时间戳保留在日志中。没有自动重试，也不跟随重定向，因此在这个示例中 3xx 响应算作 HTTP 失败。如果你的工作负载需要重定向，请在扩展脚本之前定义哪些目标站点和最终内容是可接受的。

每一行日志包含尝试编号、UTC 开始时间戳、路由模式、curl 退出码、结果、HTTP 和 CONNECT 状态，以及四个以秒为单位的累计时间节点。该脚本的结果文件既不保留响应体也不保留凭据。请另行保存一份私有的运行记录，记下供应商、池、目标、客户端版本和会话配置。

成功率的分母是**所有尝试过的请求**，包括超时和内容失败。curl 退出码（如 28（超时）、7（连接失败）和 56（接收失败））描述的是宽泛的失败类别，而不是已确认的故障组件。调查时请使用[官方退出码参考](https://everything.curl.dev/cmdline/exitcode.html)。

延迟统计只使用成功的响应，汇总中在其旁边保留失败计数。中位数是中间值，必要时取中间两个值的平均。第 95 百分位使用最近秩方法：对成功样本排序，选取从 1 开始计数的第 `ceil(0.95 × sample_count)` 位。没有成功时，两个延迟汇总均为 `null`。

例如，五次尝试中四次成功，成功率为 80%。如果它们的 TTFB 值分别为 0.10、0.12、0.20 和 0.40 秒，则中位数为 0.16 秒，最近秩 p95 为 0.40 秒。这些是用于说明计算方法的虚构数值，不是实测的代理结果。

先对脚本和你的内容规则做一次简短的受控检查，然后运行约定的观测窗口。在任何延迟比较旁边一并公布配置、样本量、失败情况和成功定义。上面的直连基线仅作示例；它不构成代理排名。

## 参考来源

- [curl manual: --write-out variables and proxy configuration](https://curl.se/docs/manpage.html#-w)
- [everything curl: exit codes](https://everything.curl.dev/cmdline/exitcode.html)
- [RFC 9110: HTTP Semantics, section 9.3.6 CONNECT](https://www.rfc-editor.org/rfc/rfc9110#name-connect)
- [ipify: a simple public IP address API](https://www.ipify.org/)

## 第一时间了解 ipvolt 开放体验。

顺便一提

ipvolt 仍在开发中。留下邮箱，开放体验时我们只会通知你一次。

开放体验时仅发一封通知邮件，不发送其他邮件。

[申请抢先体验](https://ipvolt.com/zh/blog/what-a-proxy-benchmark-should-measure#waitlist-blog-end)

[隐私政策](https://ipvolt.com/privacy)


## 相关文章

- [MostLogin 代理设置：添加、测试与团队共享](https://ipvolt.com/zh/blog/mostlogin-proxy-setup.md) (分析, 2026年9月17日, 阅读约 2 分钟): 逐步在 MostLogin 浏览器配置文件中添加代理：协议、主机和端口、账号密码、IP 检测、团队权限、批量导入，以及最常见的出错原因。
- [Amazon 价格：你的报价与 Featured Offer](https://ipvolt.com/zh/blog/amazon-offer-vs-featured-offer.md) (分析, 2026年9月16日, 阅读约 2 分钟): 使用匹配的上下文、已知运费以及一个经过测试、能保留缺失数据的离线模型，把你的 Amazon 报价与 Featured Offer 进行比较。
- [Amazon 商品信息更新：已接受不等于已生效](https://ipvolt.com/zh/blog/amazon-listing-update-reconciliation.md) (分析, 2026年9月14日, 阅读约 2 分钟): 通过区分已提交属性、在售报价、库存与可购买状态来诊断已被接受的 Amazon 商品信息更新，并附一张实用的对账矩阵供参考。

## 相关指南

- [在 curl 中使用代理：-x、环境变量、SOCKS5 与认证](https://ipvolt.com/zh/guides/curl-proxy-setup.md): 如何在 curl 中使用代理：-x 参数、http_proxy 与 https_proxy 变量、通过 socks5h 使用 SOCKS5、代理认证，以及如何解读 CONNECT 与 407 错误。
- [一次一个阶段地排查代理超时](https://ipvolt.com/zh/guides/proxy-timeout-troubleshooting.md): 用 curl 计时把代理 DNS、TCP、CONNECT、TLS 和响应延迟分开，然后设置请求截止时间，并判断重试是否安全。
- [轮换代理与粘性代理：为会话连续性做好规划](https://ipvolt.com/zh/guides/rotating-vs-sticky-proxies.md): 围绕最小的完整工作流来选择轮换策略，区分出口 IP 绑定与 Cookie 状态，并测试粘性会话提前结束时你的应用应当如何处理。

## 关于 ipvolt

来自 ipvolt 团队的技术分析。

ipvolt 尚未开放使用。

[阅读英文原文](https://ipvolt.com/blog/what-a-proxy-benchmark-should-measure.md)
