从 curl 到工程封装:网站测速诊断 API 的进阶实践

📅 2026/7/25 6:30:19 👁️ 阅读次数 📝 编程学习
从 curl 到工程封装:网站测速诊断 API 的进阶实践

适用场景与接口能力边界

当我们需要对目标网站进行全面的网络质量诊断时,传统的做法是依次使用digtraceroutecurl -w等工具手动拼凑各阶段耗时,过程繁琐且难以标准化。网站测速诊断 API 将这一过程封装为一次 HTTP 请求,返回 DNS 解析、TCP 连接、SSL 握手、TTFB、总耗时以及重定向链、SSL 证书、命中 IP/端口、页面体积等 6 大维度数据。

典型使用场景

  • CDN 加速后的节点质量评估
  • 跨地域对比同一 URL 的访问延迟
  • 监控服务商提供的第三方测速节点是否正常工作
  • CI/CD 流水线中自动检查部署后的 TTFB 是否达标

接口单次请求即可获取全链路时间线,无需分步测量。但需注意:该 API 提供的是端到端延迟快照,不能代表用户真实网络的持续变化;QPS 限制为 2/s,不适合高频率轮询。

接口鉴权与请求参数

鉴权方式

根据官方文档,请求需要在 Header 中携带 API Key。有两种常见方式:

  • X-API-Key(curl 示例中使用)
  • Authorization(Bearer Token 形式,部分接口同时支持)

实际调用时优先使用X-API-Key头部,Key 可向平台申请获取。

Query 参数

参数名类型必填说明
urlstring目标站点 URL,协议可省略(自动补https://

未传url时接口返回 400;传入example.com会被自动补全为https://example.com

从 curl 开始:单次调试与验证

以下命令可直接在终端运行,请将YOUR_API_KEY替换为实际 Key:

curl -sS \ -X GET \ -H "X-API-Key: YOUR_API_KEY" \ "https://v1.apizero.cn/api/site-check?url=baidu.com"

-sS含义:-s静默模式隐藏进度条,-S同时显示错误信息。若 Key 正确且网络畅通,响应体为 JSON 数组(单次请求返回一个元素):

[ { "code": 0, "msg": "成功", "data": { "url": "https://baidu.com", "final_url": "https://www.baidu.com/", "http_code": 200, "redirect_count": 1, "timing": { "dns_ms": 15, "connect_ms": 32.5, "ssl_ms": 78.4, "ttfb_ms": 145.2, "total_ms": 156.7 } } } ]

返回值逐字段解读

响应顶层为数组,每个元素包含:

  • code: 0 表示成功;非 0 表示业务错误(如 URL 非法、域名不存在)。
  • msg: 对应 code 的文本描述。
  • data: 测速结果主体。

data内部字段:

字段说明
url请求的原始 URL(可能被补全https://
final_url最终重定向到的 URL
http_code最终响应的 HTTP 状态码
redirect_count发生重定向的次数
timing各阶段耗时对象,均以毫秒为单位。各字段含义见下

timing子字段:

  • dns_ms: DNS 解析耗时
  • connect_ms: TCP 连接耗时(三次握手)
  • ssl_ms: SSL/TLS 握手耗时
  • ttfb_ms: TTFB(首字节时间),从请求发出到收到第一个字节的总时间(通常包含 DNS+连接+SSL+服务端处理)
  • total_ms: 总耗时,从开始到请求完全结束(包含下载响应体)

注意:total_ms通常大于ttfb_ms,但也可能出现total_ms < ttfb_ms的情况(若服务端压缩或分块传输导致计时边界不同),这种异常一般出现在 CHUNKED 编码中,可在工程中做阈值过滤。

工程封装:Python 版本

直接使用 curl 调试足够,但在自动化任务中需要程序化调用并进行防御性处理。下面是一个 Python 封装示例,包含:

  • 环境变量管理 API Key
  • 请求超时与重试
  • 响应校验与错误码映射
  • 数据结构化(命名元组)
import os import time import requests from collections import namedtuple from typing import Optional, Dict, Any SiteCheckResult = namedtuple('SiteCheckResult', [ 'url', 'final_url', 'http_code', 'redirect_count', 'dns_ms', 'connect_ms', 'ssl_ms', 'ttfb_ms', 'total_ms', 'raw_json' ]) class SiteCheckError(Exception): pass class SiteChecker: BASE_URL = "https://v1.apizero.cn/api/site-check" def __init__(self, api_key: str, timeout: float = 10.0, max_retries: int = 2): self._headers = {"X-API-Key": api_key} self._timeout = timeout self._retries = max_retries def check(self, url: str) -> SiteCheckResult: params = {"url": url} last_exc = None for attempt in range(1 + self._retries): try: resp = requests.get( self.BASE_URL, headers=self._headers, params=params, timeout=self._timeout ) except (requests.ConnectionError, requests.Timeout) as e: last_exc = e if attempt < self._retries: time.sleep(1) # 简单退避 continue if resp.status_code != 200: raise SiteCheckError(f"HTTP {resp.status_code}: {resp.text}") try: body = resp.json() except ValueError: raise SiteCheckError("Invalid JSON response") if not isinstance(body, list) or len(body) == 0: raise SiteCheckError("Response should be a non-empty array") item = body[0] if item.get("code") != 0: raise SiteCheckError(f"API error: {item.get('msg', 'unknown')}") data = item.get("data", {}) timing = data.get("timing", {}) return SiteCheckResult( url=data.get("url"), final_url=data.get("final_url"), http_code=data.get("http_code"), redirect_count=data.get("redirect_count"), dns_ms=timing.get("dns_ms"), connect_ms=timing.get("connect_ms"), ssl_ms=timing.get("ssl_ms"), ttfb_ms=timing.get("ttfb_ms"), total_ms=timing.get("total_ms"), raw_json=body ) raise SiteCheckError(f"Max retries exceeded: {last_exc}") ## 使用示例 if __name__ == "__main__": api_key = os.environ.get("APIZERO_API_KEY", "") if not api_key: print("请设置环境变量 APIZERO_API_KEY") exit(1) checker = SiteChecker(api_key) result = checker.check("github.com") print(f"最终URL: {result.final_url}") print(f"DNS: {result.dns_ms}ms, TCP: {result.connect_ms}ms, SSL: {result.ssl_ms}ms") print(f"TTFB: {result.ttfb_ms}ms, 总耗时: {result.total_ms}ms")

封装要点说明

  1. 超时控制timeout=10.0防止网络问题导致请求挂起。
  2. 重试机制:网络抖动时自动重试 2 次,间隔 1s。对于业务错误(code ≠ 0)不重试,因为多半是 URL 参数问题。
  3. 结构化结果:使用namedtuple避免手写解析,便于在测试中直接取值。
  4. 错误链:自定义异常类SiteCheckError统一上层捕获。

常见错误与排查

HTTP 状态码可能原因排查方法
400缺少必填参数url检查请求参数是否正确
401/403API Key 无效或未携带确认 Header 中X-API-Key的值
429超过 QPS 限制 (2/s)降低调用频率,增加请求间隔
500服务端测速节点内部错误重试几次,若持续出现则查看平台状态
非 JSON 响应网络代理或防火墙修改了响应体使用-w "%{http_code}"先检查状态码

另外,传入的 URL 若无法解析(如https://notexist.example),API 会返回code为非 0 的错误信息,常见 msg 值:DNS解析失败连接超时SSL握手失败

工程化注意事项

1. 异步适配

若需要同时测速多个站点(不超过 QPS 限制),建议使用asyncio+aiohttp实现并发,而不是串行循环。示例略,核心方法是将check改为异步并增加信号量控制并发数 ≤2。

2. 结果落库与超时过滤

将每次测速结果写入时序数据库(如 InfluxDB),方便观察趋势。注意:total_ms若远小于ttfb_ms(差值 > 50ms)可能是异常,应在入库前标记或丢弃。

3. 与监控系统集成

ttfb_mshttp_code作为指标上报至 Prometheus,配合 Grafana 做面板。若 90% 分位 TTFB 超过某个阈值(如 3000ms),触发告警。

4. API Key 安全管理

禁止硬编码在代码仓库中。使用环境变量(如APIZERO_API_KEY)或密钥管理服务(Vault/KMS)。

5. 日志与调用追踪

建议在封装的 http 请求处打印请求参数和耗时(非接口返回的 total,而是客户端发起请求到收到完整响应的实际耗时),便于排查是客户端网络问题还是 API 慢。

参考文档

  • API 原始文档
  • 接口详情页