파이썬 API 데이터 수집 정리 | urllib와 json으로 JSON 응답 받아 DataFrame 만들기

파이썬으로 API 데이터를 수집할 때 핵심은 단순히 요청을 보내는 데 있지 않다.

실제로 중요한 건 요청 URL을 정확히 만들고, JSON 응답 구조를 파악한 뒤, 필요한 값만 추려서 DataFrame으로 정리하는 것이다. API 수집은 결국 호출보다 정리가 더 중요하다. 자료에서도 API 요청 → JSON 파싱 → 필요한 필드 추출 → DataFrame 변환까지의 흐름이 한 세트로 설명된다.

API 데이터 수집은 보통 이 순서로 진행된다

실무나 분석용 스크립트에서 API 수집은 대체로 아래 순서로 진행된다.

  1. 어떤 API 엔드포인트를 호출할지 정한다
  2. key와 파라미터를 포함해 요청 URL을 만든다
  3. HTTP 요청을 보낸다
  4. JSON 응답을 파싱한다
  5. 필요한 필드만 추출한다
  6. 리스트에 누적한 뒤 DataFrame으로 변환한다

이 흐름을 코드로 끊어서 보면 API 수집 구조가 훨씬 명확해진다.


API는 웹에서 데이터를 공식적으로 주고받는 방식이다

API는 웹사이트의 화면을 긁어오는 방식과 다르다.

서버가 정해진 형식으로 데이터를 공식 제공하는 방식이라, 응답 구조와 요청 파라미터가 문서화되어 있는 경우가 많다. 자료에서도 웹 데이터 접근 방식 중 API는 “공식적으로 정보를 제공하는 경우”로 설명되고, JSON 응답은 파이썬 자료형으로 변환해 다루기 쉽다고 정리되어 있다.

즉, API 수집의 장점은 다음과 같다.

  • 응답 구조가 비교적 일정하다
  • 필요한 파라미터를 명시적으로 줄 수 있다
  • JSON 형태라 DataFrame과 연결하기 쉽다

1. 요청 URL부터 정확히 만들어야 한다

API 수집에서 첫 단계는 URL 조합이다.

보통 이런 구조를 가진다.

base_url + "?key=..." + "&파라미터=값"

KOBIS 영화목록 API 예시를 보면 기본 URL에 인증키와 itemPerPage를 붙여 요청 URL을 만든다. 자료에서도 영화목록 JSON API 주소에 key와 itemPerPage=50을 직접 붙여 URL을 구성하는 흐름이 나온다.

예시 코드는 이렇게 쓸 수 있다.

import urllib.request
import json
import pandas as pd

url_p1 = "<http://www.kobis.or.kr/kobisopenapi/webservice/rest/movie/searchMovieList.json>"
key = "발급받은_개인키"
item_per_page = "50"

url = url_p1 + "?key=" + key + "&itemPerPage=" + item_per_page
print(url)

여기서 중요한 건 URL이 곧 요청 내용이라는 점이다.

  • 어떤 API를 호출할지
  • 몇 건을 받을지
  • 어떤 조건으로 필터링할지

이 정보가 전부 URL 파라미터에 들어간다.


2. urllib.request.urlopen()으로 응답 받기

요청 URL이 준비되면 실제로 서버에 요청을 보낸다.

movie_page = urllib.request.urlopen(url)

이 단계는 단순해 보이지만, 여기서 끝이 아니다.

urlopen()이 반환하는 것은 바로 dict가 아니라 응답 객체다. 따라서 본문을 읽고 문자열로 디코딩해야 한다.

raw_text = movie_page.read().decode("utf-8")
print(type(raw_text))

자료에서도 urllib.request.urlopen(url)로 요청을 보낸 뒤, .read().decode("utf-8")를 거쳐 JSON 문자열을 가져오는 흐름이 확인된다. 또한 HTTP 상태 코드 200이 정상 응답 예시로 나온다.


3. JSON 응답은 json.loads()로 파싱해야 한다

API 응답은 보통 JSON 문자열 형태로 온다.

그래서 바로 response["key"]처럼 접근할 수 없고, 먼저 파싱해야 한다.

movie_data = json.loads(raw_text)
print(type(movie_data))

이제야 movie_data는 파이썬 dict처럼 다룰 수 있다.

자료에서도 응답을 json.loads(movie_page.read().decode("utf-8"))로 파싱한 뒤, 최상위 자료형이 dict이고 그 아래에 movieListResult와 movieList가 있는 구조를 확인하는 과정이 나온다.


4. 응답 구조를 먼저 확인해야 한다

이 단계가 은근히 중요하다.

API 수집에서 가장 흔한 실수는 구조를 확인하지 않고 바로 값을 뽑으려는 것이다.

예를 들어 KOBIS 응답 구조는 이런 식이다.

movie_data["movieListResult"]["movieList"]

즉,

  • 최상위는 dict
  • "movieListResult"도 dict
  • "movieList"는 list

이 구조를 알아야 반복문을 정확히 돌릴 수 있다.

확인 코드는 보통 이렇게 쓴다.

print(type(movie_data))
print(movie_data.keys())

print(type(movie_data["movieListResult"]))
print(movie_data["movieListResult"].keys())

print(type(movie_data["movieListResult"]["movieList"]))
print(len(movie_data["movieListResult"]["movieList"]))

자료에서도 movie_data.keys(), movie_data["movieListResult"].keys(), type(...["movieList"]), len(...)을 순서대로 확인하는 흐름이 나온다.


5. 필요한 값만 추출해야 한다

실제 응답은 필드가 많다.

하지만 분석용 DataFrame으로 만들 때는 전부 다 담기보다 필요한 값만 선택해서 가져오는 편이 낫다.

예를 들어 영화 목록에서 아래 정보만 쓰겠다고 정할 수 있다.

  • movieCd
  • movieNm
  • movieNmEn
  • openDt
  • directors

자료에서도 전체 응답에서 영화 코드, 제목, 영문 제목, 개봉일, 감독명을 중심으로 추출하는 예시가 직접 나온다.

영화 한 건을 기준으로 보면 이런 식이다.

sample = movie_data["movieListResult"]["movieList"][0]

print(sample["movieCd"])
print(sample["movieNm"])
print(sample["movieNmEn"])
print(sample["openDt"])
print(sample["directors"])

이렇게 한 건에서 원하는 필드가 잘 나오는지 먼저 확인한 뒤, 그 규칙을 전체 데이터에 반복 적용하는 방식이 안정적이다.


6. 빈 리스트 예외 처리가 필요하다

여기서 중요한 실무 포인트가 하나 나온다.

모든 영화 데이터에 감독 정보가 항상 들어 있는 것은 아니다.

예를 들어 아래처럼 directors가 빈 리스트일 수 있다.

sample["directors"] == []

이 상태에서 바로 아래처럼 접근하면 에러가 난다.

sample["directors"][0]["peopleNm"]   # IndexError 가능

자료에서도 실제로 두 번째 영화나 특정 영화에서 directors가 []라 IndexError: list index out of range가 발생하고, 이후 if data["directors"] == []: 조건으로 예외 처리하는 흐름이 나온다.

그래서 보통 이렇게 처리한다.

if sample["directors"] == []:
    director_name = ""
else:
    director_name = sample["directors"][0]["peopleNm"]

이 포인트가 중요한 이유는, API 응답은 “형식은 같아 보여도 값이 비어 있는 경우”가 꽤 많기 때문이다.


7. 반복문으로 필요한 값만 tot_data에 쌓기

이제 전체 영화 목록을 순회하면서 필요한 필드만 누적하면 된다.

tot_data = []

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

    if data["directors"] == []:
        director_name = ""
    else:
        director_name = data["directors"][0]["peopleNm"]

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

이 방식의 장점은 단순하다.

  • 응답 구조가 조금 복잡해도
  • 내가 필요한 데이터만
  • 깔끔하게 다시 정리할 수 있다

자료에서도 tot_data = []를 만든 뒤, 반복문 안에서 개별 영화 정보를 변수로 분해하고, 마지막에 필요한 값만 리스트 형태로 append() 하는 흐름이 나온다.


8. 마지막에 DataFrame으로 변환한다

tot_data가 준비되면 이제 Pandas로 옮기면 된다.

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

print(movie_df.head())

이렇게 하면 API 응답을 분석하기 쉬운 표 구조로 바꿀 수 있다.

자료에서도 최종적으로 pd.DataFrame(data=tot_data, columns=[...]) 형태로 DataFrame을 만들고, 이후에는 바로 분석용 테이블처럼 다루는 흐름이 나온다. 또 리스트 기반 누적뿐 아니라 dict 기반 누적으로 pd.DataFrame(tot_data)를 만드는 방식도 함께 소개된다.


전체 흐름을 한 번에 보면

실전에서는 이 흐름을 한 번에 이어서 쓴다.

import urllib.request
import json
import pandas as pd

url_p1 = "<http://www.kobis.or.kr/kobisopenapi/webservice/rest/movie/searchMovieList.json>"
key = "발급받은_개인키"
item_per_page = "50"

url = url_p1 + "?key=" + key + "&itemPerPage=" + item_per_page

movie_page = urllib.request.urlopen(url)
movie_data = json.loads(movie_page.read().decode("utf-8"))

tot_data = []

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

    if data["directors"] == []:
        director_name = ""
    else:
        director_name = data["directors"][0]["peopleNm"]

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

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

print(movie_df.head())

이 코드는 짧아 보여도, 사실 아래 네 단계가 다 들어 있다.

  • 요청 URL 만들기
  • HTTP 요청 보내기
  • JSON 파싱하기
  • 필요한 필드만 추출해서 표로 만들기

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

1. URL 설계가 수집 조건을 결정한다

몇 건을 가져올지, 어떤 API를 부를지, 어떤 옵션을 줄지는 URL 파라미터가 결정한다. 자료에서도 itemPerPage=50을 추가해 요청 수를 늘리는 방식이 직접 설명된다.

2. 응답은 바로 dict가 아니다

urlopen() 결과는 응답 객체고, .read().decode("utf-8")를 거쳐 문자열을 얻은 뒤 json.loads()로 파싱해야 한다.

3. 구조 확인 없이 바로 뽑지 않는다

keys(), type(), len()으로 응답 구조를 먼저 확인해야 반복문이 안정적으로 작성된다.

4. 필요한 필드만 따로 정리하는 습관이 중요하다

API 응답 전체를 바로 DataFrame으로 바꾸는 것도 가능하지만, 분석용 데이터셋으로 쓸 때는 필요한 값만 추려 tot_data에 쌓는 편이 훨씬 깔끔하다. 자료에서도 원본 전체를 바로 DataFrame으로 바꾸는 예시와, 필요한 값만 골라 다시 DataFrame으로 만드는 예시가 나란히 나온다.

5. 빈 리스트나 누락 필드 예외 처리를 반드시 넣어야 한다

특히 리스트 내부 첫 번째 값을 바로 참조하는 경우는 [] 여부를 먼저 확인하는 습관이 필요하다. 감독 정보 예외 처리가 대표적인 사례다.


한 줄 정리

파이썬 API 데이터 수집은 URL 요청보다, JSON 응답에서 필요한 값만 안전하게 추출해 DataFrame으로 정리하는 과정이 핵심이다.