Appearance
怎么写一个显示 IP 归属地的浏览器插件?Manifest V3 实战(JavaScript)
排查线上问题的时候,经常要跟 IP 打交道:日志里一个来源 IP 飘过来,想立刻知道它是哪个省、哪家运营商;本地出口 IP 换没换,也得开网页查。每次都要复制、开新标签、粘进查询站,一天重复十几次。Chrome 商店里其实有不少现成的 IP 查询插件,但要么带广告,要么数据源不透明,要么权限要一大堆。干脆自己写一个:Manifest V3 扩展,右键选中 IP 一键查归属地,点图标还能看本机出口 IP,全本地代码、无广告、数据来自自己指定的接口。
先说明一个边界:浏览器插件拿不到目标网站的服务器 IP(浏览器安全模型不允许扩展做 DNS 解析),能可靠拿到的是「选中文本里的 IP」和「本机出口 IP」。这篇就实现这两个能力,不写做不到的功能。
一、插件要做什么
- 本机出口 IP 查询:点工具栏图标,弹出面板,显示当前出口 IP 的归属地(国家/省市/运营商);
- 选中文本查 IP:网页里选中一个 IP,右键菜单点「查询 IP 归属地」,系统通知直接弹结果;
- 批量查询:弹出面板里粘一堆 IP,一次查完列表展示。
三种功能的数据都来自 https://ip9.com.cn/get?ip=xxx(不传参返回当前出口 IP),免费版 60 次/分钟对个人使用绰绰有余。
二、Manifest V3 项目结构
一个扩展 = 一个文件夹,四个文件:
ip-lookup-extension/
├── manifest.json # 扩展清单
├── background.js # 后台 Service Worker:右键菜单 + 通知
├── popup.html # 点图标的弹出面板
└── popup.js # 面板逻辑manifest.json
json
{
"manifest_version": 3,
"name": "IP 归属地速查",
"version": "1.0.0",
"description": "选中页面里的 IP 一键查归属地,查看本机出口 IP",
"permissions": ["contextMenus", "notifications", "storage"],
"host_permissions": ["https://ip9.com.cn/*"],
"background": { "service_worker": "background.js" },
"action": {
"default_popup": "popup.html",
"default_title": "IP 归属地速查"
}
}host_permissions 只放了 ip9.com.cn,这是 MV3 的规范做法:权限最小化,不给「任意网站」权限,商店审核更容易过。
background.js(右键菜单 + 通知)
javascript
const API = "https://ip9.com.cn/get";
async function lookup(ip) {
const r = await fetch(`${API}?ip=${encodeURIComponent(ip)}`, { cache: "no-store" });
const j = await r.json();
if (j.ret !== 200) throw new Error("查询失败: " + ip);
return j.data;
}
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: "lookup-ip",
title: "查询 IP 归属地",
contexts: ["selection"]
});
});
chrome.contextMenus.onClicked.addListener(async (info) => {
const ip = (info.selectionText || "").trim();
if (!/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) return; // 只处理 IPv4 文本
try {
const d = await lookup(ip);
chrome.notifications.create({
type: "basic",
iconUrl: "icons/icon128.png",
title: d.ip + " 归属地",
message: `${d.country} ${d.prov} ${d.city}\n运营商:${d.isp || "未知"}`
});
chrome.storage.session.set({ lastResult: d }); // 供面板展示
} catch (e) {
chrome.notifications.create({
type: "basic", iconUrl: "icons/icon128.png",
title: "查询失败", message: String(e)
});
}
});MV3 的 Service Worker 不是常驻的,所以不缓存任何内存状态,查询结果放 chrome.storage.session,面板打开时再读。
popup.html + popup.js(出口 IP + 批量查询)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<style>
body { width: 320px; font: 13px/1.6 sans-serif; margin: 12px; }
#out { background: #f6f8fa; border-radius: 6px; padding: 8px 12px; margin-bottom: 10px; }
textarea { width: 100%; height: 60px; box-sizing: border-box; }
button { margin-top: 6px; width: 100%; padding: 5px; }
</style>
</head>
<body>
<div id="out">正在查询本机出口 IP…</div>
<textarea id="ips" placeholder="每行一个 IP,支持批量查询"></textarea>
<button id="go">批量查询</button>
<div id="list"></div>
<script src="popup.js"></script>
</body>
</html>javascript
const API = "https://ip9.com.cn/get";
// 打开面板即查本机出口 IP(不传参数)
fetch(API, { cache: "no-store" })
.then(r => r.json())
.then(j => {
const d = j.data;
document.getElementById("out").textContent =
`出口 IP:${d.ip}\n归属地:${d.country} ${d.prov} ${d.city} 运营商:${d.isp}`;
});
// 批量查询:逐行查,结果追加到列表
document.getElementById("go").addEventListener("click", async () => {
const list = document.getElementById("list");
list.innerHTML = "";
const ips = document.getElementById("ips").value
.trim().split(/\s+/).filter(s => /^\d{1,3}(\.\d{1,3}){3}$/.test(s));
for (const ip of ips) {
const row = document.createElement("div");
try {
const j = await (await fetch(`${API}?ip=${ip}`, { cache: "no-store" })).json();
const d = j.data;
row.textContent = `${d.ip} — ${d.country} ${d.prov} ${d.city}(${d.isp})`;
} catch (e) {
row.textContent = `${ip} — 查询失败`;
}
list.appendChild(row);
await new Promise(s => setTimeout(s, 1100)); // 限速 1.1s,配合 60 次/分钟额度
}
});注意两处 MV3 硬性要求:html 里不能有内联 script(所以单独 popup.js),不能引用任何远程 JS(代码全本地),不然扩展无法加载。
三、加载与发布
- 本地调试:Chrome 打开
chrome://extensions→ 打开「开发者模式」→「加载已解压的扩展程序」选文件夹。图标文件(icon16/48/128.png)没有的话,先用任意 128px PNG 顶替,或直接在 manifest 里删掉 icons 字段也能加载; - 测试:随便打开一个页面,选中文中的 IP → 右键 → 「查询 IP 归属地」;点扩展图标看出口 IP、粘一批 IP 批量查。免费接口 60 次/分钟,批量查询脚本已限速 1.1 秒/次;
- 上架商店:Chrome Web Store 发布要过审核,重点准备两项——manifest 里声明
host_permissions的原因(只查接口、不读页面数据),以及隐私政策页(说明只把 IP 发给查询接口、不留存)。权限越少越容易过。
四、落地注意事项
- 只能查 IPv4 文本:上面的正则只匹配点分十进制 IPv4;要支持 IPv6 得加一段正则(接口本身支持 IPv6),这里不展开;
- 别在插件里做「页面所有 IP」:扩展没有 DNS 解析能力,想显示「当前网站的服务器 IP」做不到,别被这类需求带偏,做到选中即查就已经覆盖绝大多数用法;
- 缓存结果:同一 IP 高频查看时,先查 storage 再查接口,能省额度也更快;批量的限速不能省,不然会被接口限流(ret=429);
- 数据源可换:API 地址在 background.js 和 popup.js 顶部各有一处常量,想换别的服务商改一处字符串即可。
总结
一个 MV3 扩展总共四个文件、一百多行代码,权限最小化、数据源可控、没广告,点图标查出口 IP、选中 IP 右键即查、批量粘贴一起查,日常排查够用了。调试流程:chrome://extensions 加载已解压文件夹 → 右键验证 → 满意了再整理图标和隐私政策上架。查询接口直接用免费版 https://ip9.com.cn/get?ip=xxx(不传参查本机出口 IP,60 次/分钟,个人插件完全够用),要机房/家庭 IP 类型判断再考虑 VIP。官网:https://www.ip9.com.cn