【运维实战】Python打造基于免费API的IP地址归属地批量查询工具!
打造基于免费API的IP地址归属地批量查询工具
项目简介
基于Python的IP地址归属地免费批量查询工具,可以快速查询大量IP地址的地理位置和网络信息,支持国际和国内API双查询模式,结果自动保存为Excel文件。
效果展示
IP地址 国家 地区 城市 ISP AS 查询状态
8.8.8.8 美国 加利福尼亚 山景城 Google LLC AS15169 Google success
1.1.1.1 澳大利亚 新南威尔士 悉尼 Cloudflare, Inc. AS13335 success
114.114.114.114 中国 江苏省 南京市 南京信风网络科技 AS9808 success
223.5.5.5 中国 北京市 北京市 阿里云计算有限公司 AS37963 success
101.226.4.6 中国 上海市 上海市 中国电信 AS4812 success
218.30.118.6 中国 北京市 北京市 北京电信通 AS23724 success
61.135.169.121 中国 北京市 北京市 百度网讯科技 AS55990 success
119.29.29.29 中国 广东省 广州市 腾讯云计算 AS45090 success
202.96.134.33 中国 广东省 广州市 中国电信 AS4134 success
202.96.128.86 中国 广东省 广州市 中国电信 AS4134 success
202.96.128.166 中国 广东省 广州市 中国电信 AS4134 success
202.96.128.68 中国 广东省 广州市 中国电信 AS4134 success
基础环境要求
-
Python 3.6+
-
依赖库:
-
requests (用于API请求)
-
openpyxl (用于生成Excel文件)
-
concurrent.futures (用于并发查询)
-
安装依赖:
pip install requests openpyxl
关键代码解析
1. IP地址验证
def is_valid_ip(ip: str) -> bool:"""验证IP地址格式是否有效"""try:parts = ip.split('.')iflen(parts) != 4:returnFalsefor part in parts:ifnot part.isdigit() ornot0 <= int(part) <= 255:returnFalsereturnTrueexcept:return False
2. IP查询核心逻辑
def lookup_ip(ip: str) -> Dict:"""查询单个IP地址的归属地信息"""# 包含缓存机制、重试逻辑和国际/国内API双查询模式# ...
3. 批量查询功能
def get_ip_location_batch(ip_list: List[str], max_workers: int = 5) -> List[Dict]:"""批量查询IP地址归属地"""# 使用线程池实现并发查询# ...
重要第三方库
1. requests
用于发送HTTP请求到IP查询API,支持超时设置和异常处理。
2. openpyxl
用于将查询结果生成格式化的Excel文件,支持自动调整列宽和样式设置。
API介绍
1. ip-api.com国际API
-
免费查询限制:45次/分钟(非商业用途)
-
返回数据格式:JSON
-
包含字段:国家、地区、城市、ISP、AS号等
-
使用注意事项:
-
需要遵守API使用条款
-
商业用途需要购买专业版
-
建议添加User-Agent标识
-
2. 淘宝IP库国内API
-
免费查询限制:100次/天
-
返回数据格式:JSON
-
包含字段:国家、省份、城市、运营商等
-
使用注意事项:
-
仅限国内IP查询
-
需要处理中文编码
-
建议添加请求间隔避免被封禁
-
使用说明
-
准备一个文本文件(input.txt),每行一个IP地址
-
运行命令:
python get_ip_location_batch.py --input input.txt --output result.xlsx
-
可选参数:
-
--workers
: 设置并发查询线程数(默认5)
-
测试案例
输入文件示例(input.txt):
8.8.8.8
1.1.1.1
114.114.114.114
223.5.5.5
101.226.4.6
218.30.118.6
61.135.169.121
119.29.29.29
202.96.134.33
202.96.128.86
202.96.128.166
202.96.128.68
运行后生成的Excel文件包含以下信息:
-
IP地址
-
国家
-
地区
-
城市
-
ISP
-
AS
-
查询状态
示例数据格式(output.xls):
IP地址 国家 地区 城市 ISP AS 查询状态
8.8.8.8 美国 加利福尼亚 山景城 Google LLC AS15169 Google success
1.1.1.1 澳大利亚 新南威尔士 悉尼 Cloudflare, Inc. AS13335 success
114.114.114.114 中国 江苏省 南京市 南京信风网络科技 AS9808 success
223.5.5.5 中国 北京市 北京市 阿里云计算有限公司 AS37963 success
101.226.4.6 中国 上海市 上海市 中国电信 AS4812 success
218.30.118.6 中国 北京市 北京市 北京电信通 AS23724 success
61.135.169.121 中国 北京市 北京市 百度网讯科技 AS55990 success
119.29.29.29 中国 广东省 广州市 腾讯云计算 AS45090 success
202.96.134.33 中国 广东省 广州市 中国电信 AS4134 success
202.96.128.86 中国 广东省 广州市 中国电信 AS4134 success
202.96.128.166 中国 广东省 广州市 中国电信 AS4134 success
202.96.128.68 中国 广东省 广州市 中国电信 AS4134 success
完整代码
import requests
import concurrent.futures
import json
import time
from typing importList, Dict# 使用ip-api.com免费API进行IP查询
IP_API_URL = "http://ip-api.com/json/{ip}?fields=status,message,country,regionName,city,isp,org,as,query"
# 国内IP查询API
CN_API_URL = "https://ip.taobao.com/outGetIpInfo?ip={ip}"# 缓存字典避免重复查询
IP_CACHE = {}defis_valid_ip(ip: str) -> bool:"""验证IP地址格式是否有效"""try:parts = ip.split('.')iflen(parts) != 4:returnFalsefor part in parts:ifnot part.isdigit() ornot0 <= int(part) <= 255:returnFalsereturnTrueexcept:returnFalsedeflookup_ip(ip: str) -> Dict:"""查询单个IP地址的归属地信息"""ifnot is_valid_ip(ip):return {"ip": ip, "error": "Invalid IP address format"}# 检查缓存if ip in IP_CACHE:return IP_CACHE[ip]max_retries = 3retry_delay = 1# 初始重试延迟秒数for attempt inrange(max_retries):try:# 优先使用国际API查询try:response = requests.get(IP_API_URL.format(ip=ip), timeout=5)response.raise_for_status()ifnot response.text.strip():raise ValueError("Empty response from API")data = response.json()if data.get("status") == "success":result = {"ip": ip, "data": data}IP_CACHE[ip] = resultreturn resultelse:raise requests.exceptions.RequestException(data.get("message", "API returned error"))except requests.exceptions.RequestException as e:if"Max retries exceeded"instr(e):# 国际API失败时使用国内API作为备用response = requests.get(CN_API_URL.format(ip=ip), timeout=5)ifnot response.text.strip():raise ValueError("Empty response from API")data = response.json()if data.get("code") == 0:result = {"ip": ip, "data": data.get("data", {})}IP_CACHE[ip] = resultreturn resultelse:raise requests.exceptions.RequestException("国内API请求失败")else:raiseifnot response.text.strip():raise ValueError("Empty response from API")data = response.json()if data.get("status") == "success":result = {"ip": ip, "data": data}IP_CACHE[ip] = resultreturn resultelse:result = {"ip": ip, "error": data.get("message", "Unknown error")}IP_CACHE[ip] = resultreturn resultexcept requests.exceptions.RequestException as e:if attempt == max_retries - 1:result = {"ip": ip, "error": f"API请求失败(尝试{attempt+1}次): {str(e)}"}IP_CACHE[ip] = resultreturn resulttime.sleep(retry_delay * (attempt + 1))except ValueError as e:if attempt == max_retries - 1:result = {"ip": ip, "error": f"JSON解析失败(尝试{attempt+1}次): {str(e)}"}IP_CACHE[ip] = resultreturn resulttime.sleep(retry_delay * (attempt + 1))# 所有尝试都失败result = {"ip": ip, "error": "所有API请求尝试均失败"}IP_CACHE[ip] = resultreturn resultdefget_ip_location_batch(ip_list: List[str], max_workers: int = 5) -> List[Dict]:"""批量查询IP地址归属地"""results = []with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:future_to_ip = {executor.submit(lookup_ip, ip): ip for ip in ip_list}for future in concurrent.futures.as_completed(future_to_ip):results.append(future.result())return resultsdefread_ips_from_file(file_path: str) -> List[str]:"""从文件读取IP列表"""withopen(file_path, 'r') as f:return [line.strip() for line in f if line.strip()]defsave_results_to_file(results: List[Dict], output_file: str):"""将查询结果保存到Excel文件"""from openpyxl import Workbookfrom openpyxl.styles import Font, Alignment, PatternFillwb = Workbook()ws = wb.activews.title = "IP查询结果"# 设置表头样式header_font = Font(bold=True)header_alignment = Alignment(horizontal='center')header_fill = PatternFill(start_color='D3D3D3', end_color='D3D3D3', fill_type='solid')# 写入表头headers = ["IP地址", "国家", "地区", "城市", "ISP", "AS", "状态"]ws.append(headers)# 应用表头样式for cell in ws[1]:cell.font = header_fontcell.alignment = header_alignmentcell.fill = header_fill# 写入数据for result in results:if'data'in result:data = result['data']row = [result['ip'],data.get('country', '未知'),data.get('regionName', '未知'),data.get('city', '未知'),data.get('isp', '未知'),data.get('as', '未知'),"成功"]else:row = [result['ip'], "", "", "", "", "", f"错误: {result.get('error', '未知错误')}"]ws.append(row)# 自动调整列宽for column in ws.columns:max_length = 0column_letter = column[0].column_letterfor cell in column:try:iflen(str(cell.value)) > max_length:max_length = len(str(cell.value))except:passadjusted_width = (max_length + 2) * 1.2ws.column_dimensions[column_letter].width = adjusted_widthwb.save(output_file)if __name__ == "__main__":import argparseparser = argparse.ArgumentParser(description='IP地址归属地批量查询工具')parser.add_argument('--input', required=True, help='包含IP地址列表的输入文件路径')parser.add_argument('--output', required=True, help='查询结果输出文件路径')parser.add_argument('--workers', type=int, default=5, help='并发查询线程数')args = parser.parse_args()print(f"正在从 {args.input} 读取IP地址...")ip_list = read_ips_from_file(args.input)print(f"共读取到 {len(ip_list)} 个IP地址,开始查询...")results = get_ip_location_batch(ip_list, args.workers)print(f"查询完成,正在将结果保存到 {args.output}...")save_results_to_file(results, args.output)print("操作完成!")