已复制
全屏展示
复制代码

纯真IP地址库setup.exe程序提取qqwry.dat文件并解析txt纯文本


· 3 min read

一. qqwry.dat文件提取

setup.exe是Windows运行程序,如果有Windows程序的话,直接点击安装,然后从安装目录里面获取 qqwry.dat 文件即可。假如我没有Windows系统怎么办,可以安装innoextract命令行工具提取 qqwry.dat。

下载地址 https://update.cz88.net/help?id=free,需要关注微信公众号,然后获取最新的下载地址,下载后会得到类似 su9Izakq-2023-08-23.zip 的压缩包,解压后就有 setup.exe 文件了。

  • 安装工具 innoextract
# macos
brew install innoextract

# centos
sudo yum install innoextract

# ubuntu
sudo apt-get install innoextract
  • 解压setup.ext
$ innoextract setup.exe
Extracting "纯真IP地址数据库" - setup data version 6.1.0 (unicode)
 - "app/ip.exe"
 - "app/qqwry.dat"
 - "app/纯真网络.url"
 - "app/License.txt"
 - "app/说明.txt"
Done.

二. qqwry.dat文件转换为txt

  • qqwry.dat 的格式如下

+----------+
|  文件头  |  (8字节)
+----------+
|  记录区  | (不定长)
+----------+
|  索引区  | (大小由文件头决定)
+----------+

文件头:4字节开始索引偏移值+4字节结尾索引偏移值

记录区: 每条IP记录格式 ==> IP地址[国家信息][地区信息]

索引区: 每条索引记录格式 ==> 4字节起始IP地址 + 3字节指向IP记录的偏移值

  • 使用Python3解析qqwry.dat的代码如下
  • 输入文件 qqwry.dat,输出文件 qqwry.txt
import socket
import codecs
import mmap
from struct import pack, unpack


class QQWry:

    def __init__(self, path):
        self.path = path
        self.db = None
        self.open_db()
        self.idx_start, self.idx_end = self._read_idx()
        self.total = (self.idx_end - self.idx_start) / 7 + 1

    @staticmethod
    def decode_str(old):
        try:
            return old.decode('gbk')
        except:
            print("decode string error: " + old)
            return 'Invalid'

    def open_db(self):
        self.db = open(self.path, 'rb')
        self.db = mmap.mmap(self.db.fileno(), 0, access=1)

    def _read_idx(self):
        self.db.seek(0)
        start = unpack('I', self.db.read(4))[0]
        end = unpack('I', self.db.read(4))[0]
        return start, end

    def version(self):
        ip_end_offset = self.read_offset(self.idx_end + 4)
        a_raw, b_raw = self.read_record(ip_end_offset + 4)
        return self.decode_str(a_raw + b_raw)

    def read_offset(self, off, seek=True):
        if seek:
            self.db.seek(off)
        buf = self.db.read(3)
        return unpack('I', buf + b'\0')[0]

    def read_record(self, offset):
        self.db.seek(offset)
        flag = ord(self.db.read(1))
        if flag == 1:
            buf = self.db.read(3)
            a_offset = unpack('I', buf + b'\0')[0]
            a_raw = self.read_string(a_offset)
            a_flag = self.get_flag(a_offset)
            if a_flag == 2:
                b_raw = self.read_string(a_offset + 4)
            else:
                b_raw = self.read_string(a_offset + len(a_raw) + 1)
        elif flag == 2:
            buf = self.db.read(3)
            a_offset = unpack('I', buf + b'\0')[0]
            a_raw = self.read_string(a_offset)
            b_raw = self.read_string(offset + 4)
        else:
            a_raw = self.read_string(offset)
            b_raw = self.read_string(offset + len(a_raw) + 1)
        return a_raw, b_raw

    def read_ip(self, off, seek=True):
        if seek:
            self.db.seek(off)
        buf = self.db.read(4)
        return unpack('I', buf)[0]

    def read_string(self, offset):
        if offset == 0:
            return 'N/A1'
        flag = self.get_flag(offset)
        if flag == 0:
            return 'N/A2'
        elif flag == 2:
            offset = self.read_offset(offset + 1)
            return self.read_string(offset)
        self.db.seek(offset)
        raw_string = b''
        while True:
            x = self.db.read(1)
            if x == b'\0':
                break
            raw_string += x
        return raw_string

    def get_flag(self, offset):
        self.db.seek(offset)
        c = self.db.read(1)
        if not c:
            return 0
        return ord(c)

    def output(self, output_file):
        fp = codecs.open(output_file, 'w', 'utf8')
        fp.write(self.version() + '\n')
        idx = self.idx_start
        while idx <= self.idx_end:
            ip_int = self.read_ip(idx)
            ip_start = socket.inet_ntoa(pack('!I', ip_int))
            ip_end_offset = self.read_offset(idx + 4)
            ip_int = self.read_ip(ip_end_offset)
            ip_end = socket.inet_ntoa(pack('!I', ip_int))
            a_raw, b_raw = self.read_record(ip_end_offset + 4)
            a_info = self.decode_str(a_raw)
            b_info = self.decode_str(b_raw)
            fp.write(u'%15s\t%15s\t%s,%s\n' % (ip_start, ip_end, a_info, b_info))
            idx += 7
        fp.close()


if __name__ == '__main__':
    qqwry = QQWry("./qqwry.dat")
    qqwry.output("./qqwry.txt")

三. 使用在线IP查询

我使用纯真IP库、geoip2、asn IP段归属,部署了一个在线查询IP归属地的服务,你可以通过它查询指定IP地址的归属地信息,如果不指定任何IP地址,会返回你自己的公网IP地址以及这个IP地址的详细归属地。

访问博主的在线工具网站查询IP归属地:https://goldtools.cn/network/iplocation

也可以直接请求API获取数据

# 查询指定IP地址的归属地
$ curl ip.goldtools.cn/1.51.160.0
{
  "ip" : "1.51.160.0",
  "city" : "山东省潍坊市",
  "region" : "教育网/潍坊学院"
}

# 查询自己的IP地址,以及归属地
$ curl ip.goldtools.cn
{
  "ip" : "43.230.68.120",
  "city" : "北京市",
  "region" : "联通"
}

🔗

文章推荐