python访问纯真IP数据库的代码


Posted in Python onMay 19, 2011

核心代码:

#!/usr/bin/env python 
# -*- coding: utf-8 -*- 

from bisect import bisect 

_LIST1, _LIST2 = [], [] 
_INIT = False 

ip2int = lambda ip_str: reduce(lambda a, b: (a << 8) + b, [int(i) for i in ip_str.split('.')]) 

def _init(): 
global _LIST, _INIT 
if not _INIT: 
for l in open('ipdata.txt', 'rb'): 
ip1, ip2 = l.split()[:2] 
addr = ' '.join(l.split()[2:]) 
ip1, ip2 = ip2int(ip1), ip2int(ip2) 
_LIST1.append(ip1) 
_LIST2.append((ip1, ip2, addr)) 
_INIT = True 

def ip_from(ip): 
_init() 
i = ip2int(ip) 
idx = bisect(_LIST1, i) 
assert(idx > 0) 
if len(_LIST1) <= idx: 
return u'unknown ip address %s' % ip 
else: 
frm, to ,addr = _LIST2[idx - 1] 
if frm <= i <= to: 
return addr 
else: 
return u'unknown ip address %s' % ip 

if __name__ == '__main__': 
print ip_from('115.238.54.106') 
print ip_from('220.181.29.160') 
print ip_from('115.238.54.107') 
print ip_from('8.8.8.8')

代码打包下载 http://xiazai.3water.com/201105/yuanma/ipaddress.7z

接下来为大家分享更完美的代码:

#!/usr/bin/env python
# coding: utf-8
 
'''用Python脚本查询纯真IP库
 
QQWry.Dat的格式如下:
 
+----------+
| 文件头 | (8字节)
+----------+
| 记录区 | (不定长)
+----------+
| 索引区 | (大小由文件头决定)
+----------+
 
文件头:4字节开始索引偏移值+4字节结尾索引偏移值
 
记录区: 每条IP记录格式 ==> IP地址[国家信息][地区信息]
 
  对于国家记录,可以有三种表示方式:
 
    字符串形式(IP记录第5字节不等于0x01和0x02的情况),
    重定向模式1(第5字节为0x01),则接下来3字节为国家信息存储地的偏移值
    重定向模式(第5字节为0x02),
 
  对于地区记录,可以有两种表示方式: 字符串形式和重定向
 
  最后一条规则:重定向模式1的国家记录后不能跟地区记录
 
索引区: 每条索引记录格式 ==> 4字节起始IP地址 + 3字节指向IP记录的偏移值
 
  索引区的IP和它指向的记录区一条记录中的IP构成一个IP范围。查询信息是这个
  范围内IP的信息
 
'''
 
import sys
import socket
from struct import pack, unpack
 
class IPInfo(object):
  '''QQWry.Dat数据库查询功能集合
  '''
  def __init__(self, dbname):
    ''' 初始化类,读取数据库内容为一个字符串,
    通过开始8字节确定数据库的索引信息'''
 
    self.dbname = dbname
    # f = file(dbname, 'r')
 
    # Demon注:在Windows下用'r'会有问题,会把\r\n转换成\n
    # 详见http://demon.tw/programming/python-open-mode.html
    # 还有Python文档中不提倡用file函数来打开文件,推荐用open
    f = open(dbname, 'rb')
 
    self.img = f.read()
    f.close()
 
    # QQWry.Dat文件的开始8字节是索引信息,前4字节是开始索引的偏移值,
    # 后4字节是结束索引的偏移值。
    # (self.firstIndex, self.lastIndex) = unpack('II', self.img[:8])
 
    # Demon注:unpack默认使用的endian是和机器有关的
    # Intel x86和AMD64(x86-64)是little-endian
    # Motorola 68000和PowerPC G5是big-endian
    # 而纯真数据库全部采用了little-endian字节序
    # 所以在某些big-endian的机器上原代码会出错
    (self.firstIndex, self.lastIndex) = unpack('<II', self.img[:8])
 
    # 每条索引长7字节,这里得到索引总个数
    self.indexCount = (self.lastIndex - self.firstIndex) / 7 + 1
 
  def getString(self, offset = 0):
    ''' 读取字符串信息,包括"国家"信息和"地区"信息
 
    QQWry.Dat的记录区每条信息都是一个以'\0'结尾的字符串'''
 
    o2 = self.img.find('\0', offset)
    #return self.img[offset:o2]
    # 有可能只有国家信息没有地区信息,
    gb2312_str = self.img[offset:o2]
    try:
      utf8_str = unicode(gb2312_str,'gb2312').encode('utf-8')
    except:
      return '未知'
    return utf8_str
 
  def getLong3(self, offset = 0):
    '''QQWry.Dat中的偏移记录都是3字节,本函数取得3字节的偏移量的常规表示
    QQWry.Dat使用“字符串“存储这些值'''
    s = self.img[offset: offset + 3]
    s += '\0'
    # unpack用一个'I'作为format,后面的字符串必须是4字节
    # return unpack('I', s)[0]
 
    # Demon注:和上面一样,强制使用little-endian
    return unpack('<I', s)[0]
 
  def getAreaAddr(self, offset = 0):
    ''' 通过给出偏移值,取得区域信息字符串,'''
 
    byte = ord(self.img[offset])
    if byte == 1 or byte == 2:
      # 第一个字节为1或者2时,取得2-4字节作为一个偏移量调用自己
      p = self.getLong3(offset + 1)
      return self.getAreaAddr(p)
    else:
      return self.getString(offset)
 
  def getAddr(self, offset, ip = 0):
    img = self.img
    o = offset
    byte = ord(img[o])
 
    if byte == 1:
      # 重定向模式1
      # [IP][0x01][国家和地区信息的绝对偏移地址]
      # 使用接下来的3字节作为偏移量调用字节取得信息
      return self.getAddr(self.getLong3(o + 1))
 
    if byte == 2:
      # 重定向模式2
      # [IP][0x02][国家信息的绝对偏移][地区信息字符串]
      # 使用国家信息偏移量调用自己取得字符串信息
      cArea = self.getAreaAddr(self.getLong3(o + 1))
      o += 4
      # 跳过前4字节取字符串作为地区信息
      aArea = self.getAreaAddr(o)
      return (cArea, aArea)
 
    if byte != 1 and byte != 2:
      # 最简单的IP记录形式,[IP][国家信息][地区信息]
      # 重定向模式1有种情况就是偏移量指向包含国家和地区信息两个字符串
      # 即偏移量指向的第一个字节不是1或2,就使用这里的分支
      # 简单地说:取连续取两个字符串!
 
      cArea = self.getString(o)
      #o += 2*len(cArea) + 1
      # 我们已经修改cArea为utf-8字符编码了,len取得的长度会有变,
      # 用下面方法得到offset
 
      o = self.img.find('\0',o) + 1
      aArea = self.getString(o)
      if aArea == "?":
        aArea = "电信"
      if aArea == "信":
        aArea = ""
      if aArea == "[":
        aArea = "联通"
      return (cArea, aArea)
 
  def find(self, ip, l, r):
    ''' 使用二分法查找网络字节编码的IP地址的索引记录'''
    if r - l <= 1:
      return l
 
    m = (l + r) / 2
    o = self.firstIndex + m * 7
    #new_ip = unpack('I', self.img[o: o+4])[0]
 
    # Demon注:和上面一样,强制使用little-endian
    new_ip = unpack('<I', self.img[o: o+4])[0]
 
    if ip <= new_ip:
      return self.find(ip, l, m)
    else:
      return self.find(ip, m, r)
 
  def getIPAddr(self, ip):
    ''' 调用其他函数,取得信息!'''
    # 使用网络字节编码IP地址
    ip = unpack('!I', socket.inet_aton(ip))[0]
    # 使用 self.find 函数查找ip的索引偏移
    i = self.find(ip, 0, self.indexCount - 1)
    # 得到索引记录
    o = self.firstIndex + i * 7
    # 索引记录格式是: 前4字节IP信息+3字节指向IP记录信息的偏移量
    # 这里就是使用后3字节作为偏移量得到其常规表示(QQWry.Dat用字符串表示值)
    o2 = self.getLong3(o + 4)
    # IP记录偏移值+4可以丢弃前4字节的IP地址信息。
    (c, a) = self.getAddr(o2 + 4)
    return (c, a)
 
  def output(self, first, last):
    for i in range(first, last):
      o = self.firstIndex + i * 7
      ip = socket.inet_ntoa(pack('!I', unpack('I', self.img[o:o+4])[0]))
      offset = self.getLong3(o + 4)
      (c, a) = self.getAddr(offset + 4)
      print "%s %d %s/%s" % (ip, offset, c, a)
def getIP(ip):
  import os
  _localDir=os.path.dirname(__file__)
  _curpath=os.path.normpath(os.path.join(os.getcwd(),_localDir))
  curpath=_curpath
  i = IPInfo(curpath+'/qqwry.dat')
  (c, a) = i.getIPAddr(ip)
  return c+a
def main():
  import os
  _localDir=os.path.dirname(__file__)
  _curpath=os.path.normpath(os.path.join(os.getcwd(),_localDir))
  curpath=_curpath
  i = IPInfo(curpath+'/qqwry.dat')
  if os.path.exists(sys.argv[1]):
    for line in open(sys.argv[1],"r").readlines():
      line = line.replace("\r","").replace("\n","")
      (c, a) = i.getIPAddr(line)
      # Demon注:如果是在Windows命令行中运行把编码转回gb2312以避免乱码
      if sys.platform == 'win32':
        c = unicode(c, 'utf-8').encode('gb2312')
        a = unicode(a, 'utf-8').encode('gb2312')
      print '%s %s/%s' % (line, c, a)
  else:
    (c, a) = i.getIPAddr(sys.argv[1])
    # Demon注:如果是在Windows命令行中运行把编码转回gb2312以避免乱码
    if sys.platform == 'win32':
      c = unicode(c, 'utf-8').encode('gb2312')
      a = unicode(a, 'utf-8').encode('gb2312')
    print '%s %s/%s' % (sys.argv[1], c, a)
 
if __name__ == '__main__':
  main()

用Python脚本查询纯真IP库QQWry.dat(Demon修改版)

由于要用 Python 读取一个和纯真IP数据库 QQWry.dat 格式差不多的 IPv6 数据库,所以在网上搜索了一下,在 LinuxTOY 看到了一个 Python 脚本,发现有一些小小的问题,于是修改了一下。

#!/usr/bin/env python
# coding: utf-8

# from: http://linuxtoy.org/files/pyip.py
# Blog: http://linuxtoy.org/archives/python-ip.html
# Modified by Demon
# Blog: http://demon.tw/programming/python-qqwry-dat.html

'''用Python脚本查询纯真IP库

QQWry.Dat的格式如下:

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

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

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

  对于国家记录,可以有三种表示方式:

    字符串形式(IP记录第5字节不等于0x01和0x02的情况),
    重定向模式1(第5字节为0x01),则接下来3字节为国家信息存储地的偏移值
    重定向模式(第5字节为0x02),
  
  对于地区记录,可以有两种表示方式: 字符串形式和重定向

  最后一条规则:重定向模式1的国家记录后不能跟地区记录

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

  索引区的IP和它指向的记录区一条记录中的IP构成一个IP范围。查询信息是这个
  范围内IP的信息

'''

import sys
import socket
from struct import pack, unpack

class IPInfo(object):
  '''QQWry.Dat数据库查询功能集合
  '''
  def __init__(self, dbname):
    ''' 初始化类,读取数据库内容为一个字符串,
    通过开始8字节确定数据库的索引信息'''
    
    self.dbname = dbname
    # f = file(dbname, 'r')

    # Demon注:在Windows下用'r'会有问题,会把\r\n转换成\n
    # 详见http://demon.tw/programming/python-open-mode.html
    # 还有Python文档中不提倡用file函数来打开文件,推荐用open
    f = open(dbname, 'rb')

    self.img = f.read()
    f.close()

    # QQWry.Dat文件的开始8字节是索引信息,前4字节是开始索引的偏移值,
    # 后4字节是结束索引的偏移值。
    # (self.firstIndex, self.lastIndex) = unpack('II', self.img[:8])

    # Demon注:unpack默认使用的endian是和机器有关的
    # Intel x86和AMD64(x86-64)是little-endian
    # Motorola 68000和PowerPC G5是big-endian
    # 而纯真数据库全部采用了little-endian字节序
    # 所以在某些big-endian的机器上原代码会出错
    (self.firstIndex, self.lastIndex) = unpack('<II', self.img[:8])

    # 每条索引长7字节,这里得到索引总个数
    self.indexCount = (self.lastIndex - self.firstIndex) / 7 + 1
  
  def getString(self, offset = 0):
    ''' 读取字符串信息,包括"国家"信息和"地区"信息

    QQWry.Dat的记录区每条信息都是一个以'\0'结尾的字符串'''
    
    o2 = self.img.find('\0', offset)
    #return self.img[offset:o2]
    # 有可能只有国家信息没有地区信息,
    gb2312_str = self.img[offset:o2]
    try:
      utf8_str = unicode(gb2312_str,'gb2312').encode('utf-8')
    except:
      return '未知'
    return utf8_str

  def getLong3(self, offset = 0):
    '''QQWry.Dat中的偏移记录都是3字节,本函数取得3字节的偏移量的常规表示
    QQWry.Dat使用“字符串“存储这些值'''
    s = self.img[offset: offset + 3]
    s += '\0'
    # unpack用一个'I'作为format,后面的字符串必须是4字节
    # return unpack('I', s)[0]

    # Demon注:和上面一样,强制使用little-endian
    return unpack('<I', s)[0]

  def getAreaAddr(self, offset = 0):
    ''' 通过给出偏移值,取得区域信息字符串,'''
    
    byte = ord(self.img[offset])
    if byte == 1 or byte == 2:
      # 第一个字节为1或者2时,取得2-4字节作为一个偏移量调用自己
      p = self.getLong3(offset + 1)
      return self.getAreaAddr(p)
    else:
      return self.getString(offset)

  def getAddr(self, offset, ip = 0):
    img = self.img
    o = offset
    byte = ord(img[o])

    if byte == 1:
      # 重定向模式1
      # [IP][0x01][国家和地区信息的绝对偏移地址]
      # 使用接下来的3字节作为偏移量调用字节取得信息
      return self.getAddr(self.getLong3(o + 1))
    
    if byte == 2:
      # 重定向模式2
      # [IP][0x02][国家信息的绝对偏移][地区信息字符串]
      # 使用国家信息偏移量调用自己取得字符串信息
      cArea = self.getAreaAddr(self.getLong3(o + 1))
      o += 4
      # 跳过前4字节取字符串作为地区信息
      aArea = self.getAreaAddr(o)
      return (cArea, aArea)
      
    if byte != 1 and byte != 2:
      # 最简单的IP记录形式,[IP][国家信息][地区信息]
      # 重定向模式1有种情况就是偏移量指向包含国家和地区信息两个字符串
      # 即偏移量指向的第一个字节不是1或2,就使用这里的分支
      # 简单地说:取连续取两个字符串!

      cArea = self.getString(o)
      #o += len(cArea) + 1
      # 我们已经修改cArea为utf-8字符编码了,len取得的长度会有变,
      # 用下面方法得到offset
      o = self.img.find('\0',o) + 1
      aArea = self.getString(o)
      return (cArea, aArea)

  def find(self, ip, l, r):
    ''' 使用二分法查找网络字节编码的IP地址的索引记录'''
    if r - l <= 1:
      return l

    m = (l + r) / 2
    o = self.firstIndex + m * 7
    #new_ip = unpack('I', self.img[o: o+4])[0]

    # Demon注:和上面一样,强制使用little-endian
    new_ip = unpack('<I', self.img[o: o+4])[0]

    if ip <= new_ip:
      return self.find(ip, l, m)
    else:
      return self.find(ip, m, r)
    
  def getIPAddr(self, ip):
    ''' 调用其他函数,取得信息!'''
    # 使用网络字节编码IP地址
    ip = unpack('!I', socket.inet_aton(ip))[0]
    # 使用 self.find 函数查找ip的索引偏移
    i = self.find(ip, 0, self.indexCount - 1)
    # 得到索引记录
    o = self.firstIndex + i * 7
    # 索引记录格式是: 前4字节IP信息+3字节指向IP记录信息的偏移量
    # 这里就是使用后3字节作为偏移量得到其常规表示(QQWry.Dat用字符串表示值)
    o2 = self.getLong3(o + 4)
    # IP记录偏移值+4可以丢弃前4字节的IP地址信息。
    (c, a) = self.getAddr(o2 + 4)
    return (c, a)
    
  def output(self, first, last):
    for i in range(first, last):
      o = self.firstIndex + i * 7
      ip = socket.inet_ntoa(pack('!I', unpack('I', self.img[o:o+4])[0]))
      offset = self.getLong3(o + 4)
      (c, a) = self.getAddr(offset + 4)
      print "%s %d %s/%s" % (ip, offset, c, a)


def main():
  i = IPInfo('QQWry.Dat')
  (c, a) = i.getIPAddr(sys.argv[1])

  # Demon注:如果是在Windows命令行中运行把编码转回gb2312以避免乱码
  if sys.platform == 'win32':
    c = unicode(c, 'utf-8').encode('gb2312')
    a = unicode(a, 'utf-8').encode('gb2312')
  print '%s %s/%s' % (sys.argv[1], c, a)

if __name__ == '__main__':
  main()

# changelog
# 时间:2009年5月29日
# 1. 工具下面网友的建议,修改"o += len(cArea) + 1"
#  http://linuxtoy.org/archives/python-ip.html#comment-113960
#  因为这个时候我已经把得到的字符串变成utf-8编码了,长度会有变化!
Python 相关文章推荐
python实现向ppt文件里插入新幻灯片页面的方法
Apr 28 Python
python3安装pip3(install pip3 for python 3.x)
Apr 03 Python
Python实现登陆文件验证方法
Oct 06 Python
Django如何自定义model创建数据库索引的顺序
Jun 20 Python
python自定义时钟类、定时任务类
Feb 22 Python
django 快速启动数据库客户端程序的方法示例
Aug 16 Python
使用pytorch和torchtext进行文本分类的实例
Jan 08 Python
pycharm如何实现跨目录调用文件
Feb 28 Python
Python tempfile模块生成临时文件和临时目录
Sep 30 Python
使用python把xmind转换成excel测试用例的实现代码
Oct 12 Python
Python调用飞书发送消息的示例
Nov 10 Python
怎么用Python识别手势数字
Jun 07 Python
Python模块学习 re 正则表达式
May 19 #Python
PYTHON正则表达式 re模块使用说明
May 19 #Python
python 随机数生成的代码的详细分析
May 15 #Python
python 生成不重复的随机数的代码
May 15 #Python
精确查找PHP WEBSHELL木马的方法(1)
Apr 12 #Python
Python中删除文件的程序代码
Mar 13 #Python
python 中文乱码问题深入分析
Mar 13 #Python
You might like
php新建文件自动编号的思路与实现
2011/06/27 PHP
PHP实现文件下载详解
2014/11/27 PHP
使用户点击后退按钮使效三行代码
2007/07/07 Javascript
Javascript 面向对象 重载
2010/05/13 Javascript
js动态给table添加/删除tr的方法
2013/08/02 Javascript
NodeJS与Mysql的交互示例代码
2013/08/18 NodeJs
javascript实现checkbox全选的代码
2015/04/30 Javascript
Bootstrap BootstrapDialog使用详解
2017/02/17 Javascript
vue实现单选和多选功能
2017/08/11 Javascript
基于vue 添加axios组件,解决post传参数为null的问题
2018/03/05 Javascript
Vue实现动态创建和删除数据的方法
2018/03/17 Javascript
vue.js中npm安装教程图解
2018/04/10 Javascript
利用vscode调试编译后的js代码详解
2018/05/14 Javascript
Angular4.x Event (DOM事件和自定义事件详解)
2018/10/09 Javascript
jQuery+Datatables实现表格批量删除功能【推荐】
2018/10/24 jQuery
React 实现拖拽功能的示例代码
2019/01/06 Javascript
JavaScript展开操作符(Spread operator)详解
2019/07/20 Javascript
原生JS实现烟花效果
2020/03/10 Javascript
python通过zlib实现压缩与解压字符串的方法
2014/11/19 Python
简介Python中用于处理字符串的center()方法
2015/05/18 Python
python实现的用于搜索文件并进行内容替换的类实例
2015/06/28 Python
Python计算已经过去多少个周末的方法
2015/07/25 Python
python3 requests中使用ip代理池随机生成ip的实例
2018/05/07 Python
使用Python生成200个激活码的实现方法
2019/11/22 Python
Python3+selenium实现cookie免密登录的示例代码
2020/03/18 Python
Html5 滚动穿透的方法
2019/05/13 HTML / CSS
UDP协议功能
2013/01/06 面试题
外贸主管求职简历的自我评价
2013/10/23 职场文书
运动会广播稿200米
2014/01/27 职场文书
收银员岗位职责
2014/02/07 职场文书
《美丽的公鸡》教学反思
2014/02/25 职场文书
上海世博会口号
2014/06/19 职场文书
创业计划书之面包店
2019/09/12 职场文书
Python之基础函数案例详解
2021/08/30 Python
利用JuiceFS使MySQL 备份验证性能提升 10 倍
2022/03/17 MySQL
阿里云国际版 使用Nginx作为HTTPS转发代理服务器
2022/05/11 Servers