diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..7e1bab0 --- /dev/null +++ b/README.rst @@ -0,0 +1,195 @@ +用于在qqwry.dat里查找IP地址归属地,另提供一个从纯真网络更新qqwry.dat的小工具。 + +已上传到PyPI,执行此命令即可安装:``pip install qqwry-py3`` + +特点 +====== + +1. for Python 3.0+。 + +2. 提供两套实现供选择。有一个查找速度更快,但加载慢、占用内存多。 + +3. 在i3 3.6GHz,Python 3.6上查询速度达18.0万次/秒。 + +4. 提供一个从纯真网络(cz88.net)更新qqwry.dat的小工具,用法见本文最后一部分。 + +用法 +====== + + >>> from qqwry import QQwry + >>> q = QQwry() + >>> q.load_file('qqwry.dat') + >>> q.lookup('8.8.8.8') + ('美国', '加利福尼亚州圣克拉拉县山景市谷歌公司DNS服务器') + +解释q.load_file(filename, loadindex=False)函数 +---------------------------------------------- + +| +| 加载qqwry.dat文件。成功返回True,失败返回False。 +| 参数filename可以是qqwry.dat的文件名(str类型),也可以是bytes类型的文件内容。 +| +| 当参数loadindex=False时(默认参数): +| 程序行为:把整个文件读入内存,从中搜索 +| 加载速度:很快,0.004 秒 +| 进程内存:较少,16.9 MB +| 查询速度:较慢,5.3 万次/秒 +| 使用建议:适合桌面程序、大中小型网站 +| +| 当参数loadindex=True时: +| 程序行为:把整个文件读入内存。额外加载索引,把索引读入更快的数据结构 +| 加载速度:★★★非常慢,因为要额外加载索引,0.78 秒★★★ +| 进程内存:较多,22.0 MB +| 查询速度:较快,18.0 万次/秒 +| 使用建议:仅适合高负载服务器 +| +| (以上是在i3 3.6GHz, Win10, Python 3.6.2 64bit,qqwry.dat 8.86MB时的数据) + +解释q.lookup('8.8.8.8')函数 +--------------------------- + +| +| 找到则返回一个含有两个字符串的元组,如:('国家', '省份') +| 没有找到结果,则返回一个None + +解释q.clear()函数 +----------------- + +| +| 清空已加载的qqwry.dat +| 再次调用load_file时不必执行q.clear() + +解释q.is_loaded()函数 +--------------------- + +q对象是否已加载数据,返回True或False + +解释q.get_lastone()函数 +----------------------- + +| +| 返回最后一条数据,最后一条通常为数据的版本号 +| 没有数据则返回一个None + + >>> q.get_lastone() + ('纯真网络', '2020年9月30日IP数据') + +从纯真网络(cz88.net)更新qqwry.dat的小工具 +========================================= + + >>> from qqwry import updateQQwry + >>> ret = updateQQwry(filename) + +| +| 当参数filename是str类型时,表示要保存的文件名。 +| 成功后返回一个正整数,是文件的字节数;失败则返回一个负整数。 +| +| 当参数filename是None时,函数直接返回qqwry.dat的文件内容(一个bytes对象)。 +| 成功后返回一个bytes对象;失败则返回一个负整数。这里要判断一下返回值的类型是bytes还是int。 + + +| +| 负整数表示的错误: +| -1:下载copywrite.rar时出错 +| -2:解析copywrite.rar时出错 +| -3:下载qqwry.rar时出错 +| -4:qqwry.rar文件大小不符合copywrite.rar的数据 +| -5:解压缩qqwry.rar时出错 +| -6:保存到最终文件时出错 + + +Features +======== + +1. for Python 3.0+. + +2. Provide two sets of implementations for selection. One finds faster, but loads slowly and takes up more memory. + +3. The query speed on i3 3.6GHz and Python 3.6 is 180,000 times per second. + +4. Provide a small tool to update qqwry.dat from Chunzhen Network (cz88.net), see the last part of this article for usage. + +usage +====== + + >>> from qqwry import QQwry + >>> q = QQwry() + >>> q.load_file('qqwry.dat') + >>> q.lookup('8.8.8.8') + ('United States','Google DNS server in Mountain View, Santa Clara County, California') + +Explain the q.load_file(filename, loadindex=False) function +----------------------------------------------------------- + +| +| Load the qqwry.dat file. Return True on success, False on failure. +| The parameter filename can be the file name of qqwry.dat (str type), or the file content of bytes type. +| +| When the parameter loadindex=False (default parameter): +| Program behavior: read the entire file into memory, search from it +| Loading speed: very fast, 0.004 seconds +| Process memory: less, 16.9 MB +| Query speed: slower, 53,000 times per second +| Suggestions for use: suitable for desktop programs, large, medium and small websites +| +| When the parameter loadindex=True: +| Program behavior: Read the entire file into memory. Load an additional index, read the index into a faster data structure +| Loading speed: ★★★Very slow, because of the additional loading index, 0.78 seconds★★★ +| Process memory: more, 22.0 MB +| Query speed: faster, 180,000 times per second +| Recommendations for use: only suitable for high-load servers +| +| (The above is the data when i3 3.6GHz, Win10, Python 3.6.2 64bit, qqwry.dat 8.86MB) + +Explain the q.lookup('8.8.8.8') function +---------------------------------------- + +| +| If found, return a tuple containing two strings, such as: ('country','province') +| If no result is found, a None is returned + +Explain the q.clear() function +------------------------------ + +| +| Clear the loaded qqwry.dat +| It is not necessary to execute q.clear() when calling load_file again + +Explain the q.is_loaded() function +---------------------------------- + +Whether the q object has loaded data, return True or False + +Explain the q.get_lastone() function +------------------------------------ + +| +| Return the last piece of data, the last piece is usually the version number of the data +| Return None if there is no data + + >>> q.get_lastone() + ('纯真网络', '2020年9月30日IP数据') + +Update the widget of qqwry.dat from Chunzhen Network (cz88.net) +=============================================================== + + >>> from qqwry import updateQQwry + >>> ret = updateQQwry(filename) + +| +| When the parameter filename is of type str, it indicates the name of the file to be saved. +| Upon success, it returns a positive integer, which is the number of bytes in the file; +| Upon failure, it returns a negative integer. +| +| When the parameter filename is None, the function directly returns the content of the qqwry.dat file (a bytes object). +| Return a bytes object on success; return a negative integer on failure. Here to determine whether the type of the return value is bytes or int. + + +| +| Errors represented by negative integers: +| -1: An error occurred while downloading copywrite.rar +| -2: Error when parsing copywrite.rar +| -3: An error occurred when downloading qqwry.rar +| -4: qqwry.rarfile size does not match the data of copywrite.rar +| -5: Error when decompressing qqwry.rar +| -6: An error occurred while saving to the final file diff --git a/cz88update.py b/cz88update.py deleted file mode 100644 index fb57198..0000000 --- a/cz88update.py +++ /dev/null @@ -1,106 +0,0 @@ -# coding=utf-8 -# -# 用于从纯真网络(cz88.net)更新qqwry.dat -# for Python 3.0+ -# 来自 https://github.com/animalize/qqwry-python3 -# -# 用法: -# from cz88update import updateQQwry -# result = updateQQwry(filename) #filename为要保存的文件名 -# -# updateQQwry函数返回值 -# 正整数:表示已成功更新,为qqwry.dat的字节数 -# -1:下载copywrite.rar出错 -# -2:解析copywrite.rar出错 -# -3:下载qqwry.rar出错 -# -4:qqwry.rar文件大小不符合copywrite.rar的数据 -# -5:解压缩qqwry.rar出错 -# -6:保存到最终文件时出错 - -import struct -import urllib.request -import zlib - -def updateQQwry(filename): - def get_fetcher(): - # no proxy - proxy = urllib.request.ProxyHandler({}) - # opener - opener = urllib.request.build_opener(proxy) - - def open_url(url): - # request对象 - req = urllib.request.Request(url) - ua = ('Mozilla/5.0 (Windows NT 6.1; rv:38.0)' - ' Gecko/20100101 Firefox/38.0') - req.add_header('User-Agent', ua) - - try: - # r是HTTPResponse对象 - r = opener.open(req, timeout=60) - return r.read() - except Exception as e: - return None - - return open_url - - fetcher = get_fetcher() - - # download copywrite.rar - url = 'http://update.cz88.net/ip/copywrite.rar' - data = fetcher(url) - if not data: - return -1 - - # extract infomation from copywrite.rar - if len(data) <= 24 or data[:4] != b'CZIP': - return -2 - - version, unknown1, size, unknown2, key = \ - struct.unpack_from(' 1: - ret = updateQQwry(sys.argv[1]) - if ret > 0: - print('成功更新到%s,%s字节' % - (sys.argv[1], format(ret, ',')) - ) - else: - print('更新失败,错误代码:%d' % ret) - else: - print('用法:以想要保存的文件名作参数。') diff --git a/qqwry.py b/qqwry.py deleted file mode 100644 index 048e5b1..0000000 --- a/qqwry.py +++ /dev/null @@ -1,222 +0,0 @@ -# coding=utf-8 -# -# for Python 3.0+ -# 来自 https://github.com/animalize/qqwry-python3 -# 版本:2015-09-06a -# -# 用法: -# from qqwry import QQwry -# q = QQwry() -# q.load_file(filename, loadindex=False) -# q.lookup('8.8.8.8') -# -# q.load_file(filename, loadindex=False)函数: -# 参数loadindex为False时,不加载索引,进程耗内存12.6MB,查找稍慢 -# 参数loadindex为True时,加载索引,进程耗内存17.7MB,查找稍快 -# 后者比前者查找更快(3.9万次/秒,10.2万次/秒),但加载文件稍慢 -# 以上是在i3 3.6GHz, Win10, Python 3.5.0rc2 64bit,qqwry.dat 8.85MB时的数据 -# 成功返回True,失败返回False -# -# q.lookup('8.8.8.8')函数: -# 没有找到结果返回一个None -# 找到则返回一个含有两个字符串的元组:('国家', '省份') -# -# q.get_lastone()函数: -# 返回最后一条数据,最后一条通常为数据版本号 -# 没有数据则返回None -# -# q.is_loaded()函数: -# 是否已加载数据,返回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() - - # read file - try: - with open(filename, 'br') as f: - self.data = buffer = f.read() - except Exception as e: - print('打开、读取文件时出错:', e) - return False - - if self.data == None: - print('%s load failed' % filename) - self.clear() - 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) - - return None - - def __index_search(self, ip): - posi = bisect.bisect_right(self.idx1, ip) - - if posi > 0 and self.idx1[posi-1] <= ip <= self.idx2[posi-1]: - return self.__get_addr(self.idxo[posi-1]) - 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作为参数运行') - \ No newline at end of file diff --git a/qqwry/__init__.py b/qqwry/__init__.py new file mode 100644 index 0000000..966d0f7 --- /dev/null +++ b/qqwry/__init__.py @@ -0,0 +1,8 @@ +from .qqwry import QQwry +from .cz88update import updateQQwry + +__version__ = '1.2.1' + +__all__ = ('QQwry', 'updateQQwry') + +__doc__ = 'Document of this module: https://pypi.org/project/qqwry-py3/' \ No newline at end of file diff --git a/qqwry/cz88update.py b/qqwry/cz88update.py new file mode 100644 index 0000000..1c58884 --- /dev/null +++ b/qqwry/cz88update.py @@ -0,0 +1,138 @@ +# coding=utf-8 +# +# 用于从纯真网络(cz88.net)更新qqwry.dat +# for Python 3.0+ +# 来自 https://pypi.python.org/pypi/qqwry-py3 +# +# 用法: +# from cz88update import updateQQwry +# result = updateQQwry(filename) +# +# 当参数filename是str类型时,表示要保存的文件名。 +# 成功后返回一个正整数,是文件的字节数;失败则返回一个负整数。 +# +# 当参数filename是None时,函数直接返回qqwry.dat的文件内容(一个bytes对象)。 +# 成功后返回一个bytes对象;失败则返回一个负整数。这里要判断一下返回值的类型是bytes还是int。 +# +# 负整数表示的错误: +# -1:下载copywrite.rar时出错 +# -2:解析copywrite.rar时出错 +# -3:下载qqwry.rar时出错 +# -4:qqwry.rar文件大小不符合copywrite.rar的数据 +# -5:解压缩qqwry.rar时出错 +# -6:保存到最终文件时出错 + +import struct +import urllib.request +import zlib +import logging +from typing import Union + +__all__ = ('updateQQwry',) + +logger = logging.getLogger(__name__) + +def updateQQwry(filename: Union[str, None]) -> Union[int, bytes]: + '''1.当参数filename是str类型时,表示要保存的文件名。 + 成功后返回一个正整数,是文件的字节数;失败则返回一个负整数。 + + 2.当参数filename是None时,函数直接返回qqwry.dat的文件内容(一个bytes对象)。 + 成功后返回一个bytes对象;失败则返回一个负整数。 + 这里要判断一下返回值的类型是bytes还是int。''' + def get_fetcher(): + # no proxy + proxy = urllib.request.ProxyHandler({}) + # opener + opener = urllib.request.build_opener(proxy) + + def open_url(file_name, url): + # request对象 + headers = { + 'User-Agent': 'Mozilla/3.0 (compatible; Indy Library)', + 'Host': 'update.cz88.net' + } + req = urllib.request.Request(url, headers=headers) + + try: + # r是HTTPResponse对象 + r = opener.open(req, timeout=60) + dat = r.read() + if not dat: + raise Exception('文件大小为零') + return dat + except Exception as e: + logger.error('下载%s时出错: %s' % (file_name, str(e))) + return None + + return open_url + + fetcher = get_fetcher() + + # download copywrite.rar + url = 'http://update.cz88.net/ip/copywrite.rar' + data = fetcher('copywrite.rar', url) + if not data: + return -1 + + # extract infomation from copywrite.rar + if len(data) <= 24 or data[:4] != b'CZIP': + logger.error('解析copywrite.rar时出错') + return -2 + + version, unknown1, size, unknown2, key = \ + struct.unpack_from(' 1: + ret = updateQQwry(sys.argv[1]) + if ret > 0: + print('成功更新到%s,%s字节' % + (sys.argv[1], format(ret, ',')) + ) + else: + print('更新失败,错误代码:%d' % ret) + else: + print('用法:以想要保存的文件名作参数。') diff --git a/qqwry/py.typed b/qqwry/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/qqwry/qqwry.py b/qqwry/qqwry.py new file mode 100644 index 0000000..eb28b87 --- /dev/null +++ b/qqwry/qqwry.py @@ -0,0 +1,273 @@ +# coding=utf-8 +# +# for Python 3.0+ +# 来自 https://pypi.python.org/pypi/qqwry-py3 +# 版本:2020-06-25 +# +# 用法 +# ============ +# from qqwry import QQwry +# q = QQwry() +# q.load_file('qqwry.dat') +# result = q.lookup('8.8.8.8') +# +# +# 解释q.load_file(filename, loadindex=False)函数 +# -------------- +# 加载qqwry.dat文件。成功返回True,失败返回False。 +# +# 参数filename可以是qqwry.dat的文件名(str类型),也可以是bytes类型的文件内容。 +# +# 当参数loadindex=False时(默认参数): +# 程序行为:把整个文件读入内存,从中搜索 +# 加载速度:很快,0.004 秒 +# 进程内存:较少,16.9 MB +# 查询速度:较慢,5.3 万次/秒 +# 使用建议:适合桌面程序、大中小型网站 +# +# 当参数loadindex=True时: +# 程序行为:把整个文件读入内存。额外加载索引,把索引读入更快的数据结构 +# 加载速度:★★★非常慢,因为要额外加载索引,0.78 秒★★★ +# 进程内存:较多,22.0 MB +# 查询速度:较快,18.0 万次/秒 +# 使用建议:仅适合高负载服务器 +# +# (以上是在i3 3.6GHz, Win10, Python 3.6.2 64bit,qqwry.dat 8.86MB时的数据) +# +# +# 解释q.lookup('8.8.8.8')函数 +# -------------- +# 找到则返回一个含有两个字符串的元组,如:('国家', '省份') +# 没有找到结果,则返回一个None +# +# +# 解释q.clear()函数 +# -------------- +# 清空已加载的qqwry.dat +# 再次调用load_file时不必执行q.clear() +# +# +# 解释q.is_loaded()函数 +# -------------- +# q对象是否已加载数据,返回True或False +# +# +# 解释q.get_lastone()函数 +# -------------- +# 返回最后一条数据,最后一条通常为数据的版本号 +# 没有数据则返回一个None + +import array +import bisect +import struct +import socket +import logging +from typing import Tuple, Union + +__all__ = ('QQwry',) + +logger = logging.getLogger(__name__) + +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) -> None: + self.clear() + + def clear(self) -> None: + '''清空加载的数据,再次调用.load_file()时不必执行.clear()。''' + 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: Union[str, bytes], loadindex: bool=False) -> bool: + '''加载qqwry.dat文件。成功返回True,失败返回False。 + 参数filename可以是qqwry.dat的文件名(str类型),也可以是bytes类型的文件内容。''' + 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: + logger.error('%s open failed:%s' % (filename, str(e))) + self.clear() + return False + + if self.data == None: + logger.error('%s load failed' % filename) + self.clear() + return False + else: + self.clear() + return False + + if len(buffer) < 8: + logger.error('%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): + logger.error('%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: + logger.info('%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: + logger.error('%s load index error' % filename) + self.clear() + return False + + logger.info('%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: str) -> Union[Tuple[str, str], None]: + '''查找IP地址的归属地。 + 找到则返回一个含有两个字符串的元组,如:('国家', '省份') + 没有找到结果,则返回一个None。''' + ip = struct.unpack(">I", socket.inet_aton(ip_str.strip()))[0] + + try: + return self.__fun(ip) + except: + if not self.is_loaded(): + logger.error('Error: qqwry.dat not loaded yet.') + else: + raise + + 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) -> bool: + '''是否已加载数据,返回True或False。''' + return self.__fun != None + + def get_lastone(self) -> Union[Tuple[str, str], None]: + '''返回最后一条数据,最后一条通常为数据的版本号。 + 没有数据则返回一个None。 + 如:('纯真网络', '2020年9月30日IP数据')''' + 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作为参数运行') diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..2fcfb8a --- /dev/null +++ b/setup.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +import os +from setuptools import setup + +def read_file(path): + with open(os.path.join(os.path.dirname(__file__), path), encoding='utf-8-sig') as fp: + return fp.read() + +setup( + name='qqwry-py3', + version='1.2.1', + description='Lookup location of IP in qqwry.dat, for Python 3.0+', + long_description=read_file('README.rst'), + long_description_content_type='text/x-rst', + author='Ma Lin', + author_email='malincns@163.com', + url='https://github.com/animalize/qqwry-python3', + license='BSD', + keywords = 'qqwry cz88 纯真 ip归属地', + platforms=['any'], + package_dir={'qqwry': 'qqwry'}, + py_modules=['qqwry.__init__', 'qqwry.qqwry', 'qqwry.cz88update'], + packages=['qqwry'], + package_data={"qqwry": ['py.typed']}, + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3 :: Only', + 'Topic :: Utilities' + ] +)