requests.get 정리 | params로 API URL 깔끔하게 보내는 방법

파이썬에서 API를 호출할 때 requests를 쓰는 가장 큰 이유는 URL 문자열을 직접 조립하지 않아도 되고, 응답 JSON도 바로 파싱할 수 있기 때문이다.

같은 API 요청이라도 urllib 방식이 “문자열 URL 완성”에 가깝다면, requests는 base_url과 params를 분리해 더 읽기 쉬운 코드로 관리하는 방식에 가깝다. 자료에서도 기존 urllib 방식과 대비해서, requests.get(base_url, params=my_params) 구조를 “좀 더 깔끔한 방식”으로 소개하고 있다.

이 글의 포인트

이 글은 “API가 무엇인가”를 다시 설명하는 글이 아니다.

핵심은 이미 알고 있는 JSON API 요청을 requests 스타일로 바꿨을 때 코드가 왜 더 좋아지는가다.

즉, 초점은 이거다.

  • URL 직접 조립 대신 params 딕셔너리 사용
  • urllib.request.urlopen() 대신 requests.get()
  • json.loads() 대신 res.json()
  • 요청 조건이 코드에서 더 잘 보이는 구조

urllib 방식이 불편한 이유부터 보면

기존 urllib 스타일은 보통 이렇게 시작한다.

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

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

이 방식도 동작은 한다.

그런데 요청 파라미터가 많아질수록 이런 문제가 생긴다.

  • 문자열 연결이 길어진다
  • ?, & 위치를 직접 신경 써야 한다
  • 어떤 요청값을 넣었는지 한눈에 잘 안 보인다
  • 파라미터를 추가하거나 수정할 때 실수하기 쉽다

즉, 동작은 하지만 관리하기 좋은 코드는 아니다.


requests는 요청 정보를 딕셔너리로 분리한다

requests 스타일의 핵심은 URL과 요청 파라미터를 분리하는 것이다.

자료에서도 아래처럼 기본 URL은 따로 두고, 실제 요청사항은 my_params 딕셔너리로 정리한 뒤 requests.get(base_url, params=my_params) 형태로 요청한다.

import requests

base_url = "<http://www.kobis.or.kr/kobisopenapi/webservice/rest/movie/searchMovieList.json>"

my_params = {
    "key": "발급받은_개인키",
    "itemPerPage": 50
}

res = requests.get(base_url, params=my_params)

이 구조가 좋은 이유는 바로 보인다.

  • 어디가 기본 주소인지 명확하다
  • 어떤 파라미터를 넘기는지 딕셔너리에서 한 번에 보인다
  • 값만 바꾸면 같은 요청 구조를 재사용하기 쉽다

즉, 문자열을 만들기보다 요청 조건을 선언하는 코드에 가깝다.


params를 쓰면 코드가 왜 깔끔해질까

예를 들어 요청조건이 늘어난다고 해보자.

my_params = {
    "key": "발급받은_개인키",
    "itemPerPage": 50,
    "curPage": 2,
    "openStartDt": "20260101"
}

이렇게 쓰면 “내가 어떤 조건으로 요청하는지”가 바로 읽힌다.

반면 문자열 조립 방식이면 보통 이런 식이 된다.

url = base_url + "?key=" + key + "&itemPerPage=50&curPage=2&openStartDt=20260101"

이건 길어질수록 읽기 불편하다.

그래서 requests는 단순 편의성보다도 가독성과 유지보수성에서 강하다.

자료에서도 이 방식을 “파라미터와 그에 대한 값을 줄 테니, 최종 주소는 네가 만들어줘”라는 개념으로 설명한다. 즉, 내가 직접 URL을 조립하지 않고, 요청 조건만 넘기는 방식이다.


상태 확인도 requests 쪽이 더 읽기 쉽다

응답을 받았다고 해서 바로 데이터를 쓰면 안 된다.

먼저 정상 응답인지 확인해야 한다.

자료에서는 res.ok를 이용해 성공 여부를 체크한 뒤, 성공했을 때만 res.json()을 호출하는 흐름이 나온다.

if res.ok:
    result = res.json()
else:
    print("HTTP 통신 체크~~~")

이 흐름이 좋은 이유는 단순하다.

  • 성공/실패 분기가 눈에 잘 보인다
  • 바로 JSON 파싱까지 이어진다
  • 코드가 짧고 실전에서 쓰기 편하다

urllib에서는 상태 코드 확인, 응답 읽기, 디코딩, JSON 파싱이 상대적으로 분리되어 있었는데, requests는 그 흐름이 더 짧다.


res.json()이 중요한 이유

이 부분이 requests의 제일 큰 장점 중 하나다.

기존 방식에서는 보통 이렇게 했다.

raw_text = response.read().decode("utf-8")
data = json.loads(raw_text)

그런데 requests에서는 JSON 응답이면 보통 아래 한 줄로 끝난다.

result = res.json()

즉,

  • json 모듈 import
  • decode("utf-8")
  • json.loads()

이 단계를 직접 쓰지 않아도 된다.

자료에서도 requests는 “json 변환에 대한 모듈이 내장”되어 있어서 import json이 굳이 필요 없다고 설명한다.


KOBIS API 예시로 전체 흐름 보기

자료의 KOBIS 영화목록 API 예시를 requests 방식으로 정리하면 이런 구조다.

import requests
import pandas as pd

base_url = "<http://www.kobis.or.kr/kobisopenapi/webservice/rest/movie/searchMovieList.json>"

my_params = {
    "key": "발급받은_개인키",
    "itemPerPage": 50
}

res = requests.get(base_url, params=my_params)

if res.ok:
    result = res.json()
else:
    raise ValueError("HTTP 요청 실패")

여기까지 오면 result는 이미 파이썬 dict다.


응답 구조 확인은 여전히 필요하다

requests를 써도 JSON 구조를 안 보면 안 된다.

편해진 건 “요청과 파싱 방식”이지, 데이터 구조가 단순해진 건 아니기 때문이다.

자료에서도 result.keys(), result["movieListResult"].keys()로 구조를 먼저 확인하는 흐름이 나온다.

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

KOBIS 영화목록 구조는 보통 이렇게 본다.

movieListResult
└── movieList

즉, 실제 데이터는 여기 있다.

result["movieListResult"]["movieList"]

DataFrame으로 바로 바꿀 수도 있다

requests 방식과 JSON 구조가 잘 맞으면, 응답 일부는 바로 DataFrame으로 넘기기도 쉽다.

자료에서도 pd.DataFrame(result["movieListResult"]["movieList"])처럼 한 번에 DataFrame으로 바꾸는 예시가 나온다.

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

이 방식은 원본 구조를 빨리 훑어볼 때 유용하다.

다만 여기엔 한 가지 문제가 있다.

중첩된 필드가 그대로 들어온다는 점이다. 예를 들어 directors처럼 list 안에 dict가 있는 값은 바로 분석하기엔 지저분하다. 자료에서도 원본 DataFrame으로 바꾸면 앞쪽 필드는 깔끔하지만 뒤쪽의 중첩 구조는 그대로 남는다고 설명한다.

그래서 실전에서는 보통 필요한 값만 다시 추출해서 정리된 DataFrame을 만든다.


requests 방식에서도 결국 필요한 값만 다시 정리한다

예를 들어 영화 코드, 제목, 영문 제목, 개봉일, 감독명을 쓰고 싶다면 이렇게 처리할 수 있다.

new_list = []

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

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

    new_list.append(one_data)

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

이 코드가 중요한 이유는 두 가지다.

  1. requests는 요청과 파싱을 더 깔끔하게 만든다
  2. 하지만 데이터 정리 기준은 여전히 내가 설계해야 한다

즉, requests가 모든 걸 자동으로 해결해주는 건 아니다.

다만 “받아오는 과정”이 더 깔끔해진다는 게 포인트다.

자료에서도 감독 정보 리스트를 따로 보고, **dict 병합으로 첫 번째 감독 정보를 원본 dict에 붙이는 흐름이 나온다.


urllib와 비교하면 어디가 달라졌나

이 글을 10일차 글과 안 겹치게 보려면, 차이를 명확히 봐야 한다.

urllib 스타일

  • URL을 직접 문자열로 만든다
  • 응답을 읽고 디코딩해야 한다
  • json.loads()를 직접 호출한다
  • 요청 코드보다 문자열 조립이 먼저 눈에 들어온다

requests 스타일

  • base_url과 params를 분리한다
  • requests.get()으로 바로 요청한다
  • res.ok로 상태 확인이 읽기 쉽다
  • res.json()으로 바로 dict/list 변환이 가능하다

즉, 핵심 차이는 성능보다 코드 구조와 가독성이다.


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

1. params는 요청조건을 코드로 드러내는 방식이다

문자열 URL 조립보다 훨씬 읽기 쉽다.

요청 파라미터가 늘어날수록 차이가 커진다.

2. res.ok와 res.json() 조합이 깔끔하다

응답 확인과 JSON 파싱이 짧은 흐름으로 이어진다. 자료에서도 이 조합을 기본 흐름으로 사용한다.

3. requests가 구조를 단순하게 만들어주진 않는다

JSON 응답 구조는 여전히 직접 확인해야 한다. keys(), type() 확인 습관은 그대로 필요하다.

4. 원본 DataFrame 변환과 분석용 DataFrame 변환은 다르다

pd.DataFrame(result["movieListResult"]["movieList"])는 빠른 확인용으로 좋고, 실제 분석용 데이터셋은 필요한 필드만 다시 정리하는 편이 낫다.


한 줄 정리

requests.get()은 API 요청을 “문자열 URL 조립”이 아니라 “기본 주소 + 파라미터 선언” 방식으로 바꿔서, 더 읽기 쉽고 관리하기 좋은 코드로 만들어준다.