获取IP地址的地理位置

使用Python和DB-IP免费数据库实现IP地理位置的查询。复习一下二分查找算法。

我在工作中负责系统的日志部分,正好又对数据分析和挖掘有些兴趣,所以想利用最近比较空闲的这段时间做一些日志分析方面的工作。之前没有太多经验,先从一些简单的东西入手:看看用户的地理分布情况。接入层日志中带有请求来源的IP地址,我要做的就是提取请求IP,转换成对应的地理位置,然后按区域统计。这里介绍一下我获取地理位置的方法。

首先需要一个IP数据库。谷歌之后有3个选择:国外的DB-IPMaxMind和国内的IPIP。其中后两者不但提供免费的数据库下载,还有封装好的Python接口可以直接使用。不过什么都是拿来主义,就没意思了。所以我决定使用DB-IP的数据库,自己实现查找。这里使用DB-IP的城市级IP数据库。数据库以纯文本的csv格式提供,每一行包含5列,对应一个IP地址段的起始地址、终止地址和国家、省份、城市等位置信息。下面是几个例子:

"1.0.8.0","1.0.15.255","CN","Guangdong","Guangzhou"
"1.0.16.0","1.0.31.255","JP","Tokyo","Chiyoda"
"1.0.32.0","1.0.63.255","CN","Guangdong","Guangzhou"
"1.0.64.0","1.0.127.255","JP","Hiroshima","Hiroshima"

DB-IP的数据库按照IP地址升序排列,因此这是个典型的有序序列查找问题,可以用二分查找来解决。首先要把文件读进内存建立索引。IP地址其实是一个四字节(32位)的整数[1],写成“192.168.1.1”这种形式只是为了方便人的阅读和记忆,现在既然要让机器来处理,不妨把它再转换成整数。下面代码实现了这个转换过程。

from struct import pack, unpack

def ip2int(ip_str):
    b = map(lambda x: int(x), ip_str.split('.'))
    buf = pack('!BBBB', b[0], b[1], b[2], b[3])
    o = unpack('!I', buf)[0]
    return o

ip2int()函数的第一行将IP字符串按“.”分割成四部分,分别转换成整数,然后放入一个list。这里使用了两个函数式编程的小技巧,让代码更简洁一些:

  1. map()函数。第一个参数是函数f,第二个参数是一个iterable对象(可以是list、tuple等),map函数将对参数2中的每一个对象调用函数f。
  2. lambda表达式。即匿名函数,lambda x表示该函数接受一个参数x,函数返回值就是“:”后面的表达式的值。

第二行将4个整数打包(pack)到一段4字节的缓冲区中,每个整数占一个字节,并以网络序存放,第三行再以32位无符号整数(unsigned int)的形式将缓冲区解包(unpack)。这段利用了Python标准库中的struct包提供的pack()和unpack()两个函数,实现了将4个单字节整数合并成一个4字节整数的过程。如果用传统的写法,代码可能是下面这个样子:

o = 0
for x in b:
    o = o << 8
    o |= x
return o

load_ipdb()函数把数据库文件读入内存,每行记录转换成一个元组(tuple):(ip_start, ip_end, location),将所有元组依次追加到一个列表(list)中。由于文件本身是有序的,我们就得到了一个有序的索引。

def load_ipdb(file_path):
    ip_range_list = []
    with open(file_path) as f:
        for line in f:
            fields = line.strip().split(',')
            fields = [f[1:-1] for f in fields]
            if len(fields) != 5:
                stderr.write(line)
                continue
            ip_start, ip_end, nation, province, city = fields
            ip_start = ip2int(ip_start)
            ip_end = ip2int(ip_end)
            ip_range_list.append((ip_start, ip_end, province, city))

    return ip_range_list

接下来就可以使用二分查找算法,根据给定的IP地址,找到对应的地址段,从而确定其地理位置。

def ip_lookup(ip_range_list, ip):
    ip_bin = ip2int(ip)
    min_idx = 0
    max_idx = len(ip_range_list)
    mid = 0
    while True:
        if min_idx > max_idx:
            break
        mid = (min_idx + max_idx) / 2
        entry = ip_range_list[mid]
        if ip_bin > entry[1]:
            min_idx = mid + 1
            continue
        elif ip_bin < entry[0]:
            max_idx = mid - 1
            continue
        else:
            break
    if ip_bin >= entry[0] and ip_bin <= entry[1]:
        return entry[2]
    else:
        return None

DB-IP的数据库包含了全球的IP地址,有630MB。而实际上我们感兴趣的只是国内的部分,可以先筛选出国家代码为CN的记录,只需要一条grep命令,就可以大大缩短日志统计时查找地理位置的时间。最后附上一张根据统计结果绘制的热度图。绘图使用的是百度ECharts。可以看到,来自广东的请求数量完爆其他省份,其次则是河南、河北、山东和江苏这一片区域。用户的热度分布大致上跟各省的人口情况是相符的。

用户热度图

  1. 这里只讨论IPv4,IPv6地址为6个字节。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,799评论 25 707
  • 需要原文的可以留下邮箱我给你发,这里的文章少了很多图,懒得网上粘啦 1数据库基础 1.1数据库定义 1)数据库(D...
    极简纯粹_阅读 7,406评论 0 46
  • 名词延伸 通俗的说,域名就相当于一个家庭的门牌号码,别人通过这个号码可以很容易的找到你。如果把IP地址比作一间房子...
    杨大虾阅读 20,592评论 2 57
  • 读书真的很辛苦 必须一步一步坚持 就像明天要高考了 每次想起心里还是会后悔的 没有尽全力考 所以哦 高考真的很重要...
    哦安妮阅读 276评论 0 3
  • 有句话儿, 不愿告诉风, 不愿告诉雨, 只想告诉你。 可后来, 告诉了风, 告诉了雨, 还不敢告诉你。
    一片草药解了青春的毒阅读 336评论 18 20