当前时区为 UTC + 8 小时



发表新帖 回复这个主题  [ 10 篇帖子 ] 
作者 内容
1 楼 
 文章标题 : QQWry.py ---- 读取纯真IP数据库的模块 (2008年 04月 29日 更新)
帖子发表于 : 2008-04-26 20:16 
头像

注册: 2006-07-02 11:16
帖子: 12522
地址: 廣州
送出感谢: 0 次
接收感谢: 8
一个练习, 那个文件的数据结构真的很囧...

代码:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# filename: QQWry.py
'''QQWry 模块, 提供读取纯真IP数据库的数据的功能.

纯真数据库格式参考 http://lumaqq.linuxsir.org/article/qqwry_format_detail.html
作者 AutumnCat. 最后修改在 2008年 04月 29日
本程序遵循 GNU GENERAL PUBLIC LICENSE Version 2 (http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt)
'''
from struct import unpack, pack
import sys, _socket, mmap

def _ip2ulong(ip):
    '''点分十进制 -> unsigned long
    '''
    return unpack('>L', _socket.inet_aton(ip))[0]

def _ulong2ip(ip):
    '''unsigned long -> 点分十进制
    '''
    return _socket.inet_ntoa(pack('>L', ip))

class QQWryBase:
    '''QQWryBase 类, 提供基本查找功能.

    注意返回的国家和地区信息都是未解码的字符串, 对于简体版数据库应为GB编码, 对于繁体版则应为BIG5编码.
    '''
    class ipInfo(tuple):
        '''方便输出 ip 信息的类.

        ipInfo((sip, eip, country, area)) -> ipInfo object
        '''
        def __str__(self):
            '''str(x)
            '''
            return str(self[0]).ljust(16) + ' - ' + str(self[1]).rjust(16) + '    ' + self[2] + self[3]

        def normalize(self):
            '''转化ip地址成点分十进制.
            '''
            return QQWryBase.ipInfo((_ulong2ip(self[0]), _ulong2ip(self[1]), self[2], self[3]))

    def __init__(self, dbfile):
        '''QQWryBase(dbfile) -> QQWryBase object

        dbfile 是数据库文件的 file 对象.
        '''
        self.f = dbfile
        self.f.seek(0)
        self.indexBaseOffset = unpack('<L', self.f.read(4))[0] #索引区基址
        self.Count = (unpack('<L', self.f.read(4))[0] - self.indexBaseOffset) / 7 # 索引数-1

    def Lookup(self, ip):
        '''x.Lookup(ip) -> (sip, eip, country, area) 查找 ip 所对应的位置.

        ip, sip, eip 是点分十进制记录的 ip 字符串.
        sip, eip 分别是 ip 所在 ip 段的起始 ip 与结束 ip.
        '''
        return self.nLookup(_ip2ulong(ip))

    def nLookup(self, ip):
        '''x.nLookup(ip) -> (sip, eip, country, area) 查找 ip 所对应的位置.

        ip 是 unsigned long 型 ip 地址.
        其它同 x.Lookup(ip).
        '''
        si = 0
        ei = self.Count
        if ip < self._readIndex(si)[0]:
            raise StandardError('IP NOT Found.')
        elif ip >= self._readIndex(ei)[0]:
            si = ei
        else: # keep si <= ip < ei
            while (si + 1) < ei:
                mi = (si + ei) // 2
                if self._readIndex(mi)[0] <= ip:
                    si = mi
                else:
                    ei = mi
        ipinfo = self[si]
        if ip > ipinfo[1]:
            raise StandardError('IP NOT Found.')
        else:
            return ipinfo

    def __str__(self):
        '''str(x)
        '''
        tmp = []
        tmp.append('RecCount:')
        tmp.append(str(len(self)))
        tmp.append('\nVersion:')
        tmp.extend(self[self.Count].normalize()[2:])
        return ''.join(tmp)

    def __len__(self):
        '''len(x)
        '''
        return self.Count + 1

    def __getitem__(self, key):
        '''x[key]

        若 key 为整数, 则返回第key条记录(从0算起, 注意与 x.nLookup(ip) 不一样).
        若 key 为点分十进制的 ip 描述串, 同 x.Lookup(key).
        '''
        if type(key) == type(0):
            if (key >=0) and (key <= self.Count):
                index = self._readIndex(key)
                sip = index[0]
                self.f.seek(index[1])
                eip = unpack('<L', self.f.read(4))[0]
                (country,area) = self._readRec()
                return QQWryBase.ipInfo((sip, eip, country, area))
            else:
                raise KeyError('INDEX OUT OF RANGE.')
        elif type(key) == type(''):
            try:
                return self.Lookup(key).normalize()
            except StandardError, e:
                if e.message == 'IP NOT Found.':
                    raise KeyError('IP NOT Found.')
                else:
                    raise e
        else:
            raise TypeError('WRONG KEY TYPE.')

    def __iter__(self):
        '''返回迭代器(生成器).
        '''
        for i in range(0, len(self)):
            yield self[i]

    def _read3ByteOffset(self):
        '''_read3ByteOffset() -> unsigned long 从文件 f 读入长度为3字节的偏移.
        '''
        return unpack('<L', self.f.read(3) + '\x00')[0]

    def _readCStr(self):
        '''x._readCStr() -> string 读 '\0' 结尾的字符串.
        '''
        if self.f.tell() == 0:
            return 'Unknown'
        tmp = []
        ch = self.f.read(1)
        while ch != '\x00':
            tmp.append(ch)
            ch = self.f.read(1)
        return ''.join(tmp)

    def _readIndex(self, n):
        '''x._readIndex(n) -> (ip ,offset) 读取第n条索引.
        '''
        self.f.seek(self.indexBaseOffset + 7 * n)
        return unpack('<LL', self.f.read(7) + '\x00')

    def _readRec(self, onlyOne=False):
        '''x._readRec() -> (country, area) 读取记录的信息.
        '''
        mode = unpack('B', self.f.read(1))[0]
        if mode == 0x01:
            rp = self._read3ByteOffset()
            bp = self.f.tell()
            self.f.seek(rp)
            result = self._readRec(onlyOne)
            self.f.seek(bp)
            return result
        elif mode == 0x02:
            rp = self._read3ByteOffset()
            bp = self.f.tell()
            self.f.seek(rp)
            result = self._readRec(True)
            self.f.seek(bp)
            if not onlyOne:
                result.append(self._readRec(True)[0])
            return result
        else: # string
            self.f.seek(-1,1)
            result = [self._readCStr()]
            if not onlyOne:
                result.append(self._readRec(True)[0])
            return result
    pass # End of class QQWryBase

class QQWry(QQWryBase):
    '''QQWry 类.
    '''
    def __init__(self, filename='QQWry.Dat'):
        '''QQWry(filename) -> QQWry object

        filename 是数据库文件名.
        '''
        f = open(filename, 'rb')
        QQWryBase.__init__(self, f)

class MQQWry(QQWryBase):
    '''MQQWry 类.

    将数据库放到内存的 QQWry 类.
    查询速度大约快两倍.
    '''
    def __init__(self, filename='QQWry.Dat', dbfile=None):
        '''MQQWry(filename[,dbfile]) -> MQQWry object

        filename 是数据库文件名.
        也可以直接提供 dbfile 文件对象. 此时 filename 被忽略.
        '''
        if dbfile == None:
            dbf = open(filename, 'rb')
        else:
            dbf = dbfile
        bp = dbf.tell()
        dbf.seek(0)
        QQWryBase.__init__(self, mmap.mmap(dbf.fileno(), 0, access = 1))
        dbf.seek(bp)

    def _readCStr(self):
        '''x._readCStr() -> string 读 '\0' 结尾的字符串.
        '''
        pstart = self.f.tell()
        if pstart == 0:
            return 'Unknown'
        else:
            pend = self.f.find('\x00', pstart)
            if pend < 0:
                raise StandardError('Fail To Read CStr.')
            else:
                self.f.seek(pend + 1)
                return self.f[pstart:pend]

    def _readIndex(self, n):
        '''x._readIndex(n) -> (ip ,offset) 读取第n条索引.
        '''
        startp = self.indexBaseOffset + 7 * n
        return unpack('<LL', self.f[startp:startp + 7] + '\x00')

if __name__ == '__main__':
    try:
        Q = MQQWry() # 数据库文件名为 ./QQWry.Dat
        print Q
        for i in sys.argv[1:]:
            print Q[i]
        #遍历示例代码
        # for i in Q:
        #     print i
    except StandardError, e:
        if e.message != '':
            print e.message
        else:
            raise e
    finally:
        pass


使用前先将 QQWry.Dat 放在当前目录..
代码:
$ ./QQWry.py 59.36.101.19 | enconv
RecCount:339271
Version:純真網絡2008年4月20日IP數據
59.36.92.82      -     59.36.111.89    廣東省東莞市電信


_________________
^_^ ~~~
要理解递归,首先要理解递归。

地球人都知道,理论上,理论跟实际是没有差别的,但实际上,理论跟实际的差别是相当大滴。


最后由 BigSnake.NET 编辑于 2008-04-29 19:34,总共编辑了 18 次

页首
 用户资料  
 
2 楼 
 文章标题 :
帖子发表于 : 2008-04-26 20:24 
头像

注册: 2005-07-02 14:41
帖子: 4133
系统: Ubuntu 14.04 (Kylin)
送出感谢: 53
接收感谢: 11
貌似ee想搞但没搞成的?

++


_________________
https://weakish.github.io


页首
 用户资料  
 
3 楼 
 文章标题 :
帖子发表于 : 2008-04-26 20:28 
头像

注册: 2006-07-02 11:16
帖子: 12522
地址: 廣州
送出感谢: 0 次
接收感谢: 8
只是简单的二分查索引+递归找信息, 不适合密集的查询...
代码:
$ time for i in `seq 0 255` ; do ./QQWry.py 218.61.$i.1; done > /dev/null

real    0m4.313s
user    0m3.233s
sys     0m1.040s


数据库格式详解
http://lumaqq.linuxsir.org/article/qqwr ... etail.html


_________________
^_^ ~~~
要理解递归,首先要理解递归。

地球人都知道,理论上,理论跟实际是没有差别的,但实际上,理论跟实际的差别是相当大滴。


页首
 用户资料  
 
4 楼 
 文章标题 :
帖子发表于 : 2008-04-26 21:39 
头像

注册: 2005-03-30 0:27
帖子: 3294
送出感谢: 0 次
接收感谢: 2
赞猫猫


_________________
跃过无数的时间断层,只为了在


页首
 用户资料  
 
5 楼 
 文章标题 :
帖子发表于 : 2008-04-27 14:59 
头像

注册: 2006-07-02 11:16
帖子: 12522
地址: 廣州
送出感谢: 0 次
接收感谢: 8
性能测试
代码:
#!/usr/bin/python2.5

import timeit

n = 500000
t = timeit.Timer(r'''
for i in testset:
    Q[i]
    pass
''',
r'''
import QQWry, random
Q = QQWry.QQWry()
testset = []
for i in range(0,''' + str(n) + '''):
    testset.append(str(random.randint(0,0xffffffffL)))
''')

print n/t.timeit(1)

平均每秒查找 3045.48577629 条记录(E6550, 2G DDR800)
而用 MQQWry (使用 mmap), 查询速度达到 7300 条/秒 以上..


_________________
^_^ ~~~
要理解递归,首先要理解递归。

地球人都知道,理论上,理论跟实际是没有差别的,但实际上,理论跟实际的差别是相当大滴。


页首
 用户资料  
 
6 楼 
 文章标题 :
帖子发表于 : 2008-04-27 16:39 
头像

注册: 2006-07-02 11:16
帖子: 12522
地址: 廣州
送出感谢: 0 次
接收感谢: 8
最后再改进了一下读入... 程序更新在一楼.
MQQWry 速度上万了..

代码:
autumncat@autumncat-host:~/qqwry
$ ./test.py
3638.10264545
11293.7421223
Exit status: 0
autumncat@autumncat-host:~/qqwry
$ ./test.py
3526.25304998
11836.5370236
Exit status: 0


测试用的程序
代码:
#!/usr/bin/python2.5

import timeit
import QQWry, random
n = 200000
testset = []
for i in range(0,n):
    testset.append(str(random.randint(0,0xffffffffL)))

Q = QQWry.QQWry()
MQ = QQWry.MQQWry()

testtxt = '''
for i in testset:
    Q[i]
'''

prep1 = '''
import __main__
Q = __main__.Q
testset = __main__.testset
'''
prep2 = '''
import __main__
Q = __main__.MQ
testset = __main__.testset
'''
t1 = timeit.Timer(testtxt, prep1)
t2 = timeit.Timer(testtxt, prep2)

print n/t1.timeit(1)
print n/t2.timeit(1)


_________________
^_^ ~~~
要理解递归,首先要理解递归。

地球人都知道,理论上,理论跟实际是没有差别的,但实际上,理论跟实际的差别是相当大滴。


页首
 用户资料  
 
7 楼 
 文章标题 :
帖子发表于 : 2008-04-27 16:47 
头像

注册: 2005-08-14 21:55
帖子: 58428
地址: 长沙
送出感谢: 4
接收感谢: 272
和我的有何不同啊。?
至少我的不要enconv了。 :lol: 球猫又搞半落子工程。enconv应该集成进脚本啊。py怎么也不会快的。


_________________
● 鸣学


页首
 用户资料  
 
8 楼 
 文章标题 :
帖子发表于 : 2008-04-27 16:53 
头像

注册: 2006-07-02 11:16
帖子: 12522
地址: 廣州
送出感谢: 0 次
接收感谢: 8
eexpress 写道:
和我的有何不同啊。?
至少我的不要enconv了。 :lol:


encoding 的问题有两个原因:

1. 我还没找到识别编码的标准方法
2. 我觉得 QQWry 只负责读出信息就够了, 编码什么的应该给类使用者处理

PS: .. ee你那个东西函数很长.. 看不懂..


_________________
^_^ ~~~
要理解递归,首先要理解递归。

地球人都知道,理论上,理论跟实际是没有差别的,但实际上,理论跟实际的差别是相当大滴。


页首
 用户资料  
 
9 楼 
 文章标题 :
帖子发表于 : 2008-06-02 10:53 

注册: 2007-12-18 10:18
帖子: 57
地址: 武汉
送出感谢: 0 次
接收感谢: 0 次
python 哎!是说怎么有点看不懂语法呢!先备份,有时间研究个php的出来


页首
 用户资料  
 
10 楼 
 文章标题 :
帖子发表于 : 2008-06-06 20:50 
头像

注册: 2007-05-08 16:26
帖子: 2058
地址: 火星内核某分子内某原子核内
系统: arch
送出感谢: 20
接收感谢: 6
爽啊,收藏


_________________
笔记本 :
F208S : gentoo
A460P i3G D6 : UBUNTU + WIN7
UN43D1 : UBUNTU + WIN7
1000人超级QQ群 LINUX + WIN : 31465544 或 18210387


页首
 用户资料  
 
显示帖子 :  排序  
发表新帖 回复这个主题  [ 10 篇帖子 ] 

当前时区为 UTC + 8 小时


在线用户

正在浏览此版面的用户:没有注册用户 和 1 位游客


不能 在这个版面发表主题
不能 在这个版面回复主题
不能 在这个版面编辑帖子
不能 在这个版面删除帖子
不能 在这个版面提交附件

前往 :  
本站点为公益性站点,用于推广开源自由软件,由 DiaHosting VPSBudgetVM VPS 提供服务。
我们认为:软件应可免费取得,软件工具在各种语言环境下皆可使用,且不会有任何功能上的差异;
人们应有定制和修改软件的自由,且方式不受限制,只要他们自认为合适。

Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
简体中文语系由 王笑宇 翻译