🌍
一次网络请求背后的技术探索,从基础原理到生产级代码的完整演进
📖 目录
项目背景与价值
在当今互联网时代,IP地址定位已成为网络安全、用户行为分析、广告投放等领域的核心基础能力。通过IP地址获取地理位置信息,可以帮助我们:
- 网络安全审计:追踪异常访问来源,识别潜在攻击
- 用户体验优化:根据用户位置提供本地化内容和服务
- 业务数据分析:分析用户地域分布,优化市场策略
- 合规性检查 :验证用户访问权限,符合地域限制要求
本文将从基础代码出发,逐步构建一个生产级的IP地理位置查询服务。
技术方案对比
主流IP地理定位服务对比
| 服务名称 | 免费额度 | 精度 | 响应速度 | 特点 |
|---|---|---|---|---|
| ip-api.com | 45次/分钟 | 城市级 | <50ms | 简单易用,无认证 |
| ipinfo.io | 50,000次/天 | 城市级 | <35ms | 数据详细,包含ASN信息 |
| 百度地图API | 需申请 | 街道级 | <10ms | 高精度,适合商业应用 |
| GeoIP2 | 免费数据库 | 城市级 | 离线查询 | 离线可用,需更新数据库 |
选择建议
开发测试/个人项目
商业应用/高精度需求
离线环境/高频查询
详细网络信息
IP定位需求
使用场景
ip-api.com
百度地图API
GeoIP2数据库
ipinfo.io
优点: 简单免费
缺点: 有频率限制
优点: 精度高
缺点: 需申请Key
优点: 离线快速
缺点: 需定期更新
优点: 信息全面
缺点: 免费版有局限
完整代码实现
基础版本:核心功能实现
python
# -*- coding: utf-8 -*-
"""
IP地理位置查询服务
支持多种查询方式和异常处理
"""
import json
import requests
from bs4 import BeautifulSoup
from datetime import datetime
import socket
from typing import Optional, Dict, List
import time
from requests.exceptions import RequestException, Timeout, ConnectionError, HTTPError
import logging
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class IPinfoService:
"""IP地理位置查询服务类"""
def __init__(self):
"""初始化服务"""
self.timeout = 10 # 默认超时时间
self.max_retries = 3 # 最大重试次数
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
})
def get_all_ips(self) -> List[str]:
"""
获取本机所有网络接口的IP地址
Returns:
IP地址列表
"""
ips = []
hostname = socket.gethostname()
try:
for addr_info in socket.getaddrinfo(hostname, None):
family, _, _, _, sockaddr = addr_info
if family == socket.AF_INET:
ips.append(sockaddr[0])
logger.info(f"获取到 {len(ips)} 个本地IP地址")
return ips
except Exception as e:
logger.error(f"获取本地IP失败: {e}")
return []
def get_local_ip(self) -> str:
"""
获取本机主要IP地址
Returns:
本机IP地址
"""
try:
# 创建UDP socket连接到公共DNS服务器
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
local_ip = s.getsockname()[0]
s.close()
logger.info(f"获取到本机IP: {local_ip}")
return local_ip
except Exception as e:
logger.error(f"获取本机IP失败: {e}")
return "127.0.0.1" # 回退地址
def get_public_ip(self) -> Optional[str]:
"""
获取本机公网IP地址
Returns:
公网IP地址或None
"""
services = [
'https://api.ipify.org',
'https://ifconfig.me/ip',
'https://icanhazip.com',
'https://api.ip.sb/ip'
]
for service in services:
try:
response = self.session.get(service, timeout=self.timeout)
response.raise_for_status()
ip = response.text.strip()
# 验证IP格式
if self._validate_ip(ip):
logger.info(f"通过 {service} 获取到公网IP: {ip}")
return ip
except Exception as e:
logger.debug(f"服务 {service} 获取失败: {e}")
continue
logger.error("所有公网IP查询服务均失败")
return None
def get_city_by_ip_api(self, ip_address: str) -> Optional[str]:
"""
使用ip-api.com查询IP地理位置
Args:
ip_address: IP地址
Returns:
城市名称或None
"""
url = f"http://ip-api.com/json/{ip_address}"
try:
response = self.session.get(url, timeout=self.timeout)
response.raise_for_status()
data = response.json()
if data.get('status') == 'success':
city = data.get('city', '未知城市')
logger.info(f"IP {ip_address} 通过ip-api.com定位到: {city}")
return city
else:
logger.warning(f"IP {ip_address} 查询失败: {data.get('message')}")
return None
except Exception as e:
logger.error(f"ip-api.com查询异常: {e}")
return None
def get_city_by_ipinfo(self, ip_address: str) -> Optional[str]:
"""
使用ipinfo.io查询IP地理位置
Args:
ip_address: IP地址
Returns:
城市名称或None
"""
url = f"https://ipinfo.io/{ip_address}/json"
try:
response = self.session.get(url, timeout=self.timeout)
response.raise_for_status()
data = response.json()
city = data.get('city', '未知城市')
logger.info(f"IP {ip_address} 通过ipinfo.io定位到: {city}")
return city
except Exception as e:
logger.error(f"ipinfo.io查询异常: {e}")
return None
def get_location_by_baidu(self, ip_address: str, ak: Optional[str] = None) -> Optional[Dict]:
"""
使用百度地图API查询IP地理位置
Args:
ip_address: IP地址
ak: 百度地图API Key(可选)
Returns:
位置信息字典或None
"""
if not ak:
logger.warning("未提供百度地图API Key")
return None
url = "https://api.map.baidu.com/location/ip"
params = {
'ip': ip_address,
'ak': ak,
'coor': 'bd09ll'
}
try:
response = self.session.get(url, params=params, timeout=self.timeout)
response.raise_for_status()
data = response.json()
if data.get('status') == 0:
content = data.get('content', {})
address_detail = content.get('address_detail', {})
location_info = {
'ip': data.get('address'),
'province': address_detail.get('province'),
'city': address_detail.get('city'),
'district': address_detail.get('district'),
'latitude': content.get('point', {}).get('y'),
'longitude': content.get('point', {}).get('x'),
'source': '百度地图API'
}
logger.info(f"IP {ip_address} 通过百度地图API定位到: {location_info.get('city')}")
return location_info
else:
logger.warning(f"百度地图API查询失败: {data.get('message')}")
return None
except Exception as e:
logger.error(f"百度地图API查询异常: {e}")
return None
def get_location_from_baidu_search(self) -> Optional[Dict]:
"""
通过爬取百度搜索页面获取IP地理位置信息
Returns:
包含IP和位置信息的字典或None
"""
url = 'https://www.baidu.com/s'
params = {'wd': 'IP', 'ie': 'utf-8'}
try:
response = self.session.get(url, params=params, timeout=self.timeout)
response.encoding = 'utf-8'
# 使用正则表达式提取信息
import re
ip_pattern = r'本机IP[::]\s*([\d.]+)'
location_pattern = r'来自[::]\s*([^<\s]+)'
ip_match = re.search(ip_pattern, response.text)
location_match = re.search(location_pattern, response.text)
result = {
'ip': ip_match.group(1) if ip_match else None,
'location': location_match.group(1) if location_match else None,
'source': '百度搜索'
}
logger.info(f"通过百度搜索获取到IP: {result.get('ip')}, 位置: {result.get('location')}")
return result
except Exception as e:
logger.error(f"百度搜索查询异常: {e}")
return None
def compare_services(self, ip_address: Optional[str] = None) -> Dict[str, Dict]:
"""
对比不同IP查询服务的结果
Args:
ip_address: 要查询的IP地址,如果为None则查询当前公网IP
Returns:
各服务查询结果的字典
"""
if ip_address is None:
ip_address = self.get_public_ip()
if not ip_address:
logger.error("无法获取IP地址进行对比")
return {}
services = {
'ip-api': self.get_city_by_ip_api,
'ipinfo': self.get_city_by_ipinfo,
}
results = {}
for name, method in services.items():
try:
start_time = time.time()
result = method(ip_address)
elapsed = time.time() - start_time
results[name] = {
'city': result,
'response_time': f"{elapsed:.3f}s",
'status': '成功'
}
# 避免请求过快
time.sleep(0.5)
except Exception as e:
results[name] = {
'status': '失败',
'error': str(e)
}
logger.info("服务对比完成")
return results
def _validate_ip(self, ip: str) -> bool:
"""
验证IP地址格式
Args:
ip: IP地址字符串
Returns:
是否为有效IP地址
"""
try:
socket.inet_aton(ip)
return True
except socket.error:
return False
def get_location_info(self, ip_address: str) -> Dict:
"""
综合获取IP地址的位置信息
Args:
ip_address: IP地址
Returns:
包含多种信息的位置字典
"""
info = {
'ip': ip_address,
'timestamp': datetime.now().isoformat(),
'sources': {}
}
# 尝试不同的查询服务
services = [
('ip-api', self.get_city_by_ip_api),
('ipinfo', self.get_city_by_ipinfo),
]
for name, method in services:
try:
result = method(ip_address)
if result:
info['sources'][name] = {
'city': result,
'status': 'success'
}
else:
info['sources'][name] = {
'status': 'failed',
'message': '查询失败'
}
except Exception as e:
info['sources'][name] = {
'status': 'error',
'error': str(e)
}
# 选择最可信的结果
for name in ['ip-api', 'ipinfo']:
if name in info['sources'] and info['sources'][name].get('status') == 'success':
info['best_match'] = info['sources'][name]['city']
break
logger.info(f"获取IP {ip_address} 的位置信息完成")
return info
def close(self):
"""关闭会话"""
self.session.close()
logger.info("IP查询服务会话已关闭")
def main():
"""主函数"""
print("=" * 60)
print("IP地理位置查询服务演示")
print("=" * 60)
service = IPinfoService()
try:
# 1. 获取本机IP信息
print("\n### 本机网络信息 ###")
local_ips = service.get_all_ips()
print(f"所有网络接口IP: {local_ips}")
local_ip = service.get_local_ip()
print(f"本机主要IP: {local_ip}")
# 2. 获取公网IP
print("\n### 公网IP信息 ###")
public_ip = service.get_public_ip()
if public_ip:
print(f"本机公网IP: {public_ip}")
# 3. 查询公网IP的地理位置
print("\n### 公网IP地理位置查询 ###")
# 使用不同服务查询
print("1. 使用ip-api.com查询:")
city1 = service.get_city_by_ip_api(public_ip)
print(f" 城市: {city1}")
print("\n2. 使用ipinfo.io查询:")
city2 = service.get_city_by_ipinfo(public_ip)
print(f" 城市: {city2}")
# 4. 对比服务结果
print("\n### 服务结果对比 ###")
comparison = service.compare_services(public_ip)
for service_name, result in comparison.items():
print(f"{service_name}:")
if result.get('status') == '成功':
print(f" 城市: {result.get('city')}")
print(f" 响应时间: {result.get('response_time')}")
else:
print(f" 状态: {result.get('status')}")
print(f" 错误: {result.get('error', '未知错误')}")
# 5. 综合位置信息
print("\n### 综合位置信息 ###")
location_info = service.get_location_info(public_ip)
print(json.dumps(location_info, indent=2, ensure_ascii=False))
else:
print("无法获取公网IP地址")
# 6. 查询指定IP
print("\n### 查询指定IP ###")
test_ips = ["8.8.8.8", "114.114.114.114", "1.1.1.1"]
for ip in test_ips:
print(f"\n查询IP: {ip}")
city = service.get_city_by_ip_api(ip)
print(f"城市: {city}")
time.sleep(1) # 避免请求过快
finally:
service.close()
print("\n" + "=" * 60)
print("演示结束")
print("=" * 60)
if __name__ == '__main__':
main()
异常处理与重试机制
为什么需要异常处理?
在网络请求中,异常情况(如网络波动、服务器错误、参数错误等)难以避免。若不妥善处理,可能导致程序崩溃或数据丢失。
常见异常类型
| 异常类型 | 触发场景 | 处理策略 |
|---|---|---|
| HTTPError | 服务器返回错误状态码(4xx、5xx) | 根据状态码采取不同措施 |
| ConnectionError | 网络中断、域名解析失败 | 重试机制+资源释放 |
| Timeout | 连接或读取数据超过设定时间 | 增加重试次数和延迟 |
| JSONDecodeError | 响应数据格式错误 | 提供默认值或错误提示 |
实战异常处理框架
🔧 查看完整异常处理代码
python
import requests
import time
from requests.exceptions import HTTPError, ConnectionError, Timeout, RequestException
from typing import Optional, Dict, Any
class RobustIPQuery:
"""带异常处理和重试机制的IP查询服务"""
def __init__(self, timeout: int = 10, max_retries: int = 3):
"""
初始化
Args:
timeout: 超时时间(秒)
max_retries: 最大重试次数
"""
self.timeout = timeout
self.max_retries = max_retries
self.session = requests.Session()
def call_api_with_retry(
self,
url: str,
method: str = "GET",
params: Optional[Dict] = None,
json_data: Optional[Dict] = None,
headers: Optional[Dict] = None
) -> Optional[Dict[Any, Any]]:
"""
带异常处理和重试机制的API调用函数
Args:
url: 接口URL
method: 请求方法(GET/POST)
params: GET请求参数
json_data: POST请求体
headers: 请求头
Returns:
接口返回的JSON数据或None
"""
# 初始化请求参数
params = params or {}
json_data = json_data or {}
headers = headers or {"Content-Type": "application/json"}
retries = 0
retry_delay = 1 # 初始重试间隔
while retries <= self.max_retries:
try:
# 发送请求
if method.upper() == "GET":
response = self.session.get(
url,
params=params,
headers=headers,
timeout=self.timeout
)
else: # POST
response = self.session.post(
url,
json=json_data,
headers=headers,
timeout=self.timeout
)
# 检查HTTP状态码
response.raise_for_status()
# 返回JSON数据
return response.json()
except HTTPError as e:
status_code = e.response.status_code if e.response else None
# 4xx客户端错误,不需要重试
if status_code and 400 <= status_code < 500:
print(f"客户端错误: {status_code}, 不重试")
return None
# 5xx服务端错误,可以重试
print(f"服务端错误: {status_code}, 重试 {retries + 1}/{self.max_retries}")
except (ConnectionError, Timeout) as e:
print(f"网络异常: {e}, 重试 {retries + 1}/{self.max_retries}")
except Exception as e:
print(f"未知异常: {e}")
return None
# 等待后重试
if retries < self.max_retries:
time.sleep(retry_delay)
retry_delay *= 2 # 指数退避策略
retries += 1
print("达到最大重试次数,放弃")
return None
def get_ip_location(self, ip_address: str) -> Optional[str]:
"""
获取IP地址的地理位置
Args:
ip_address: IP地址
Returns:
城市名称或None
"""
url = f"http://ip-api.com/json/{ip_address}"
result = self.call_api_with_retry(url)
if result and result.get('status') == 'success':
return result.get('city')
return None
def close(self):
"""关闭会话"""
self.session.close()
# 使用示例
if __name__ == '__main__':
query = RobustIPQuery(timeout=10, max_retries=3)
# 测试不同情况
test_cases = [
"8.8.8.8", # 正常IP
"invalid-ip", # 无效IP
"192.168.1.1", # 私有IP
]
for ip in test_cases:
print(f"\n查询IP: {ip}")
city = query.get_ip_location(ip)
print(f"结果: {city}")
query.close()
关键设计原则
- 分类处理异常:根据异常类型采取不同策略
- 指数退避重试:避免加重服务器负担
- 最大重试次数限制:防止无限循环
- 资源清理:确保网络连接正确关闭
性能优化与缓存策略
缓存机制实现
📊 查看缓存实现代码
python
import json
import os
from datetime import datetime, timedelta
from typing import Optional, Dict, Any
import threading
class IPQueryCache:
"""IP查询结果缓存"""
def __init__(self, cache_file: str = "ip_cache.json", expire_hours: int = 24):
"""
初始化缓存
Args:
cache_file: 缓存文件路径
expire_hours: 缓存过期时间(小时)
"""
self.cache_file = cache_file
self.expire_hours = expire_hours
self.lock = threading.Lock()
self.cache = self._load_cache()
def _load_cache(self) -> Dict[str, Any]:
"""加载缓存文件"""
if os.path.exists(self.cache_file):
try:
with open(self.cache_file, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception:
return {}
return {}
def _save_cache(self):
"""保存缓存到文件"""
with self.lock:
try:
with open(self.cache_file, 'w', encoding='utf-8') as f:
json.dump(self.cache, f, ensure_ascii=False, indent=2)
except Exception as e:
print(f"保存缓存失败: {e}")
def get(self, ip: str) -> Optional[Dict[str, Any]]:
"""
获取缓存结果
Args:
ip: IP地址
Returns:
缓存的位置信息或None
"""
with self.lock:
if ip in self.cache:
entry = self.cache[ip]
# 检查是否过期
cache_time = datetime.fromisoformat(entry.get('timestamp', ''))
if datetime.now() - cache_time < timedelta(hours=self.expire_hours):
return entry.get('data')
else:
# 删除过期缓存
del self.cache[ip]
self._save_cache()
return None
def set(self, ip: str, data: Dict[str, Any]):
"""
设置缓存
Args:
ip: IP地址
data: 位置信息数据
"""
with self.lock:
self.cache[ip] = {
'data': data,
'timestamp': datetime.now().isoformat()
}
self._save_cache()
def clear_expired(self):
"""清除过期缓存"""
with self.lock:
expired_ips = []
for ip, entry in self.cache.items():
cache_time = datetime.fromisoformat(entry.get('timestamp', ''))
if datetime.now() - cache_time >= timedelta(hours=self.expire_hours):
expired_ips.append(ip)
for ip in expired_ips:
del self.cache[ip]
if expired_ips:
self._save_cache()
def get_stats(self) -> Dict[str, Any]:
"""获取缓存统计信息"""
with self.lock:
total = len(self.cache)
expired = 0
for entry in self.cache.values():
cache_time = datetime.fromisoformat(entry.get('timestamp', ''))
if datetime.now() - cache_time >= timedelta(hours=self.expire_hours):
expired += 1
return {
'total': total,
'active': total - expired,
'expired': expired,
'hit_rate': 0.0 # 需要实际统计
}
# 使用缓存的IP查询服务
class CachedIPQuery:
"""带缓存的IP查询服务"""
def __init__(self):
self.cache = IPQueryCache()
self.session = requests.Session()
def get_location(self, ip: str) -> Optional[Dict[str, Any]]:
"""
获取IP位置信息(带缓存)
Args:
ip: IP地址
Returns:
位置信息字典
"""
# 首先检查缓存
cached = self.cache.get(ip)
if cached:
return cached
# 缓存未命中,查询API
url = f"http://ip-api.com/json/{ip}"
try:
response = self.session.get(url, timeout=10)
response.raise_for_status()
data = response.json()
if data.get('status') == 'success':
result = {
'ip': ip,
'city': data.get('city'),
'country': data.get('country'),
'isp': data.get('isp'),
'timestamp': datetime.now().isoformat()
}
# 存入缓存
self.cache.set(ip, result)
return result
except Exception as e:
print(f"查询异常: {e}")
return None
def close(self):
"""关闭服务"""
self.session.close()
self.cache._save_cache()
性能优化建议
- 使用会话(Session):复用TCP连接,减少连接开销
- 设置合理超时:避免程序卡死
- 批量查询:减少API调用次数
- 异步查询:提高并发性能
实际应用场景
1. 网络安全监控
python
class SecurityMonitor:
"""网络安全监控"""
def __init__(self):
self.ip_service = IPinfoService()
self.suspicious_ips = set()
def analyze_access_log(self, log_file: str):
"""分析访问日志"""
with open(log_file, 'r') as f:
for line in f:
# 解析日志,提取IP
ip = self._extract_ip_from_log(line)
if ip and ip not in self.suspicious_ips:
location = self.ip_service.get_location_info(ip)
# 检查可疑地区
if self._is_suspicious(location):
self.suspicious_ips.add(ip)
self._send_alert(ip, location)
def _is_suspicious(self, location: Dict) -> bool:
"""判断是否为可疑地区"""
suspicious_countries = ['XX', 'YY'] # 示例国家代码
return location.get('country') in suspicious_countries
def _send_alert(self, ip: str, location: Dict):
"""发送告警"""
print(f"安全告警: 可疑访问来自 {ip} ({location.get('city')})")
2. 用户行为分析
python
class UserAnalytics:
"""用户行为分析"""
def __init__(self):
self.ip_service = IPinfoService()
self.user_locations = {}
def track_user_location(self, user_id: str, ip: str):
"""跟踪用户地理位置"""
location = self.ip_service.get_location_info(ip)
if user_id not in self.user_locations:
self.user_locations[user_id] = []
self.user_locations[user_id].append({
'timestamp': datetime.now(),
'location': location,
'ip': ip
})
def get_user_travel_history(self, user_id: str) -> List[Dict]:
"""获取用户旅行历史"""
return self.user_locations.get(user_id, [])
def analyze_user_patterns(self):
"""分析用户行为模式"""
# 实现用户行为模式分析
pass
3. 广告地域定向
python
class AdTargeting:
"""广告地域定向"""
def __init__(self):
self.ip_service = IPinfoService()
def get_targeted_ads(self, ip: str) -> List[Dict]:
"""获取定向广告"""
location = self.ip_service.get_location_info(ip)
# 根据地理位置选择广告
ads = []
if location.get('country') == 'CN':
ads.append({'type': 'local', 'content': '本地服务'})
elif location.get('country') == 'US':
ads.append({'type': 'global', 'content': '国际产品'})
return ads
常见问题与解决方案
问题1:API请求频率限制
症状 :返回429错误,请求被限制
解决方案:
- 实现请求限流:控制请求频率
- 使用缓存:减少重复查询
- 轮换多个API服务:分散请求压力
问题2:定位不准确
症状 :返回的城市信息与实际不符
解决方案:
- 使用多个服务交叉验证
- 选择精度更高的付费服务
- 结合用户自行填写的信息
问题3:网络不稳定
症状 :请求超时或连接失败
解决方案:
- 实现重试机制:指数退避策略
- 设置合理超时时间:根据网络环境调整
- 使用异步请求:提高并发性能
问题4:私有IP地址查询
症状 :查询局域网IP地址失败
解决方案:
- 检测IP地址类型:过滤私有IP地址
- 提供手动输入功能:允许用户输入位置信息
- 结合GPS定位:移动设备获取更精确位置
总结与展望
本文从基础代码出发,逐步构建了一个生产级的IP地理位置查询服务。关键要点包括:
- 多种查询方式:支持ip-api.com、ipinfo.io、百度地图API等多种服务
- 健壮的异常处理:处理各种网络异常和API错误
- 性能优化:会话复用、缓存机制、异步请求
- 实际应用:网络安全监控、用户行为分析、广告定向
未来发展方向
- 机器学习:使用机器学习提高定位精度
- 实时更新:自动更新IP地理数据库
- 可视化分析:将查询结果可视化展示
- 边缘计算:在边缘节点进行IP查询,减少延迟
学习建议
- 实践项目:在实际项目中应用IP查询功能
- 深入理解:学习IP地址分配和地理定位原理
- 性能调优:优化查询性能,减少资源消耗
- 安全考虑 :注意隐私保护和数据安全
通过本文的学习,您应该能够独立实现一个功能完善、性能优良的IP地理位置查询服务,为实际项目提供有力支持。
项目地址 :https://github.com/moduwusuowei
技术支持:如有问题,请在评论区留言或提交Issue