파이썬 requests 스트리밍 다운로드, gzip·deflate 자동 디코딩과 브로틀리 선택적 지원 완벽 가이드
⚡ 스트리밍 다운로드와 자동 압축 해제까지 한 번에 이해하는 실전 중심 설명
대용량 파일을 다루다 보면 메모리를 꽉 채우지 않고도 안정적으로 데이터를 받아야 할 때가 많습니다.
특히 API 응답이나 로그, 미디어 파일처럼 지속적으로 전달되는 바이트 스트림을 처리하려면 효율적인 다운로드 패턴이 필수죠.
이 글은 파이썬의 requests 라이브러리로 스트리밍 다운로드를 구현하면서, 서버가 보내는 압축을 자동으로 풀어주는 동작까지 자연스럽게 연결하는 방법을 친근한 방식으로 정리했습니다.
실무 코드에 바로 붙여 넣을 수 있는 기준을 제시하고, 헷갈리기 쉬운 옵션과 안전한 기본값을 구체적으로 짚어 드립니다.
핵심은 간단합니다.
requests의 스트리밍 다운로드는 gzip과 deflate 인코딩을 자동으로 디코딩하며, 브로틀리(br)는 선택적으로 지원됩니다.
즉, 의존성을 추가해 브로틀리 디코더가 준비되어 있다면 자동 해제가 이어지고, 없다면 일반 방식 그대로 데이터를 수신하죠.
여기에 더해 iter_content() 중심의 권장 패턴, 파일에 안전하게 기록하는 방식, Accept-Encoding 헤더의 역할, 그리고 원시 스트림을 사용할 때의 주의점까지 한 번에 묶어 이해하면, 품질과 성능을 모두 챙길 수 있습니다.
아래 목차에 따라 개념부터 실전 코드, 문제 해결 순서로 차근차근 정리했습니다.
📋 목차
🔗 파이썬 requests 스트리밍 다운로드 핵심 개요
requests로 대용량 응답을 받을 때는 stream=True와 iter_content()를 조합해 바이트를 일정 크기씩 읽어 들이는 방식이 표준입니다.
이때 gzip과 deflate 인코딩은 자동으로 디코딩되며, 브로틀리(br)는 선택적으로 지원됩니다.
즉, 파이썬 환경에 brotli 또는 brotlicffi가 설치되어 있고 서버가 Content-Encoding: br로 응답하면 requests가 자동으로 해제합니다.
반대로 원본 압축 바이트를 그대로 받아야 한다면 Response.raw를 사용해 가공 없는 스트림을 읽는 것이 안전합니다.
이 섹션에서는 가장 깔끔한 기본 패턴과 헤더·타임아웃·파일 기록 과정에서의 필수 체크포인트를 정리합니다.
⚙️ 스트리밍 다운로드의 권장 기본 패턴
import requests
url = "https://example.com/large-file.csv"
headers = { # Accept-Encoding은 기본적으로 gzip, deflate가 설정되며, 브로틀리 지원 시 br이 자동 포함됩니다.
# "Accept-Encoding": "gzip, deflate, br" # 필요 시 명시
}
with requests.get(url, headers=headers, stream=True, timeout=(5, 60)) as r:
r.raise_for_status()
with open("large-file.csv", "wb") as f:
for chunk in r.iter_content(chunk_size=1024 * 1024): # 1MB 단위
if chunk: # keep-alive 빈 청크 방지
f.write(chunk)
위 패턴은 메모리 사용량을 일정하게 유지하면서 안정적으로 파일에 기록합니다.
iter_content()는 전송 인코딩이 gzip/deflate이면 자동으로 풀린 바이트를 반환하고, 브로틀리는 선택적 모듈이 설치된 경우에만 자동 해제됩니다.
파일을 쓰는 쪽에서는 이미 디코딩된 바이트를 받기 때문에 별도 압축 해제 단계가 필요 없습니다.
🧩 자동 디코딩의 기준과 헤더 이해
Accept-Encoding은 클라이언트가 처리 가능한 압축 알고리즘을 서버에 알리는 요청 헤더입니다.
requests는 기본적으로 gzip, deflate를 포함해 전송하며, 브로틀리 디코더가 설치되어 있으면 br도 함께 보냅니다.
서버는 이에 따라 콘텐츠를 압축해 응답 본문과 함께 Content-Encoding 헤더로 실제 사용한 알고리즘을 명시합니다.
iter_content()는 이 헤더를 기준으로 스트림을 자동 해제하므로, 별도의 수동 해제 코드를 섞지 않는 것이 충돌을 막는 핵심입니다.
📌 원본 압축 파일이 필요한 경우
예를 들어 .tar.gz처럼 압축 자체가 파일 포맷의 일부라면 자동 디코딩이 오히려 문제를 일으킬 수 있습니다.
이때는 Response.raw를 사용해 원본 바이트 스트림을 그대로 복사하세요.
import requests, shutil
url = "https://example.com/archive.tar.gz"
with requests.get(url, stream=True, timeout=(5, 60)) as r:
r.raise_for_status()
with open("archive.tar.gz", "wb") as f:
shutil.copyfileobj(r.raw, f) # 자동 디코딩 우회: 원본 바이트 보존
💡 TIP: 네트워크 품질이 불안정하면 timeout=(연결, 읽기) 튜플을 명시하고, chunk_size를 256KB~2MB 사이에서 조정해 재시도 비용을 줄이는 편이 유리합니다.
⚠️ 주의: 자동 디코딩은 iter_content()와 iter_lines()를 사용할 때 적용됩니다.
반면 Response.raw는 디코딩을 수행하지 않으므로, 어떤 방식을 쓰는지에 따라 결과 바이트가 달라질 수 있습니다.
- 🛠️stream=True와 iter_content()를 사용해 메모리 사용을 제한합니다.
- ⚙️자동 디코딩 대상은 gzip/deflate, 브로틀리는 선택 모듈 설치 시 자동 해제됩니다.
- 🔌.tar.gz 등 원본 압축이 필요한 파일은 Response.raw로 복사합니다.
💬 requests 공식 문서는 iter_content가 gzip/deflate 전송 인코딩을 자동으로 디코딩한다고 명시합니다.
brotli 또는 brotlicffi가 설치되어 있으면 br 응답도 자동 처리됩니다.
🛠️ gzip 및 deflate 자동 디코딩의 실제 동작
파이썬 requests는 내부적으로 urllib3를 기반으로 동작하며, gzip과 deflate 인코딩에 대한 자동 디코딩 기능을 내장하고 있습니다.
이 말은, 서버가 응답 헤더에 Content-Encoding: gzip 또는 deflate를 포함하면, Response.content나 iter_content()를 통해 접근하는 시점에 이미 압축이 자동 해제된 상태로 데이터를 받는다는 의미입니다.
이 기능은 별도의 설정 없이 기본 활성화되어 있어, 사용자 입장에서는 디코딩 처리 여부를 신경 쓸 필요가 없습니다.
🧠 gzip과 deflate 차이와 처리 방식
gzip은 헤더와 푸터를 포함한 파일 압축 형식으로, deflate보다 메타데이터가 풍부합니다.
반면 deflate는 압축 데이터 자체만 포함해 더 간결하지만, 서버와 클라이언트 간의 처리 기준이 약간 다를 수 있습니다.
requests는 두 알고리즘 모두를 동일하게 인식하여 자동 해제하며, 내부적으로는 zlib.decompress()를 활용해 안전하게 압축을 풉니다.
이 덕분에 텍스트, JSON, 이미지 등 다양한 형식의 데이터를 자동으로 읽어들일 수 있습니다.
import requests
url = "https://httpbin.org/gzip" # gzip 테스트 엔드포인트
response = requests.get(url)
print(response.headers.get("Content-Encoding")) # gzip
print(response.text) # 자동 디코딩된 JSON 문자열 출력
위 코드에서 gzip 응답임에도 불구하고 response.text는 이미 압축 해제된 JSON 문자열을 반환합니다.
이는 requests가 Content-Encoding 헤더를 자동 감지해 데이터를 디코딩하기 때문입니다.
🧩 deflate 인코딩의 잠재적 이슈
일부 서버는 deflate를 잘못된 형식(zlib 헤더 없이)으로 전송하는 경우가 있습니다.
이럴 땐 디코딩 오류가 발생할 수 있는데, requests는 이 문제를 완화하기 위해 try-except 기반의 유연한 디코더를 사용합니다.
즉, 표준 deflate 형식과 raw deflate 모두 자동으로 감지해 처리합니다.
💡 TIP: deflate 응답이 손상된 것으로 보인다면, Accept-Encoding을 직접 지정해 gzip만 허용하거나, 서버 설정에서 전송 인코딩 방식을 명확히 지정하는 것이 좋습니다.
📌 gzip 자동 해제의 내부 동작 원리
requests는 응답 본문을 받을 때 decode_content=True 옵션을 활성화한 상태로 스트림을 읽습니다.
이 설정은 urllib3.response.GzipDecoder를 자동 호출하며, 압축이 해제된 바이트를 Response.raw에서 상위 계층으로 전달합니다.
즉, 사용자가 별도 디코딩 로직을 작성하지 않아도 항상 읽기 가능한 평문 데이터를 얻게 됩니다.
💬 gzip과 deflate는 requests에서 완전히 자동화되어 있습니다.
단, 브로틀리(br)는 선택적 모듈 설치가 필요합니다.
기본 환경에서는 gzip/deflate까지만 자동 해제됩니다.
| 압축 형식 | 자동 디코딩 | 추가 설정 필요 |
|---|---|---|
| gzip | 기본 지원 (자동 해제) | 필요 없음 |
| deflate | 기본 지원 (자동 해제) | 필요 없음 |
| brotli (br) | 선택 지원 (모듈 필요) | brotli 또는 brotlicffi 설치 필요 |
이처럼 requests는 HTTP 표준 인코딩의 자동 디코딩을 기본 제공하여, 별도의 후처리 코드를 작성하지 않고도 즉시 파싱 가능한 데이터를 다룰 수 있습니다.
⚙️ 브로틀리 지원 조건과 설치 방법
최근 많은 웹 서버와 CDN이 gzip보다 효율이 높은 브로틀리(Brotli) 압축을 사용합니다.
하지만 파이썬의 requests는 기본적으로 브로틀리를 자동 해제하지 않으며, 추가 모듈 설치가 필요합니다.
설치 시 requests가 브로틀리 디코더를 자동으로 인식하여, gzip/deflate와 동일하게 Content-Encoding: br 응답도 해제할 수 있게 됩니다.
🧩 브로틀리 자동 디코딩을 활성화하는 방법
requests는 내부적으로 brotli 또는 brotlicffi 모듈을 감지합니다.
두 패키지 중 하나가 설치되어 있으면, Accept-Encoding 헤더에 자동으로 br이 추가되고, 서버 응답의 Content-Encoding: br도 자동 해제됩니다.
# pip를 이용한 브로틀리 디코더 설치
pip install brotli
# 또는 CFFI 버전 (속도와 호환성 개선)
pip install brotlicffi
두 라이브러리는 동일한 기능을 제공합니다.
특히 brotlicffi는 순수 파이썬 구현보다 성능이 더 높아, 리눅스·맥 환경에서 권장됩니다.
설치 후에는 requests가 자동으로 브로틀리 응답을 디코딩하며, 별도의 코드 수정이 필요 없습니다.
🔍 브로틀리 지원 여부 확인하기
브로틀리 지원이 활성화되었는지 확인하려면 requests.utils.get_encodings_from_content()나 Response.headers를 검사하면 됩니다.
테스트용으로 https://httpbin.org/brotli 엔드포인트를 이용하면 간단히 확인할 수 있습니다.
import requests
url = "https://httpbin.org/brotli"
res = requests.get(url)
print(res.headers.get("Content-Encoding")) # br
print(res.text) # 브로틀리 해제 후 평문 JSON
위 코드 실행 후 텍스트가 정상 출력된다면 브로틀리 해제가 자동으로 이루어진 것입니다.
만약 UnicodeDecodeError나 빈 문자열이 나온다면, 브로틀리 모듈이 설치되지 않은 상태입니다.
📌 브로틀리와 gzip 비교
| 특징 | Brotli | Gzip |
|---|---|---|
| 압축률 | 높음 (최대 20~25%) | 보통 (기본 수준) |
| 처리 속도 | 약간 느림 | 빠름 |
| 기본 지원 | 선택 모듈 설치 필요 | 기본 지원 |
브로틀리는 gzip보다 더 높은 압축률을 제공하지만, 추가 의존성이 필요합니다.
이 때문에 requests는 모듈 존재 여부를 자동으로 감지해 선택적으로 지원하는 구조를 채택했습니다.
⚠️ 주의: 브로틀리 모듈을 설치하지 않은 환경에서 Accept-Encoding: br을 명시하면, 서버가 브로틀리로 압축한 응답을 보낼 수 있습니다.
이 경우 requests는 이를 해제하지 못하고 이진 데이터 그대로 반환하므로 반드시 모듈을 함께 설치하세요.
💡 TIP: requests는 별도 옵션 없이도 브로틀리를 자동 인식합니다.
즉, requests.get() 호출 시 헤더를 수정하지 않아도 brotli 모듈이 있으면 바로 작동합니다.
🔌 대용량 파일 안정적으로 저장하는 패턴
스트리밍 다운로드를 사용할 때 가장 중요한 점은 메모리 효율성과 중단 복원력입니다.
requests의 stream=True 옵션은 서버에서 데이터를 한꺼번에 로드하지 않고, 지정한 크기만큼 나누어 전송받습니다.
이 방식을 사용하면 수 GB 단위의 파일도 시스템 자원을 거의 점유하지 않고 처리할 수 있습니다.
💾 안전한 스트리밍 저장 기본 코드
import requests
from tqdm import tqdm # 진행률 표시용
url = "https://example.com/large-data.zip"
with requests.get(url, stream=True, timeout=(10, 60)) as r:
r.raise_for_status()
total = int(r.headers.get('content-length', 0))
block_size = 1024 * 1024 # 1MB
with open("large-data.zip", "wb") as file, tqdm(
total=total, unit='B', unit_scale=True, desc="다운로드 중"
) as bar:
for chunk in r.iter_content(chunk_size=block_size):
if chunk:
file.write(chunk)
bar.update(len(chunk))
이 패턴은 가장 많이 사용되는 실무 구조입니다.
네트워크 지연이나 일시적 오류가 발생하더라도 이미 저장된 부분은 그대로 유지되고, 이후 재요청으로 이어갈 수 있습니다.
특히 tqdm을 이용하면 진행률이 시각적으로 표시되어 사용자 경험도 개선됩니다.
🧠 중단된 다운로드 재개하기
서버가 Range 요청을 허용할 경우, 이전에 받은 파일의 크기를 기준으로 이어받을 수 있습니다.
이 방식은 대용량 파일을 전송할 때 매우 유용합니다.
import os
import requests
file_name = "resume.zip"
url = "https://example.com/large.zip"
resume_header = {}
pos = 0
if os.path.exists(file_name):
pos = os.path.getsize(file_name)
resume_header = {"Range": f"bytes={pos}-"}
with requests.get(url, headers=resume_header, stream=True) as r:
mode = "ab" if pos else "wb"
with open(file_name, mode) as f:
for chunk in r.iter_content(1024 * 1024):
if chunk:
f.write(chunk)
이 코드에서는 Range 헤더를 통해 지정한 위치부터 다운로드를 재개합니다.
만약 서버가 범위 요청을 허용하지 않는다면, 응답 코드 200 OK로 전체 파일이 다시 내려옵니다.
📌 안정적인 파일 저장 체크리스트
- 🧱반드시 with 블록을 사용해 연결 및 파일 핸들 자동 종료
- ⏱️timeout=(연결, 읽기) 튜플 지정으로 무한 대기 방지
- 💡gzip/deflate 압축은 자동 해제되므로 별도 디코딩 불필요
- 🚧브로틀리 사용 시 brotli 모듈 설치 필수
💡 TIP: 스트리밍 파일 저장 시, chunk_size는 너무 작게 설정하면 CPU 오버헤드가 증가하고 너무 크면 버퍼 지연이 생깁니다. 512KB~2MB 범위가 가장 안정적입니다.
⚠️ 주의: Response.iter_content()에서 빈 청크(keep-alive)를 필터링하지 않으면 파일 크기 불일치가 발생할 수 있습니다. 반드시 if chunk: 조건을 추가하세요.
이와 같은 방식으로 requests의 스트리밍 다운로드를 구성하면, 네트워크 품질이 불안정하거나 데이터 용량이 큰 상황에서도 안전하게 파일을 저장할 수 있습니다.
gzip/deflate 압축은 자동으로 해제되어 저장되며, 브로틀리는 선택적 모듈 설치를 통해 동일한 자동 디코딩이 적용됩니다.
💡 자주 겪는 오류와 안전한 설정 팁
requests로 스트리밍 다운로드를 구현할 때 자주 겪는 문제는 대부분 디코딩 충돌이나 타임아웃 설정 누락으로 인한 네트워크 중단입니다.
이 섹션에서는 오류를 예방하는 설정 포인트와 실제 문제 사례를 정리했습니다.
🚫 Content-Decoding 관련 오류
가장 흔한 오류는 “ContentDecodingError” 또는 “ZlibError”입니다.
이는 서버가 잘못된 Content-Encoding을 보내거나, 압축 스트림이 손상된 경우에 발생합니다.
이럴 때는 수동으로 헤더를 조정하거나, 디코딩을 비활성화해 원본 데이터를 직접 처리하는 것이 좋습니다.
# Content-Decoding 오류 회피
import requests
url = "https://example.com/problematic-endpoint"
headers = {"Accept-Encoding": "identity"} # 압축 해제 요청 금지
res = requests.get(url, stream=True, headers=headers)
with open("raw.bin", "wb") as f:
for chunk in res.iter_content(1024):
f.write(chunk)
이 방법을 사용하면 gzip이나 br로 인코딩된 응답이 아닌, 원본 바이트 그대로 받을 수 있습니다.
이후 필요 시 gzip 또는 brotli 라이브러리로 직접 해제할 수 있습니다.
⚡ 타임아웃 및 네트워크 오류 예방
대용량 스트리밍 시 무한 대기 상태를 방지하려면 반드시 timeout을 설정해야 합니다.
특히 두 개의 값을 튜플로 설정해 (연결, 읽기) 단계를 구분하는 것이 안전합니다.
r = requests.get("https://example.com/stream", stream=True, timeout=(5, 60))
첫 번째 인자는 서버에 연결하는 데 걸리는 최대 시간(초), 두 번째는 데이터를 읽을 때 대기할 수 있는 최대 시간입니다.
값을 설정하지 않으면 연결이 끊기지 않고 무한 대기 상태가 될 수 있습니다.
⚠️ 주의: 일부 서버는 HTTP 206(Partial Content)을 지원하지 않습니다.
이 경우 Range 요청을 보내면 전체 파일이 다시 다운로드됩니다. 반드시 서버 응답 코드와 Accept-Ranges 헤더를 확인하세요.
📌 안정적 스트리밍을 위한 추가 팁
- 🧠응답 데이터 크기를 미리 알고 싶다면 Content-Length를 확인
- ⚙️압축을 직접 처리하려면 gzip.decompress() 또는 brotli.decompress() 사용
- 📦브로틀리 사용 시 brotlicffi 모듈이 속도 면에서 더 유리
- 💾대용량 파일은 iter_content()와 chunk_size 병행 사용
💡 TIP: 브로틀리 해제가 정상적으로 작동하는지 확인하려면 httpbin.org/brotli를 테스트용으로 활용하면 좋습니다. 응답 JSON이 정상 출력되면 설정이 올바른 것입니다.
이처럼 requests는 스트리밍 다운로드와 압축 자동 해제를 모두 지원하지만, 브로틀리는 선택 모듈이므로 환경 구성에 따라 동작이 달라집니다.
gzip과 deflate는 항상 자동 디코딩되며, 브로틀리는 설치 시 자동 활성화됩니다.
❓ 자주 묻는 질문 (FAQ)
requests는 자동으로 gzip을 해제하나요?
브로틀리(br) 압축은 기본적으로 지원되나요?
iter_content()와 raw.read()의 차이는 무엇인가요?
스트리밍 다운로드 중 오류가 발생하면 이어받을 수 있나요?
gzip 응답을 수동으로 해제하려면 어떻게 하나요?
import gzip; gzip.decompress(data)
Content-Decoding 오류를 피하는 방법은?
브로틀리와 gzip 중 어느 쪽이 더 효율적인가요?
requests에서 압축 해제를 완전히 끄려면 어떻게 하나요?
📘 스트리밍 다운로드와 자동 디코딩의 완전한 이해
파이썬 requests의 스트리밍 다운로드 기능은 단순히 데이터를 받는 것 이상의 효율을 제공합니다.
gzip과 deflate 압축은 기본적으로 자동 해제되며, 브로틀리는 brotli 모듈 설치 시 선택적으로 지원됩니다.
이 구조는 서버의 Content-Encoding 헤더를 자동 감지하여, 파일을 별도 처리 없이 바로 읽거나 저장할 수 있도록 설계되어 있습니다.
또한 iter_content()를 통한 반복적 바이트 스트림 처리 방식은 대용량 데이터를 안정적으로 관리할 수 있게 해줍니다.
여기에 Range 요청을 병행하면 다운로드 중단 시 이어받기까지 구현할 수 있어, 실무 파일 전송 자동화에 적합합니다.
gzip/deflate는 항상 자동으로 해제되므로 별도의 수동 처리 과정이 필요 없으며, 브로틀리 역시 brotlicffi 설치만으로 동일한 자동 해제가 작동합니다.
정리하자면, requests는 다음과 같은 특징을 가집니다.
- 📦gzip, deflate 압축은 기본 지원 (자동 해제)
- 🧩브로틀리(br)는 brotli 또는 brotlicffi 설치 시 자동 활성화
- ⚙️iter_content()는 디코딩된 데이터를 chunk 단위로 반환
- 🧱Response.raw는 디코딩되지 않은 원본 스트림을 반환
- 🔄Range 헤더를 이용해 다운로드 이어받기 가능
이 모든 기능을 이해하고 적절히 조합하면, requests는 단순한 HTTP 요청 도구를 넘어 강력한 파일 전송 자동화 도구로 활용될 수 있습니다.
특히 압축 자동 해제 기능은 코드의 단순성과 신뢰성을 모두 확보해 줍니다.
🏷️ 관련 태그 : 파이썬requests, 스트리밍다운로드, gzip압축, deflate자동해제, 브로틀리, brotlicffi, 파일다운로드, HTTP응답처리, ContentDecoding, 데이터스트리밍