用户登录

技术文章

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

API.PLUS 接入教程:Python、PHP、JavaScript 调用实战

一句话回答

先注册获取 apikey,用带超时的 curl 确认调用链路可用;再分别用 Python 的 Session 配合元组超时与 GET 退避重试、PHP 的 cURL 封装函数、JavaScript 的 AbortController 超时来调用,Key 只留在后端,即可做到生产可用。

核心要点 TL;DR
  • Key 属于账户凭证,禁止写入前端 bundle 或公开仓库
  • 上线前先用一条带 --max-time 的 curl 命令验证 Key 与网络
  • 响应按 HTTP 状态码、业务 code、data 三层分层处理
  • Python 用 Session 复用连接,timeout 传元组区分连接与读取超时
  • 浏览器 fetch 没有 timeout 选项,必须用 AbortController 中断请求

引言:一次 API 调用要写成可维护的代码#

调用第三方 API 的难点通常不在业务逻辑,而在超时、重试、错误码分层和密钥管理。很多项目在测试环境跑通一次就上线,结果线上遇到 429 或网关抖动时,代码直接把异常抛给用户。本文以 API.PLUS 的公开接口为例,用 Python、PHP、JavaScript 三种语言把「拿到 Key → 发出请求 → 校验响应 → 生产可用」这条链路完整走一遍。

示例选用 GET示例接口(参数少、返回 JSON,适合验证链路是否通)和 随机动漫壁纸接口(返回图片或 JSON 元数据,适合演示内容类型差异)。三份示例代码都可以直接复制到项目里,改掉 Key 就能跑。

一、动手前要确认的三件事:Key、超时、响应约定#

所有请求都要携带 apikey。免费 Key 在 API.PLUS 注册后即可在控制台获取,它属于账户凭证:不要写进前端 bundle、不要提交到公开仓库、不要贴进 issue。浏览器端调用必须经过你自己的后端代理,由后端注入 Key 并做频率限制,否则 Key 等于公开。

写代码之前,先用一条 curl 命令确认 Key 与网络都正常,这是排查问题最快的一步:

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

curl -sS --max-time 5 'https://www.api.plus/api/get?apikey=YOUR_KEY&ip=8.8.8.8'

注意 --max-time 这个参数。客户端不设超时是线上事故最常见的来源之一。如果这条命令能返回 JSON,说明 Key、路径、网络都没有问题,剩下的就只是代码问题。

响应要分三层判断

JSON 接口通常是「HTTP 状态码 + 业务 code + data」的三段式结构,三层含义不同,处理方式也不同:

层级判断依据处理方式
HTTP 状态码200 / 4xx / 5xx4xx 不重试(429 除外),5xx 可退避重试
业务 code响应体里的 code 字段非成功码记录 msg 后放弃重试
data实际业务字段先做类型与空值校验,再落库或渲染

二、Python:Session 复用、元组超时与退避重试#

requests 是最常见的选择。要点有三个:用 Session 复用连接池,用元组分别设置连接超时与读取超时,用 Retry 只对幂等请求和可重试状态码生效。

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

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

session = requests.Session()
session.headers.update({'User-Agent': 'api-plus-demo/1.0'})
retry = Retry(
    total=3,
    backoff_factor=0.5,
    status_forcelist=(429, 500, 502, 503, 504),
    allowed_methods=frozenset(['GET']),
)
session.mount('https://', HTTPAdapter(max_retries=retry, pool_maxsize=20))

def query_ip(ip):
    params = {'apikey': API_KEY, 'ip': ip}
    try:
        resp = session.get(ENDPOINT, params=params, timeout=(3.05, 8))
    except requests.RequestException as exc:
        raise RuntimeError('请求失败: {}'.format(exc)) from exc
    if resp.status_code != 200:
        raise RuntimeError('HTTP {}'.format(resp.status_code))
    payload = resp.json()
    if payload.get('code') not in (200, 0):
        raise RuntimeError('业务错误: {}'.format(payload.get('msg', 'unknown')))
    return payload.get('data', {})

if __name__ == '__main__':
    start = time.time()
    print(query_ip('8.8.8.8'))
    print('cost {:.0f}ms'.format((time.time() - start) * 1000))

几个容易忽略的细节:timeout=(3.05, 8) 的第一个值是连接超时,第二个是读取超时,而传单个数字会同时作用于两者;Retry 里把 allowed_methods 限定为 GET,是因为查询类接口幂等、可以安全重放,而写操作重放可能产生重复数据;业务 code 的判断放在 HTTP 状态码之后,避免把 200 里的错误响应当成成功。

如果需要提交表单或 JSON,POST 场景可以参考 POST 示例接口。另外不要对超时的 POST 直接重试,应该先用幂等键或查询接口确认服务端状态,再决定是否补发。

三、PHP:用 cURL 封装一个可复用的请求函数#

file_get_contents 写起来快,但超时粒度粗、无法精细控制、错误只能靠 warning 捕获,不适合生产环境。更稳妥的做法是用 cURL 封装一个返回数组的函数,把传输层错误和业务层错误分开抛出。

function api_get(string $url, int $timeoutMs = 8000): array
{
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT_MS => 2000,
        CURLOPT_TIMEOUT_MS => $timeoutMs,
        CURLOPT_FOLLOWLOCATION => false,
        CURLOPT_USERAGENT => 'api-plus-demo/1.0',
    ]);

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

    if ($errno !== 0) {
        throw new RuntimeException('传输层错误 ' . $errno . ': ' . $error);
    }
    if ($status !== 200) {
        throw new RuntimeException('HTTP ' . $status);
    }

    $data = json_decode((string) $body, true);
    if (!is_array($data)) {
        throw new RuntimeException('响应不是合法 JSON');
    }
    if (isset($data['code']) && (int) $data['code'] !== 200) {
        throw new RuntimeException('业务错误: ' . ($data['msg'] ?? 'unknown'));
    }

    return $data['data'] ?? [];
}

$key = getenv('API_PLUS_KEY') ?: 'YOUR_KEY';
$url = 'https://www.api.plus/api/get?apikey=' . urlencode($key) . '&ip=8.8.8.8';
print_r(api_get($url));

这段代码有三个刻意的设计:Key 从环境变量读取,避免硬编码进仓库;用 urlencode 处理参数,防止特殊字符破坏查询串;curl_error 在 curl_close 之前取出,否则拿不到错误信息。调用方只需要一层 try/catch,就能区分网络问题和业务问题,日志里也能留下可定位的错误码。

四、JavaScript:浏览器与 Node 的两套写法#

浏览器端:能调什么、不能调什么

浏览器里绝对不能放 apikey,任何人都能在 DevTools 里看到。带 Key 的请求必须走你自己的后端。对于无需鉴权的公开接口,前端可以直接请求,但必须用 AbortController 设置超时,否则弱网下请求会挂很久:

async function fetchDongman(signal) {
  const resp = await fetch('https://www.api.plus/api/dongman?type=json', {
    method: 'GET',
    signal,
    headers: { accept: 'application/json' },
  });
  if (!resp.ok) {
    throw new Error('HTTP ' + resp.status);
  }
  return resp.json();
}

const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 8000);

try {
  const data = await fetchDongman(controller.signal);
  console.log(data);
} catch (err) {
  console.error(err.name === 'AbortError' ? '请求超时' : err.message);
} finally {
  clearTimeout(timer);
}

AbortController 是浏览器端唯一可靠的超时手段,fetch 本身没有 timeout 选项。把 signal 传进去,超时后请求会被真正中断,而不只是抛出一个错误。上面的顶层 await 需要写在 ES Module 里,普通脚本请包进 async 函数。

Node 18+:用 undici 控制连接与超时

Node 18 之后有了全局 fetch,但生产环境里连接复用和分阶段超时更依赖 undici。下面这段是查询 IP 归属地的最小实现:

import { request } from 'undici';

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

const res = await request(url, {
  method: 'GET',
  headers: { accept: 'application/json' },
  headersTimeout: 5000,
  bodyTimeout: 8000,
});

const body = await res.body.json();
if (res.statusCode !== 200) {
  throw new Error('HTTP ' + res.statusCode);
}
if ((body.code ?? 200) !== 200) {
  throw new Error('业务错误: ' + (body.msg ?? 'unknown'));
}
console.log(body.data);

undici 的 headersTimeout 与 bodyTimeout 分别覆盖「等待响应头」和「读取响应体」两个阶段,比单一超时更贴近真实故障。如果项目已经用了 axios 或 got,思路完全一样:设置超时、限制重试次数、校验响应结构。

五、把示例代码变成生产能力#

示例能跑通只是第一步。真正上线之前,建议逐项确认下面五点:

  • 超时分层:连接 2~3 秒、读取 5~8 秒,前端再加一层中断兜底,任何一层都不能省。
  • 重试边界:只对 GET 和 429/5xx 重试,指数退避加随机抖动,最多 3 次,避免把对方打垮。
  • Key 管理:走环境变量或密钥管理服务,按环境分发并定期轮换,发现泄漏立即作废重发。
  • 可观测性:记录 traceId、耗时、状态码与业务 code,日志里不要出现完整 Key,出问题时才能定位。
  • 缓存:壁纸这类更新不频繁的内容可以加 Cache-Control 与本地缓存,减少重复请求和配额消耗。

常见故障可以按下表对号入座:

现象常见原因处理建议
401 / 403Key 缺失、拼写错误或被停用确认 Key 放在查询参数里且未被编码破坏
429超出配额或频率限制降速并退避重试,读取响应头里的限流信息
请求长时间无响应没有设置超时连接 2~3 秒、读取 5~8 秒,前端用 AbortController 兜底
返回 HTML 而不是 JSON路径写错或命中网关错误页先看 Content-Type 与状态码,不要直接解析 JSON

想继续深入限流、缓存与响应设计,可以翻 API 文章中心 里的专题;遇到具体接口问题,走 技术支持 通常比在评论区追问更快。

六、总结#

接入 API.PLUS 只需要三步:先用 curl 验证 URL 与 Key,再用 Session、cURL 或 fetch 封装统一的请求函数,最后补上超时、重试与错误分层。Python 侧重连接复用与退避重试,PHP 侧重传输层与业务层错误分离,JavaScript 侧重中断与分阶段超时,三者的共同点是:永远不要假设一次请求一定会成功。

如果你还没有 Key,可以到 API.PLUS 注册账号,在控制台免费获取 API Key,先用 GET示例接口 跑通本文的示例,再替换成业务真正需要的接口,例如 随机动漫壁纸接口。

常见问题(FAQ)

API.PLUS 的 apikey 能直接放在前端 JavaScript 里调用吗?
不能。Key 是账户凭证,放在前端会被任何人在 DevTools 里看到;浏览器端调用必须经你自己的后端代理,由后端注入 Key 并做频率限制。
为什么重试策略只对 GET 请求启用?
查询类接口幂等,可安全重放;写操作重放可能产生重复数据。超时的 POST 不要直接重试,应先用幂等键或查询接口确认服务端状态再决定。
浏览器端用 fetch 怎么设置请求超时?
fetch 本身没有 timeout 选项,唯一可靠手段是 AbortController:把 controller.signal 传入请求,到时间调用 controller.abort() 真正中断请求。
Python 里 timeout=(3.05, 8) 分别代表什么?
第一个值是连接超时,第二个是读取超时,单位秒;如果只传一个数字,会同时作用于两者,无法区分网络连不上还是服务端响应慢。
怎么快速排查是 Key 问题还是代码问题?
先用一条带 --max-time 的 curl 请求 GET示例接口,能正常返回 JSON 说明 Key、路径、网络都正常,剩下的就只是代码问题。

作者:沐小悠

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

更多文章 GET 示例接口

「」标签下的其他文章