最近 Python で GeoIP を使うことが多いけど、なんか色々種類があってわからなくなってきたのでちょっとまとめておく。
- GeoIP2
- maxminddb
- GeoIP
データベースファイル(GeoLite2-City.mmdb)は下記からダウンロードできる。
GeoIP2
GeoLite2-City.mmdb が使えるモジュール。インストールは apt
または pip
から。
sudo apt install python3-geoip2
pip3 install geoip2
Reader
クラスのインスタンスを作って city()
で IP アドレスを渡せばよい。Reader
クラスは __init__(self, fileish, locales=None, mode=0)
となっているので mode
に以下のいずれかを渡すことでモードの指定が可能。
MODE_MMAP_EXT - use the C extension with memory map. MODE_MMAP - read from memory map. Pure Python. MODE_FILE - read database as standard file. Pure Python. MODE_MEMORY - load database into memory. Pure Python. MODE_FD - the param passed via fileish is a file descriptor, not a path. This mode implies MODE_MEMORY. Pure Python. MODE_AUTO - try MODE_MMAP_EXT, MODE_MMAP, MODE_FILE in that order. Default.
書き方は2種類かな?
import geoip2.database reader = geoip2.database.Reader('GeoLite2-City.mmdb') response = reader.city('54.70.157.111') reader.close()
import geoip2.database with geoip2.database.Reader('GeoLite2-City.mmdb') as reader: response = reader.city('54.70.157.111')
戻り値は geoip2.models.City
オブジェクト。
<class 'geoip2.models.City'>
geoip2.models.City({'country': {'names': {'ru': 'США', 'es': 'Estados Unidos', 'fr': 'États-Unis', 'zh-CN': '美国', 'en': 'United States', 'pt-BR': 'Estados Unidos', 'de': 'USA', 'ja': 'アメリカ合衆国'}, 'geoname_id': 6252001, 'iso_code': 'US'}, 'location': {'accuracy_radius': 1000, 'time_zone': 'America/Los_Angeles', 'metro_code': 810, 'longitude': -119.7143, 'latitude': 45.8491}, 'continent': {'names': {'ru': 'Северная Америка', 'es': 'Norteamérica', 'fr': 'Amérique du Nord', 'zh-CN': '北美洲', 'en': 'North America', 'pt-BR': 'América do Norte', 'de': 'Nordamerika', 'ja': '北アメリカ'}, 'code': 'NA', 'geoname_id': 6255149}, 'traits': {'ip_address': '54.70.157.111'}, 'registered_country': {'names': {'ru': 'США', 'es': 'Estados Unidos', 'fr': 'États-Unis', 'zh-CN': '美国', 'en': 'United States', 'pt-BR': 'Estados Unidos', 'de': 'USA', 'ja': 'アメリカ合衆国'}, 'geoname_id': 6252001, 'iso_code': 'US'}, 'subdivisions': [{'names': {'ru': 'Орегон', 'es': 'Oregón', 'fr': 'Oregon', 'zh-CN': '俄勒冈州', 'en': 'Oregon', 'pt-BR': 'Oregão', 'de': 'Oregon', 'ja': 'オレ ゴン州'}, 'geoname_id': 5744337, 'iso_code': 'OR'}], 'postal': {'code': '97818'}, 'city': {'names': {'ru': 'Бордман', 'en': 'Boardman'}, 'geoname_id': 5714964}}, ['en'])
各キーに入っている値を見て見ると geoip2.records
というオブジェクトで入っている。(_locales
を除いて)raw
は辞書で入っているので全体が欲しければこれを取り出すのが簡単。
>>> pprint({k: type(v) for k, v in response.__dict__.items()}) {'_locales': <class 'list'>, 'city': <class 'geoip2.records.City'>, 'continent': <class 'geoip2.records.Continent'>, 'country': <class 'geoip2.records.Country'>, 'location': <class 'geoip2.records.Location'>, 'maxmind': <class 'geoip2.records.MaxMind'>, 'postal': <class 'geoip2.records.Postal'>, 'raw': <class 'dict'>, 'registered_country': <class 'geoip2.records.Country'>, 'represented_country': <class 'geoip2.records.RepresentedCountry'>, 'subdivisions': <class 'geoip2.records.Subdivisions'>, 'traits': <class 'geoip2.records.Traits'>}
location
には latitude
と longitude
以外にもデータが入っているので Kibana で geo_point
として扱う場合は編集が必要。また、Region Map を使って都道府県別にマッピングしたい場合は {国コード}-{区域コード}
を組み合わせるので {country.iso_code}-{subdivisions.iso_code}
(例: JP-01
)みたいになるのだけど、subdivisions
のキーが存在しないことがあるので確認した方がいいかも。
{'city': {'geoname_id': 5714964, 'names': {'en': 'Boardman', 'ru': 'Бордман'}}, 'continent': {'code': 'NA', 'geoname_id': 6255149, 'names': {'de': 'Nordamerika', 'en': 'North America', 'es': 'Norteamérica', 'fr': 'Amérique du Nord', 'ja': '北アメリカ', 'pt-BR': 'América do Norte', 'ru': 'Северная Америка', 'zh-CN': '北美洲'}}, 'country': {'geoname_id': 6252001, 'iso_code': 'US', 'names': {'de': 'USA', 'en': 'United States', 'es': 'Estados Unidos', 'fr': 'États-Unis', 'ja': 'アメリカ合衆国', 'pt-BR': 'Estados Unidos', 'ru': 'США', 'zh-CN': '美国'}}, 'location': {'accuracy_radius': 1000, 'latitude': 45.8491, 'longitude': -119.7143, 'metro_code': 810, 'time_zone': 'America/Los_Angeles'}, 'postal': {'code': '97818'}, 'registered_country': {'geoname_id': 6252001, 'iso_code': 'US', 'names': {'de': 'USA', 'en': 'United States', 'es': 'Estados Unidos', 'fr': 'États-Unis', 'ja': 'アメリカ合衆国', 'pt-BR': 'Estados Unidos', 'ru': 'США', 'zh-CN': '美国'}}, 'subdivisions': [{'geoname_id': 5744337, 'iso_code': 'OR', 'names': {'de': 'Oregon', 'en': 'Oregon', 'es': 'Oregón', 'fr': 'Oregon', 'ja': 'オレゴン州', 'pt-BR': 'Oregão', 'ru': 'Орегон', 'zh-CN': '俄勒冈州'}}], 'traits': {'ip_address': '54.70.157.111'}}
ついでに geoip2.models.City
オブジェクトの属性も見てみる。
>>> print(*dir(response), sep='\n') __class__ __delattr__ __dict__ __dir__ __doc__ __eq__ __format__ __ge__ __getattribute__ __gt__ __hash__ __init__ __le__ __lt__ __metaclass__ __module__ __ne__ __new__ __reduce__ __reduce_ex__ __repr__ __setattr__ __sizeof__ __str__ __subclasshook__ __weakref__ _locales city continent country location maxmind postal raw registered_country represented_country subdivisions traits
🤔 ホスト名はわかるけど IP アドレスがわからない
socket.gethostbyname()
で変換するとか。
import socket socket.gethostbyname('elastic.co')
'54.70.157.111'
dnspython
を使うとか。こっちは nameservers
属性で問い合わせ先の変更ができる。戻り値は dns.resolver.Answer
オブジェクトで、リストに出来るのでそれぞれから address
属性を取り出す。
import dns.resolver resolver = dns.resolver.Resolver() resolver.nameservers = ['1.1.1.1'] response = resolver.query('elastic.co') list(response) [x.address for x in response]
[<DNS IN A rdata: 52.11.225.213>, <DNS IN A rdata: 54.70.157.111>] ['52.11.225.213', '54.70.157.111']
maxminddb
GeoIP2 よりもシンプル。
sudo apt install python3-maxminddb
pip3 install maxminddb
Reader()
または open_database()
でデータベースファイルのパスを与えてインスタンスを作成する。open_database()
ではモードの指定が出来る。モードについては help(maxminddb)
を参照。
from pprint import pprint import maxminddb reader = maxminddb.Reader('GeoLite2-City.mmdb') # reader = maxminddb.open_database('GeoLite2-City.mmdb', maxminddb.MODE_MMAP response = reader.get('54.70.157.111') print(type(response)) pprint(response)
戻り値は辞書型。
<class 'dict'>
出力は GeoIP2 の raw
とほぼ一緒。ただし、問い合わせた IP アドレスは含まれない。
{'city': {'geoname_id': 5714964, 'names': {'en': 'Boardman', 'ru': 'Бордман'}}, 'continent': {'code': 'NA', 'geoname_id': 6255149, 'names': {'de': 'Nordamerika', 'en': 'North America', 'es': 'Norteamérica', 'fr': 'Amérique du Nord', 'ja': '北アメリカ', 'pt-BR': 'América do Norte', 'ru': 'Северная Америка', 'zh-CN': '北美洲'}}, 'country': {'geoname_id': 6252001, 'iso_code': 'US', 'names': {'de': 'USA', 'en': 'United States', 'es': 'Estados Unidos', 'fr': 'États-Unis', 'ja': 'アメリカ合衆国', 'pt-BR': 'Estados Unidos', 'ru': 'США', 'zh-CN': '美国'}}, 'location': {'accuracy_radius': 1000, 'latitude': 45.8491, 'longitude': -119.7143, 'metro_code': 810, 'time_zone': 'America/Los_Angeles'}, 'postal': {'code': '97818'}, 'registered_country': {'geoname_id': 6252001, 'iso_code': 'US', 'names': {'de': 'USA', 'en': 'United States', 'es': 'Estados Unidos', 'fr': 'États-Unis', 'ja': 'アメリカ合衆国', 'pt-BR': 'Estados Unidos', 'ru': 'США', 'zh-CN': '美国'}}, 'subdivisions': [{'geoname_id': 5744337, 'iso_code': 'OR', 'names': {'de': 'Oregon', 'en': 'Oregon', 'es': 'Oregón', 'fr': 'Oregon', 'ja': 'オレゴン州', 'pt-BR': 'Oregão', 'ru': 'Орегон', 'zh-CN': '俄勒冈州'}}]}
GeoIP
(古い?)dat 形式のデータベースを使う方。new()
または open()
でインスタンスを作成する。 open()
の場合はデータベースファイルとモードを指定する。GeoIP2 と異なり、IP アドレスだけでなく record_by_name()
でホスト名を与えることが出来る。
from pprint import pprint import GeoIP gi = GeoIP.open('/usr/share/GeoIP/GeoIPCity.dat', GeoIP.GEOIP_MEMORY_CACHE) response = gi.record_by_name('elastic.co') # response = gi.record_by_addr('54.70.157.111') print(type(response)) pprint(response)
戻り値は辞書型。
<class 'dict'>
GeoIP2 に比べると情報が少ないが、country_code3
を持っていたり、緯度経度情報が細かったりする。(ただしこれは日本の場合は都庁や皇居などであることが多く、ピンポイントで建物を示しているわけではない)
{'area_code': 541, 'city': 'Boardman', 'country_code': 'US', 'country_code3': 'USA', 'country_name': 'United States', 'dma_code': 810, 'latitude': 45.869598388671875, 'longitude': -119.68800354003906, 'metro_code': 810, 'postal_code': '97818', 'region': 'OR', 'region_name': 'Oregon', 'time_zone': 'America/Los_Angeles'}