资讯专栏INFORMATION COLUMN

Python--Redis实战:第五章:使用Redis构建支持程序:第3节:查找IP所属城市以及国家

fengxiuping / 2684人阅读

摘要:下面清单展示了地址所属地查找程序的具体实现方法将地址转换为分值以便执行命令查找唯一城市方法用来根据指定的分隔符将字符串进行分割。

上一篇文章:Python--Redis实战:第五章:使用Redis构建支持程序:第2节:计数器和统计数据
下一篇文章:Python--Redis实战:第五章:使用Redis构建支持程序:第4节:服务的发现与配置

通过将统计数据和日志存储到Redis里面,我们可以收集访客在系统中的行为信息。但是直到目前为止,我们都忽略了访客行为中非常重要的一部分,那就是:这些访客是从哪里来的?为了回答这个问题,在这一节中,我们将构建一系列用于分析和载入IP所属地数据库的函数,并编写一个可以根据访客的IP地址来查找访客所在城市、行政区、国家的函数。我们先来看看下面这个例子。

随着Fake Game公司的游戏越来越受追捧,来自世界各地的玩家也越来越多。尽管像Google Analytics这样的工具可以让Fake Game公司知道玩家主要来自哪些国家或地区,但是为了更深入的了解玩家,Fake Game公司还希望自己能够知道玩家们所在的城市,而我们要做的就是将一个IP所属城市数据库载入Redis里面,然后通过搜索整个数据库来发现玩家所在的位置。

我们之所以使用Redis而不是传统的关系数据库来实现IP所属地查找功能,是因为Redis实现的IP所属地查找程序在运行速度上更具有优势。另一方面,因为对用户进行定位所需的信息量非常庞大,在应用程序启动时载入这些信息将影响应用程序的启动速度,所以我们也没有使用本地查找表来实现IP所属地查找功能。实现IP所属地查找功能首先要做的就是将一些数据表载入Redis里面,接下来的小节将对这个步骤进行介绍。

载入城市表格

为了开发IP所属地查找程序,我们将使用一个IP所属城市数据库作为测试数据。这个数据库包含两个非常重要的文件:

一个是GeoLiteCity-Blocks.csv,它记录了多个IP地址段以及这些地址段所属城市的ID

另一个是GeoLiteCity-Location.csv它记录了城市ID与城市名、地区名、州县名以及我们不会用到的其他信息之间的映射。

实现IP所属地查找程序会用到两个查找表:

第一个查找表需要根据输入的IP地址来查找IP所属城市的ID

第二个查找表则需要根据输入的城市ID来查找ID对应城市的实际信息(这个城市信息中还会包括城市所在地区的其他信息)

根据IP地址来查找城市ID的查找表由有序集合实现,这个有序集合的成员为具体的城市ID,而分值则是一个根据IP地址计算出来的整数值。为了创建IP地址与城市ID之间的映射,程序需要将点分十进制格式的IP地址转换为一个整数分值,下面的ip_to_score()函数定义了整个转化过程:IP地址中的每8个二进制会被看做是无符号整数中的1字节,其中IP地址最开头的8个二进制位最高位。

def ip_ti_score(ip_address):
    score=0
    for v in ip_address.split("."):
        score=score*256+int(v,10)
    return score

if __name__ == "__main__":
    y=ip_ti_score("117.61.12.128")
    print(y)

    x=117
    x=x*256+61
    x=x*256+12
    x=x*256+128
    print(x)

运行结果:

1966935168
1966935168

在将IP地址转换为整数分值之后,程序就可以创建IP地址与城市ID之间的映射了。因为多个IP地址范围可能会被映射到同一个城市ID,所以程序会在普通的城市ID后面,加上一个_字符以及有序集合目前已知的城市ID的数量,以此来构建一个独一无二的唯一城市ID。下面代码展示了程序时如何创建IP地址与城市ID之前的映射的。

import csv


#这个函数在执行时需要输入GeoLiteCity-Blocks.csv文件所在的路径
def import_ips_to_redis(conn,filename):
    csv_file=csv.reader(open(filename,"rb"))
    #enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,
    # 同时列出数据和数据下标,一般用在 for 循环当中。
    for count,row in enumerate(csv_file):
        start_ip=row[0] if row else ""
        #按照需要将IP地址转换为分值
        if "i" in start_ip.lower():
            continue
        if "." in start_ip:
            start_ip=ip_to_score(start_ip)
        elif start_ip.isdigit():
            start_ip=int(start_ip,10)
        else:
            #略过文件的第一行以及格式不正确的条目
            continue
        #构建唯一的城市ID
        city_id=row[2]+"_"+str(count)
        #将城市ID及其对应的IP地址分值添加到有序集合里面。
        conn_zadd("ip2cityid:",city_id,start_ip)

在调用import_ips_to_redis()函数并将所有IP地址都载入Redis之后,我们会像下面代码展示的那样,创建一个城市ID映射至城市信息的散列。因为所有城市信息的格式都是固定的,并且不会随着时间而发生变化,所有我们会将这些信息编码为JSON列表然后再进行存储。

def import_cities_to_redis(conn,filename):
    for row in csv.reader(open(filename,"rb")):
        if len(row)<4 or not row[0].isdigit():
            continue
        row=[i.decode("latin-1") for i in row]
        city_id=row[0]
        country=row[1]
        region=row[2]
        city=row[3]
        conn.hset("cityid2city:",city_id,json.dumps([city,region,country]))

在将所需的信息全部存储到Redis里面之后,接下来要考虑的就是如何实现IP地址查找功能了。

查找IP所属城市

为了实现IP地址查找功能,我们在上一个小节已经将代表城市ID所属IP地址段起始端的整数分值添加到了有序集合里面。要根据给定IP地址来查找所属城市,程序首先会使用ip_to_score()函数将给定的IP地址转换为分值,然后在所有分值小于或等于给定IP地址里面,找出分值最大的那个IP地址所对应的城市ID。这个查找城市ID的操作可以通过调用zrevrangebyscore命令并将选项start和num的参数分别设为0和1来完成。在找到城市ID之后,程序就可以在存储着城市ID与城市信息映射的散列里面获取ID对应城市的信息了。

下面清单展示了IP地址所属地查找程序的具体实现方法:;

def find_city_by_ip(conn,ip_address):
    if isinstance(ip_address,str):
        #将IP地址转换为分值以便执行zrevrangebyscore命令
        ip_address=ip_to_score(ip_address)

    #查找唯一城市ID
    city_id=conn.zrevrangebyscore("ip2cityed:",ip_address,0,start=0,num=1)

    if not city_id:
        return None
    #partition() 方法用来根据指定的分隔符将字符串进行分割。
    # 如果字符串包含指定的分隔符,则返回一个3元的元组,
    # 第一个为分隔符左边的子串,第二个为分隔符本身,第三个为分隔符右边的子串。
    #将唯一城市ID转换为普通城市ID
    city_id=city_id[0].partition("_")[0]
    #从散列里面取出城市信息
    return json.loads(conn.hget("cityid2city:",city_id))

通过上面函数,我们现在可以基于IP地址来查找相应的城市信息并对用户的来源地进行分析了。

本节介绍的【将数据转换为整数并搭配有序集合进行操作】的做法非常有用,它可以极大简化对特定元素或特定范围的查找工作。

上一篇文章:Python--Redis实战:第五章:使用Redis构建支持程序:第2节:计数器和统计数据
下一篇文章:Python--Redis实战:第五章:使用Redis构建支持程序:第4节:服务的发现与配置

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/42725.html

相关文章

  • Python--Redis实战五章使用Redis构建支持程序3查找IP所属城市以及国家

    摘要:下面清单展示了地址所属地查找程序的具体实现方法将地址转换为分值以便执行命令查找唯一城市方法用来根据指定的分隔符将字符串进行分割。 上一篇文章:Python--Redis实战:第五章:使用Redis构建支持程序:第2节:计数器和统计数据下一篇文章:Python--Redis实战:第五章:使用Redis构建支持程序:第4节:服务的发现与配置 通过将统计数据和日志存储到Redis里面,我们...

    Sanchi 评论0 收藏0
  • Python--Redis实战五章使用Redis构建支持程序4:服务的发现与配置

    摘要:在通常情况下,即使只更新配置中的一个标志,也会导致更新后的配置文件被强制推送至所有服务器,收到更新的服务器可能需要重新载入配置甚至可能还要重启应用程序服务器。将被包裹函数的一些有用的元数据复制给配置处理器。 上一篇文章:Python--Redis实战:第五章:使用Redis构建支持程序:第3节:查找IP所属城市以及国家 随着我们越来越多地使用Redis以及其他服务,如何存储各项服务的...

    printempw 评论0 收藏0
  • Python--Redis实战五章使用Redis构建支持程序4:服务的发现与配置

    摘要:在通常情况下,即使只更新配置中的一个标志,也会导致更新后的配置文件被强制推送至所有服务器,收到更新的服务器可能需要重新载入配置甚至可能还要重启应用程序服务器。将被包裹函数的一些有用的元数据复制给配置处理器。 上一篇文章:Python--Redis实战:第五章:使用Redis构建支持程序:第3节:查找IP所属城市以及国家 随着我们越来越多地使用Redis以及其他服务,如何存储各项服务的...

    jackzou 评论0 收藏0
  • Python--Redis实战五章使用Redis构建支持程序2:计数器和统计数据

    摘要:清理程序通过对记录已知计数器的有序集合执行命令来一个接一个的遍历所有已知的计数器。 上一篇文章:Python--Redis实战:第五章:使用Redis构建支持程序:第1节:使用Redis来记录日志下一篇文章:Python--Redis实战:第五章:使用Redis构建支持程序:第3节:查找IP所属城市以及国家 正如第三章所述,通过记录各个页面的被访问次数,我们可以根据基本的访问计数信息...

    afishhhhh 评论0 收藏0
  • Python--Redis实战五章使用Redis构建支持程序2:计数器和统计数据

    摘要:清理程序通过对记录已知计数器的有序集合执行命令来一个接一个的遍历所有已知的计数器。 上一篇文章:Python--Redis实战:第五章:使用Redis构建支持程序:第1节:使用Redis来记录日志下一篇文章:Python--Redis实战:第五章:使用Redis构建支持程序:第3节:查找IP所属城市以及国家 正如第三章所述,通过记录各个页面的被访问次数,我们可以根据基本的访问计数信息...

    sourcenode 评论0 收藏0

发表评论

0条评论

fengxiuping

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<