在纯真IP数据库(qqwry.dat)里查询IP地址归属地,Python 3版

本工具已上传到pypi:https://pypi.python.org/pypi/qqwry-py3  (此页面也包括使用文档)

 

Python 3.4及以上版本自带了pip工具,执行此命令既可安装:

pip install qqwry-py3

 

特点:

1、for Python 3.0+。

2、有两套实现供选择。有一个查找速度更快,但加载稍慢一点、占用内存稍多一点。

3、在i3 3.6GHz,Python 3.5上查询速度达10.2万次/秒。

4、仅import array和bisect这两个Python自带的模块。

5、经过较严格测试。

6、提供一个从纯真网络(cz88.net)更新qqwry.dat的小工具。

 

附一份源码:

# coding=utf-8
#
# for Python 3.0+
# 来自 https://pypi.python.org/pypi/qqwry-py3
# 版本:2015-12-16
#
# 用法
# ============
# from qqwry import QQwry
# q = QQwry()
# q.load_file('qqwry.dat', loadindex=False)
# result = q.lookup('8.8.8.8')
# 
# 
# 解释q.load_file(filename, loadindex=False)函数
# --------------
# 加载qqwry.dat文件。成功返回True,失败返回False。
# 
# 参数filename可以是qqwry.dat的文件名(str类型),也可以是bytes类型的文件内容。
# 
# 当参数loadindex=False时(默认):
# 程序行为:把整个文件读入内存,从中搜索。
# 加载速度:很快
# 进程内存:较少,12.6 MB
# 查询速度:较慢,3.9 万次/秒
# 使用建议:适合桌面程序、中小型网站。
# 
# 当参数loadindex=True时:
# 程序行为:把整个文件读入内存。额外加载索引,把索引读入更快的数据结构。
# 加载速度:慢,因为要额外加载索引。
# 进程内存:较多,17.7 MB
# 查询速度:较快,10.2 万次/秒
# 使用建议:适合高负载服务器。
# 
# (以上是在i3 3.6GHz, Win10, Python 3.5.0rc2 64bit,qqwry.dat 8.85MB时的数据)
# 
# 
# 解释q.lookup('8.8.8.8')函数
# --------------
# 找到则返回一个含有两个字符串的元组,如:('国家', '省份')
# 没有找到结果,则返回一个None
# 
# 
# 解释q.get_lastone()函数
# --------------
# 返回最后一条数据,最后一条通常为数据的版本号
# 没有数据则返回None
# 
# 
# 解释q.is_loaded()函数
# --------------
# q对象是否已加载数据,返回True或False
# 
# 
# 解释q.clear()函数
# --------------
# 清空已加载的qqwry.dat
# 再次调用load_file时不必执行q.clear()

import array
import bisect

__all__ = ('QQwry')
    
def int3(data, offset):
    return data[offset] + (data[offset+1] << 8) + \
           (data[offset+2] << 16)

def int4(data, offset):
    return data[offset] + (data[offset+1] << 8) + \
           (data[offset+2] << 16) + (data[offset+3] << 24)

class QQwry:
    def __init__(self):
        self.clear()
        
    def clear(self):
        self.idx1 = None
        self.idx2 = None
        self.idxo = None
        
        self.data = None
        self.index_begin = -1
        self.index_end = -1
        self.index_count = -1
        
        self.__fun = None
        
    def load_file(self, filename, loadindex=False):
        self.clear()
        
        if type(filename) == bytes:
            self.data = buffer = filename
            filename = 'memory data'
        elif type(filename) == str:
            # read file
            try:
                with open(filename, 'br') as f:
                    self.data = buffer = f.read()
            except Exception as e:
                print('打开、读取文件时出错:', e)
                self.clean()
                return False
            
            if self.data == None:
                print('%s load failed' % filename)
                self.clear()
                return False
        else:
            self.clean()
            return False
        
        if len(buffer) < 8:
            print('%s load failed, file only %d bytes' % 
                  (filename, len(buffer))
                  )
            self.clear()
            return False            
        
        # index range
        index_begin = int4(buffer, 0)
        index_end = int4(buffer, 4)
        if index_begin > index_end or \
           (index_end - index_begin) % 7 != 0 or \
           index_end + 7 > len(buffer):
            print('%s index error' % filename)
            self.clear()
            return False
        
        self.index_begin = index_begin
        self.index_end = index_end
        self.index_count = (index_end - index_begin) // 7 + 1
        
        if not loadindex:
            print('%s %s bytes, %d segments. without index.' %
                  (filename, format(len(buffer),','), self.index_count)
                 )
            self.__fun = self.__raw_search
            return True

        # load index
        self.idx1 = array.array('L')
        self.idx2 = array.array('L')
        self.idxo = array.array('L')
        
        try:
            for i in range(self.index_count):
                ip_begin = int4(buffer, index_begin + i*7)
                offset = int3(buffer, index_begin + i*7 + 4)
                
                # load ip_end
                ip_end = int4(buffer, offset)
                
                self.idx1.append(ip_begin)
                self.idx2.append(ip_end)
                self.idxo.append(offset+4)
        except:
            print('%s load index error' % filename)
            self.clear()
            return False

        print('%s %s bytes, %d segments. with index.' % 
              (filename, format(len(buffer),','), len(self.idx1))
               )
        self.__fun = self.__index_search
        return True
        
    def __get_addr(self, offset):        
        # mode 0x01, full jump
        mode = self.data[offset]
        if mode == 1:
            offset = int3(self.data, offset+1)
            mode = self.data[offset]
        
        # country
        if mode == 2:
            off1 = int3(self.data, offset+1)
            c = self.data[off1:self.data.index(b'\x00', off1)]
            offset += 4
        else:
            c = self.data[offset:self.data.index(b'\x00', offset)]
            offset += len(c) + 1

        # province
        if self.data[offset] == 2:
            offset = int3(self.data, offset+1)
        p = self.data[offset:self.data.index(b'\x00', offset)]
        
        return c.decode('gb18030', errors='replace'), \
               p.decode('gb18030', errors='replace')
            
    def lookup(self, ip_str):
        try:
            ip = sum(256**j*int(i) for j,i 
                      in enumerate(ip_str.strip().split('.')[::-1]))
            return self.__fun(ip)
        except:
            return None
        
    def __raw_search(self, ip):
        l = 0
        r = self.index_count
        
        while r - l > 1:
            m = (l + r) // 2
            offset = self.index_begin + m * 7
            new_ip = int4(self.data, offset)
    
            if ip < new_ip:
                r = m
            else:
                l = m
        
        offset = self.index_begin + 7 * l
        ip_begin = int4(self.data, offset)
        
        offset = int3(self.data, offset+4)
        ip_end = int4(self.data, offset)
        
        if ip_begin <= ip <= ip_end:
            return self.__get_addr(offset+4)
        else:
            return None
    
    def __index_search(self, ip):
        posi = bisect.bisect_right(self.idx1, ip) - 1
        
        if posi >= 0 and self.idx1[posi] <= ip <= self.idx2[posi]:
            return self.__get_addr(self.idxo[posi])
        else:
            return None
        
    def is_loaded(self):
        return self.__fun != None
        
    def get_lastone(self):
        try:
            offset = int3(self.data, self.index_end+4)
            return self.__get_addr(offset+4)
        except:
            return None

if __name__ == '__main__':
    import sys
    if len(sys.argv) > 1:
        fn = 'qqwry.dat'
        q = QQwry()
        q.load_file(fn)
        
        for ipstr in sys.argv[1:]:
            s = q.lookup(ipstr)
            print('%s\n%s' % (ipstr, s))
    else:
        print('请以查询ip作为参数运行')
        

 

你可能感兴趣的:(在纯真IP数据库(qqwry.dat)里查询IP地址归属地,Python 3版)