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

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

お天気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

        # valsのタイプごとに処理を分岐
        if type(vals) is dict:
            yield from conv_to_2d(objct=vals, parent=key)
        elif type(vals) is list:
            val_list = []
            for n, val in enumerate(vals):
                is_target = [type(val) is int, type(val) is float, type(val) is bool]
                if type(val) is str:
                    if val:
                        val_list += [val]
                elif any(is_target):
                    num_str = str(val)
                    if num_str:
                        val_list += [num_str]
                elif type(val) is dict:
                    yield from conv_to_2d(objct=val, parent=abs_key, num=n)
            if val_list:
                yield abs_key, ",".join(val_list)
        else:
            yield 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 = []
    for url in url_list:
        res = get_json(url)
        record = {key: val for key, val in conv_to_2d(res)}
        weather_table.append(record)
        time.speep(1)
    df = pd.DataFrame(weather_table)
    df.to_excel("sample.xlsx", index=False)


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.0pinpointLocations.link.1pinpointLocations.name.1
大牟田市http://weather.livedoor.com/area/forecast/4020300久留米市
奈良市http://weather.livedoor.com/area/forecast/2920200大和高田市
大阪市http://weather.livedoor.com/area/forecast/2714000堺市
甲府市http://weather.livedoor.com/area/forecast/1920500山梨市
千代田区http://weather.livedoor.com/area/forecast/1310200中央区
室蘭市http://weather.livedoor.com/area/forecast/0121300苫小牧市
宮古島市http://weather.livedoor.com/area/forecast/4737500多良間村
下関市http://weather.livedoor.com/area/forecast/3520200宇部市

まとめ

ネストのJSONも再帰関数もこれでこわくない。分析や再帰関数の理解に役立てたらうれしいです!

Pythonカテゴリの最新記事