用户登录

技术文章

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

免费 API 接口推荐与实测:IP 查询与动漫壁纸怎么调

一句话回答

实测两类可长期调用的免费 API 接口:IP 归属地查询与随机动漫壁纸,给出 curl、Python、PHP 三套带超时重试与降级的生产级调用代码,并附监控告警与风险清单。

核心要点 TL;DR
  • 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,口径:仅统计已上线接口,不含扫描与无效路径),可作为容量规划与稳定性判断的现实参考。

常见问题(FAQ)

免费 API 接口可以直接用在生产环境吗?
可以,但要当成不可靠依赖来用。必须设置建连与读取两个超时、限制重试次数、准备本地兜底数据,并对返回结构做校验。接口本身稳定,风险主要来自调用方没有降级。
IP 归属地查询接口传入私网地址会怎样?
通常返回空数据或错误码,不同服务商处理不一致。建议在调用前用 ipaddress 之类库判断是否为私网、回环或保留地址,直接走本地逻辑,省一次外网请求。

作者:沐小悠

本文约 24 分钟读完 · 关键词:免费 API 接口、IP 归属地查询接口、随机动漫壁纸接口、API 超时重试、接口监控告警

更多文章 GET 示例接口

「」标签下的其他文章