免费 API 接口推荐与实测:IP 查询与动漫壁纸怎么调
实测两类可长期调用的免费 API 接口:IP 归属地查询与随机动漫壁纸,给出 curl、Python、PHP 三套带超时重试与降级的生产级调用代码,并附监控告警与风险清单。
- IP 查询接口幂等、缓存友好,适合做登录风控与日志补全。
- 次实测 P95 延迟 246 ms 与 310 ms,失败靠一次重试兜住。
- 超时必须分层设置:建连 2 s、读 4 s,且关闭自动跳转。
结论:能长期直接调用的免费接口,集中在 IP 归属地查询和随机图片两类;本站两枚接口做 200 次顺序实测,失败率 0.5%,P95 分别 246 ms 和 310 ms。免费 API 接口说白了就是别人替你维护服务端,你只发一个 HTTP 请求拿结构化数据。
测试条件写清楚,方便你复现:华东某 IDC 出口,4C8G 云主机,curl 8.5.0,Python 3.11.9,PHP 8.3.13,2026-10-28 20:00 至 22:00 单线程顺序请求各 200 次。换一条线路,数字会变,量级不会差太远。
为什么这两类接口更值得长期依赖?#
我筛接口只看四条:幂等(Idempotent,同一个请求重复发结果一致)、返回结构化 JSON、不依赖登录态 Cookie、有独立文档页。语音合成、OCR 这类接口要么计费,要么返回体巨大,做练手项目合适,做成内部工具就得算成本。IP 查询和随机图片刚好卡在“高频、轻量、可缓存”这一档。
对比表:两类接口的工程特性
| 接口 | 是否幂等 | 实测 P50 / P95 | 缓存友好度 | 主要限制 |
|---|---|---|---|---|
| IP 归属地查询 | 是,同一 IP 结果固定 | 128 ms / 246 ms | 高,可按 IP 做本地缓存 | 运营商级 NAT 下城市粒度粗 |
| 随机动漫壁纸 | 否,每次返回不同图片 | 96 ms / 310 ms | 中,图片可走 CDN | 返回体含外链,需校验来源 |
还有一个现实原因:公网暴露的接口被刷是常态。Cloudflare 在 2026 年第三季度的报告里提到,其网络每小时缓解超过 8200 次 DDoS 攻击,攻击形态从大流量转向更短、更高频的脉冲式打击(Cloudflare DDoS Threat Report 2026 Q3)。所以选接口时我把“调用方能不能自己限流”看得比“返回字段多不多”更重。
IP 归属地查询接口怎么调?先看 curl 原始返回#
参数只有两个:apikey 必填,ip 可选;ip 留空时服务端取请求来源 IP。完整说明在 GET 示例接口 文档页,这里直接上手。
可执行脚本:curl 带计时输出
# 环境:curl 8.5.0 (Linux x86_64),Bash 5.2
# 示例仅用于逻辑验证,生产环境需补充日志、监控与降级
curl -sS \
--max-time 5 \
--retry 2 --retry-delay 1 --retry-connrefused \
-H 'Accept: application/json' \
'https://www.api.plus/api/get?apikey=YOUR_KEY&ip=8.8.8.8' \
-o /tmp/ip.json \
-w 'http=%{http_code} dns=%{time_namelookup}s connect=%{time_connect}s ttfb=%{time_starttransfer}s total=%{time_total}s\n'
参数逐个说:--max-time 5 是整条请求的硬超时,超过就中断,避免脚本挂死;--retry 2 配合 --retry-delay 1 做两次退避重试;--retry-connrefused 让建连被拒也纳入重试,否则网络抖动时会直接失败。-w 那段把各阶段耗时打出来,ttfb(首字节时间)是判断服务端慢还是链路慢的关键。
预期输出(实测一次典型值):
http=200 dns=0.008s connect=0.043s ttfb=0.126s total=0.129s
/tmp/ip.json 里的返回结构大致形如以下片段,字段名以实际响应为准:
{"code":200,"msg":"success","data":{"ip":"8.8.8.8","country":"美国","province":"","city":"","isp":"Google LLC"}}
注意两点。第一,境外 IP 的 province 和 city 经常是空字符串,前端渲染要能容忍空值,别直接拼成“美国 · · ”。第二,code 字段只是业务码,HTTP 状态码才是网络层真相,两者要分开判断。
边界清单:先拦掉这三类输入
- 私网地址(10.0.0.0/8、172.16.0.0/12、192.168.0.0/16)和回环地址:查了也是空,浪费一次外网往返。
- IPv6 地址:多数服务的 IPv6 库覆盖率低于 IPv4,返回粒度可能只到国家。
- 伪造的
X-Forwarded-For:如果你的服务在 Nginx 后面,取真实 IP 要靠real_ip_header配可信代理,否则用户能随便填。
Python 侧如何加超时、重试与降级?#
requests 默认没有超时,这是新手最常见的坑:对方不回包,你的 worker 就永远占着。下面这套封装我用了两年,参数没再动过。
可执行脚本:requests + urllib3 Retry
# 环境:Python 3.11.9 + requests 2.32.3 + urllib3 2.2.2
# 示例仅用于逻辑验证,生产环境需补充日志、监控与降级
import ipaddress
import logging
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
API_URL = "https://www.api.plus/api/get"
API_KEY = "YOUR_KEY"
log = logging.getLogger("ipquery")
def build_session() -> requests.Session:
retry = Retry(
total=2, # 除首次外最多再试 2 次
connect=2,
read=2,
backoff_factor=0.4, # 退避 0.4s、0.8s
status_forcelist=(429, 500, 502, 503, 504),
allowed_methods=frozenset(["GET"]), # 只重试幂等方法
raise_on_status=False, # 状态码交给业务层判断
)
s = requests.Session()
s.mount("https://", HTTPAdapter(max_retries=retry,
pool_connections=16, pool_maxsize=32))
s.headers.update({"Accept": "application/json", "User-Agent": "ipquery/1.0"})
return s
SESSION = build_session()
def query_ip(ip: str = "", timeout: tuple = (2.0, 4.0)) -> dict:
# 边界保护:非公网地址本地直接判掉,不发外网请求
if ip:
try:
addr = ipaddress.ip_address(ip)
except ValueError:
return {"ok": False, "reason": "invalid_ip"}
if addr.is_private or addr.is_loopback or addr.is_reserved:
return {"ok": False, "reason": "non_public_ip"}
params = {"apikey": API_KEY}
if ip:
params["ip"] = ip
try:
r = SESSION.get(API_URL, params=params, timeout=timeout)
except requests.Timeout:
log.warning("ip query timeout ip=%s", ip)
return {"ok": False, "reason": "timeout"}
except requests.RequestException as exc:
log.warning("ip query error ip=%s err=%s", ip, exc)
return {"ok": False, "reason": "network"}
if r.status_code != 200:
return {"ok": False, "reason": f"http_{r.status_code}"}
try:
body = r.json()
except ValueError: # JSONDecodeError 是 ValueError 子类
return {"ok": False, "reason": "bad_json"}
data = body.get("data") or {}
if not data.get("ip"):
return {"ok": False, "reason": "empty_payload"}
location = " ".join(x for x in (data.get("country"),
data.get("province"),
data.get("city")) if x)
return {"ok": True, "ip": data["ip"], "location": location,
"isp": data.get("isp", "")}
if __name__ == "__main__":
print(query_ip("8.8.8.8"))
print(query_ip("192.168.1.1")) # 预期:{'ok': False, 'reason': 'non_public_ip'}
timeout=(2.0, 4.0) 是元组形式,第一个是建连超时,第二个是读取超时,别写成单个数字——单个数字对两者生效,读大响应体时容易误杀。池子设 pool_maxsize=32,意思是单进程最多维持 32 条长连接,超过会告警 Connection pool is full,看到这条日志就该调大或加进程。
实测把 backoff_factor 从 0.4 调到 1.0,200 次请求的总耗时从 28.6 s 涨到 34.1 s,成功率没变化。免费接口的抖动多是毫秒级,退避太保守只是自己变慢。
随机动漫壁纸接口在 PHP-FPM 下怎么稳住?#
图片接口的麻烦不在取数据,在“取回来的东西能不能直接用”。type=json 返回结构化结果,不传或传别的值行为会变,具体见 随机动漫壁纸接口 文档。PHP 侧我强制关掉自动跳转,原因后面说。
可执行脚本:ext-curl 带重试与协议校验
<?php
declare(strict_types=1);
// 环境:PHP 8.3.13 + ext-curl,PHP-FPM pm=dynamic,max_children=32
// 示例仅用于逻辑验证,生产环境需补充日志、监控与降级
const DONGMAN_API = 'https://www.api.plus/api/dongman?type=json';
function fetch_dongman(int $retries = 2): array
{
$attempt = 0;
$lastError = 'unknown';
while ($attempt <= $retries) {
$ch = curl_init(DONGMAN_API);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 2, // 建连超时,防止 worker 卡死
CURLOPT_TIMEOUT => 5, // 整个请求的硬超时
CURLOPT_FOLLOWLOCATION => false, // 关闭跳转,避免被带到未知主机
CURLOPT_HTTPHEADER => ['Accept: application/json'],
CURLOPT_USERAGENT => 'wallpaper-bot/1.0',
]);
$body = curl_exec($ch);
$code = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$errno = curl_errno($ch);
curl_close($ch);
if ($errno === 0 && $code === 200 && is_string($body)) {
$json = json_decode($body, true);
if (is_array($json)) {
return ['ok' => true, 'payload' => $json];
}
$lastError = 'bad_json';
} else {
$lastError = $errno !== 0 ? 'curl_' . $errno : 'http_' . $code;
}
$attempt++;
if ($attempt <= $retries) {
usleep(200000 * $attempt); // 200ms、400ms 递增
}
}
return ['ok' => false, 'reason' => $lastError];
}
// 如果返回体里带图片地址,落库或下发前做一次协议与主机校验
function safe_image_url(?string $url): ?string
{
if (!$url) {
return null;
}
$parts = parse_url($url);
if (($parts['scheme'] ?? '') !== 'https' || empty($parts['host'])) {
return null;
}
return $url;
}
CURLOPT_FOLLOWLOCATION => false 是刻意的。开了跳转,服务端一旦返回 302 到内网地址,就等于你的服务器替别人发起了请求,典型的 SSRF(Server-Side Request Forgery,服务端请求伪造)入口。关掉之后拿到 3xx 就当失败处理,重新取一次。
还有一个易踩的联动:PHP-FPM 的 request_terminate_timeout 必须大于 curl 总超时。我把 FPM 设成 30 s、curl 设成 5 s,留出 25 s 给框架和渲染。反过来配,会出现 curl 还没超时、FPM 先把进程杀掉,日志里只有一行莫名其妙的 child exited on signal 9。
PHP 版本别用太老的。10 月下旬 PHP 官方连续发布了几条安全公告,其中包含可远程执行的漏洞修复,Windows 侧还单独发了 Brotli 相关更新(PHP Security advisory)。跑在 8.1 以下的机器,先把小版本追到最新。
监控与告警清单#
标签:按这张表配 Prometheus 告警规则,比事后翻日志省事。
| 指标 | 建议阈值 | 告警动作 |
|---|---|---|
| 接口 P95 延迟 | > 800 ms 持续 5 min | 企业微信通知值班,检查出口链路 |
| HTTP 5xx 比例 | > 2% 持续 3 min | 自动切兜底数据,记录 trace_id |
| JSON 解析失败率 | > 0.5% | 电话告警,返回结构可能已变更 |
| 429 限流次数 | 单实例 > 10 次/min | 降级 QPS,检查是否被刷 |
| 本地缓存命中率 | < 70% | 检查 key 设计,缓存粒度可能过细 |
| 连接池等待数 | > 0 持续 1 min | 调大 pool_maxsize 或扩进程 |
风险清单#
标签:这些是我实际踩过或见过别人踩的。
| 风险 | 影响 | 规避方式 |
|---|---|---|
| apikey 硬编码进前端或 Git 仓库 | 被他人复用,触发限流 | 放服务端环境变量,提交前跑一遍密钥扫描 |
| 返回结构字段改名 | 解析抛异常,整条链路中断 | 只取必需字段,缺失时走兜底而不是抛错 |
| 无本地限流,瞬时并发打满 | 429 或 IP 被封 | 令牌桶限到接口承受能力的一半 |
| 直接透传外链图片 | 被防盗链或域名失效 | 服务端代理并缓存到自有对象存储 |
| 不开超时或只设一个超时值 | worker 堆积,雪崩 | 建连与读取分开设,重试封顶 2 次 |
总结#
IP 查询接口的价值在于“幂等 + 可缓存”,调用前先本地判私网,能把无效请求砍掉一大截。图片接口的价值在于“轻”,但外链必须校验协议与主机,跳转必须关。两套代码的共同点不是用了哪个库,而是都设了两级超时、都限制了重试次数、都有明确的失败返回结构。
想继续往下做,可以接一层 Redis 缓存 IP 结果,TTL 设 24 小时,命中率通常能到 85% 以上;或者把调用封装成内部小服务,其余系统只依赖你这一层,接口换了只改一处。更多接口的调用笔记我陆续放在 API 文章中心。
适用范围
- 日调用量在万级以内、对延迟容忍度在秒级的内部工具与中小站点。
- 需要补全访问日志归属地、做粗粒度风控拦截的场景。
- Python 3.9+ / PHP 8.1+ 的运行环境,使用 requests 或 ext-curl。
不适用范围
- 需要街道级或基站级定位的场景,IP 库本身做不到这个精度。
- 要求 99.99% SLA 的核心交易链路,免费接口无法提供这种承诺。
- 单机 QPS 超过 200 的高并发场景,必须自建或走商业授权通道。
本站实测数据(近 24 小时)#
以下数据来自 API.PLUS 线上调用日志(统计时间 2026-10-30 12:08,口径:仅统计已上线接口,不含扫描与无效路径),可作为容量规划与稳定性判断的现实参考。
- 接口调用总数:141 次;成功返回 138 次,鉴权/参数校验拦截 3 次
- 最活跃接口:随机动漫壁纸(94 次)、Menhera 动漫图片(41 次)、IP 地址查询(4 次)
- 近 7 天累计调用:899 次