파이썬 requests 스트리밍 다운로드 가이드 stream=True와 iter_content로 안전하게 파일 저장
📌 대용량 파일도 끊김 없이 저장하는 파이썬 requests 스트리밍 다운로드 실전 가이드
대용량 파일을 한 번에 메모리로 읽어오다 보면 프로그램이 멈추거나, 서버와의 연결이 길어져 실패하는 일이 잦습니다.
이럴 때 가장 안정적인 해법이 바로 스트리밍 방식으로 조금씩 내려받아 바로 디스크에 기록하는 패턴입니다.
파이썬에서는 requests 라이브러리의 stream=True 옵션과 iter_content(chunk_size, decode_unicode=False)를 조합하면 손쉽게 구현할 수 있습니다.
메모리 사용량을 일정하게 유지하고, 네트워크 상태가 흔들려도 재시도 전략과 함께 견고한 다운로드 루틴을 만들 수 있습니다.
현업에서도 로그, 데이터셋, 동영상 등 다양한 형식에서 검증된 방식이라 실용성이 높습니다.
이 글은 바로 그 핵심 패턴을 예제로 풀어 설명하고, 실수하기 쉬운 옵션과 안전장치까지 깔끔히 정리합니다.
핵심은 간단합니다.
요청을 보낼 때 스트리밍을 활성화하고, 응답 바디를 iter_content로 일정한 크기의 청크로 순회하며 바로 파일에 기록합니다.
이때 텍스트가 아닌 바이너리 데이터를 다루므로 파일은 wb 모드로 열어야 하고, 디코딩이 섞이지 않도록 decode_unicode=False를 유지하는 것이 안전합니다.
추가로 timeout, retries, Content-Length 확인, 진행률 표시 같은 보완 요소를 더하면 신뢰도 높은 다운로드 스크립트를 완성할 수 있습니다.
아래 목차를 따라 원리부터 모범 코드, 운영 팁까지 한 번에 정리해 드립니다.
📋 목차
🔗 스트리밍 다운로드의 원리와 장점
스트리밍 다운로드는 응답 본문 전체를 한 번에 메모리로 적재하지 않고, 네트워크에서 도착하는 바이트를 작은 조각으로 순차 처리하는 방식입니다.
파이썬 requests에서 이 동작은 stream=True로 활성화되고, iter_content(chunk_size, decode_unicode=False)가 실제 청크 단위 반복을 담당합니다.
즉, 서버가 전송하는 바이트 스트림을 일정 크기씩 읽어 디스크에 곧바로 기록하므로, 파일 크기가 수 GB에 달해도 메모리 사용량이 폭증하지 않습니다.
또한 네트워크 상태가 일시적으로 불안정할 때도 작은 단위로 쓰기 때문에 중간 실패 지점을 파악하기 쉽고, 재시도 전략과 결합해 안정성을 높일 수 있습니다.
원리는 단순합니다.
HTTP 응답의 바디를 버퍼에 잠시 보관하고, 개발자가 지정한 chunk_size만큼 반복해서 꺼내 파일에 wb 모드로 기록합니다.
이때 바이너리 파일의 무결성을 위해 decode_unicode=False를 유지해야 하며, 텍스트 파일을 다루더라도 스트리밍 단계에서는 디코딩을 미루는 편이 안전합니다.
헤더의 Content-Length를 참고하면 전체 예상 크기를 알 수 있어 진행률 표시 및 검증에 유용합니다.
🚀 메모리 효율과 안정성 측면의 이점
대용량 다운로드에서 가장 큰 이점은 예측 가능한 메모리 사용량입니다.
청크 크기가 64KB라면, 보통의 루프에서는 수십~수백 KB 수준만 점유합니다.
이는 컨테이너 환경이나 서버리스 환경처럼 메모리 제한이 엄격한 곳에서 특히 효과적입니다.
아울러 스트림은 연결 시간을 줄이지는 않지만, 연결 유지 중에 데이터를 즉시 디스크로 흘려보내므로 가비지 컬렉션 부담을 낮추고, I/O 병목을 완화합니다.
- 🧠stream=True로 응답 바디 지연 로딩.
- 📦iter_content(chunk_size)로 청크 반복 처리.
- 🧩파일은 반드시 바이너리 모드 wb로 열기.
- 🛡️바이너리 손상 방지를 위해 decode_unicode=False 유지.
- 📏헤더의 Content-Length로 예상 크기 확인 및 검증.
import requests
url = "https://example.com/large-file.bin"
chunk_size = 1024 * 64 # 64KB
with requests.get(url, stream=True, timeout=30) as r:
r.raise_for_status()
total = int(r.headers.get("Content-Length", 0) or 0)
written = 0
with open("large-file.bin", "wb") as f:
for chunk in r.iter_content(chunk_size=chunk_size, decode_unicode=False):
if not chunk:
continue # keep-alive 청크 방지
f.write(chunk)
written += len(chunk)
# 핵심: stream=True + iter_content(chunk_size, decode_unicode=False)
💡 TIP: chunk_size는 네트워크 상황과 디스크 성능에 맞춰 32KB~1MB 사이에서 실험해 최적점을 찾는 것이 좋습니다.
⚠️ 주의: 텍스트 편의를 위해 decode_unicode=True로 두면 바이너리 파일이 손상될 수 있습니다.
이미지, 동영상, 압축파일 등 바이너리 다운로드에서는 반드시 기본값인 False를 사용하세요.
💬 스트리밍은 파일 크기와 무관하게 일정한 메모리 족적을 유지하며, 청크 단위 작성으로 중간 실패에 더 강합니다.
💎 핵심 포인트:
대용량일수록 stream=True와 iter_content 조합은 필수 패턴입니다.
메모리를 지키고, 파일 무결성을 유지하며, 예측 가능한 다운로드 동작을 제공합니다.
🛠️ requests.get stream=True 안전한 사용법
파이썬에서 파일 다운로드를 처리할 때, 가장 단순한 코드는 requests.get(url)로 응답을 받아 바로 response.content를 쓰는 방식입니다.
하지만 이 경우 응답 전체가 한꺼번에 메모리에 올라가기 때문에, 파일이 수백 MB 이상이면 프로그램이 멈추거나 시스템 리소스를 과도하게 점유할 수 있습니다.
이를 방지하려면 반드시 stream=True를 지정해 스트리밍 모드로 전환해야 합니다.
이 옵션은 응답 본문을 즉시 다운로드하지 않고, 개발자가 직접 청크 단위로 제어할 수 있게 해 줍니다.
또한 네트워크 안정성을 고려해 timeout을 설정하고, 연결 실패 시 자동 재시도를 처리하는 requests.adapters.HTTPAdapter와 Retry 객체를 함께 사용하는 것이 좋습니다.
이를 통해 일시적인 서버 오류(예: 502, 503, 504)에도 견고하게 대응할 수 있습니다.
특히 대량의 파일을 자동으로 주기적으로 내려받는 경우에는 이러한 예외 처리가 필수적입니다.
🔍 stream=True 사용 시 주의사항
스트리밍 모드를 사용할 때 가장 자주 발생하는 실수는 응답 객체를 닫지 않는 것입니다.
응답을 처리한 후에는 반드시 with 문을 사용해 세션이 자동으로 정리되도록 하는 것이 안전합니다.
이렇게 하면 연결 누수 없이 깨끗한 리소스 관리가 이루어집니다.
또한, 일부 서버는 스트리밍 요청에 대해 Transfer-Encoding: chunked 헤더를 반환할 수 있으므로, 파일이 부분적으로 저장되지 않도록 반복문 내에서 청크 검증을 수행해야 합니다.
import requests
from requests.adapters import HTTPAdapter, Retry
url = "https://example.com/large-data.zip"
session = requests.Session()
retries = Retry(total=5, backoff_factor=0.5, status_forcelist=[500, 502, 503, 504])
session.mount("https://", HTTPAdapter(max_retries=retries))
with session.get(url, stream=True, timeout=20) as r:
r.raise_for_status()
with open("data.zip", "wb") as f:
for chunk in r.iter_content(chunk_size=8192, decode_unicode=False):
if chunk:
f.write(chunk)
위 예시처럼 requests.Session()을 활용하면 연결 풀을 재사용할 수 있고, 재시도 정책을 통합 관리할 수 있습니다.
특히 stream=True와 with 블록의 조합은 리소스 해제를 자동화하는 핵심 패턴으로, 다운로드 스크립트에서 반드시 지켜야 할 관례입니다.
💡 TIP: 세션 단위로 재시도 횟수와 대기시간을 설정하면, 단순히 한 번의 요청 실패로 전체 스크립트가 중단되지 않습니다.
⚠️ 주의: response.close()를 호출하지 않고 루프를 중단하면 연결이 끊기지 않아 세션 누수가 발생할 수 있습니다.
항상 with 블록 내에서 스트리밍을 수행하세요.
💎 핵심 포인트:
스트리밍 다운로드의 핵심은 with 블록과 stream=True를 결합해 연결 누수를 방지하는 것입니다.
추가로 재시도 설정을 결합하면 실무 환경에서도 신뢰할 수 있는 파일 전송이 가능합니다.
⚙️ iter_content chunk_size decode_unicode 파라미터
파이썬 requests의 스트리밍 모드에서 실제 데이터를 나눠 읽어오는 핵심 메서드는 iter_content()입니다.
이 함수는 서버에서 전송된 응답 본문을 지정된 크기(chunk_size) 단위로 반복(iteration)하여 바이트 조각(bytes chunk)을 반환합니다.
즉, 개발자가 직접 반복문을 돌며 필요한 크기만큼 읽어 파일에 저장할 수 있게 해 줍니다.
청크 단위를 조정함으로써 네트워크 효율, 메모리 사용량, 디스크 I/O 속도를 세밀하게 제어할 수 있습니다.
📦 chunk_size의 의미와 최적값
chunk_size는 한 번에 읽어올 데이터의 바이트 수를 지정합니다.
너무 작게 설정하면 반복이 많아져 CPU 오버헤드가 증가하고, 너무 크게 설정하면 메모리 점유율이 높아질 수 있습니다.
일반적으로 64KB(1024*64)가 균형 잡힌 기본값으로 사용되며, SSD 기반 서버나 대역폭이 넉넉한 환경에서는 256KB~1MB까지 확장해도 무방합니다.
| 환경 | 추천 chunk_size |
|---|---|
| 저사양 PC 또는 라즈베리파이 | 32KB |
| 일반 데스크탑 / 노트북 | 64KB |
| 고성능 서버 / 빠른 네트워크 | 256KB~1MB |
🧩 decode_unicode 옵션의 역할
두 번째 인자인 decode_unicode는 응답 내용을 유니코드 문자열로 변환할지 여부를 결정합니다.
기본값은 False로, 바이너리 데이터를 그대로 반환합니다.
텍스트 파일을 다루는 경우 True로 지정해도 되지만, 파일이 혼합 인코딩을 포함하거나 바이너리 형태(예: 이미지, zip, pdf)라면 False를 유지해야 합니다.
이는 스트리밍 시 데이터 손상 방지를 위한 안전장치입니다.
# decode_unicode 사용 예시
import requests
url = "https://example.com/sample.txt"
with requests.get(url, stream=True) as r:
r.raise_for_status()
with open("sample.txt", "w", encoding="utf-8") as f:
for chunk in r.iter_content(chunk_size=1024, decode_unicode=True):
f.write(chunk)
💡 TIP: decode_unicode=True는 텍스트 파일에만 권장됩니다.
이미지나 바이너리 파일에 사용하면 손상 위험이 있습니다.
💬 iter_content()는 파이썬 requests 스트리밍의 핵심입니다.
청크 크기와 디코딩 옵션을 올바르게 이해하면 대용량 파일도 안정적으로 처리할 수 있습니다.
💎 핵심 포인트:
chunk_size는 성능을, decode_unicode는 데이터 무결성을 좌우합니다.
적절히 조합하면 텍스트와 바이너리 다운로드 모두 효율적이고 안전하게 처리할 수 있습니다.
🔒 파일 저장 패턴과 예외 처리 모범 사례
스트리밍 다운로드의 완성은 데이터를 어떻게 안전하게 파일로 저장하느냐에 달려 있습니다.
다운로드 중 네트워크 끊김, 디스크 용량 부족, 권한 오류 같은 문제가 발생하면 파일이 손상되거나 비정상 종료될 수 있습니다.
이를 방지하기 위해서는 try–except 블록과 임시 파일 패턴을 함께 사용하는 것이 권장됩니다.
임시 파일에 먼저 데이터를 기록하고, 다운로드가 완전히 끝난 후에만 실제 파일 이름으로 변경하면 중간 실패에도 안전합니다.
🧱 임시 파일로 무결성 보장하기
다운로드 중 문제가 생겼을 때 기존 파일이 손상되지 않게 하려면, 우선 filename.tmp 같은 임시 이름으로 데이터를 저장한 뒤 완료 시 os.rename()으로 최종 이름으로 변경합니다.
이 과정은 원자적(atomic) 파일 교체로 처리되기 때문에 시스템 충돌 시에도 기존 데이터가 보존됩니다.
import os
import requests
url = "https://example.com/big-data.csv"
tmp_file = "big-data.csv.tmp"
final_file = "big-data.csv"
try:
with requests.get(url, stream=True, timeout=30) as r:
r.raise_for_status()
with open(tmp_file, "wb") as f:
for chunk in r.iter_content(chunk_size=1024*64, decode_unicode=False):
if chunk:
f.write(chunk)
os.rename(tmp_file, final_file)
except Exception as e:
if os.path.exists(tmp_file):
os.remove(tmp_file)
print("다운로드 실패:", e)
위 패턴은 실제 기업 환경에서도 자주 사용됩니다.
예를 들어, 대규모 로그 데이터를 수집하는 서버나 정기 업데이트 스크립트에서는 다운로드 중단이 발생하더라도 임시 파일만 삭제되고 기존 데이터는 안전하게 남습니다.
또한 예외가 발생해도 파일을 강제로 닫기 때문에 리소스 누수 없이 깔끔하게 종료됩니다.
🧠 예외 처리와 리소스 해제 전략
파일 다운로드는 네트워크와 디스크 모두를 사용하는 작업이므로, ConnectionError, Timeout, OSError 등 다양한 예외가 발생할 수 있습니다.
이 모든 예외를 포괄 처리하되, 반드시 finally 구문에서 열린 파일을 닫거나 세션을 정리해야 합니다.
이를 자동화하려면 with 문을 적극적으로 활용하는 것이 좋습니다.
- 🧩with 문을 사용해 파일과 세션 자동 해제.
- 🔒임시 파일(.tmp) 저장 후 성공 시만 rename 처리.
- ⚙️예외 발생 시 os.remove()로 임시 파일 정리.
- 💾성공 시 os.rename()으로 원자적 파일 교체.
⚠️ 주의: 파일을 덮어쓰는 경우 기존 파일의 무결성을 반드시 검증해야 합니다.
특히 동일 경로의 파일이 동시에 열릴 가능성이 있다면 락(lock) 메커니즘을 추가하세요.
💎 핵심 포인트:
스트리밍 다운로드는 단순히 데이터를 받는 것 이상입니다.
예외 처리와 임시 파일 저장 패턴을 함께 적용해야 진정한 ‘안전한 다운로드’가 완성됩니다.
💡 진행률 표시 재시도 타임아웃 확장 팁
대용량 파일 다운로드에서는 단순히 데이터를 저장하는 것만으로는 부족합니다.
진행률을 눈으로 확인하고, 네트워크 오류 발생 시 자동으로 재시도하며, 전체 작업 시간을 제한하는 timeout 설정까지 갖춰야 실무 수준의 안정성을 확보할 수 있습니다.
파이썬 requests는 이를 모두 지원하므로, 간단한 설정만으로도 완성도 높은 다운로드 루틴을 구축할 수 있습니다.
📊 tqdm으로 진행률 표시하기
다운로드 상태를 실시간으로 확인하려면 tqdm 라이브러리를 사용하는 것이 가장 간편합니다.
이 라이브러리는 반복문을 감싸기만 해도 자동으로 진행률 바(progress bar)를 표시합니다.
응답 헤더의 Content-Length를 이용하면 전체 크기 대비 다운로드 진행 상황을 정확히 계산할 수 있습니다.
import requests
from tqdm import tqdm
url = "https://example.com/large-video.mp4"
with requests.get(url, stream=True, timeout=30) as r:
total = int(r.headers.get("Content-Length", 0))
chunk = 1024 * 64
with open("video.mp4", "wb") as f, tqdm(total=total, unit='B', unit_scale=True, desc="Downloading") as pbar:
for data in r.iter_content(chunk_size=chunk, decode_unicode=False):
if data:
f.write(data)
pbar.update(len(data))
이 코드의 장점은 단순합니다.
다운로드가 진행될 때마다 pbar.update()로 바이트 단위 진행률을 갱신하기 때문에, 사용자는 얼마나 내려받았는지 실시간으로 확인할 수 있습니다.
특히 CLI 환경에서는 이 시각적 피드백이 작업 신뢰도를 크게 높입니다.
🔁 재시도와 타임아웃 최적 설정
스트리밍 중 네트워크가 순간적으로 끊기거나 서버가 일시 응답하지 않는 경우, 자동 재시도와 타임아웃을 함께 설정하면 안정성이 극적으로 향상됩니다.
Retry(total, backoff_factor, status_forcelist)를 이용해 재시도 횟수와 간격을 설정하고, timeout=(연결, 읽기) 형식으로 두 가지 제한을 동시에 제어합니다.
from requests.adapters import HTTPAdapter, Retry
import requests
session = requests.Session()
retry = Retry(
total=5,
backoff_factor=0.5,
status_forcelist=[500, 502, 503, 504],
)
adapter = HTTPAdapter(max_retries=retry)
session.mount("http://", adapter)
session.mount("https://", adapter)
response = session.get("https://example.com/data.bin", stream=True, timeout=(5, 30))
이 설정으로 인해 일시적인 서버 오류나 네트워크 지연이 발생해도 자동으로 재시도되며, 응답 대기 시간이 초과되면 즉시 연결이 종료됩니다.
특히 데이터베이스 백업, IoT 로그 수집 등 자동화된 다운로드 환경에서 유용합니다.
💡 TIP: backoff_factor는 재시도 간격 증가율을 제어합니다.
0.5로 설정하면 각 재시도 간격이 0.5, 1, 2, 4초처럼 지수적으로 늘어나 서버 과부하를 방지합니다.
💎 핵심 포인트:
tqdm으로 진행률을 표시하고, retry와 timeout을 함께 사용하면 안정적이면서도 사용자 친화적인 다운로드 경험을 제공합니다.
❓ 자주 묻는 질문 (FAQ)
stream=True를 꼭 사용해야 하나요?
그렇지 않으면 모든 응답 데이터를 한 번에 메모리에 올려 프로그램이 멈출 수 있습니다.
iter_content와 iter_lines의 차이는 무엇인가요?
파일 다운로드에는 iter_content를 사용해야 안전합니다.
chunk_size는 어느 정도로 설정하는 게 좋은가요?
네트워크 속도나 디스크 성능이 높을 경우 256KB~1MB까지 확장할 수 있습니다.
decode_unicode 옵션은 언제 True로 설정하나요?
이미지, PDF, ZIP 등 바이너리 파일에는 False를 유지해야 데이터 손상을 방지할 수 있습니다.
진행률 표시를 추가하려면 어떻게 해야 하나요?
iter_content 반복문 안에서 pbar.update(len(chunk))를 호출하면 자동으로 진행률이 갱신됩니다.
파일이 손상되었을 때 자동으로 복구할 수 있나요?
그러나 임시 파일(.tmp)로 저장하고, 재시도 로직을 추가하면 다음 실행 시 이어받기가 가능합니다.
스트리밍 중 예외가 발생하면 파일이 손상되지 않나요?
예외 발생 시 임시 파일을 삭제하는 방식으로 무결성을 지킬 수 있습니다.
requests 말고 다른 라이브러리도 있나요?
📂 파이썬 requests 스트리밍 다운로드 핵심 정리
파이썬의 requests 모듈은 단순한 HTTP 요청을 넘어, 효율적인 대용량 다운로드까지 지원하는 강력한 도구입니다.
특히 stream=True 옵션과 iter_content(chunk_size, decode_unicode=False) 조합은 대형 파일 전송에서 사실상 표준으로 자리 잡았습니다.
메모리 절약, 안정적 전송, 무결성 보장까지 세 가지를 모두 충족시키며, 개발자 입장에서도 코드 유지보수가 간단합니다.
또한 tqdm을 이용한 진행률 표시, Retry 기반 자동 재시도, timeout 제어, 그리고 임시 파일을 활용한 안전한 저장까지 결합하면 완벽한 다운로드 시스템이 완성됩니다.
이러한 구성은 단순 스크립트부터 서버 기반 데이터 파이프라인까지 다양하게 확장 가능합니다.
결국 핵심은 단 한 가지, ‘스트리밍으로 처리하라’는 것입니다.
이 원칙만 지키면, 파일 크기가 아무리 커도 안정적인 다운로드를 보장할 수 있습니다.
🏷️ 관련 태그 : 파이썬requests, stream옵션, iter_content, 파일다운로드, 스트리밍다운로드, chunk_size, decode_unicode, tqdm, HTTP요청, 프로그래밍팁