Python--Redis實戰:第五章:使用Redis構建支持程序:第3節:查找IP所屬城市以及國家

上一篇文章: Python--Redis實戰:第五章:使用Redis構建支持程序:第2節:計數器和統計數據
下一篇文章: Python--Redis實戰:第五章:使用Redis構建支持程序:第4節:服務的發現與配置

經過將統計數據和日誌存儲到Redis裏面,咱們能夠收集訪客在系統中的行爲信息。可是直到目前爲止,咱們都忽略了訪客行爲中很是重要的一部分,那就是:這些訪客是從哪裏來的?爲了回答這個問題,在這一節中,咱們將構建一系列用於分析和載入IP所屬地數據庫的函數,並編寫一個能夠根據訪客的IP地址來查找訪客所在城市、行政區、國家的函數。咱們先來看看下面這個例子。git

隨着Fake Game公司的遊戲愈來愈受追捧,來自世界各地的玩家也愈來愈多。儘管像Google Analytics這樣的工具可讓Fake Game公司知道玩家主要來自哪些國家或地區,可是爲了更深刻的瞭解玩家,Fake Game公司還但願本身可以知道玩家們所在的城市,而咱們要作的就是將一個IP所屬城市數據庫載入Redis裏面,而後經過搜索整個數據庫來發現玩家所在的位置。redis

咱們之因此使用Redis而不是傳統的關係數據庫來實現IP所屬地查找功能,是由於Redis實現的IP所屬地查找程序在運行速度上更具備優點。另外一方面,由於對用戶進行定位所需的信息量很是龐大,在應用程序啓動時載入這些信息將影響應用程序的啓動速度,因此咱們也沒有使用本地查找表來實現IP所屬地查找功能。實現IP所屬地查找功能首先要作的就是將一些數據表載入Redis裏面,接下來的小節將對這個步驟進行介紹。數據庫

載入城市表格

爲了開發IP所屬地查找程序,咱們將使用一個IP所屬城市數據庫做爲測試數據。這個數據庫包含兩個很是重要的文件:json

  • 一個是GeoLiteCity-Blocks.csv,它記錄了多個IP地址段以及這些地址段所屬城市的ID
  • 另外一個是GeoLiteCity-Location.csv它記錄了城市ID與城市名、地區名、州縣名以及咱們不會用到的其餘信息之間的映射。

實現IP所屬地查找程序會用到兩個查找表:segmentfault

  • 第一個查找表須要根據輸入的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節:服務的發現與配置
相關文章
相關標籤/搜索