上一篇文章: 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
實現IP所屬地查找程序會用到兩個查找表:segmentfault
根據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地址查找功能,咱們在上一個小節已經將表明城市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節:服務的發現與配置