Skip to content

查 IP 归属地:在线 API 还是离线 IP 库?开发者选型指南(附 Python 实测)

先给结论:要省事、要数据新,选在线 API;要零网络延迟、数据不出内网,才选离线库。 国内中小项目(个人网站、小程序后端、内部风控)九成场景在线 API 更划算——IP 段数据天天在变,离线库那份数据文件你不可能每周记得更新。下面把两条路掰开对比,附一段同一批 IP 两边实测的 Python 代码。

一、先搞清两类方案差在哪

在线 API:一个 HTTP GET 查一次。服务商负责收集数据、更新、抗并发,你拿到的是他最新一版的结果。代价是请求要走公网,有延迟(一般几十毫秒),免费档通常限速(比如 IP9 免费版 60 次/分钟/IP),量大了要付费。

离线 IP 库:把数据文件下载到本地,用 SDK 查。ip2region 是社区最常用的,数据文件叫 xdb,另外还有 MaxMind 的 mmdb、纯真的 dat。查询在微秒级,不出网、无次数限制、无费用。代价是:文件要自己下载、自己定期更新,更新不及时结果就旧——IP 段被运营商重新划分、机房地址搬迁,都是按月发生的事情。

有个反直觉的点先讲清楚:两类方案的上游数据源高度重叠,大多来自运营商 IP 段分配记录加网络测绘。准不准主要不取决于"在线还是离线",取决于数据多久更新一次。免费离线库的更新以月计,商业在线接口通常按周甚至按天迭代。你在意的"查得准",一半是更新速度决定的。

二、决策清单:别拍脑袋,按这四条过

优先在线 API 的情况:

  1. 量不大(每天几千次以内),免费档就够——没必要为低频查询养一个本地库;
  2. 在意数据新鲜度,比如做风控、做地区活动,IP 段一变旧库就误判;
  3. 不想碰运维:没有更新流水线、没有发布流程,装个 SDK 都嫌多;
  4. 需要省市区县+运营商+IP 类型这些组合字段,免费离线库给不全。

优先离线库的情况:

  1. QPS 极高(每秒几百上千),在线接口的限速和延迟扛不住;
  2. 数据合规要求严,用户 IP 不允许出内网;
  3. 纯内网环境(堡垒机、隔离区),根本访问不了公网接口。

三、Python 实测:同一批 IP,两条路各查一遍

思路:在线接口循环查 5 个 IP,注意限速和错误处理;离线库用 ip2region。先准备环境——注意 ip2region 的 Python 包不在 PyPI,要从仓库装,数据文件也要单独下(v4/v6 还是分开的两个文件,这本身就是个维护点):

bash
pip install requests
git clone --depth 1 https://github.com/lionsoul2014/ip2region.git
cd ip2region/binding/python && pip install . && cd -
# 数据文件在仓库 data/ 目录:ip2region_v4.xdb(IPv4)、ip2region_v6.xdb(IPv6)
python
import io
import time
import requests
from ip2region.searcher import new_with_vector_index
import ip2region.util as util

IPS = ["114.114.114.114", "223.5.5.5", "180.101.50.242", "8.8.8.8", "1.1.1.1"]
DB = "ip2region_v4.xdb"   # 换成你本地数据文件路径

def query_online(ip: str) -> dict:
    """在线 API:GET 单 IP 接口,免费版 60 次/分钟/IP"""
    try:
        j = requests.get(f"https://ip9.com.cn/get?ip={ip}", timeout=3).json()
        if j.get("ret") != 200:
            return {"ip": ip, "来源": "在线API", "结果": f"查询失败 ret={j.get('ret')}"}
        d = j["data"]
        loc = f"{d['country']} {d['prov']} {d['city']}".replace("  ", " ").strip()
        return {"ip": ip, "来源": "在线API", "结果": f"{loc} / {d.get('isp','')}"}
    except Exception as e:
        return {"ip": ip, "来源": "在线API", "结果": f"异常: {e}"}

def make_searcher(db_path: str):
    """打开 ip2region 离线库(vectorIndex 方式,省内存)"""
    handle = io.open(db_path, "rb")
    util.verify(handle)
    header = util.load_header(handle)
    version = util.version_from_header(header)
    v_index = util.load_vector_index(handle)
    searcher = new_with_vector_index(version, db_path, v_index)
    handle.close()
    return searcher

if __name__ == "__main__":
    searcher = make_searcher(DB)
    print("=== ip2region 离线库 ===")
    for ip in IPS:
        try:
            region = searcher.search(util.parse_ip(ip))   # 国家|省|市|运营商|国家码
            parts = region.split("|")
            print({"ip": ip, "来源": "离线库",
                   "结果": f"{parts[0]} {parts[1]} {parts[2]} / {parts[3]}"})
        except Exception as e:
            print({"ip": ip, "来源": "离线库", "结果": f"异常: {e}"})
    searcher.close()

    print("=== 在线 API ===")
    for ip in IPS:
        print(query_online(ip))
        time.sleep(1.1)   # 限速留余量:免费版 60 次/分钟

这是我实际跑过的输出(同一批 IP):

=== ip2region 离线库 ===
114.114.114.114 -> 中国 江苏省 南京市 / 0          (运营商字段缺失)
223.5.5.5        -> 中国 浙江省 杭州市 / 阿里
8.8.8.8          -> United States California 0 / Google LLC
=== 在线 API ===
114.114.114.114 -> 中国 江苏 南京 / 114DNS
223.5.5.5        -> 中国 浙江 杭州 / AliDNS/DoH/DoT/阿里云
8.8.8.8          -> 美国 / Google Cloud

看出差别没:城市基本一致,但运营商标识、海外 IP 的可用字段,在线接口明显更全;离线库免费的 xdb 里不少段运营商直接是 0。这就是选型的核心差异——不是谁的技术强,是谁的数据维护得勤。

四、落地注意事项

  • 先缓存再谈选型:不管哪条路,同一 IP 一天内查几百次都是浪费。内存里按 IP 缓存 24 小时,命中率上来后,在线 API 的免费额度通常用不完。
  • 离线库必须配更新任务:crontab 每月拉一次新文件、重启加载,否则数据越放越旧。这条做不到,就别选离线库。
  • 免费额度怎么估:注册、下单这类低频动作,一天几千次请求,去重后真正要调接口的可能就几百个 IP——免费版 60 次/分钟/IP 是够的;真到了按分钟打满,再考虑付费档(IP9 VIP 按次计费,千次 5 分钱起)也不迟。
  • 合规一句:用户 IP 属个人信息。离线库把数据落在自己服务器、不出内网,反而好交代;用在线接口则要选承诺不落库的服务商,并在隐私政策里写清楚。

总结

选型一句话:更新勤不勤决定准不准,量大量小决定贵不贵。 中小项目先用免费在线接口跑起来(https://ip9.com.cn/get?ip=1.2.3.4 这种,免注册直接调,支持 IPv4/IPv6),等 QPS 真上去了再评估离线库或更高档位,别一开始就背一个要自己维护的数据文件。官网:https://www.ip9.com.cn