IPアドレスから国名を知る
IPアドレスから国名を知る方法について、ときどき必要になったり忘れたりするので、メモ。
MaxMind社がCreate Commonsライセンスで公開してくれている、GeoLite2っていうデータベースを使わせてもらう。
http://dev.maxmind.com/geoip/geoip2/geolite2/
これは有料版のGeoIP2ってやつの簡易版らしいのだけど、十分な精度だと思う。
ここでダウンロードできる、GeoLite2 CountryのCSVフォーマットをつかう。
(Cityのほうは多分都市まで分かるようなものなんだろうけど、僕には用がないし、データは大量だし、中身を見たことはない。)
このデータの中身はこんなふう。
"1.0.0.0","1.0.0.255","16777216","16777471","AU","Australia" "1.0.1.0","1.0.3.255","16777472","16778239","CN","China" "1.0.4.0","1.0.7.255","16778240","16779263","AU","Australia" ... "223.255.252.0","223.255.253.255","3758095360","3758095871","CN","China" "223.255.254.0","223.255.254.255","3758095872","3758096127","SG","Singapore" "223.255.255.0","223.255.255.255","3758096128","3758096383","AU","Australia"
This product includes GeoLite2 data created by MaxMind, available from http://www.maxmind.com.
6つのフィールドの1つめと2つめが、IPアドレスの範囲。3つめと4つめもIPアドレス範囲なんだけど、これはIPを整数値に直したもの。0から255までの値が4つ並ぶのがよく見るIPアドレスの姿なのだけど、これは 256*256*256*256 = 4294967296 までの整数でも表せるというわけ。
5つめと6つめのフィールドは、国名ですね。
このデータを、まずは扱いやすいようにB-treeにする。B-treeなら、並んだデータの頭出しがしやすいから。使うのはpython。
import bsddb sourcefile = 'GeoIPCountryWhois.csv' btreefile = 'geo' ipdb = bsddb.btopen(btreefile) for line in open(sourcefile): a = line[:-1].split(",") a = [i.strip('"') for i in a] k = '%010d' % int(a[3]) ipdb[k] = a[5]
整数であらわしたIPアドレスを、10桁にそろえてbtreeのキーに使っている。文字列しかキーとして扱ってくれないっぽいので、こんな感じになった。
このデータを実際に検索して国名を出すのは、こんな感じ。
import bsddb btreefile = 'geo' ipdb = bsddb.btopen(btreefile) def ip2country(ip): a = [int(i) for i in ip.split(".")] n = (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3] c = ipdb.set_location('%010d' % n) return c[1] print ip2country("13.14.15.16")
btreeを頭出しして、調べるIPの直前の並びにいるものが、たぶん調べるレコード。
この例では 13.14.15.16 を調べてみた。結果は "United States" で、あってるっぽい。
実際には終了位置もチェックしないと万全ではないのだけど、データを眺める限り、ほぼスキマなくIPが網羅されているようなので、別にいいんじゃないかな。(ローカルIPとかそういうのを調べたらダメだけど)
B-treeファイルを作りたくないな、というときには、メモリ上のリストを二分探索する方式でもいいね。↓がサンプル。
import bisect sourcefile = 'GeoIPCountryWhois.csv' iplist = [] iplistdict = {} for line in open(sourcefile): a = line[:-1].split(",") a = [i.strip('"') for i in a] k = int(a[3]) iplist.append(k) iplistdict[k] = a[5] def ip2country(ip): a = [int(i) for i in ip.split(".")] n = (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3] i = bisect.bisect_left(iplist, n) return iplistdict[iplist[i]] print ip2country("13.14.15.16")