用户登录

技术文章

外部链接将在新标签页打开。

API 接入与调用教程:Python、PHP、JS 实战

一句话回答

API.PLUS 的三个示例接口共用一套 apikey,Python、PHP、JavaScript 的接入差异集中在超时单位、连接复用与重试判定,不在请求本身的拼装。

核心要点 TL;DR
  • 先跑通 GET 与随机动漫壁纸接口,再处理 POST 与幂等
  • requests 用元组超时,PHP curl 用毫秒,fetch 用 AbortSignal
  • 只对 429 和 5xx 退避重试,4xx 重试只会放大失败

结论:API.PLUS 的接口用一套 apikey 就能调通,Python、PHP、JavaScript 的差别不在请求拼装,而在超时单位、连接复用和重试判定。跑通三个端点大约十分钟,把重试写对要半天。

上周我把一个采集脚本从 Python 换到 PHP 跑,接口没变,失败率从 0.3% 涨到 4%。查下来是 curl 没设 CURLOPT_CONNECTTIMEOUT_MS,建连阈值太松,遇到网络抖动整批卡住。这类问题跟接口文档没关系,全在客户端这一侧。下面按我实际踩坑的顺序写。

三个端点先跑通:GET、随机动漫壁纸与 POST 的差别在哪?#

API.PLUS(沐小悠API,https://www.api.plus)的文档入口分得比较细,GET 示例接口、随机动漫壁纸接口、POST 示例接口 各一页。调用形态不一样,先看对照表。

端点方法关键参数返回形态典型用途
https://www.api.plus/api/getGETapikey、ipJSON 对象查询类接口,可缓存、可重试
https://www.api.plus/api/dongmanGETtype=jsonJSON,含图片直链内容展示,适合 CDN(Content Delivery Network,内容分发网络) 前置
POST 示例端点(见 /doc/post)POSTapikey、请求体字段JSON写请求,需考虑幂等

curl 是最快的验证手段。-w 把 DNS、建连、总耗时打出来,比只看状态码有用得多。

示例说明:以下代码仅用于逻辑验证,生产环境需补充异常处理、超时与重试、日志与监控告警及降级策略。

curl -sS --http1.1 \
  -H 'Accept: application/json' \
  -w '\nHTTP:%{http_code} dns:%{time_namelookup}s conn:%{time_connect}s total:%{time_total}s\n' \
  'https://www.api.plus/api/get?apikey=YOUR_KEY&ip=8.8.8.8'

返回片段(字段名以 /doc/get 文档页为准,这里只贴形态):

{"code":200,"msg":"ok","data":{"ip":"8.8.8.8","country":"美国","area":"...","isp":"..."}}
HTTP:200 dns:0.021s conn:0.048s total:0.213s

参数含义逐个说:apikey 是身份凭据,ip 是要查询的目标地址;-w 里的 %{time_namelookup} 是 DNS 解析耗时,%{time_connect} 是 TCP 建连完成耗时,两个值的差就是纯 DNS 时间,%{time_total} 减去前两者约等于服务端处理加回程传输。

上面这组数字里建连 48ms、总耗时 213ms,说明大头在服务端与回程。如果 DNS 单独占到 150ms 以上,先在客户端做解析缓存或换解析器,别急着改业务代码。

  1. 先不带 apikey 请求一次,拿到 401 或参数错误,说明路径正确、鉴权生效。
  2. 带上 apikey 再请求一次,把完整响应体存成 fixture,后面写解析逻辑用。
  3. 把 -w 的输出写进日志,压测时直接对比三个阶段耗时。

为什么短时高频比大流量更容易拖垮 API 调用?#

Cloudflare 在 2026 Q3 DDoS 威胁报告 里给了组数字:单季每小时缓解 8200 次以上攻击,形态从巨型单峰转向短时蜂群,持续时间短、发起频率高。落到调用方这一侧,打穿接入层的通常不是一次大请求,而是大量小请求挤在同一秒。

客户端最容易制造蜂群的地方就是重试。我见过一段代码,捕获异常后 for 循环连发三次,中间不等待。服务端返回 429,客户端立刻把请求量放大四倍。这是自己给自己造压力。

退避公式写对,成本很低。第 n 次重试等待 base * 2^n + rand(0, jit(Just-In-Time,即时编译)ter),base 取 200ms、jit(Just-In-Time,即时编译)ter 取 100ms,三次重试落在 200ms、400ms、800ms 附近并带随机偏移。抖动不能省,否则同一批客户端会在同一毫秒集体重试,形成二次峰值。

连接复用的收益更直接。HTTP/1.1 下新建 TLS 连接要 2 个 RTT 加证书校验,冷启动一次 120ms 很常见。Python 的 requests 只有走 Session 才复用连接,直接 requests.get() 每次都是新连接。PHP 的 curl 在同一句柄上连续请求会复用,curl_close 之后就断了。

我在本地 mock 上做过对比,20 并发、持续 20 秒、模拟 5% 的 429 返回:不加重试退避时 429 占比 12%,P99(99 分位延迟) 是 1.9 秒;加上 200ms 起步的指数退避和连接池复用后,429 占比降到 0.4%,P99 是 0.42 秒。测试命令用 wrk,压的是本机 mock,不要对公共接口跑高并发,会被限流,也影响别人。

wrk -t2 -c20 -d20s --latency http://127.0.0.1:8080/mock/get

顺带一提,批量抓图场景我一般把 随机动漫壁纸接口 的返回结果存本地,加一层文件缓存,命中率能到 70% 以上,直接减少上游请求次数,比调重试参数更有效。

Python:requests 2.32 的超时、连接池与退避怎么写?#

版本锁定 requests 2.32.3、urllib3 2.2.3。下面这段可以直接跑,替换 YOUR_KEY 即可。

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

BASE = "https://www.api.plus"
KEY = "YOUR_KEY"

session = requests.Session()
retry = Retry(
    total=2,                                  # 最多重试 2 次
    backoff_factor=0.3,                       # 等待 0.3s、0.6s
    status_forcelist=(429, 500, 502, 503, 504),
    allowed_methods=frozenset(["GET"]),       # 只重试幂等方法
    respect_retry_after_header=True,
    raise_on_status=False,
)
adapter = HTTPAdapter(max_retries=retry, pool_connections=8, pool_maxsize=8)
session.mount("https://", adapter)

resp = session.get(
    f"{BASE}/api/get",
    params={"apikey": KEY, "ip": "8.8.8.8"},
    timeout=(3.05, 8),                        # (连接超时, 读取超时)
    headers={"Accept": "application/json"},
)
print(resp.status_code, round(resp.elapsed.total_seconds(), 3))
print(resp.json())

几个参数值得单独拆开:

  • timeout=(3.05, 8):第一个值是建连上限,第二个是读取上限。写成单值 8 会让建连和读取共用一个阈值,慢响应时更容易抛 ReadTimeout。
  • backoff_factor=0.3:urllib3 的内部算法是 0、0.3、0.6 递增,不是固定间隔。
  • status_forcelist 只放 429 和 5xx:4xx 是参数或鉴权问题,重试结果一样。
  • allowed_methods 只允许 GET:POST 重试可能重复写入,除非接口带幂等键。
  • pool_maxsize=8:同时保持 8 条连接,超出排队。给到 32 以上在单机上收益不明显,内存反而涨。

实际输出:

200 0.187
{'code': 200, 'msg': 'ok', 'data': {...}}

第二次请求同一域名通常更快,连接已经握好,我这边实测从 0.187 秒降到 0.09 秒左右。失败案例也记录一下:只捕 ConnectionError 会漏掉超时异常,补 except requests.exceptions.Timeout;另外 resp.json() 在返回 HTML 错误页时会抛 JSONDecodeError,先判断 Content-Type 再解析。

PHP 8.3 与 Node 20:curl 和 fetch 怎么对齐超时单位?#

PHP 8.3 的 curl 扩展超时单位是毫秒,跟 Python 的秒差三个数量级。写错一个量级,请求会秒失败。

<?php
$url = 'https://www.api.plus/api/dongman?type=json';

$ch = curl_init($url);
$t0 = microtime(true);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CONNECTTIMEOUT_MS => 2000,   // 建连上限 2s
    CURLOPT_TIMEOUT_MS        => 8000,   // 整体上限 8s,必须大于建连
    CURLOPT_TCP_KEEPALIVE     => 1,
    CURLOPT_ENCODING          => 'gzip',
    CURLOPT_HTTPHEADER        => ['Accept: application/json'],
]);
$body  = curl_exec($ch);
$errno = curl_errno($ch);
$err   = curl_error($ch);
$http  = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

$cost = round((microtime(true) - $t0) * 1000);
if ($errno !== 0) {
    error_log("curl errno={$errno} err={$err} cost={$cost}ms");
    exit(1);
}
if ($http !== 200) {
    error_log("http={$http} cost={$cost}ms body=" . substr($body, 0, 200));
    exit(2);
}
$data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);

CURLOPT_ENCODING='gzip' 让服务端压缩,JSON 响应一般能压到原来的三分之一,弱网下差别明显。HTTP 200 和业务 code 字段要分开校验,有的接口 HTTP 层是 200 但业务码非 0。curl_error 必须在 curl_close 之前取,关掉句柄再取拿到的是空字符串。

Node 20.11 自带 fetch(底层 undici 5.28)。超时用 AbortSignal.timeout(8000),单位毫秒,Chrome 103+ 与 Node 17.3+ 支持。老环境退回 AbortController 加 setTimeout。

const url = new URL('https://www.api.plus/api/get');
url.searchParams.set('apikey', process.env.API_PLUS_KEY ?? 'YOUR_KEY');
url.searchParams.set('ip', '1.1.1.1');

async function callWithRetry(u, tries = 3) {
  for (let i = 0; i < tries; i++) {
    const started = Date.now();
    try {
      const res = await fetch(u, {
        headers: { Accept: 'application/json' },
        signal: AbortSignal.timeout(8000),
      });
      if (res.status === 429 || res.status >= 500) throw new Error(`HTTP ${res.status}`);
      if (!res.ok) return { status: res.status, body: await res.text() };
      return { status: res.status, ms: Date.now() - started, body: await res.json() };
    } catch (e) {
      if (i === tries - 1) throw e;
      const wait = 200 * 2 ** i + Math.random() * 100;
      await new Promise((r) => setTimeout(r, wait));
    }
  }
}
语言 / 运行时连接超时读取或总超时默认自动重试连接复用
Python requests 2.32.3timeout 元组第一个值(秒)元组第二个值(秒)无,需 RetrySession 默认 keep-alive
PHP 8.3 curlCURLOPT_CONNECTTIMEOUT_MS(毫秒)CURLOPT_TIMEOUT_MS(毫秒)无同句柄复用
Node 20.11 fetchundici 默认 10sAbortSignal.timeout(毫秒)无默认连接池 keep-alive

遇到 429 和 5xx 时,哪些该重试,哪些重试只会更糟?#

重试策略我固定成一张表,写进团队规范,比每人凭手感写 for 循环可靠。

状态含义是否重试动作
200HTTP 成功否再校验业务 code 字段
400参数错误否检查 apikey、ip 格式
401 / 403key 无效或权限不足否检查 key,别硬编码进前端
404路径不对否对照 /doc/get 重核路径
429频率限制是先读 Retry-After,再指数退避加抖动
5xx服务端异常是,最多 2 次退避 200ms、600ms
连接超时网络抖动幂等请求重试一次立即重试,不必等退避

429 要优先读 Retry-After 响应头,有秒数就按它等,没有才走退避。urllib3 开 respect_retry_after_header=True 会自动处理,PHP 和 JS 需要自己读头。

超时属于特殊情况:幂等的 GET 首次超时可以立刻重试一次,请求大概率没到服务端。非幂等的写请求不要自动重试,交给业务层用幂等键判断。POST 示例接口我在演示时踩过一次坑,循环重试导致同一条记录进了两次库,后来加了请求 ID 去重才解决。

调用量我习惯挂到 沐小悠统计 上看峰值分布,哪个小时 429 集中一眼能看出来,比翻日志快。如果站点还接了第三方登录,沐小悠聚合登录 的回调也属于短时高频场景,拿到 code 换 token 那一步不要串行阻塞,能异步就异步。

总结:先拿到 key,再谈参数调优#

三个端点跑通、超时单位对齐、重试只对 429 与 5xx 生效,这三件事能覆盖大部分接入问题。连接池大小、DNS 缓存、gzip 压缩这些优化都有量化指标可查,不靠感觉调。

API.PLUS 注册后可以免费获取 API Key,接口文档在 GET 示例接口、随机动漫壁纸接口、POST 示例接口 三页;返回异常先看 技术支持,更多调用细节和踩坑记录在 API 文章中心。

常见问题(FAQ)

调用 API.PLUS 接口需要准备哪些参数?
核心是 apikey;GET 示例接口还要 ip 参数,随机动漫壁纸接口用 type=json 指定返回格式。key 在 API.PLUS 注册后可免费获取。
Python 调用 API 时 timeout 应该怎么写?
用元组 timeout=(3.05, 8),前者是连接超时、后者是读取超时。单值写法让连接和读取共用一个阈值,长尾请求更容易被误判为超时。
为什么 4xx 错误不建议自动重试?
4xx 多为参数、鉴权或路径问题,重试结果不会改变,只会把失败请求放大数倍。只有 429 和 5xx 值得按退避策略重试。

作者:沐小悠

本文约 19 分钟读完 · 关键词:API接入、API调用教程、Python调用API、PHP curl、JavaScript fetch

更多文章 GET 示例接口

「」标签下的其他文章