你有没有遇过这种情况:服务器日志里冒出一个陌生 IP,想知道它是哪来的?或者想确认自己的公网 IP 到底在哪个城市?今天我们用 OpenCode 写一个命令行 IP 归属地查询工具,零依赖、纯 Python 标准库,4 分钟搞定。
功能清单: 查询任意 IP 的国家、城市、ISP、ASN、时区、经纬度;支持批量查询多个 IP;支持 JSON 原始输出;不填 IP 自动查本机公网地址。数据源是 ip-api.com 免费接口。
安装后在终端输入 opencode 进入交互界面,就可以开始用中文"下指令"了。
对 OpenCode 的需求描述越具体,生成结果越精准。我的第一句提示词是这样的:
> 用 Python 写一个命令行 IP 归属地查询工具 ipquery.py。调用 ip-api.com 免费接口(http://ip-api.com/json/{ip}),支持传入 IP 作为参数,不传参数则查本机公网 IP。用 argparse 解析参数,用 urllib 发请求,输出国家、城市、ISP、时区等信息。纯标准库,不要第三方依赖。
OpenCode 几秒钟就生成了完整的 ipquery.py,包含 URL 拼接、请求头设置、异常处理和格式化输出,直接可运行:
#!/usr/bin/env python3
import sys
import json
import argparse
import urllib.request
API_URL = "http://ip-api.com/json/{}"
FIELDS = "status,message,country,regionName,city,isp,org,as,query,timezone,lat,lon"
def query_ip(ip: str, lang: str = "zh-CN"):
params = f"?lang={lang}&fields={FIELDS}"
url = API_URL.format(ip) + params
req = urllib.request.Request(url, headers={"User-Agent": "ipquery/1.0"})
try:
with urllib.request.urlopen(req, timeout=10) as resp:
data = json.loads(resp.read().decode())
if data.get("status") == "fail":
return {"error": data.get("message", "未知错误")}
return data
except urllib.error.URLError as e:
return {"error": f"网络错误: {e.reason}"}
except Exception as e:
return {"error": str(e)}
def format_output(data: dict):
if "error" in data:
print(f"[错误] {data['error']}")
return
label_width = 12
mapping = [
("IP 地址", "query"), ("国家/地区", "country"),
("省份/州", "regionName"), ("城市", "city"),
("ISP", "isp"), ("组织", "org"),
("ASN", "as"), ("时区", "timezone"),
("坐标", "lat,lon"),
]
for label, key in mapping:
if key == "lat,lon":
value = f"{data.get('lat', '?')}, {data.get('lon', '?')}"
else:
value = data.get(key)
if value:
print(f" {label:<{label_width}}: {value}")
def main():
parser = argparse.ArgumentParser(description="IP 归属地查询工具")
parser.add_argument("ip", nargs="?", default="", help="要查询的 IP 地址")
parser.add_argument("-l", "--lang", default="zh-CN", choices=["zh-CN", "en"])
args = parser.parse_args()
display = args.ip or "本机公网 IP"
print(f"查询: {display}\n")
result = query_ip(args.ip, args.lang)
format_output(result)
if __name__ == "__main__":
main()
运行 python ipquery.py 8.8.8.8,输出类似:
查询: 8.8.8.8 IP 地址 : 8.8.8.8 国家/地区 : 美国 省份/州 : Virginia 城市 : Ashburn ISP : Google LLC ASN : AS15169 Google LLC 时区 : America/New_York
要点: 描述需求时要明确技术栈、输入输出和约束条件("纯标准库、不要第三方依赖"),OpenCode 就能一次性生成可直接运行的代码。
基础版跑通后,我想加一个 --raw 参数,方便把结果 pipe 给 jq 或其他脚本处理:
> 给 ipquery.py 加一个 -r / --raw 参数,输出原始 JSON 而不是格式化文本。
OpenCode 精准地新增了参数定义和分支逻辑,没有改动其他代码结构:
parser.add_argument("-r", "--raw", action="store_true", help="输出原始 JSON")
# format_output 改为 format_output(data, raw=False)
if raw:
print(json.dumps(data, ensure_ascii=False, indent=2))
return
要点: 追加功能时,上下文是关键。OpenCode 会记住之前的完整代码,只做增量修改。你的提示词只需描述"要加什么",不用重复交代项目全貌。
单个 IP 查起来不过瘾,我让它支持一次传多个 IP:
> 把 ip 参数改成可以接收多个 IP 地址(nargs="*"),遍历查询每个地址,之间用空行分隔。
命令变成了 python ipquery.py 8.8.8.8 1.1.1.1 114.114.114.114,一次查三个。
这个改动涉及参数定义、遍历逻辑和输出格式三处,但 OpenCode 一次性全部改好了,没有遗漏:
parser.add_argument("ips", nargs="*", default=[], help="要查询的 IP 地址")
targets = args.ips if args.ips else [""]
for i, ip in enumerate(targets):
display = ip or "本机公网 IP"
print(f"=== {display} ===")
result = query_ip(ip, args.lang)
format_output(result)
if i < len(targets) - 1:
print()
最后一步,让输出更好看——加分隔线,用 emoji 图标标注字段:
> 美化 format_output,用分隔线框起来,每个字段前面加对应的 emoji 图标,比如🌐 IP 地址、📍 国家、🏙 城市、📡 ISP、🕐 时区。
最终输出风格变成这样:
──────────────────────────────────────────────────── 🌐 IP 地址 : 8.8.8.8 📍 国家/地区 : 美国 🏛 省份/州 : Virginia 🏙 城市 : Ashburn 📡 ISP : Google LLC 🏢 组织 : Google LLC 🔢 ASN : AS15169 Google LLC 🕐 时区 : America/New_York 🗺 坐标 : 39.03, -77.5 ────────────────────────────────────────────────────
四轮对话,从零到完整工具,耗时不到 4 分钟。
#!/usr/bin/env python3
"""IP 归属地查询工具 - 查询 IP 地址的地理位置、ISP 等信息"""
import sys
import json
import argparse
import urllib.request
import urllib.error
API_URL = "http://ip-api.com/json/{}"
FIELDS = "status,message,country,regionName,city,isp,org,as,query,timezone,lat,lon"
def query_ip(ip: str, lang: str = "zh-CN"):
params = f"?lang={lang}&fields={FIELDS}"
url = API_URL.format(ip) + params
req = urllib.request.Request(url, headers={"User-Agent": "ipquery/1.0"})
try:
with urllib.request.urlopen(req, timeout=10) as resp:
data = json.loads(resp.read().decode())
if data.get("status") == "fail":
return {"error": data.get("message", "未知错误")}
return data
except urllib.error.URLError as e:
return {"error": f"网络错误: {e.reason}"}
except Exception as e:
return {"error": str(e)}
def format_output(data: dict, raw: bool = False):
if raw:
print(json.dumps(data, ensure_ascii=False, indent=2))
return
if "error" in data:
print(f"❌ {data['error']}")
return
label_width = 16
sep = "─" * 50
mapping = [
("🌐 IP 地址", "query"),
("📍 国家/地区", "country"),
("🏛 省份/州", "regionName"),
("🏙 城市", "city"),
("📡 ISP", "isp"),
("🏢 组织", "org"),
("🔢 ASN", "as"),
("🕐 时区", "timezone"),
("🗺 坐标", lambda d: f"{d.get('lat', '?')}, {d.get('lon', '?')}"
if d.get('lat') else None),
]
print(sep)
for label, key in mapping:
if callable(key):
value = key(data)
else:
value = data.get(key)
if value:
print(f" {label:<{label_width}}: {value}")
print(sep)
def main():
parser = argparse.ArgumentParser(
description="IP 归属地查询工具 - 查询 IP 地址的地理位置、ISP 等信息",
epilog="示例: python ipquery.py 8.8.8.8\n"
" python ipquery.py 1.1.1.1 -r\n"
" python ipquery.py 8.8.8.8 1.1.1.1",
)
parser.add_argument("ips", nargs="*", default=[], help="要查询的 IP 地址")
parser.add_argument("-r", "--raw", action="store_true", help="输出原始 JSON")
parser.add_argument("-l", "--lang", default="zh-CN", choices=["zh-CN", "en"],
help="返回语言 (默认: zh-CN)")
args = parser.parse_args()
targets = args.ips if args.ips else [""]
for i, ip in enumerate(targets):
display = ip or "本机公网 IP"
print(f"🔍 {display}")
result = query_ip(ip, args.lang)
format_output(result, args.raw)
if i < len(targets) - 1:
print()
if __name__ == "__main__":
main()
整个开发过程四轮对话、不到 4 分钟,最终代码 78 行,零依赖、可直接运行。如果手写这个工具,从查 API 文档、写 argparse 到调异常处理,至少得 20 分钟。
OpenCode 的核心价值在于:你只管描述"要什么",代码生成、错误处理、参数校验这些脏活累活它全包了。而且增量修改时它能精准定位要改的代码,不会"牵一发而动全身"。
试试下次遇到"缺个小工具"的需求时别自己敲,打开 OpenCode 说句话就完了。