파이썬 Requests 응답 JSON 처리 완벽 가이드: resp.json(strict=False) 활용과 ValueError 대처법
🐍 Python Requests로 API 응답 JSON, 안전하고 정확하게 다루는 핵심 노하우
웹 API와 통신하며 데이터를 가져올 때 파이썬의 requests 라이브러리는 필수입니다.
응답을 받은 후 가장 흔하게 마주치는 작업은 응답 본문(Response Body)을 파이썬 객체(주로 딕셔너리나 리스트)로 변환하는 JSON 처리일 것입니다.
이 과정에서 예상치 못한 에러나 까다로운 서버 응답 포맷 때문에 종종 어려움을 겪게 됩니다.
특히 JSON 디코딩 실패를 의미하는 ValueError나, 서버가 잘못된 Content-Type 헤더를 보낼 때의 문제를 해결하는 것이 중요합니다.
이번 글이 파이썬 Requests를 사용해 JSON 응답을 안전하고 정확하게 처리하는 데 큰 도움이 될 것입니다.
Requests 라이브러리는 응답 객체의 resp.json() 메서드를 통해 JSON 처리를 매우 쉽게 만들어줍니다.
하지만 서버 응답이 표준 JSON 형식이 아니거나, 응답에 아무 내용이 없는 경우 등 다양한 예외 상황에 대비해야 합니다.
이 가이드에서는 resp.json()의 동작 원리를 깊이 있게 파헤치고, 특히 새롭게 추가된 strict 인수를 활용하여 JSON 표준을 엄격하게 따르지 않는 응답도 유연하게 처리하는 방법을 다룹니다.
또한, 서버가 HTTP 표준을 무시하고 잘못된 Content-Type을 보낼 때 발생하는 문제의 원인과 해결책까지 상세히 알려드리겠습니다.
📋 목차
💻 requests 응답 객체와 resp.json() 메서드 이해하기
파이썬 requests 라이브러리로 API 요청을 보내면 requests.Response 객체가 반환됩니다.
이 객체는 HTTP 응답의 모든 정보를 담고 있으며, 우리가 가장 관심을 갖는 부분은 응답 본문(Body)입니다.
응답 본문은 resp.text나 resp.content를 통해 문자열 또는 바이트 형태로 접근할 수 있습니다.
그러나 JSON 데이터를 처리할 때는 resp.json() 메서드를 사용하는 것이 가장 안전하고 효율적입니다.
resp.json()은 내부적으로 파이썬의 표준 json 모듈을 사용하여 응답 본문(텍스트)을 파이썬 객체로 역직렬화(Deserialize)합니다.
이때 기본적으로 응답의 ‘Content-Type’ 헤더를 확인하여 해당 데이터가 JSON 형식인지 검증하는 과정을 거칩니다.
만약 Content-Type이 application/json 또는 이와 유사한 형태가 아니라면, requests는 기본적으로 JSON 파싱을 시도하지 않고 오류를 발생시키려고 합니다.
이 메서드는 성공적으로 파싱을 완료하면 파이썬의 딕셔너리(Dict) 또는 리스트(List)를 반환합니다.
만약 응답 본문이 비어 있거나 유효한 JSON 형식이 아닌 경우에는 json.JSONDecodeError를 래핑한 ValueError 예외를 발생시킵니다.
따라서 JSON 처리를 할 때는 반드시 try...except ValueError 구문으로 예외 처리를 해주는 것이 중요합니다.
resp.json()을 사용할 때 가장 흔한 오류 패턴과 이를 해결하는 방법을 코드와 함께 살펴보겠습니다.
import requests
from requests.exceptions import JSONDecodeError
url = "https://api.example.com/data"
response = requests.get(url)
try:
# 일반적인 JSON 파싱 시도
data = response.json()
print("JSON 데이터:", data)
except JSONDecodeError as e:
# requests v2.31.0 이상에서 직접 JSONDecodeError 처리 권장
print(f"JSON 디코딩 오류 발생: {e}")
print("응답 텍스트:", response.text)
except ValueError as e:
# 구버전 requests 대응 및 일반적인 JSON 파싱 오류 처리
print(f"ValueError 발생 (JSON 파싱 실패): {e}")
print("응답 텍스트:", response.text)
# Content-Type 확인
print(f"Content-Type 헤더: {response.headers.get('Content-Type')}")
위 예시에서 볼 수 있듯이, response.json()은 응답의 Content-Type 헤더가 JSON을 명시하지 않더라도 실제로 응답 본문이 유효한 JSON 형식이면 파싱을 시도하고 성공하는 경우가 많습니다.
하지만 Content-Type을 검사하는 기본 메커니즘을 우회할 수 있도록 Requests 라이브러리가 유연하게 처리해준다는 점을 이해해야 합니다.
다음 단계에서는 이 기본 검증 과정을 더 유연하게 만드는 strict 인자의 활용법을 자세히 알아보겠습니다.
⚙️ resp.json(strict=False) 활용: 유효하지 않은 JSON 처리
파이썬의 json 모듈은 기본적으로 JSON 표준(RFC 8259)을 매우 엄격하게 준수합니다.
표준 JSON에서는 객체 키에 반드시 쌍따옴표(")를 사용해야 하고, 파이썬의 True, False, None 대신 true, false, null을 사용해야 합니다.
그러나 일부 구형 시스템이나 비표준적인 API는 따옴표 없는 키를 사용하거나, 주석을 포함하거나, 작은따옴표(')를 허용하는 등 유효하지 않은 JSON을 반환하는 경우가 있습니다.
이런 경우, 기본 resp.json() 메서드는 ValueError를 발생시키며 파싱에 실패합니다.
이 문제를 해결하기 위해 Requests 라이브러리는 resp.json() 메서드에 strict 인자를 제공합니다.
이 인자는 내부적으로 json.loads()에 전달되어 JSON 파싱의 엄격성을 제어합니다.
기본값은 True이며, JSON 표준을 벗어난 형식은 허용하지 않습니다.
resp.json(strict=False)로 설정하면, json 모듈은 JSON 확장 기능을 허용하는 jsonc (JSON with Comments) 포맷과 같은 비표준 형식을 더 유연하게 처리할 수 있습니다.
💡 TIP: strict=False는 특히 자바스크립트 객체 리터럴(JavaScript Object Literal) 형태의 응답을 처리할 때 유용합니다. JS 객체 리터럴은 JSON과 매우 유사하지만, 키에 따옴표를 생략하거나 작은따옴표를 사용할 수 있습니다.
하지만 strict=False가 만능은 아닙니다.
이것은 단순히 JSON 파서의 유연성을 높여주는 것일 뿐, 근본적으로 JSON 형식이 크게 훼손된 경우에는 여전히 ValueError를 발생시킵니다.
또한, JSON 표준을 벗어난 데이터를 처리하는 것은 잠재적인 보안 위험이나 예기치 않은 동작을 유발할 수 있으므로, 가능하다면 API 제공자에게 표준 JSON 응답을 요청하는 것이 가장 좋습니다.
다음은 strict=False를 활용하여 비표준 JSON을 처리하는 코드 예시입니다.
서버가 아닌 로컬에서 임의의 비표준 JSON 문자열을 생성하여 requests의 응답처럼 시뮬레이션 해보았습니다.
import requests
import json
# 비표준 JSON (JS 객체 리터럴 형태)
non_standard_json = "{'name': 'API Data', 'status': true, /* 주석 */ 'count': 10}"
# requests 응답 객체 시뮬레이션
class MockResponse:
def __init__(self, text):
self._text = text
def json(self, **kwargs):
# resp.json()의 핵심 로직을 흉내냅니다
return json.loads(self._text, **kwargs)
# 1. strict=True (기본값) 시도: 실패
mock_resp = MockResponse(non_standard_json)
try:
mock_resp.json(strict=True) # 기본 동작
except ValueError as e:
print(f"1. strict=True 실패: {e}\n")
# 2. strict=False 시도: 성공 가능성
try:
# requests는 내부적으로 'strict' 인자를 json.loads()에 전달합니다.
data = mock_resp.json(strict=False)
print(f"2. strict=False 성공: {data}")
print(f"처리된 'status' 값 타입: {type(data['status'])}")
except ValueError as e:
# 참고: 실제 json 모듈은 strict=False로도 주석이나 작은따옴표를 처리 못할 수 있습니다.
# 이 경우, 직접 응답 텍스트를 정제해야 합니다.
print(f"2. strict=False로도 실패: {e}")
위 코드를 실행해보면 strict=True일 때는 대부분 ValueError가 발생하지만, strict=False를 사용하면 파이썬의 json 모듈이 해당 비표준 요소를 해석하여 성공적으로 파싱할 가능성이 높아집니다.
그러나 만약 strict=False로도 해결이 안 된다면, 응답 텍스트(resp.text)를 직접 가져와서 문자열 정제(예: 작은따옴표를 쌍따옴표로 바꾸기, 주석 제거) 후 json.loads()를 사용하는 수동 처리 방법을 고려해야 합니다.
❌ JSON 디코딩 실패의 주범, ValueError 처리 전략
Requests에서 resp.json() 메서드를 호출했을 때 가장 흔하게 발생하는 예외는 ValueError입니다.
requests 라이브러리는 내부적으로 json.loads()에서 발생하는 json.JSONDecodeError를 잡아 ValueError로 변환하여 던집니다 (requests v2.31.0 이후부터는 JSONDecodeError도 직접 처리할 수 있습니다).
이 오류는 근본적으로 응답 본문이 유효한 JSON 형식이 아니라는 것을 의미합니다.
이 오류가 발생하는 주요 원인은 크게 세 가지로 나눌 수 있으며, 이에 따라 적절한 처리 전략을 세워야 합니다.
✨ 주요 발생 원인과 대처법
- 1️⃣응답 본문이 비어있는 경우: 서버 응답 코드가 204 No Content처럼 본문이 없는 경우입니다. 처리: if response.content: 또는 if response.text:로 본문 존재 여부를 먼저 검사합니다.
- 2️⃣유효하지 않은 JSON 형식: 키에 따옴표가 없거나, 주석이 있거나, HTML 에러 페이지 등이 반환된 경우입니다. 처리: resp.json(strict=False)를 시도하거나, response.text를 출력하여 어떤 내용이 반환되었는지 확인합니다.
- 3️⃣잘못된 Content-Type 헤더: 서버가 JSON 응답을 보내면서 Content-Type을 text/plain 등으로 잘못 설정한 경우입니다. requests는 보통 파싱을 시도하지만 실패할 가능성이 있습니다. 처리: resp.json()이 실패하면 response.text를 수동으로 json.loads()에 전달하여 파싱을 시도합니다.
가장 안전한 처리 전략은 try...except 구문으로 JSON 디코딩 오류를 포착하고, 오류 발생 시 응답 텍스트를 검사하여 문제의 원인을 진단하는 것입니다.
특히, 응답 코드가 200 OK임에도 ValueError가 발생하면, 서버가 HTML 형식의 에러 메시지나 알 수 없는 형식의 텍스트를 JSON인 것처럼 보냈을 가능성이 매우 높습니다.
⚠️ 주의: resp.json()을 호출하기 전에 반드시 HTTP 응답 코드(response.status_code)가 200번대 성공 코드인지 확인하는 것이 좋습니다. 4xx나 5xx 에러 코드는 JSON 형식이 아닌 에러 메시지를 포함하는 경우가 많아 ValueError의 직접적인 원인이 됩니다.
아래는 가장 일반적인 패턴인 응답 본문이 비어있을 때와 유효하지 않은 JSON을 받았을 때를 모두 처리할 수 있는 견고한 코드 패턴입니다.
requests 최신 버전에서는 JSONDecodeError를 직접 포착하는 것이 더 명확합니다.
import requests
from requests.exceptions import JSONDecodeError
def safe_json_load(response):
# 1. 응답 코드가 성공인지 확인 (선택 사항이지만 권장)
response.raise_for_status()
# 2. 응답 텍스트가 비어있는지 확인 (204 No Content 등의 경우)
if not response.text:
return {} # 빈 딕셔너리 반환 또는 None 반환 등 정책에 따름
try:
# 3. JSON 파싱 시도 (strict=False를 유연하게 적용 가능)
return response.json(strict=False)
except (ValueError, JSONDecodeError) as e:
# 4. 파싱 실패 시 예외 처리
print(f"JSON 파싱 실패 (Error: {e})")
print(f"문제의 응답 텍스트: {response.text[:100]}...")
# 필요한 경우, response.text를 반환하거나 다른 방식으로 처리
return None
# Example Usage: (실제 API URL로 대체 필요)
# resp = requests.get("https://api.example.com/endpoint")
# data = safe_json_load(resp)
이와 같은 함수를 만들어 사용하면, 비어있는 응답이나 유효하지 않은 응답 본문 때문에 프로그램이 갑작스럽게 종료되는 것을 방지하고, 어떤 오류가 발생했는지 명확하게 기록할 수 있습니다.
⚠️ Content-Type 헤더 무시 시 생기는 문제점과 해결책
HTTP 프로토콜에서 Content-Type 헤더는 서버가 클라이언트에게 보내는 응답 본문이 어떤 형식(application/json, text/html 등)인지 알려주는 중요한 역할을 합니다.
requests 라이브러리의 resp.json() 메서드는 기본적으로 이 Content-Type 헤더를 검사하여 JSON 응답이 맞는지 확인합니다.
서버 개발자의 실수나 설정 오류로 인해, 실제로는 JSON 데이터를 담고 있음에도 Content-Type이 text/plain이나 text/html 등으로 잘못 설정되어 반환되는 경우가 종종 발생합니다.
이 경우, resp.json()은 Content-Type이 JSON이 아니라고 판단하여 파싱을 시도하지 않고 ValueError 또는 JSONDecodeError를 발생시킬 수 있습니다.
✨ Content-Type 검사를 무시하고 JSON을 파싱하는 방법
가장 확실한 해결책은 resp.text를 직접 가져와 파이썬의 json 모듈에 전달하는 것입니다.
이렇게 하면 requests의 Content-Type 검증 로직을 완전히 우회하고, 응답 본문 문자열을 무조건 JSON으로 해석하도록 강제할 수 있습니다.
import requests
import json
from requests.exceptions import RequestException
def force_json_load(response):
try:
# 1. requests 기본 메서드 시도 (Content-Type 검사 통과 여부 확인)
return response.json()
except (requests.JSONDecodeError, ValueError):
# 2. 실패 시, Content-Type 검사 무시하고 텍스트 직접 파싱 시도
try:
return json.loads(response.text)
except json.JSONDecodeError as e:
# 3. 텍스트 자체가 유효한 JSON이 아닌 경우 최종 실패 처리
print(f"강제 JSON 파싱 실패: {e}")
print(f"헤더 Content-Type: {response.headers.get('Content-Type')}")
return None
# 실제 사용 예시 (resp는 requests.Response 객체)
# data = force_json_load(resp)
위의 force_json_load 함수는 Content-Type 헤더에 관계없이 응답 본문을 JSON으로 처리하려 할 때 매우 유용합니다.
다만, 이 방식은 응답 텍스트가 실제로 유효한 JSON 포맷이어야만 성공하며, 만약 HTML 오류 페이지 같은 엉뚱한 텍스트가 들어있다면 여전히 json.JSONDecodeError를 발생시킬 것입니다.
💬 이러한 Content-Type 문제를 자주 겪는다면, 해당 API 제공자에게 HTTP 표준을 준수하도록 요청하는 것이 장기적으로 가장 좋은 해결책입니다. 클라이언트 측에서 매번 예외 처리를 하는 것은 비효율적입니다.
Content-Type 헤더의 중요성은 API 통신에서 데이터 처리의 정확성을 보장하는 데 있습니다.
비록 requests가 때로는 유연하게 처리해주지만, 문제가 발생했을 때 response.text를 통한 수동 파싱은 Content-Type 검사를 무시하는 최후의 수단이 될 수 있습니다.
다음 단계에서는 JSON 응답을 다룰 때 항상 염두에 두어야 할 안전 코딩 가이드를 총정리하겠습니다.
🔑 JSON 응답 처리 시 반드시 기억해야 할 안전 코딩 가이드
Requests를 사용하여 JSON 응답을 다룰 때, 단순히 resp.json()을 호출하는 것만으로는 부족합니다.
실제 운영 환경에서 발생할 수 있는 모든 예외 상황에 대비하여 코드를 견고하게 만드는 안전 코딩 습관이 중요합니다.
다음은 안전하고 신뢰할 수 있는 JSON 데이터 처리를 위한 핵심 가이드입니다.
✨ JSON 처리 시 핵심 안전 수칙 5가지
- ✅HTTP Status Code 검사: resp.json() 호출 전에 response.raise_for_status()를 사용하여 4xx/5xx 에러를 즉시 감지하고 HTTPError를 발생시키세요. JSON 파싱 이전에 통신 오류부터 명확히 분리해야 합니다.
- ✅명시적인 예외 처리: try...except (ValueError, requests.JSONDecodeError) 구문을 사용하여 JSON 파싱 실패를 명시적으로 처리하고, 실패 시 응답 텍스트를 로그에 남기세요.
- ✅빈 응답 처리: response.text가 비어 있는지(204 No Content 등) 확인하여 ValueError를 방지하세요. 빈 문자열에 json.loads()를 시도하면 오류가 발생합니다.
- ✅딕셔너리 키 접근 안전화: JSON 데이터를 파이썬 딕셔너리로 받은 후, 키에 접근할 때는 data.get('key_name', default_value)를 사용하여 키가 존재하지 않을 때 발생하는 KeyError를 방지해야 합니다.
- ✅strict=False 신중히 사용: 비표준 JSON을 처리할 때만 strict=False를 사용하고, 가능하다면 수동 파싱 후 데이터 검증 단계를 추가하여 예상치 못한 데이터 형식을 필터링하세요.
JSON 응답 처리의 전 과정은 데이터 요청, 응답 수신, 상태 코드 확인, JSON 파싱, 데이터 추출 순서로 진행됩니다. 이 흐름 중 어느 하나라도 실패할 가능성이 있으므로, 각 단계마다 적절한 예외 처리가 필요합니다.
💎 핵심 포인트:
Requests는 resp.json()을 통해 JSON 처리를 자동화해주지만, 이 메서드는 Content-Type 검사와 JSON 표준 검사를 동시에 수행합니다. 비표준 응답(Content-Type 오류, 비표준 JSON 포맷 등)에 대해서는 try...except ValueError를 사용하거나 resp.json(strict=False)로 유연성을 확보한 후, 최종적으로 response.text를 통한 수동 파싱을 최후의 방어선으로 구축해야 합니다.
이 가이드라인을 따르면 API 서버의 불안정성이나 비표준적인 응답 형태에 크게 영향을 받지 않고 안정적으로 데이터를 처리할 수 있습니다.
견고한 코드는 오류 발생 시에도 시스템이 멈추지 않고, 문제를 정확히 진단할 수 있도록 도와줍니다.
❓ 자주 묻는 질문 (FAQ)
resp.json()을 사용하면 Content-Type 검사를 자동으로 수행하나요?
resp.json(strict=False)는 어떤 상황에서 사용해야 하나요?
JSON 디코딩 실패 시 발생하는 ValueError와 JSONDecodeError는 어떤 차이가 있나요?
응답 텍스트가 비어 있을 때 resp.json()을 호출하면 어떻게 되나요?
서버가 Content-Type을 text/plain으로 보냈지만 내용은 JSON일 때 처리 방법은 무엇인가요?
JSON 응답을 딕셔너리로 받은 후 키가 없을 때의 KeyError는 어떻게 방지해야 하나요?
JSON 파싱 전에 HTTP 상태 코드를 확인해야 하는 이유는 무엇인가요?
resp.json()이 실패했을 때 응답 텍스트를 확인하는 것이 왜 중요한가요?
💾 Requests JSON 응답 처리, 데이터 안정성 확보로 마무리
지금까지 파이썬 requests 라이브러리를 활용하여 API 응답의 JSON 데이터를 안전하게 처리하는 심화 과정을 살펴보았습니다.
단순히 resp.json()을 호출하는 것에서 멈추지 않고, 발생 가능한 모든 예외 상황을 미리 예측하고 대처하는 것이 데이터 안정성 확보의 핵심입니다.
strict=False 인자의 전략적 사용, 그리고 ValueError 예외에 대한 견고한 처리 루틴은 실제 서비스 운영에서 필수적인 요소입니다.
Content-Type이 잘못 설정된 비표준 API 통신 환경에서는 response.text를 통한 수동 파싱이 불가피할 때도 있지만, 이는 어디까지나 차선책이라는 것을 기억해야 합니다.
가장 이상적인 코딩 방식은 HTTP 상태 코드 검사(raise_for_status)를 선행하고, try...except로 JSON 디코딩 오류를 포착하여 예상치 못한 응답 텍스트를 로그에 남기는 것입니다.
API 데이터 연동은 프로그래밍의 가장 기본적이면서도 중요한 부분입니다.
오늘 배운 내용을 바탕으로 여러분의 파이썬 코드가 어떤 외부 환경에서도 흔들림 없이 안정적으로 데이터를 처리할 수 있기를 바랍니다.
🏷️ 관련 태그 : 파이썬requests, resp.json, JSONDecodeError, ValueError처리, strict=False, requests라이브러리, PythonAPI, ContentType, JSON파싱, 안전코딩