JSON 응답을 Pandas DataFrame으로 정리하는 방법 | 리스트와 딕셔너리 두 방식 비교

JSON 응답을 DataFrame으로 바꿀 때 중요한 건 pd.DataFrame() 자체가 아니다.

진짜 중요한 건 중첩된 JSON을 분석하기 좋은 2차원 표 구조로 어떻게 “정리해서” 넘길 것인가다. 실전에서는 원본 JSON을 바로 DataFrame으로 바꾸기보다, 필요한 필드만 추출해서 리스트 또는 딕셔너리 형태로 다시 쌓은 뒤 DataFrame으로 변환하는 방식이 훨씬 많이 쓰인다. 자료에서도 KOBIS 영화목록 JSON을 바로 DataFrame으로 넘기는 방식과, 필요한 값만 추려 tot_data에 다시 쌓아 DataFrame으로 만드는 방식이 모두 나오고, 후자가 실제 분석용 정리 방식으로 이어진다.

왜 JSON을 바로 DataFrame으로 만들면 애매할까

처음엔 이렇게 쓰고 싶어진다.

import pandas as pd

df = pd.DataFrame(result["movieListResult"]["movieList"])
print(df.head())

이 코드는 분명 동작한다.

문제는 동작하는 것과 분석하기 좋은 것은 다르다는 점이다.

예를 들어 KOBIS 영화목록 JSON에는 이런 필드가 섞여 있다.

  • movieCd → 문자열
  • movieNm → 문자열
  • movieNmEn → 문자열
  • openDt → 문자열
  • directors → 리스트 안에 딕셔너리
  • companys → 리스트 안에 딕셔너리

즉, 표로 만들면 앞쪽 컬럼은 그럴듯하지만, directors, companys 같은 컬럼은 리스트/딕셔너리 덩어리 그대로 들어온다. 자료에서도 원본 JSON 리스트를 바로 DataFrame으로 바꾸는 예시가 나오고, 그 상태에서는 nested 필드가 그대로 남는다는 흐름이 보인다.

실전에서는 이런 DataFrame이 애매하다.

  • 바로 필터링하기 불편함
  • CSV로 저장하면 보기 지저분함
  • 감독 이름으로 정렬/집계하기 어려움
  • 컬럼 하나가 “값”이 아니라 “구조물”이 돼버림

그래서 보통은 원본 응답을 그대로 DataFrame으로 만들지 않고, 필요한 값만 평평하게(flat) 펴서 다시 만든다.


실전에서 더 많이 쓰는 흐름

실무나 개인 프로젝트에서는 보통 이 흐름으로 간다.

JSON 응답 받기
→ 필요한 필드만 결정
→ 중첩 구조 풀기
→ row 단위로 리스트/딕셔너리 누적
→ DataFrame 변환

핵심은 이거다.

DataFrame은 마지막 단계다.

먼저 JSON을 “내가 쓰기 좋은 표 형태”로 재구성해야 한다.


예시 JSON 구조부터 보면

KOBIS 영화목록에서 필요한 값만 본다고 하면 보통 이 정도다.

  • movieCd
  • movieNm
  • movieNmEn
  • openDt
  • directors[0]["peopleNm"]

대략 구조는 이렇게 생겼다.

data = {
    "movieListResult": {
        "movieList": [
            {
                "movieCd": "20252373",
                "movieNm": "넘버원",
                "movieNmEn": "",
                "openDt": "20260211",
                "directors": [{"peopleNm": "김태용"}]
            },
            {
                "movieCd": "20263372",
                "movieNm": "처제의 고백; 언니 없을 땐 우리 둘뿐이야",
                "movieNmEn": "",
                "openDt": "",
                "directors": []
            }
        ]
    }
}

여기서 바로 보이는 실전 포인트가 하나 있다.

👉 directors는 항상 값이 있는 게 아니다.

자료에서도 실제로 어떤 영화는 directors가 []라서 data["directors"][0]["peopleNm"] 접근 시 IndexError가 발생하는 예시가 나온다. 그래서 이 필드는 반드시 예외 처리가 들어가야 한다.


방식 1. 리스트 기반으로 쌓기

가장 기본적인 방식이다.

필요한 값만 순서대로 꺼내서, 한 줄(row)을 리스트 하나로 만든다.

tot_data = []

for item in data["movieListResult"]["movieList"]:
    movie_cd = item["movieCd"]
    movie_nm = item["movieNm"]
    movie_nm_en = item["movieNmEn"]
    open_dt = item["openDt"]

    if item["directors"] == []:
        dir_name = ""
    else:
        dir_name = item["directors"][0]["peopleNm"]

    tot_data.append([movie_cd, movie_nm, movie_nm_en, open_dt, dir_name])

그다음 DataFrame으로 바꾸면 된다.

import pandas as pd

df = pd.DataFrame(
    data=tot_data,
    columns=["movieCd", "movieTitle", "movieETitle", "openDay", "dirName"]
)

print(df.head())

리스트 방식이 좋은 경우

이 방식은 이런 상황에 잘 맞는다.

  • 컬럼 구조가 이미 명확할 때
  • 순서가 딱 정해져 있을 때
  • 빠르게 표 형태를 만들고 싶을 때
  • 코드 길이를 줄이고 싶을 때

자료에서도 JSON 예시에서 tot_data.append([i_code, i_name, i_name_e, i_day, i_dir])처럼 리스트로 한 줄씩 누적한 뒤 pd.DataFrame(..., columns=[...])로 변환하는 흐름이 먼저 나온다.

리스트 방식의 단점

하지만 실전에서 조금만 필드가 많아지면 단점이 나온다.

tot_data.append([movie_cd, movie_nm, movie_nm_en, open_dt, dir_name])

이 줄만 보면,

  • 3번째 값이 뭐였는지
  • 4번째 값이 뭘 의미하는지
  • 컬럼 순서를 내가 잘 맞췄는지

매번 확인해야 한다.

즉, 짧지만 읽기 좋은 코드는 아닐 수 있다.


방식 2. 딕셔너리 기반으로 쌓기

실전에서는 이 방식이 더 자주 쓰인다.

각 row를 딕셔너리 하나로 표현하는 방식이다.

tot_data = []

for item in data["movieListResult"]["movieList"]:
    if item["directors"] == []:
        dir_name = ""
    else:
        dir_name = item["directors"][0]["peopleNm"]

    tot_data.append({
        "movieCd": item["movieCd"],
        "movieTitle": item["movieNm"],
        "movieETitle": item["movieNmEn"],
        "openDay": item["openDt"],
        "dirName": dir_name
    })

그리고 DataFrame으로 바꾸면 끝이다.

df = pd.DataFrame(tot_data)
print(df.head())

딕셔너리 방식이 실전적인 이유

이 방식의 장점은 코드만 봐도 바로 드러난다.

{
    "movieCd": item["movieCd"],
    "movieTitle": item["movieNm"],
    "movieETitle": item["movieNmEn"],
    "openDay": item["openDt"],
    "dirName": dir_name
}

이건 읽는 순간 알 수 있다.

  • 어떤 컬럼을 만들 건지
  • 어떤 값을 넣는지
  • 컬럼명이 뭔지
  • 최종 표 구조가 어떻게 생길지

즉, 코드가 곧 데이터 설계서처럼 보인다.

자료에서도 dict 기반 누적 예시가 따로 나오고, 이 경우 pd.DataFrame(tot_data)만 해도 키값이 자동으로 컬럼명으로 올라온다고 설명한다.


리스트 방식 vs 딕셔너리 방식, 실전 기준으로 비교하면

리스트 방식

tot_data.append([movie_cd, movie_nm, movie_nm_en, open_dt, dir_name])

장점:

  • 짧다
  • 단순하다
  • 빠르게 만들기 좋다

단점:

  • 의미가 덜 보인다
  • 컬럼 순서를 따로 관리해야 한다
  • 나중에 컬럼 하나 추가할 때 실수하기 쉽다

딕셔너리 방식

tot_data.append({
    "movieCd": movie_cd,
    "movieTitle": movie_nm,
    "movieETitle": movie_nm_en,
    "openDay": open_dt,
    "dirName": dir_name
})

장점:

  • 가독성이 좋다
  • 컬럼명이 자동으로 맞춰진다
  • 컬럼 추가/삭제가 쉽다
  • 유지보수에 강하다

단점:

  • 코드가 조금 길어진다

그래서 실전에서는 뭘 더 많이 쓰냐

개인적으로도 그렇고, 협업/유지보수 기준으로도 보통 딕셔너리 방식이 더 실전적이다.

이유는 단순하다.

  • 나중에 다시 봐도 읽기 쉽다
  • 필드가 늘어나도 버티기 좋다
  • 누가 봐도 무슨 컬럼인지 알 수 있다
  • pd.DataFrame(dict_list)만으로 끝난다

즉, 짧은 코드보다 오래 버티는 코드가 된다.


한 단계 더 실전적으로: 중첩 필드 붙이기

자료에서는 여기서 한 단계 더 나간다.

감독 정보가 원래 directors 리스트 안에 있으니, 첫 번째 감독의 정보를 꺼내서 기존 dict에 병합하는 방식도 보여준다.

예를 들면 이런 식이다.

new_list = []

for item in data["movieListResult"]["movieList"]:
    one_data = {
        "movieCd": item["movieCd"],
        "movieNm": item["movieNm"],
        "movieNmEn": item["movieNmEn"],
        "openDt": item["openDt"]
    }

    if len(item["directors"]) != 0:
        one_data = {**one_data, **item["directors"][0]}

    new_list.append(one_data)

이 방식은 왜 좋냐면,

  • 기본 필드 유지
  • 중첩된 감독 정보 펼치기
  • dict 병합으로 코드가 깔끔함

즉, JSON flattening을 더 유연하게 할 수 있다.


“원본 DataFrame”과 “분석용 DataFrame”은 다르다

이건 꼭 기억해야 한다.

원본 확인용 DataFrame

df_raw = pd.DataFrame(data["movieListResult"]["movieList"])

이건 이런 용도로 좋다.

  • 응답 구조 빠르게 확인
  • 어떤 컬럼이 들어오는지 확인
  • nested 필드가 어디 있는지 보기

분석용 DataFrame

df_clean = pd.DataFrame(tot_data)

이건 이런 용도다.

  • 저장
  • 필터링
  • 그룹화
  • 시각화
  • 후처리

즉, 원본을 한 번 보고, 분석용은 다시 만든다는 생각이 더 맞다.

자료에서도 원본 JSON 리스트를 바로 DataFrame으로 바꾸는 흐름과, 필요한 정보만 다시 수집해 DataFrame으로 만드는 흐름이 분리되어 나온다.


JSON → DataFrame 정리에서 자주 하는 실수

1. 무조건 바로 DataFrame부터 만들기

pd.DataFrame(json_data)

이렇게 시작하면 중첩 구조 때문에 나중에 다시 뜯어야 할 때가 많다.


2. 없는 값 예외 처리 안 하기

item["directors"][0]["peopleNm"]

이건 directors == []인 경우 바로 터진다.

자료에서도 실제 IndexError 예시가 나온다.


3. DataFrame 변환 전에 컬럼 설계를 안 하기

결국 분석할 때 필요한 컬럼은 몇 개 안 되는데,

처음부터 전체 JSON 구조를 다 끌고 오면 오히려 더 지저분해진다.


실전에서 추천하는 패턴

내 기준으로는 보통 이렇게 간다.

빠르게 구조 확인할 때

df_raw = pd.DataFrame(result["movieListResult"]["movieList"])

실제 저장/분석용 만들 때

tot_data = []

for item in result["movieListResult"]["movieList"]:
    dir_name = item["directors"][0]["peopleNm"] if item["directors"] else ""

    tot_data.append({
        "movieCd": item["movieCd"],
        "movieTitle": item["movieNm"],
        "movieETitle": item["movieNmEn"],
        "openDay": item["openDt"],
        "dirName": dir_name
    })

df = pd.DataFrame(tot_data)

이 패턴이 좋은 이유는 딱 두 개다.

  • 원본 응답 확인과 분석용 테이블 생성을 분리한다
  • 최종 DataFrame이 깔끔하다

구현 관점에서 기억할 포인트

1. DataFrame은 마지막에 만든다

먼저 JSON을 “표처럼 쓸 수 있는 구조”로 재정리해야 한다. 자료에서도 tot_data를 먼저 만들고 나중에 DataFrame으로 넘긴다.

2. 리스트 방식은 빠르고, 딕셔너리 방식은 읽기 좋다

짧은 코드는 리스트, 실전성은 딕셔너리 쪽이 더 강하다.

3. 중첩 구조는 반드시 평탄화해야 한다

directors, companys 같은 nested 필드는 그대로 두지 말고 필요한 값만 뽑는 게 좋다.

4. 원본 확인용 DataFrame과 최종 분석용 DataFrame은 다르게 가져간다

이 구분을 해두면 수집 코드가 훨씬 깔끔해진다.


JSON 응답을 DataFrame으로 정리할 때는 바로 변환하기보다, 필요한 필드만 평탄화해서 리스트나 딕셔너리로 다시 쌓은 뒤 넘기는 방식이 가장 실전적이다.