Scriptone

Python으로 네스트된 JSON(딕셔너리 타입)을 CSV나 df로 변환하는 방법

Table of Contents

날씨 웹 서비스의 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.sleep(<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의 2차원화

본 게시물의 주제입니다. 획득한 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에 읽어 들여 데이터 프레임으로 변환합니다. 리스트 타입 > 딕셔너리 타입의 2차원 구성으로 키의 구성이 다양해도 잘 테이블 변환할 수 있도록 되어 있습니다. 그 후 데이터 프레임으로 변환하면 일반적인 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도 재귀 함수도 이제 두렵지 않다. 분석이나 재귀 함수의 이해에 도움이 되었으면 좋겠습니다!