Appearance
Excel 怎么批量查 IP 归属地?不装付费插件,一个 Python 脚本填完整张表
运营小张每个月要处理一份"用户登录 IP"表,一千多行,领导要求补上省市。她百度"excel 批量查询ip归属地",前三条全是"方方格子"教程——下载插件、注册账号、开会员,一套流程走完才发现免费额度只够查几十个。换成在线工具站,一次粘贴 20 条,多了要注册要付费,还担心把用户 IP 传上去泄露。
最后这活落到开发头上:写个脚本,半小时跑完一千个 IP,还不用花一分钱。这篇就把这个脚本给全:Excel 里有一列 IP,自动在右边补上省份、城市、运营商三列,原文件不动,另存一份新的。
一、为什么用脚本而不是插件和在线工具
插件要钱,在线工具要注册、限条数,这俩还共享同一个毛病:你的 IP 数据要经过他们的服务器。登录日志、活动名单里的 IP 算个人信息,往第三方工具里一贴,合规上就是个坑。脚本在本地跑,数据不出机器,这是最大的好处。
技术上也不复杂:IP 归属地本来就有免费的在线接口,GET https://ip9.com.cn/get?ip=<ip> 免注册直接调,返回省市、运营商这些字段。免费版限速 60 次/分钟/IP,脚本里每次查询间隔 1.1 秒,一千个 IP 二十来分钟跑完,挂那不用管。唯一的"开发工作"就是读写 Excel——Python 的 openpyxl 库干这个最顺手。
二、脚本设计
- 找列:自动在表头前几行里找名字带
IP的列(登录IP、IP地址、客户端IP都能认),不用手填列号。 - 去重缓存:同一 IP 在表里可能出现几十次(一个公司出口 IP 一串登录记录),查一次存进本地 JSON 缓存,后面的行直接读缓存——既省时间也省接口额度。
- 限速查询:每次请求间隔 1.1 秒,贴合免费版 60 次/分钟的限额;失败自动重试一次,再失败记空值,不让单个坏 IP 卡死整轮。
- 断点续跑:每查一个 IP 就写一次缓存文件,跑到一半断了、停电了,重跑一遍只补没查过的,不重复消耗。
- 写回另存:省份、城市、运营商写到表格右侧三列,结果另存为
原文件名_归属地.xlsx,原文件保持不动——万一手滑还能重来。
三、Python 实现
先装依赖:pip install openpyxl requests。然后保存下面脚本,命令行执行 python ip_geo_excel.py 待查.xlsx:
python
# -*- coding: utf-8 -*-
"""Excel 批量查 IP 归属地:找到表头为 IP 的列,右侧补省份/城市/运营商
用法: python ip_geo_excel.py 待查.xlsx
"""
import json
import sys
import time
import requests
from openpyxl import load_workbook
API = "https://ip9.com.cn/get"
INTERVAL = 1.1 # 秒;免费接口 60 次/分钟/IP,留点余量
CACHE_FILE = "ipgeo_cache.json"
def load_cache():
try:
with open(CACHE_FILE, encoding="utf-8") as f:
return json.load(f)
except Exception:
return {}
def save_cache(cache):
with open(CACHE_FILE, "w", encoding="utf-8") as f:
json.dump(cache, f, ensure_ascii=False)
def is_likely_public(ip):
"""粗校验:IPv6 或合法 IPv4 且不是私网段,就算可查"""
s = str(ip).strip()
if ":" in s: # IPv6:排除回环和链路本地就放行
return s.lower() not in ("::1",) and not s.lower().startswith("fe80")
parts = s.split(".")
if len(parts) != 4:
return False
try:
nums = [int(p) for p in parts]
except ValueError:
return False
if any(n < 0 or n > 255 for n in nums):
return False
a, b = nums[0], nums[1]
if a == 10 or (a == 172 and 16 <= b <= 31) or (a == 192 and b == 168) or a == 127:
return False
return True
def query(ip, cache):
"""查单个 IP 归属地;网络失败重试一次,再失败记空值"""
# 不带 User-Agent 会被接口拦(403),headers 里必须给一个
HEADERS = {"User-Agent": "Mozilla/5.0 (compatible; IP9Demo/1.0)"}
for attempt in (1, 2):
try:
r = requests.get(API, params={"ip": ip}, timeout=5, headers=HEADERS)
d = r.json()
if d.get("ret") == 200:
x = d["data"]
cache[ip] = {"prov": x.get("prov", ""), "city": x.get("city", ""),
"isp": x.get("isp", "")}
save_cache(cache) # 每查一个存一次,中断后重跑不重复查
return cache[ip]
except Exception:
pass
time.sleep(INTERVAL) # 重试前也歇一下
cache[ip] = {"prov": "", "city": "", "isp": ""}
save_cache(cache)
return cache[ip]
def main():
if len(sys.argv) < 2:
print("用法: python ip_geo_excel.py <表格.xlsx>")
sys.exit(1)
src = sys.argv[1]
wb = load_workbook(src)
ws = wb.active
# 1) 在前 5 行里找表头带 IP 的列(认"登录IP/IP地址/客户端IP",不认"IP归属地"这种结果列)
ip_col = None
for row in ws.iter_rows(min_row=1, max_row=5):
for cell in row:
h = str(cell.value).strip().lower() if cell.value else ""
if "ip" in h and not any(k in h for k in ("归属", "省", "市", "运营")):
ip_col = cell.column
break
if ip_col:
break
if not ip_col:
print("没找到 IP 列:请把 IP 所在列的表头改成 IP / 登录IP / IP地址 等含 ip 的名字")
sys.exit(1)
# 2) 右侧补三列表头
prov_col, city_col, isp_col = ws.max_column + 1, ws.max_column + 2, ws.max_column + 3
ws.cell(row=1, column=prov_col, value="省份")
ws.cell(row=1, column=city_col, value="城市")
ws.cell(row=1, column=isp_col, value="运营商")
cache = load_cache()
done = 0
for row in range(2, ws.max_row + 1):
val = ws.cell(row=row, column=ip_col).value
if val is None or not is_likely_public(val):
continue
ip = str(val).strip()
if ip in cache: # 缓存命中不查接口也不限速
info = cache[ip]
else:
info = query(ip, cache)
time.sleep(INTERVAL) # 限速点:两次真实查询之间至少隔 1.1 秒
ws.cell(row=row, column=prov_col, value=info["prov"])
ws.cell(row=row, column=city_col, value=info["city"])
ws.cell(row=row, column=isp_col, value=info["isp"])
done += 1
if done % 20 == 0:
print(f"已处理 {done} 个,最近: {ip} -> {info['prov']}{info['city']} {info['isp']}")
out = src.replace(".xlsx", "_归属地.xlsx")
wb.save(out)
print(f"完成:处理 {done} 个 IP,结果另存为 {out},原文件未改动")
if __name__ == "__main__":
main()跑完打开 xxx_归属地.xlsx,每行 IP 右边就是省市和运营商,可以直接筛选"省份=广东"或者按运营商透视。表里重复的 IP 只查一次,一千行去重后通常只剩两三百个,实际比预想快得多。
四、落地注意事项
- IP 列必须是文本格式。Excel 里如果 IP 被存成数字(单元格显示 202.96.128.86,点进去没有前导内容),实际值是 2029612886,脚本会跳过。全选该列 → 数据 → 分列 → 下一步 → 文本,转一下就好。
- 私网 IP 直接跳过是对的。10.x、192.168.x、127.x 是内网地址,查了也是白查;脚本里已经过滤。倒是 100.64.x.x 这种运营商大内网地址会被放行,查出来的省市是运营商出口位置,移动宽带用户多的地方会看到整片"同一个城市",正常现象。
- 别把缓存文件删了。
ipgeo_cache.json就是断点续跑的命根子,跑一半断了重跑,命中的 IP 不查接口不限速,几分钟就补完剩下的。表更新了想重查,删掉缓存文件再跑即可。 - 量再大的话:免费版 60 次/分钟,一小时能查三千多个 IP,日常表格够用;要是几十万行、要赶时间,可以看按次计费的 VIP 版(0.05 元/千次起、18 万次/分钟),还能拿到区县、
ip_type(家庭宽带/企业/机房)这些字段——区分机房 IP 刷量时用得上。 - 数据安全别松:脚本在本地跑就是为了不让 IP 数据出机器。跑完的结果文件同样别乱发、别传网盘公开链接,里面能对出真实位置,按个人信息管。
总结
"Excel 批量查 IP 归属地"这个需求,插件和在线工具能解决,但要么花钱要么限条数,还得把数据交给第三方。其实一个不到一百行的 Python 脚本就能干完:找列、去重、限速查询、断点续跑、另存结果,全自动,免费,数据还不离本机。运营同事以后再甩表格过来,把脚本丢给她就行。
查询用的免费接口是 https://ip9.com.cn/get?ip=<ip>,免注册直接调,返回国家/省市/邮编/区号/运营商/经纬度,IPv4 和 IPv6 都支持,字段和额度说明见官网 https://www.ip9.com.cn 。脚本里换个接口地址和字段名,就能适配任意同类 API——但先把 openpyxl 装好,这是整个活最值钱的一步。