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

eyecatch-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.0

pinpointLocations.link.1

pinpointLocations.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も再帰関数もこれでこわくない。分析や再帰関数の理解に役立てたらうれしいです!