前回に引き続き、これまでにチュートリアルで学んできたことを使ってPythonで実用的なツールを作成する方法を紹介していく。今回は、IPアドレスから、IPアドレスを使っているサーバの位置情報を取得する方法を取り上げる。
実用的なコマンド - IPアドレスから位置情報を取得する
IPアドレスから、IPアドレスを使っているサーバの位置情報を取得する方法はいろいろとあるのだが、ここではcurlコマンドを利用する方法をPythonで実装するやり方を紹介しよう。
■curlコマンドでIPアドレスから位置情報を得る
% curl https://ipvigilante.com/93.184.216.34
{"status":"success","data":{"ipv4":"93.184.216.34","continent_name":"North America","country_name":"United States","subdivision_1_name":"Massachusetts","subdivision_2_name":null,"city_name":"Norwell","latitude":"42.15080","longitude":"-70.82280"}}
%
URLにIPアドレスを含めてipvigilante.comにHTTP GETリクエストを送信すると、該当するIPアドレスの位置情報がJSON形式で返ってくるわけだ。Pythonで同じようにデータを取得し、人が見やすい形式に変換して出力してみよう。
urlopen()でデータを取得
URLを指定してコンテンツを取得する方法として、以前、Pythonのurlopen()を使う方法を取り上げた。前述したcurlコマンドの実行は、Pythonで実装するなら次のようになる。
■Pythonで実装したcurlコマンドの実行(get_geoipinfo.py)
#!/usr/bin/env python3
from urllib.request import urlopen
ipaddr='93.184.216.34'
geoipurl='https://ipvigilante.com/'
geodata=''
with urlopen(geoipurl + ipaddr) as response:
for line in response:
geodata += line.decode('utf-8')
print(geodata)
このスクリプトを実行すると次のようになる。
% ./get_geoipinfo.py
{"status":"success","data":{"ipv4":"93.184.216.34","continent_name":"North America","country_name":"United States","subdivision_1_name":"Massachusetts","subdivision_2_name":null,"city_name":"Norwell","latitude":"42.15080","longitude":"-70.82280"}}
%
curlコマンドで実行したように、指定したURLからコンテンツが取得できたことがわかる。
JSONデータを扱う
PythonはjsonモジュールでJSONを扱うための基本的な方法を提供している。jsonモジュールを使うと、JSON形式の文字列をPythonのオブジェクトに変換することや、その逆も可能だ。まず、このモジュールの使い方を探っていこう。
次のサンプルは、JSONデータを文字列として用意してから、json.loads(文字列)を使ってPythonのオブジェクトへ変換するというものだ。Pythonのオブジェクトに変換した後は、json.dumps()で文字列に変換し直すことができる。いったんPythonオブジェクトに変換してしまえばPythonで扱うことができるので、json.loads()とjson.dumps()の扱いがわかればいいというわけだ。以下に示す「gen_json_1.py」をご覧いただきたい。
#!/usr/bin/env python3
import json
# JSONに相当するデータを文字列で用意
jsonstr = '{"status":"success","data":{"ipv4":"93.184.216.34","continent_name":"North America","country_name":"United States","subdivision_1_name":"Massachusetts","subdivision_2_name":null,"city_name":"Norwell","latitude":"42.15080","longitude":"-70.82280"}}'
# 文字列からJSONに相当するディクショナリデータを生成
jsondic = json.loads(jsonstr)
# ディクショナリデータを形式指定なしでJSON文字列として出力
print('----')
print(json.dumps(jsondic))
# ディクショナリデータをインデント指定してJSON文字列として出力
print('----')
print(json.dumps(jsondic, indent=4))
上記のスクリプトを実行すると次のようになる。
% ./gen_json_1.py
----
{"status": "success", "data": {"ipv4": "93.184.216.34", "continent_name": "North America", "country_name": "United States", "subdivision_1_name": "Massachusetts", "subdivision_2_name": null, "city_name": "Norwell", "latitude": "42.15080", "longitude": "-70.82280"}}
----
{
"status": "success",
"data": {
"ipv4": "93.184.216.34",
"continent_name": "North America",
"country_name": "United States",
"subdivision_1_name": "Massachusetts",
"subdivision_2_name": null,
"city_name": "Norwell",
"latitude": "42.15080",
"longitude": "-70.82280"
}
}
%
なお、json.loads()とjson.dumps()の「s」はstring(文字列)の「s」を意味している。
では、JSON文字列ではなく、最初からPythonオブジェクトとしてデータを用意しておけばjson.loads()で読み込む必要はないんじゃないか、と思われるかもしれない。その通りだ。次に示す「gen_json_2.py」のように、データを直接Pythonオブジェクトとして用意してあれば、json.loads()することなくjson.dumps()に指定して利用することができる。
#!/usr/bin/env python3
import json
# JSONに相当するデータをディクショナリで用意
jsondic = {"status":"success","data":{"ipv4":"93.184.216.34","continent_name":"North America","country_name":"United States","subdivision_1_name":"Massachusetts","subdivision_2_name":None,"city_name":"Norwell","latitude":"42.15080","longitude":"-70.82280"}}
# ディクショナリデータを形式指定なしでJSON文字列として出力
print('----')
print(json.dumps(jsondic))
# ディクショナリデータをインデント指定してJSON文字列として出力
print('----')
print(json.dumps(jsondic, indent=4))
gen_json_2.pyを実行すると次のようになる。
% ./gen_json_2.py
----
{"status": "success", "data": {"ipv4": "93.184.216.34", "continent_name": "North America", "country_name": "United States", "subdivision_1_name": "Massachusetts", "subdivision_2_name": null, "city_name": "Norwell", "latitude": "42.15080", "longitude": "-70.82280"}}
----
{
"status": "success",
"data": {
"ipv4": "93.184.216.34",
"continent_name": "North America",
"country_name": "United States",
"subdivision_1_name": "Massachusetts",
"subdivision_2_name": null,
"city_name": "Norwell",
"latitude": "42.15080",
"longitude": "-70.82280"
}
}
%
Pythonから直接JSONデータを生成する場合には、上記のようにPythonオブジェクトから直接JSONデータに変換することになる。なお、JSONでは「null」という表記だが、Pythonではこれが「None」になる点に留意しておこう。
次に、JSON文字列をPythonオブジェクトに変換して、そこから特定のフォーマットに整形した文字列を出力する方法を考える。得られるデータはディクショナリになっているので、Pythonの文字列フォーマット機能を使えばフォーマットされた文字列を簡単に用意することができる。スクリプトは次の通りだ(gen_json_3.py)。
#!/usr/bin/env python3
import json
# JSONに相当するデータを文字列で用意
jsonstr = '{"status":"success","data":{"ipv4":"93.184.216.34","continent_name":"North America","country_name":"United States","subdivision_1_name":"Massachusetts","subdivision_2_name":null,"city_name":"Norwell","latitude":"42.15080","longitude":"-70.82280"}}'
# 文字列からJSONに相当するディクショナリデータを生成
jsondic = json.loads(jsonstr)
# 出力する文字列を作成
outputstr = """\
IPv4 : {ip}
国 : {cn}
市 : {ci}
経度 : {la}
緯度 : {lo}\
""".format( ip=jsondic["data"]["ipv4"],
co=jsondic["data"]["continent_name"],
cn=jsondic["data"]["country_name"],
s1=jsondic["data"]["subdivision_1_name"],
s2=jsondic["data"]["subdivision_2_name"],
ci=jsondic["data"]["city_name"],
la=jsondic["data"]["latitude"],
lo=jsondic["data"]["longitude"])
# 加工済み文字列を出力
print(outputstr)
gen_json_3.pyを実行すると、次のようになる。
% ./gen_json_3.py
IPv4 : 93.184.216.34
国 : United States
市 : Norwell
経度 : 42.15080
緯度 : -70.82280
%
こんな感じで、Pythonでは比較的簡単にJSONデータを扱うことができる。JSONとPythonは、そもそも用意されているデータ型的に相性が良いのである。
ip2geoinfoコマンド
上記サンプルを組み合わせて、「ip2geoinfo.py」というコマンドを作ってみよう。これは、引数にIPアドレスを指定すると、指定されたIPアドレスの位置情報を出力するコマンドだ。引数はsysモジュールの「argv」で利用できる。まとめたスクリプト「ip2geoinfo.py」は次のようになる。
#!/usr/bin/env python3
from sys import argv
from urllib.request import urlopen
import json
# コマンドの使い方
usage="""\
usage:
ip2geoinfo.py IPアドレス"""
# 引数が1つ以外だった場合はコマンドの使い方を出力して終了
if (2 != len(argv)):
print(usage)
exit()
# 指定されたIPアドレス
ipaddr=argv[1]
geoipurl='https://ipvigilante.com/'
# IPアドレスの医師情報をJSON文字列として取得
jsonstr=''
with urlopen(geoipurl + ipaddr) as response:
for line in response:
jsonstr += line.decode('utf-8')
# 文字列からJSONに相当するディクショナリデータを生成
jsondic = json.loads(jsonstr)
# 出力する文字列を作成
outputstr = """\
IPv4 : {ip}
国 : {cn}
市 : {ci}
経度 : {la}
緯度 : {lo}\
""".format( ip=jsondic["data"]["ipv4"],
co=jsondic["data"]["continent_name"],
cn=jsondic["data"]["country_name"],
s1=jsondic["data"]["subdivision_1_name"],
s2=jsondic["data"]["subdivision_2_name"],
ci=jsondic["data"]["city_name"],
la=jsondic["data"]["latitude"],
lo=jsondic["data"]["longitude"])
# 加工済み文字列を出力
print(outputstr)
ちなみに、コマンドらしくするために、引数の指定が間違っていた場合には「使い方」を出力するようにしておいた。引数の指定を間違えると、次のように引数部分に「IPアドレス」を入力することを示す。
% ./ip2geoinfo.py
usage:
ip2geoinfo.py IPアドレス
%
ip2geoinfo.pyにIPアドレスを指定して実行すると、次のような出力を得ることができる。
% ./ip2geoinfo.py 93.184.216.34
IPv4 : 93.184.216.34
国 : United States
市 : Norwell
経度 : 42.15080
緯度 : -70.82280
% ./ip2geoinfo.py 172.217.25.100
IPv4 : 172.217.25.100
国 : United States
市 : Mountain View
経度 : 37.41920
緯度 : -122.05740
% ./ip2geoinfo.py 99.84.143.16
IPv4 : 99.84.143.16
国 : United States
市 : None
経度 : 37.75100
緯度 : -97.82200
% ./ip2geoinfo.py 183.79.135.206
IPv4 : 183.79.135.206
国 : Japan
市 : Akasaka
経度 : 34.85000
緯度 : 137.30000
%
* * *
意外と簡単にツールを作れることがおわかりいただけたのではないだろうか。上記のサンプルは、JSONを返すタイプのWebサービス全般で応用が効く。シンプルな実装でここまでできることで、Pythonの便利さや強力さが少なからず、わかっていただけただろう。