用户登录

技术文章

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

API.PLUS 三语言调用实战:请求、解析与重试

一句话回答

文章以 API.PLUS 的 IP 查询和随机动漫壁纸接口为例,说明 Python、PHP、JavaScript 调用时的端点、鉴权、响应解析与重试要点,强调区分可重试错误并按退避策略处理。

核心要点 TL;DR
  • 先固定端点、apikey 鉴权与响应格式,再写三语言代码
  • Python 用 (connect, read) 超时元组区分连接与读取超时
  • xx、超时和连接失败可重试,400/401/404 不重试
  • 退避按 0.5×2^n 递增,429 优先读 Retry-After
  • PHP 用 cURL 精确控制连接/读取超时并获取独立错误码

为什么“请求发出去”只是开始#

很多接入教程止步于“拿到 200 就打印结果”,而真实项目里真正消耗时间的是另一半:超时怎么设、429 之后等多久、Key 该放在哪一层、返回体不是 JSON 时怎么定位问题。本文以 API.PLUS(沐小悠API)的公开接口为例,用 Python、PHP、JavaScript 各写一份可直接落地的调用代码,并给出统一的错误处理与上线检查清单。

文中使用两个真实端点:IP 查询接口与随机动漫壁纸接口;如果需要验证写入链路,可参考 POST 示例接口 的字段说明。

接入准备:端点、密钥与响应契约#

动手写代码前,先把三件事定死:请求地址、鉴权方式、响应格式。三者确定后,三种语言只是语法差异。

  • 端点:IP 查询使用 https://www.api.plus/api/get?apikey=YOUR_KEY&ip=8.8.8.8;随机壁纸使用 https://www.api.plus/api/dongman?type=json,其中 type=json 表示返回结构化数据而不是直接重定向图片。
  • 鉴权:密钥以查询参数 apikey 传递,控制台生成后请放进环境变量或密钥管理系统,不要硬编码进仓库。
  • 响应:先看 Content-Type 再解析 JSON;解析失败时保留原始文本的前 512 字节,排障时比一句“解析错误”有用得多。
用途方法与地址说明
IP 归属地查询GET https://www.api.plus/api/get?apikey=YOUR_KEY&ip=8.8.8.8通过 ip 参数指定目标地址
随机动漫壁纸GET https://www.api.plus/api/dongman?type=json返回 JSON,便于前端或后端二次加工
写入 / 表单验证见 POST 示例接口用于验证 POST 提交与字段校验

Python:超时元组与指数退避#

Python 侧最常见的坑是只写 timeout=5。这个数字同时约束连接和读取,上游稍慢就会被误判为超时;改用 (connect, read) 元组,能把“连不上”和“连上了但没返回”区分开。下面的实现把可重试错误(429、5xx、超时、连接失败)与不可重试错误(400、401、404)分开处理。

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

import json
import time

import requests

API_URL = 'https://www.api.plus/api/get'
API_KEY = 'YOUR_KEY'


def query_ip(ip: str, retries: int = 2) -> dict:
    params = {'apikey': API_KEY, 'ip': ip}
    last_error = None
    for attempt in range(retries + 1):
        try:
            resp = requests.get(API_URL, params=params, timeout=(3, 8))
            if resp.status_code == 200:
                return resp.json()
            if resp.status_code == 429 or resp.status_code >= 500:
                last_error = RuntimeError(f'retryable status: {resp.status_code}')
            else:
                resp.raise_for_status()
        except (requests.Timeout, requests.ConnectionError) as exc:
            last_error = exc
        if attempt < retries:
            time.sleep(0.5 * (2 ** attempt))
    raise RuntimeError(f'query failed after {retries + 1} attempts: {last_error}')


if __name__ == '__main__':
    print(json.dumps(query_ip('8.8.8.8'), ensure_ascii=False, indent=2))

两个细节值得注意:参数交给 params 做 URL 编码,避免 Key 或 IP 中的特殊字符破坏查询串;退避时间按 0.5 * 2^n 递增,命中 429 时优先读取响应头 Retry-After,按服务端给出的节奏等待,比盲目重试更礼貌。

PHP:用 cURL 控制毫秒级超时#

PHP 侧用 cURL 而不是 file_get_contents,原因是前者能精确控制连接超时、读取超时、是否跟随重定向,并且能拿到独立的错误码。

<?php

function api_plus_get(string $url, int $timeoutMs = 8000): array
{
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT_MS => 3000,
        CURLOPT_TIMEOUT_MS => $timeoutMs,
        CURLOPT_FOLLOWLOCATION => false,
        CURLOPT_HTTPHEADER => ['Accept: application/json'],
    ]);

    $body = curl_exec($ch);
    $errno = curl_errno($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);

    if ($errno !== 0) {
        throw new RuntimeException('curl error: ' . $errno);
    }
    if ($status !== 200) {
        throw new RuntimeException('http status: ' . $status);
    }

    $data = json_decode((string) $body, true);
    if (json_last_error() !== JSON_ERROR_NONE) {
        throw new RuntimeException('invalid json: ' . json_last_error_msg());
    }

    return $data;
}

$key = getenv('API_PLUS_KEY') ?: 'YOUR_KEY';
$url = 'https://www.api.plus/api/get?apikey=' . urlencode($key) . '&ip=' . urlencode('8.8.8.8');
echo json_encode(api_plus_get($url), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), PHP_EOL;

要点:关闭 CURLOPT_FOLLOWLOCATION 可以避免被意外重定向到非预期地址;json_decode 的第二个参数传 true 得到关联数组,便于直接取值;Key 从环境变量读取,部署时由容器或面板注入。

JavaScript:fetch 天生没有超时#

浏览器和 Node 18+ 都内置了 fetch,但它没有超时参数,需要配合 AbortController 手动实现。下面这段代码可以直接贴进脚本或 Serverless 函数,并用一个自增的 fatal 标记区分“值得重试”和“重试也没用”。

const API_KEY = process.env.API_PLUS_KEY ?? 'YOUR_KEY';
const URL_BASE = 'https://www.api.plus/api/dongman?type=json';

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function getJson(url, { timeoutMs = 8000, retries = 2 } = {}) {
  let lastError;
  for (let attempt = 0; attempt <= retries; attempt++) {
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), timeoutMs);
    try {
      const sep = url.includes('?') ? '&' : '?';
      const resp = await fetch(`${url}${sep}apikey=${encodeURIComponent(API_KEY)}`, {
        headers: { Accept: 'application/json' },
        signal: controller.signal,
      });
      if (resp.ok) return await resp.json();
      if (resp.status !== 429 && resp.status < 500) {
        throw Object.assign(new Error(`http ${resp.status}`), { fatal: true });
      }
      lastError = new Error(`http ${resp.status}`);
    } catch (err) {
      if (err.fatal) throw err;
      lastError = err.name === 'AbortError' ? new Error('timeout') : err;
    } finally {
      clearTimeout(timer);
    }
    if (attempt < retries) await sleep(500 * 2 ** attempt);
  }
  throw lastError;
}

const data = await getJson(URL_BASE);
console.log(Array.isArray(data) ? data.slice(0, 1) : data);

提醒一句:在浏览器里直接写 Key,等于把它交给所有打开 DevTools 的人。正确做法是自己的后端代理一次,前端只请求自家接口,由后端转发到 API.PLUS 并统一做缓存与限流。

把状态码翻译成动作#

三种语言写完会发现,真正的公共逻辑是同一张映射表。把它固化下来,代码就只需要照着执行。

状态码含义建议动作
200成功解析并校验必需字段是否齐全
400 / 422参数缺失或格式错误不重试,把错误原样暴露给调用方
401 / 403Key 无效、被禁用或权限不足不重试,立即告警并人工排查
404路径写错对照文档修正地址,不重试
429触发限流读取 Retry-After,退避后重试
5xx服务端异常最多重试 2 次,指数退避
超时 / 连接失败网络抖动或上游阻塞同 5xx 处理,注意整体时间预算

一个常见误区是把 401 也丢进重试队列,结果在 Key 失效时用三次请求把限流额度打满,日志里还全是“重试失败”,掩盖了真正的原因。

上线前的六项检查#

  1. 密钥是否只存在于服务端环境变量或密钥管理系统,前端代码与 Git 历史中是否已彻底清除。
  2. 是否同时设置了连接超时与读取超时,且整条调用链的总耗时受调用方预算约束。
  3. 是否区分可重试与不可重试错误,重试次数是否有上限。
  4. 是否在解析前检查 Content-Type,解析失败时是否保留原始响应片段。
  5. 是否对热点结果做本地或 CDN(Content Delivery Network,内容分发网络) 缓存,例如同一个 IP 的归属地在短时间内无需重复查询。
  6. 日志中是否记录了状态码、耗时与请求参数摘要,便于对接 技术支持 时快速定位。

需要更多组合玩法,可以翻阅 API 文章中心 中的缓存、限流与响应契约相关文章,把单次调用扩展成一条稳定的链路。

总结#

把 API 接好,重点不在“会不会发请求”,而在“出错时是否知道该怎么办”。本文的三份代码共享同一套思路:明确的超时、区分错误类型的重试、解析前的格式检查,以及密钥始终留在服务端。Python 用超时元组加指数退避,PHP 用 cURL 的毫秒级超时,JavaScript 用 AbortController 补上 fetch 缺失的超时能力,照着改掉地址与密钥即可复用。

如果你还没开始动手,可以在 API.PLUS 注册账号并获取免费 API Key,先用 https://www.api.plus/api/get?apikey=YOUR_KEY&ip=8.8.8.8 跑通一次最小请求,再把上面的重试与日志逻辑逐步加上去,比一次性写完整套封装更容易排查问题。

常见问题(FAQ)

调用 API.PLUS 时 apikey 应如何传递和保管?
apikey 以查询参数传递;应在控制台生成后放入环境变量或密钥管理系统,不要硬编码进代码仓库,避免密钥泄露。
返回体不是 JSON 时应该怎么排查?
先检查响应头 Content-Type,再尝试解析;解析失败时保留原始文本前 512 字节,用于定位是网关错误、鉴权失败还是返回格式变化。
Python 请求超时为什么建议用元组?
timeout=5 会同时约束连接和读取,上游稍慢就可能误判超时;改用 (connect, read) 元组能区分连不上和连上未返回,便于针对性重试。
哪些 HTTP 状态或异常适合重试?
429、5xx、请求超时和连接失败适合重试;400、401、404 等客户端错误不应重试,应先修正参数、密钥或地址。
遇到 429 限流时应该怎样等待?
优先读取响应头 Retry-After,按服务端给出的节奏等待;没有该头时再采用指数退避,例如 0.5×2^n 秒逐步增加间隔。

作者:沐小悠

本文约 16 分钟读完 · 关键词:API.PLUS、API 接入、Python 调用 API、PHP cURL、JavaScript fetch

更多文章 GET 示例接口

「」标签下的其他文章