JAVASCRIPT 离线解析IP地址 幽冥大陆(七十) —东方仙盟练气期

代码

复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>东方仙盟-离线IP 归属地查询</title>
    <style>
        /* 全局修仙暗黑风格 */
        body {
            font-family: "SimSun", "Microsoft YaHei", serif;
            max-width: 800px;
            margin: 20px auto;
            padding: 20px;
            background-color: #0a0a0a; /* 纯黑底色 */
            background-image: url("data:image/svg+xml,%3Csvg width='100' height='100' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 18c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm48 25c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm-43-7c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm63 31c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM34 90c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm56-76c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM12 86c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm28-65c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm23-11c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-6 60c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm29 22c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zM32 63c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm57-13c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-9-21c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM60 91c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM35 41c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM12 60c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2z' fill='%238b4513' fill-opacity='0.05' fill-rule='evenodd'/%3E%3C/svg%3E");
            color: #e0d8c0; /* 米黄色文字 */
            line-height: 1.8;
        }

        /* 修仙风格标题 */
        h1 {
            color: #d4af37; /* 烫金颜色 */
            text-align: center;
            font-size: 30px;
            text-shadow: 0 0 10px rgba(212, 175, 55, 0.5);
            border-bottom: 2px solid #8b4513;
            padding-bottom: 20px;
            margin-bottom: 30px;
            letter-spacing: 3px;
        }

        h3 {
            color: #c2b280; /* 浅金色 */
            font-size: 18px;
            margin-bottom: 10px;
        }

        /* 容器样式 - 古风卷轴/石板 */
        .container {
            margin-bottom: 15px;
            padding: 10px;
            border: 1px solid #332b18;
            border-radius: 8px;
            background-color: rgba(20, 20, 15, 0.8);
            box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
        }

        /* 文件选择框 */
        input[type="file"] {
            margin: 10px 0;
            padding: 8px;
            background-color: #1a1a16;
            border: 1px solid #443a20;
            color: #e0d8c0;
            border-radius: 4px;
        }

        /* IP输入框 */
        input[type="text"] {
            padding: 10px 15px;
            width: 300px;
            margin-right: 10px;
            background-color: #1a1a16;
            border: 1px solid #443a20;
            color: #e0d8c0;
            border-radius: 4px;
            font-size: 16px;
        }

        /* 按钮样式 - 古风按钮 */
        button {
            padding: 10px 25px;
            cursor: pointer;
            background-color: #8b4513; /* 红棕色/檀木色 */
            color: #fff8e1;
            border: 1px solid #d4af37;
            border-radius: 4px;
            font-size: 16px;
            letter-spacing: 1px;
            transition: all 0.3s ease;
        }

        button:hover {
            background-color: #a0522d;
            box-shadow: 0 0 10px rgba(212, 175, 55, 0.3);
            transform: translateY(-2px);
        }

        button:disabled {
            background-color: #333;
            border-color: #555;
            color: #888;
            cursor: not-allowed;
            transform: none;
            box-shadow: none;
        }

        /* 结果展示区 - 修仙卷轴风格 */
        #result {
            margin-top: 10px;
            padding: 15px;
            border: 1px solid #443a20;
            border-radius: 6px;
            min-height: 60px;
            white-space: pre-wrap;
            background-color: rgba(15, 15, 10, 0.9);
            color: #e8e0c8;
            font-size: 16px;
            box-shadow: inset 0 0 10px rgba(139, 69, 19, 0.2);
        }

        /* 提示文字 */
        .tip {
            color: #998866;
            font-size: 14px;
            margin-top: 8px;
            font-style: italic;
        }

        /* 底部标语 */
        .footer {
            margin-top: 40px;
            padding-top: 20px;
            border-top: 1px solid #332b18;
            text-align: center;
            color: #d4af37;
            font-size: 18px;
            letter-spacing: 4px;
            text-shadow: 0 0 8px rgba(212, 175, 55, 0.3);
        }
    </style>
</head>
<body>
    <h1>东方仙盟-离线IP 归属地查询</h1>
    
    <div class="container">
        第一步:选择本地的   IP 仙库文件
        <input type="file" id="fileInput" accept=".dat" />
       
    </div>

    <div class="container">
        <h3>第二步:输入要查询的 IP 地址</h3>
        <input type="text" id="ipInput" placeholder="例如:8.8.8.8、114.114.114.114" value="8.8.8.8" />
        <button id="queryBtn" disabled>解析IP归属地</button>
        <div id="result"> 仙库文件,再输入 IP 查询</div>
    </div>

    <!-- 底部标语 -->
    <div class="footer">东方仙盟万众一心,共创星河 4900+资源</div>

    <script>
        // GBK 编码转 UTF-8 工具(前端无原生 GBK 支持,需内置编码表)
        class GBK2UTF8 {
            static gbkToUtf8(buffer) {
                const uint8Array = new Uint8Array(buffer);
                let str = '';
                let i = 0;
                while (i < uint8Array.length) {
                    const byte = uint8Array[i];
                    if (byte < 0x80) {
                        // 单字节
                        str += String.fromCharCode(byte);
                        i++;
                    } else {
                        // 双字节(GBK)
                        if (i + 1 >= uint8Array.length) break;
                        const byte2 = uint8Array[i + 1];
                        const code = (byte << 8) | byte2;
                        // 简化版 GBK 转 UTF-8(覆盖常用字符,完整版需引入完整编码表)
                        str += this._gbkCodeToUnicode(code) || '?';
                        i += 2;
                    }
                }
                return str;
            }

            static _gbkCodeToUnicode(code) {
                // 简化映射(完整版需引入 GBK 编码表,可自行扩展)
                const gbkMap = {
                    0xB0A1: '啊', 0xB0A2: '阿', 0xB0A3: '埃', 0xB0A4: '挨', 0xB0A5: '哎',
                    0xD3C9: '网', 0xB1BE: '北', 0xB9C5: '国', 0xC1AC: '联', 0xCDF8: '通',
                    0xB9FA: '中', 0xD1C7: '华', 0xD6DD: '省', 0xB7F0: '市', 0xC7F8: '区',
                    0xB5D8: '地', 0xC7BF: '山', 0xBAFE: '东', 0xD1B0: '西', 0xC4CF: '南',
                    0xB1B1: '北', 0xD4AD: '京', 0xC9CF: '上', 0xD0D0: '海', 0xB9E3: '广',
                    0xD6DD: '东', 0xCBCE: '江', 0xCEF7: '苏', 0xD3C3: '浙', 0xB2FD: '江'
                };
                return gbkMap[code] || String.fromCharCode(code);
            }
        }

        // 核心 QQWry 解析类
        class QQWry {
            constructor(arrayBuffer) {
                this.dataView = new DataView(arrayBuffer);
                this.buffer = arrayBuffer;
                // 解析文件头(前8字节)
                this.firstIndex = this.read4Byte(0);
                this.lastIndex = this.read4Byte(4);
                this.totalIndex = Math.floor((this.lastIndex - this.firstIndex) / 7) + 1;
            }

            // 读取4字节无符号整数(小端序)
            read4Byte(offset) {
                return this.dataView.getUint32(offset, true);
            }

            // 读取3字节无符号整数(小端序)
            read3Byte(offset) {
                return (
                    this.dataView.getUint8(offset) +
                    (this.dataView.getUint8(offset + 1) << 8) +
                    (this.dataView.getUint8(offset + 2) << 16)
                );
            }

            // 读取字符串(GBK 转 UTF-8)
            readString(offset) {
                const uint8Array = new Uint8Array(this.buffer);
                let strBuffer = [];
                while (offset < uint8Array.length) {
                    const byte = uint8Array[offset];
                    if (byte === 0x00) break;
                    strBuffer.push(byte);
                    offset++;
                }
                // 转 UTF-8
                return GBK2UTF8.gbkToUtf8(strBuffer);
            }

            // IP 转无符号整数
            ipToInt(ip) {
                const parts = ip.split('.').map(Number);
                if (parts.length !== 4 || parts.some(p => isNaN(p) || p < 0 || p > 255)) {
                    throw new Error('IP 格式错误');
                }
                return (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3];
            }

            // 二分查找 IP 归属地
            query(ip) {
                try {
                    const ipInt = this.ipToInt(ip);
                    let left = 0;
                    let right = this.totalIndex - 1;
                    let result = { ip, country: '未知', area: '未知' };

                    while (left <= right) {
                        const mid = Math.floor((left + right) / 2);
                        const indexOffset = this.firstIndex + mid * 7;
                        const curIp = this.read4Byte(indexOffset);

                        if (curIp > ipInt) {
                            right = mid - 1;
                        } else {
                            left = mid + 1;
                            const dataPtr = this.read3Byte(indexOffset + 4);
                            // 读取国家
                            let country = this.readString(dataPtr + 4);
                            // 读取地区(计算地区起始偏移)
                            let areaOffset = dataPtr + 4 + country.length + 1;
                            let area = this.readString(areaOffset);

                            // 过滤无效字符
                            country = country.replace(/CZ88.NET/g, '未知').trim();
                            area = area.replace(/CZ88.NET/g, '未知').trim();

                            result = { ip, country, area };
                            if (curIp === ipInt) break;
                        }
                    }
                    return result;
                } catch (e) {
                    return { ip, country: '解析失败', area: e.message };
                }
            }
        }

        // 页面交互逻辑
        let qqwryInstance = null;
        const fileInput = document.getElementById('fileInput');
        const ipInput = document.getElementById('ipInput');
        const queryBtn = document.getElementById('queryBtn');
        const result = document.getElementById('result');

        // 监听文件选择
        fileInput.addEventListener('change', async (e) => {
            const file = e.target.files[0];
            if (!file) return;

            try {
                result.textContent = '正在加载并解析 QQWry.dat 仙库文件...';
                // 读取文件为 ArrayBuffer
                const arrayBuffer = await file.arrayBuffer();
                // 初始化解析实例
                qqwryInstance = new QQWry(arrayBuffer);
                queryBtn.disabled = false;
                result.textContent = `仙库文件加载成功!共解析到 ${qqwryInstance.totalIndex} 条 IP 记录,请输入 IP 查询`;
            } catch (err) {
                result.textContent = `仙库文件解析失败:${err.message}`;
                queryBtn.disabled = true;
            }
        });

        // 监听查询按钮
        queryBtn.addEventListener('click', () => {
            const ip = ipInput.value.trim();
            if (!ip) {
                result.textContent = '请输入有效的 IP 地址';
                return;
            }
            if (!qqwryInstance) {
                result.textContent = '请先选择 QQWry.dat 仙库文件';
                return;
            }

            // 查询 IP
            const res = qqwryInstance.query(ip);
            result.textContent = `IP: ${res.ip}\n归属地: ${res.country} ${res.area}`;
        });

        // 支持回车查询
        ipInput.addEventListener('keydown', (e) => {
            if (e.key === 'Enter' && !queryBtn.disabled) {
                queryBtn.click();
            }
        });
    </script>
</body>
</html>

离线IP解析:优势、场景与入门指南

在数字化时代,IP地址作为网络世界的"身份标识",其归属地解析功能广泛应用于网络安全、用户分析、服务优化等多个领域。IP解析分为在线与离线两种模式,其中离线解析凭借独特的技术特性,在特定场景下展现出不可替代的价值。本文将详细拆解离线解析IP的核心好处、典型应用场景,并为初学者提供一套可落地的入门方案。

一、离线解析IP的核心好处:摆脱网络束缚,兼顾效率与安全

相较于依赖云端接口的在线解析,离线解析IP通过本地部署解析库(如纯真QQWry.dat、GeoLite2等)完成解析工作,其优势集中体现在"自主可控"与"高效稳定"两大维度。

1. 脱离网络依赖,实现全天候可用

离线解析的核心优势在于无需连接外网,仅通过本地存储的IP库即可完成解析。这意味着即便处于无网络环境(如内网系统、封闭实验室),或遭遇网络中断、云端服务故障,IP解析功能仍能正常运行。对于依赖IP解析的核心业务(如内网安全审计、离线设备管理)而言,这种"全天候可用"的特性直接保障了业务连续性。

2. 解析速度更快,降低系统延迟

在线解析需要经过"发送请求---云端处理---返回结果"的网络传输流程,而离线解析直接调用本地资源,解析耗时通常以毫秒级计算,远快于在线模式。对于高并发场景(如大型网站的用户访问日志分析、即时通讯工具的IP定位),离线解析能有效降低系统整体延迟,提升用户体验与数据处理效率。

3. 数据自主可控,保障信息安全

在线解析需将待解析的IP地址发送至第三方云端服务,存在数据泄露的风险------尤其对于敏感IP数据(如企业内网IP、用户隐私相关IP),这种传输过程可能违反数据安全法规(如《个人信息保护法》)。离线解析全程在本地完成,数据不经过任何第三方服务器,从源头规避了数据泄露风险,同时企业可自主掌控IP库的更新与管理,符合合规要求。

4. 降低长期成本,避免接口限制

多数在线IP解析服务存在免费额度限制,超出额度后需支付高额费用,且部分服务会限制解析频率与并发量。离线解析仅需一次性获取IP库(部分库免费开源,商业库按版本收费),后续无额外使用成本,尤其适合需要大量解析IP的场景(如日志分析、批量设备排查),能显著降低长期运营成本。

二、离线解析IP的典型应用场景:覆盖多行业核心需求

离线解析IP的特性使其在多个行业中成为刚需,尤其适合对网络、安全、效率有严格要求的场景,以下是最具代表性的应用方向:

1. 内网安全与运维管理

企业内网、政府机关内网、金融机构核心网络等封闭网络环境,通常禁止连接外网,但需对内部设备的IP地址进行归属地解析(如定位异常访问设备的物理位置、排查内网故障节点)。离线解析无需外网权限,可直接部署于内网服务器,帮助运维人员快速定位问题,保障内网安全。例如,某大型国企通过离线解析IP,实现了对全国分支机构内网设备的统一管理,及时发现并阻断了多起异常访问行为。

2. 日志分析与数据挖掘

网站、APP、服务器会产生大量包含IP地址的访问日志,分析师需通过IP解析获取用户的地域分布、运营商信息,从而优化产品策略(如根据地域调整内容推送、优化服务器节点布局)。离线解析能支持批量日志的本地快速处理,避免了在线解析的频率限制与网络延迟,尤其适合TB级以上日志的离线分析场景。例如,电商平台通过离线解析用户访问日志,精准定位高活跃用户地域,针对性开展区域促销活动,提升转化率。

3. 嵌入式设备与物联网(IoT)

智能设备(如监控摄像头、工业传感器、智能家居设备)多部署于无外网或弱网络环境,且需通过IP解析实现设备定位与远程管理。离线解析可轻量化部署于嵌入式系统中,占用资源少、响应快,能满足设备的实时解析需求。例如,安防企业在户外监控设备中集成离线IP解析功能,可快速定位设备安装位置,方便后期维护与故障排查。

4. 应急响应与灾难恢复

在网络攻击、自然灾害(如地震、洪水)导致外网中断的应急场景下,救援人员、安全团队需通过IP解析定位受影响设备、排查攻击源头。离线解析无需依赖外部服务,可在应急环境中快速部署,为应急响应提供关键支持。例如,某地区遭遇网络中断后,当地公安部门通过离线解析内网IP,快速定位了被困人员携带的智能设备位置,为救援工作争取了时间。

5. 合规性要求严格的行业(金融、医疗、政务)

金融机构、医疗机构、政务部门对数据隐私与合规性要求极高,禁止敏感数据外流。离线解析全程本地处理,无需向第三方传输IP数据,符合《网络安全法》《个人信息保护法》等法规要求,是这类行业的首选解析方案。例如,银行通过离线解析用户登录IP,验证用户地域与预留地址的一致性,提升账户安全等级。

三、初学者入门离线IP解析:从基础到实践的完整路径

离线IP解析的技术门槛不高,初学者可按照"理论认知---工具选择---实践操作---进阶优化"的步骤逐步掌握,核心是理解IP库的结构与解析原理,结合实际需求选择合适的工具与语言。

第一步:夯实基础认知,理解核心原理

在动手实践前,需先掌握两个核心概念:

  1. IP库的本质:IP库是包含"IP地址段---归属地信息"映射关系的数据库文件(如QQWry.dat为二进制文件,GeoLite2为CSV/MMDB文件),其核心逻辑是将待解析的IP地址与库中的IP段进行匹配,从而获取对应的地域、运营商等信息。

  2. 解析的核心流程:① 加载本地IP库到内存;② 将待解析的IP地址转换为整数格式(便于比较);③ 通过二分查找等算法,在IP库中匹配对应的IP段;④ 提取该IP段对应的归属地信息,完成解析。

建议初学者先了解IPv4地址的编码规则(如点分十进制与整数的转换)、二分查找算法的基本原理,这是理解解析过程的关键。

第二步:选择合适的工具与IP库,降低入门难度

初学者无需从零开发解析逻辑,可借助成熟的工具与开源库快速上手,推荐组合如下:

  1. 入门级IP库选择:
  • 纯真QQWry.dat:免费开源,覆盖国内IP地址的精度高,包含地域与运营商信息,适合国内场景;缺点是更新需手动下载最新版本。

  • MaxMind GeoLite2:免费开源,支持全球IP解析,包含国家、地区、城市等多维度信息,适合国际场景;提供CSV、MMDB等多种格式,更新频率高。

  1. 解析工具/语言选择:
  • 零基础入门:优先使用Python,搭配第三方库(如qqwry-py3、geoip2),代码简洁易懂,无需深入理解底层解析逻辑。

  • 前端场景:可使用JavaScript加载QQWry.dat,通过本地解析库(如本文之前优化的解析类)实现浏览器端离线解析。

  • 后端/嵌入式场景:可使用Java(搭配ip2region库)、C/C++(直接操作二进制IP库),适合高性能、轻量化需求。

第三步:动手实践,完成第一个离线解析案例

以"Python+QQWry.dat"为例,新手可按以下步骤完成第一个离线解析案例,全程不超过30分钟:

  1. 准备工作:① 下载最新版QQWry.dat文件(从纯真IP官网或开源仓库获取);② 安装Python环境,通过pip安装解析库:pip install qqwry-py3

  2. 编写核心代码(仅需5行):

复制代码

from qqwry import QQwry # 初始化解析器,加载本地QQWry.dat文件 qqwry = QQwry() qqwry.load_file("qqwry.dat") # 填写本地IP库文件路径 # 解析IP归属地 result = qqwry.query("183.225.207.45") print(f"IP归属地:{result[0]},运营商:{result[1]}")

  1. 运行测试:执行代码后,若输出"IP归属地:浙江省杭州市,运营商:中国移动",则说明解析成功。

通过这个案例,初学者可直观理解离线解析的流程,后续可尝试解析批量IP、结合日志文件进行分析,逐步熟悉功能。

第四步:进阶优化,满足复杂场景需求

掌握基础解析后,可针对实际需求进行进阶优化,重点关注以下方向:

  1. IP库更新:建立定期更新机制(如每月自动下载最新QQWry.dat/GeoLite2库),避免因IP库过时导致解析误差。

  2. 性能优化:对于高并发场景,可将IP库预加载到内存、使用缓存机制(如Redis)存储高频解析结果,提升解析效率。

  3. 多库融合:结合多个IP库的优势(如用GeoLite2解析国际IP,用QQWry.dat解析国内IP),提升解析精度。

  4. 自定义功能:开发IP段批量匹配、解析结果导出(如Excel/CSV)等功能,适配具体业务需求。

四、总结:离线解析IP的价值与学习建议

离线解析IP并非在线解析的"替代品",而是在"无网络、高安全、高并发、强合规"场景下的"最优解"。其核心价值在于自主可控与高效稳定,随着数据安全需求的提升,离线解析在企业级应用中的占比正逐步提高。

对于初学者而言,无需畏惧底层技术细节,可从"工具+案例"入手,先通过Python等简单语言完成基础解析,再逐步深入理解IP库结构与解析算法。建议在实践中结合具体场景(如日志分析、内网管理)进行练习,通过解决实际问题巩固知识------离线解析的学习重点的是"实用",而非复杂的理论推导。

随着技术积累,你还可以尝试开发自定义解析工具、优化IP库匹配算法,甚至参与开源IP库的维护,逐步从"使用者"转变为"实践者"。在数字化时代,掌握离线IP解析这一基础技能,将为你的技术简历增添一份独特的竞争力。

阿雪技术观

在科技发展浪潮中,我们不妨积极投身技术共享。不满足于做受益者,更要主动担当贡献者。无论是分享代码、撰写技术博客,还是参与开源项目维护改进,每一个微小举动都可能蕴含推动技术进步的巨大能量。东方仙盟是汇聚力量的天地,我们携手在此探索硅基生命,为科技进步添砖加瓦。

Hey folks, in this wild tech - driven world, why not dive headfirst into the whole tech - sharing scene? Don't just be the one reaping all the benefits; step up and be a contributor too. Whether you're tossing out your code snippets, hammering out some tech blogs, or getting your hands dirty with maintaining and sprucing up open - source projects, every little thing you do might just end up being a massive force that pushes tech forward. And guess what? The Eastern FairyAlliance is this awesome place where we all come together. We're gonna team up and explore the whole silicon - based life thing, and in the process, we'll be fueling the growth of technology

相关推荐
程序员阿鹏2 小时前
分布式事务管理
java·开发语言·分布式
爱学大树锯2 小时前
【594 · 字符串查找 II】
java·开发语言·算法
zhixingheyi_tian2 小时前
Yarn 之 run job
java·开发语言·前端
指尖跳动的光2 小时前
如何减少项目里面if-else
前端·javascript
2501_916766542 小时前
【Java】代理模式---静态代理与动态代理
java·开发语言·代理模式
写代码的【黑咖啡】2 小时前
Python常用数据处理库全解析
开发语言·python
AAA阿giao2 小时前
用 LangChain 玩转大模型:从零搭建你的第一个 AI 应用
javascript·人工智能·langchain·llm·ai编程·ai开发
缺点内向2 小时前
Java:轻松实现 Excel 文档属性添加
java·开发语言·excel
刺客xs2 小时前
c++多线程 线程池的实现
开发语言·c++