파이썬 requests에서 data와 json의 차이는 값을 담는 문법보다 API에 어떤 요청 본문과 Content-Type을 보낼지에 있습니다. API 문서가 application/json을 요구하면 보통 json=payload를 사용하고, 폼 형식이면 data=payload, 파일을 함께 보내는 multipart 요청이면 files=를 검토합니다. 요청이 예상과 다를 때는 응답만 보지 말고 res.request.headers와 res.request.body로 실제 전송 구성을 먼저 확인해야 합니다.
먼저 API 문서에서 요청 형식을 확인합니다
requests.post()를 호출하기 전에 API 문서의 request body 예시와 Content-Type을 한 쌍으로 확인합니다. JSON 객체 예시와 application/json이 함께 제시되었는지, key-value 폼 예시인지, 파일 필드가 있는 multipart 예시인지가 선택 기준입니다.
이 단계에서 중요한 점은 Python 딕셔너리라는 입력 모양만으로 서버가 원하는 형식을 추측하지 않는 것입니다. 같은 딕셔너리라도 data=와 json=에 넣으면 요청 본문이 달라집니다. 실제 API가 요구하는 필드 이름과 형식은 해당 API 문서를 기준으로 판단해야 합니다.
data=, json=, files=는 이렇게 구분합니다
data=: 폼 형식이나 직접 준비한 본문
data에 딕셔너리를 넘기면 requests가 form-encoded 요청 본문으로 인코딩합니다. API 문서가 폼 필드를 요구할 때는 다음처럼 작성합니다.
import requests
payload = {
"name": "kim",
"enabled": "true",
}
res = requests.post("https://api.example.com/items", data=payload)
res.raise_for_status()
data는 딕셔너리나 튜플 목록 외에도 bytes와 파일 객체처럼 요청 본문에 직접 보낼 수 있는 값도 받을 수 있습니다. 따라서 data라는 이름만 보고 항상 JSON이라고 생각하면 안 됩니다.
json=: JSON 직렬화가 필요한 API
API가 JSON 본문을 요구한다면 json=payload가 의도를 가장 분명하게 보여 줍니다. requests는 JSON으로 직렬화하고 application/json을 설정합니다.
payload = {
"name": "kim",
"enabled": True,
}
res = requests.post("https://api.example.com/items", json=payload)
res.raise_for_status()
Python의 True처럼 Python 객체로 작성한 값을 JSON 본문으로 보내야 할 때 특히 읽기 쉽습니다. 다만 어떤 필드가 필수인지, 값의 타입이 무엇인지는 여전히 API 문서가 정한 계약을 따라야 합니다.
files=: multipart 파일 전송
파일 업로드가 포함된 API라면 files=를 사용해 multipart 전송을 구성합니다.
with open("report.csv", "rb") as report:
res = requests.post(
"https://api.example.com/uploads",
files={"file": report},
data={"description": "monthly report"},
)
res.raise_for_status()
파일이 없는 JSON 요청을 files= 방식으로 바꿀 이유는 없습니다. multipart가 필요한지 여부도 API 문서의 Content-Type과 필드 설명으로 확인합니다.
data=json.dumps()를 쓸 때 생기는 함정
기존 코드에서 JSON 문자열을 직접 만들어 data로 넘기는 경우가 있습니다.
import json
import requests
payload = {"name": "kim", "enabled": True}
res = requests.post(
"https://api.example.com/items",
data=json.dumps(payload),
)
이 방식은 JSON 문자열을 본문으로 만들지만 Content-Type: application/json을 자동으로 추가하지 않습니다. 서버가 미디어 타입을 기준으로 파싱한다면 본문 내용이 JSON처럼 보여도 요청 형식이 맞지 않을 수 있습니다. 직접 직렬화해야 할 이유가 없다면 json=payload를 우선 검토하고, 꼭 data=json.dumps()를 사용한다면 API 문서에 맞는 헤더를 명시적으로 관리합니다.
res = requests.post(
"https://api.example.com/items",
data=json.dumps(payload),
headers={"Content-Type": "application/json"},
)
여기서도 헤더만 맞추면 충분하다고 단정하지 말고, 서버가 요구하는 JSON 구조와 필드 타입을 함께 확인해야 합니다.
json=과 다른 본문 인자를 섞지 않습니다
json=을 지정하면서 data= 또는 files=도 함께 넘기는 코드는 의도를 다시 확인해야 합니다. Requests 문서와 API 레퍼런스 기준으로 data 또는 files가 함께 지정되면 json=은 무시됩니다.
즉 다음 코드는 JSON과 폼을 합치는 표현이 아닙니다.
res = requests.post(
"https://api.example.com/items",
data={"source": "batch"},
json={"name": "kim"},
)
이 경우 코드에 적힌 json만 보고 JSON이 전송되었다고 판단하면 안 됩니다. 하나의 요청에서 어떤 본문 방식을 쓸지 정하고, 추가 필드가 필요하면 그 방식에 맞춰 구성합니다. 파일과 일반 필드를 함께 보내야 한다면 API가 multipart를 요구하는지 확인한 뒤 files=와 data= 조합을 사용합니다.
실제 전송된 헤더와 body를 확인하는 순서
코드가 그럴듯해 보여도 서버에 도착한 요청을 확인해야 할 때가 있습니다. 응답을 받은 뒤 준비된 요청의 헤더와 body를 기록하면 선택한 인자가 실제 요청으로 어떻게 변환되었는지 볼 수 있습니다.
res = requests.post(
"https://api.example.com/items",
json={"name": "kim", "enabled": True},
)
print("request headers:", dict(res.request.headers))
print("request body:", res.request.body)
print("response status:", res.status_code)
print("response text:", res.text)
res.raise_for_status()
확인할 것은 네 가지입니다.
Content-Type이 API 문서의 요구와 같은가- body가 JSON 문자열인지, 폼 인코딩인지, multipart 구성인지
- 필드 이름과 값의 타입이 문서의 예시와 같은가
- 응답의 상태와 본문이 요청 처리 결과를 설명하는가
응답이 JSON으로 파싱된다는 사실만으로 요청이 성공했다고 판단하지 않는 편이 안전합니다. Requests 공식 문서도 r.json()만으로 성공 여부를 판단하지 말고 status_code 또는 raise_for_status()를 확인하는 흐름을 설명합니다.
운영 코드에서는 요청 body에 비밀번호, 토큰, 개인정보가 들어갈 수 있으므로 그대로 로그에 남기지 말고 민감한 값을 가린 뒤 필요한 헤더와 구조만 기록합니다.
선택과 검증을 한 줄로 정리하면
- 폼 필드:
data=payload - JSON 객체:
json=payload - 파일을 포함한 multipart:
files=와 API가 요구하는 추가 필드 구성 - 직접 JSON 문자열을 만들 때:
Content-Type을 별도로 확인 - 문제가 생겼을 때:
res.request.headers,res.request.body, 응답 상태와 본문 순서로 확인
저라면 서버 응답 메시지부터 추측하지 않고 API 문서의 Content-Type을 확인한 다음, 코드의 인자를 하나로 단순화하고 준비된 요청을 출력해 비교하겠습니다. 이 순서면 data가 폼으로 인코딩된 문제인지, JSON 헤더가 빠진 문제인지, 아니면 필드 구조 자체가 다른 문제인지 구분하기 쉬워집니다.
출처
- Quickstart — Requests 2.34.2 documentation — Requests Project
- Developer Interface — Requests 2.34.2 documentation — Requests Project
- requests.models — Requests 2.34.2 documentation — Requests Project