PythonでネストしたJSON(辞書型)をcsvやdfに変換する方法

Table of Contents
お天気WebサービスのAPIよりJSONを取得して、入れ子になっているJSONの値を再帰関数を使い根こそぎ取得して、ExcelやCSV、Pandasのデータフレーム等に変換するコードを書きました。簡単に解説をします。
コード
import time
import requests as req
import pandas as pd
def conv_to_2d(objct, parent=None, num=None):
for key, vals in objct.items():
# keyの設定
if parent is not None and num is not None:
abs_key = ”{}.{}.{}“.format(parent, key, num)
elif parent is not None:
abs_key = ”{}.{}“.format(parent, key)
else:
abs_key = key
<span class="hljs-comment"># valsのタイプごとに処理を分岐</span>
<span class="hljs-keyword">if</span> <span class="hljs-built_in">type</span>(vals) <span class="hljs-keyword">is</span> <span class="hljs-built_in">dict</span>:
<span class="hljs-keyword">yield</span> <span class="hljs-keyword">from</span> conv_to_2d(objct=vals, parent=key)
<span class="hljs-keyword">elif</span> <span class="hljs-built_in">type</span>(vals) <span class="hljs-keyword">is</span> <span class="hljs-built_in">list</span>:
val_list = []
<span class="hljs-keyword">for</span> n, val <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(vals):
is_target = [<span class="hljs-built_in">type</span>(val) <span class="hljs-keyword">is</span> <span class="hljs-built_in">int</span>, <span class="hljs-built_in">type</span>(val) <span class="hljs-keyword">is</span> <span class="hljs-built_in">float</span>, <span class="hljs-built_in">type</span>(val) <span class="hljs-keyword">is</span> <span class="hljs-built_in">bool</span>]
<span class="hljs-keyword">if</span> <span class="hljs-built_in">type</span>(val) <span class="hljs-keyword">is</span> <span class="hljs-built_in">str</span>:
<span class="hljs-keyword">if</span> val:
val_list += [val]
<span class="hljs-keyword">elif</span> <span class="hljs-built_in">any</span>(is_target):
num_str = <span class="hljs-built_in">str</span>(val)
<span class="hljs-keyword">if</span> num_str:
val_list += [num_str]
<span class="hljs-keyword">elif</span> <span class="hljs-built_in">type</span>(val) <span class="hljs-keyword">is</span> <span class="hljs-built_in">dict</span>:
<span class="hljs-keyword">yield</span> <span class="hljs-keyword">from</span> conv_to_2d(objct=val, parent=abs_key, num=n)
<span class="hljs-keyword">if</span> val_list:
<span class="hljs-keyword">yield</span> abs_key, <span class="hljs-string">","</span>.join(val_list)
<span class="hljs-keyword">else</span>:
<span class="hljs-keyword">yield</span> abs_key, vals
def get_json(url):
r = req.get(url)
return r.json()
def main():
base_url = “http://weather.livedoor.com/forecast/webservice/json/v1?city={}”
city_id = [“400040”, “290010”, “270000”, “190010”, “130010”, “015010”, “473000”, “350010”]
url_list = [base_url.format(id_) for id_ in city_id]
weather_table = []
<span class="hljs-keyword">for</span> url <span class="hljs-keyword">in</span> url_list:
res = get_json(url)
record = {key: val <span class="hljs-keyword">for</span> key, val <span class="hljs-keyword">in</span> conv_to_2d(res)}
weather_table.append(record)
time.speep(<span class="hljs-number">1</span>)
df = pd.DataFrame(weather_table)
df.to_excel(<span class="hljs-string">"sample.xlsx"</span>, index=<span class="hljs-literal">False</span>)
if name == “main”:
main()
処理内容
STEP1 URLの設定
main関数を動作させることでプログラム全体を動かすため、main関数より順番に処理を追っていきます。http://weather.livedoor.com/forecast/webservice/json/v1?city=
の=の後にcity_idを入力すると、city_idに対応した都市のお天気情報を取得することができます。city_idを入力したリストを作り、url_list変数では内包表記を使ってurlにcity_idを埋め込んだurlのリストを生成しています。
STEP2 JSONの取得
for文の外にレスポンスを格納するための空リストweather_table
を生成します。url_listをfor文にかけてget_json関数でJSONを取得します。get_json関数ではurlを引数にとり、requestsでHTTPリクエストGETメソッドでAPIよりレスポンスを取得します。取得したレスポンスをjsonとして読み込ませてmain関数に値を返します。
STEP3 JSONの二次元化
本投稿の主題です。取得したJSONを再帰関数を使ってキーと値のセットを取得します。conv_to_2d
が今回記述した関数です。conv_to_2d
はジェネレータ関数としても動作して順番にキーと値のセットを取得できるので辞書内包表記で辞書レコードを生成します。再帰関数とは、関数aの中で自身(関数a)を呼び出す処理のことを指します。
再帰関数を採用する理由
今回、再帰関数を使用する理由は、ほかのJSONの利用も考えデータ量がどれだけあるか不明、ネスト(入れ子)がどれだけあるか不明であることを一旦は想定して、正しい形式のJSONであれば変換できることを前提にしたいと思います。その時にN重のforにかける必要があり、その処理を実現する方法として再帰関数が最適であることが再帰関数を利用する理由です。
再帰関数の停止条件
conv_to_2d関数からconv_to_2d関数を呼び出しをするため無条件にこれを実行すると無限にconv_to_2dが呼び出されて処理が終わらない(エラー)となります。そのため何かしらの条件で関数を止めさせる必要がありますが、今回は読み出すべきJSONの値がなくなるまでが停止条件です。yieldで値を返しながらすべての値を返し終わるのを待つほうが処理のフローはシンプルそうなのでreturnを使わずに書きました。
引数と関数の使用方法
引数 | 説明 |
---|---|
objct | 辞書型のオブジェクト |
parent=None | 親のキーの値 |
num=None | リストのインデックス |
引数は辞書型のオブジェクトを読み出すobjct引数、親のキーの値を読み出すparent引数、リストのインデックス番号を示すnum引数で構成されています。一番最初に関数を読み出すときにはobjctだけに引数を指定して使用します。
動作
関数の呼び出し時は辞書型の最上層を読み出すため、親のキーやリストのインデックスは存在しません。そのため、メイン関数ではobjctだけを指定して実行します。実行したのちにfor文とitems()で辞書をキーと値に分解して個別に値の確認をします。
キー
親キーやインデックス番号がある場合には親情報とforで読みだした子キーを.で区切りつつ結合してネスト構造を表現しています。
値
値が辞書かリストか文字列や数値、真偽値であるかで処理を分けます。辞書の場合にはキーを添えてconv_to_2d
を読み出しさらに深い階層の辞書の内容を確認します。これによりN重のfor動作を実現します。リストの場合にはリストのインデックス番号をenumerateで取得しつつ再度辞書か文字列等かであるかをチェックしています。リスト内にリストがあることは考慮していません。リスト内に辞書があれば親キーとインデックス番号を添えてconv_to_2d
を再帰します。再帰関数の値をジェネレータ関数として取得したいときには再帰呼び出しの手前にyield from
を添えます。
STEP4 レコードの登録
weather_table
に読みだした辞書レコードを格納します。軽量で少量の処理ですが相手サーバーに負荷をかけないように1秒待機します。
STEP5 データフレームの生成と変換
forが動作し終わったのちにweather_table
をPandasに読み込ませてデータフレームに変換します。リスト型>辞書型の二次元構成でキーの構成がバラバラでもうまいことテーブル変換ができるようになっています。あとはデータフレームに変換すれば通常のPandasと同様にフィルタリングや変換ができるので、Excel形式・CSV・TSV・Pickle・HTMLなど好きな形式に変換できます。
出力イメージ
※データ量の都合で列はかなり端折っております。
pinpointLocations.name.0 | pinpointLocations.link.1 | pinpointLocations.name.1 |
---|---|---|
大牟田市 | 久留米市 | |
奈良市 | 大和高田市 | |
大阪市 | 堺市 | |
甲府市 | 山梨市 | |
千代田区 | 中央区 | |
室蘭市 | 苫小牧市 | |
宮古島市 | 多良間村 | |
下関市 | 宇部市 |
まとめ
ネストのJSONも再帰関数もこれでこわくない。分析や再帰関数の理解に役立てたらうれしいです!