先看个最基础但最容易翻车的问题:IP地址查询到底查的是什么?
为什么你的IP定位总不准?
很多人以为IP地址查询能精确定位到你家门口,这想法比算命还不靠谱。IP地址的物理位置信息,本质上是个“地理标签”,由几级数据拼起来的:
这三个数据源,误差从几公里到几百公里不等。我实测过,同一个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)})
“`