IP地址查询避坑指南:从入门到精准定位

先看个最基础但最容易翻车的问题:IP地址查询到底查的是什么?

为什么你的IP定位总不准?

很多人以为IP地址查询能精确定位到你家门口,这想法比算命还不靠谱。IP地址的物理位置信息,本质上是个“地理标签”,由几级数据拼起来的:

  • WHOIS注册信息:谁申请了这个IP段,填的地址在哪
  • BGP路由信息:这个IP段通过哪个ISP路由,机房在哪
  • 用户上报数据:某些APP通过GPS纠正的位置
  • 这三个数据源,误差从几公里到几百公里不等。我实测过,同一个IP在不同工具上查出来的结果能差三个省。

    “`python
    # 一个简单的IP查询示例,用ipinfo.io的API
    import requests

    def query_ip(ip_address):
    “””
    为什么这么写:ipinfo.io的免费版每月50000次请求,够个人用了
    注意:API返回的loc字段是“纬度,经度”格式,不是单独的值
    坑:免费版可能不返回loc字段(取决于计划),需要加判断
    “””
    url = f”https://ipinfo.io/{ip_address}/json”
    response = requests.get(url)

    if response.status_code == 200:
    data = response.json()
    # 坑:如果IP是内网地址,返回的是空数据
    if ‘loc’ in data:
    lat, lng = data[‘loc’].split(‘,’)
    return {
    ‘ip’: data.get(‘ip’),
    ‘city’: data.get(‘city’),
    ‘region’: data.get(‘region’),
    ‘country’: data.get(‘country’),
    ‘org’: data.get(‘org’), # ISP信息
    ‘lat’: float(lat),
    ‘lng’: float(lng)
    }
    else:
    return {‘error’: ‘内网地址或保留地址,或免费版不返回loc字段’}
    else:
    return {‘error’: f’请求失败,状态码:{response.status_code}’}

    # 测试一下
    print(query_ip(‘8.8.8.8’))
    # 输出示例:{‘ip’: ‘8.8.8.8’, ‘city’: ‘Mountain View’, ‘region’: ‘California’, …}
    “`

    另一个坑:有些工具号称能查到“精确到街道”,实际上用的是IP数据库里的历史数据。我见过一个案例,某工具把一个在深圳用了三年的IP定位到了北京——因为ISP迁移了机房。

    免费工具vs付费工具,差别在哪?

    我试过十几个IP查询工具,整理了一张对比表:

    | 工具 | 免费版限制 | 付费版价值 | 我的评价 |
    |——|———–|———–|———|
    | ipinfo.io | 每月5万次 | $50/月起 | 数据质量稳定,免费版够用 |
    | ip2location | 每天50次 | $99/年起 | 免费版基本废了 |
    | MaxMind GeoIP | 免费版GeoLite2无调用限制,精度较低 | 付费版GeoIP2起价$25/月 | 离线数据库,适合高并发 |
    | 百度IP | 每天1000次 | 按量付费 | 国内数据还行,国外就瞎了 |

    官方文档那段文档不够清晰,特别是MaxMind的README,看完我怀疑自己英语是不是退步了。后来直接看他们GitHub上的issue,反而搞明白了。

    “`python
    # 另一个坑:多个IP批量查询,别一个个调API
    import asyncio
    import aiohttp

    async def batch_query_ips(ip_list):
    “””
    为什么要这么写:用异步请求同时查询多个IP
    如果一个个调,100个IP可能要等几分钟
    异步的话,几秒就搞定
    “””
    async def query_single(session, ip):
    url = f”https://ipinfo.io/{ip}/json”
    try:
    async with session.get(url) as response:
    if response.status == 200:
    return await response.json()
    else:
    return {‘ip’: ip, ‘error’: f’HTTP {response.status}’}
    except Exception as e:
    return {‘ip’: ip, ‘error’: str(e)}

    async with aiohttp.ClientSession() as session:
    tasks = [query_single(session, ip) for ip in ip_list]
    results = await asyncio.gather(*tasks)
    return results

    # 使用示例
    ips = [‘8.8.8.8’, ‘1.1.1.1’, ‘114.114.114.114’]
    results = asyncio.run(batch_query_ips(ips))
    for r in results:
    print(f”{r.get(‘ip’)}: {r.get(‘city’, ‘未知’)}”)
    “`

    这个设计真的反人类:很多免费API会故意延迟响应,或者返回不完整数据。ipinfo.io的免费版不返回邮编和时区,但如果你只需要城市和ISP信息,免费版完全够用。

    还有个技巧:如果你做的是国内业务,优先用国内的IP库。因为国外工具对中国的IP数据更新特别慢,我遇到过用ipinfo.io查上海联通的IP,结果返回“东京”的离谱情况。

    实战:搭建自己的IP查询系统

    既然免费工具有各种坑,不如自己搭一个。这里用MaxMind的GeoLite2数据库(免费版),配合Flask做个轻量服务。

    “`python
    # 先安装:pip install geoip2 flask
    import os
    import geoip2.database
    from flask import Flask, request, jsonify

    app = Flask(__name__)

    # 加载数据库(从MaxMind官网下载,要注册账号)
    # 坑:数据库文件路径别写错,我一开始放在项目根目录,结果读取失败
    # 加个文件存在性检查,避免运行时崩溃
    db_path = ‘./GeoLite2-City.mmdb’
    if not os.path.exists(db_path):
    raise FileNotFoundError(f”数据库文件 {db_path} 未找到,请从MaxMind官网下载”)
    reader = geoip2.database.Reader(db_path)

    @app.route(‘/ip-query’)
    def ip_query():
    “””
    为什么这么写:支持GET参数,/?ip=8.8.8.8
    返回JSON格式,方便前端调用
    “””
    ip = request.args.get(‘ip’, ”)
    if not ip:
    return jsonify({‘error’: ‘缺少ip参数’})

    try:
    response = reader.city(ip)
    # 注意:免费版GeoLite2不包含ISP信息(response.traits.isp不可用)
    # 建议用autonomous_system_organization获取ASN组织信息作为替代
    isp_info = response.traits.autonomous_system_organization if hasattr(response.traits, ‘autonomous_system_organization’) else ‘免费版不提供ISP信息’
    return jsonify({
    ‘ip’: ip,
    ‘city’: response.city.name,
    ‘region’: response.subdivisions.most_specific.name,
    ‘country’: response.country.name,
    ‘lat’: response.location.latitude,
    ‘lng’: response.location.longitude,
    ‘isp’: isp_info # 免费版用ASN信息代替ISP
    })
    except Exception as e:
    return jsonify({‘error’: str(e)})
    “`

    滚动至顶部