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采用socket模拟TCP通讯的实现方法
Nov 19 Python
Python使用Flask框架获取当前查询参数的方法
Mar 21 Python
在Python中用split()方法分割字符串的使用介绍
May 20 Python
浅谈python中列表、字符串、字典的常用操作
Sep 19 Python
Python之csv文件从MySQL数据库导入导出的方法
Jun 21 Python
Django基础知识 web框架的本质详解
Jul 18 Python
使用selenium和pyquery爬取京东商品列表过程解析
Aug 15 Python
python判断字符串以什么结尾的实例方法
Sep 18 Python
python 下载m3u8视频的示例代码
Nov 11 Python
Scrapy-Redis之RedisSpider与RedisCrawlSpider详解
Nov 18 Python
用python删除文件夹中的重复图片(图片去重)
May 12 Python
Python移位密码、仿射变换解密实例代码
Jun 27 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读取数据库信息的几种方法
2008/05/24 PHP
php 传值赋值与引用赋值的区别
2010/12/29 PHP
php 广告调用类代码(支持Flash调用)
2011/08/11 PHP
用来解析.htpasswd文件的PHP类
2012/09/05 PHP
php中通过curl检测页面是否被百度收录
2013/09/27 PHP
图解找出PHP配置文件php.ini的路径的方法
2014/08/20 PHP
ThinkPHP中order()使用方法详解
2016/04/19 PHP
PHP实现的迪科斯彻(Dijkstra)最短路径算法实例
2017/09/16 PHP
PHP读取Excel内的图片(phpspreadsheet和PHPExcel扩展库)
2019/11/19 PHP
JavaScript setTimeout和setInterval的使用方法 说明
2010/03/25 Javascript
jQuery调用WebService的实现代码
2011/06/19 Javascript
jquery实现手机发送验证码的倒计时代码
2014/02/12 Javascript
Angular的事件和表单详解
2016/12/26 Javascript
Angularjs单选改为多选的开发过程及问题解析
2017/02/17 Javascript
nodejs 图解express+supervisor+ejs的用法(推荐)
2017/09/08 NodeJs
薪资那么高的Web前端必看书单
2017/10/13 Javascript
微信小程序swiper使用网络图片不显示问题解决
2019/12/13 Javascript
解决vue无法侦听数组及对象属性的变化问题
2020/07/17 Javascript
Python Mysql数据库操作 Perl操作Mysql数据库
2009/01/12 Python
在Python的Flask中使用WTForms表单框架的基础教程
2016/06/07 Python
python使用pil库实现图片合成实例代码
2018/01/20 Python
Python堆排序原理与实现方法详解
2018/05/11 Python
基于YUV 数据格式详解及python实现方式
2019/12/09 Python
IE滤镜与CSS3效果(详细整理分享)
2013/01/25 HTML / CSS
Html5插件教程之添加浏览器放大镜效果的商品橱窗
2016/01/07 HTML / CSS
波兰在线运动商店:YesSport
2020/07/23 全球购物
实习心得体会
2014/01/02 职场文书
设备动力科岗位职责范本
2014/02/23 职场文书
爱护公物演讲稿
2014/09/09 职场文书
领导班子个人对照检查材料(群众路线)
2014/09/26 职场文书
假释思想汇报范文
2014/10/11 职场文书
2014年环保工作总结
2014/11/26 职场文书
研究生毕业论文导师评语
2014/12/31 职场文书
盘点2020年适合农村地区创业的项目
2019/10/16 职场文书
Python 实现Mac 屏幕截图详解
2021/10/05 Python
Java实现添加条码或二维码到Word文档
2022/06/01 Java/Android