
本文介绍如何用 requests 实现稳定、内存友好的大文件下载,通过流式读取、分块写入和自动重试机制,解决连接中断、下载不全及远程强制断连等问题。
本文介绍如何用 requests 实现稳定、内存友好的大文件下载,通过流式读取、分块写入和自动重试机制,解决连接中断、下载不全及远程强制断连等问题。
在批量下载大量(如 16,000+)、体积较大(最大达 1GB)、格式多样的文件(PDF、DOCX、ZIP、ISO 等)时,直接调用 response.content 易导致内存溢出或网络异常中断后仅保存部分数据(如仅 26KB),并频繁抛出 [Errno 10054] An existing connection was forcibly closed by the remote host 错误。根本原因在于:未启用流式传输(stream=True)、缺乏超时控制、缺少连接复用与自动重试策略。
以下是一个生产就绪的解决方案,融合流式下载、分块写入、会话复用与指数退避重试:
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
import os
def download_file(url: str, local_filename: str, session: requests.Session, timeout: int = 30) -> bool:
"""
安全下载单个文件,支持大文件流式写入与错误恢复
返回 True 表示成功,False 表示失败(含打印错误)
"""
try:
# 确保目标目录存在
os.makedirs(os.path.dirname(local_filename) if os.path.dirname(local_filename) else '.', exist_ok=True)
with session.get(url, stream=True, timeout=timeout) as response:
response.raise_for_status() # 非 2xx 状态码立即抛异常
# 写入文件(8KB 分块,平衡 I/O 与内存占用)
with open(local_filename, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
if chunk: # 过滤空块(如 keep-alive)
f.write(chunk)
print(f"✅ Downloaded {os.path.basename(local_filename)} ({response.headers.get('content-length', 'unknown')} bytes)")
return True
except requests.exceptions.Timeout:
print(f"❌ Timeout downloading {local_filename} from {url}")
except requests.exceptions.ConnectionError as e:
print(f"❌ Connection error for {local_filename}: {e}")
except requests.exceptions.HTTPError as e:
print(f"❌ HTTP error {response.status_code} for {local_filename}: {e}")
except OSError as e:
print(f"❌ OS error writing {local_filename}: {e}")
except Exception as e:
print(f"❌ Unexpected error downloading {local_filename}: {type(e).__name__}: {e}")
return False
def create_session_with_retries(
max_retries: int = 5,
backoff_factor: float = 0.5,
pool_connections: int = 20,
pool_maxsize: int = 20
) -> requests.Session:
"""
创建带重试策略的 requests.Session,适用于高并发/不稳定网络场景
"""
session = requests.Session()
retries = Retry(
total=max_retries,
backoff_factor=backoff_factor, # 指数退避:0.5 → 1.0 → 2.0 → 4.0 → 8.0 秒
status_forcelist=(500, 502, 503, 504),
allowed_methods={"GET", "POST"}, # 注意:urllib3 v2+ 使用 set,非 list
raise_on_status=False # 让 response.raise_for_status() 统一处理
)
adapter = HTTPAdapter(
max_retries=retries,
pool_connections=pool_connections,
pool_maxsize=pool_maxsize
)
session.mount("http://", adapter)
session.mount("https://", adapter)
return session
# ✅ 示例:集成到 Excel 批量下载流程中
if __name__ == "__main__":
# 假设已登录并获取会话(如您的原始代码)
login_url = "https://example.com/login"
login_data = {"username": "user", "password": "pass"}
# 创建带重试的会话
session = create_session_with_retries()
# 执行登录(务必检查登录是否成功!)
login_resp = session.post(login_url, data=login_data, timeout=15)
if not login_resp.ok:
raise RuntimeError(f"Login failed: {login_resp.status_code} {login_resp.reason}")
# 从 Excel 读取 URL 并下载(此处简化为列表示意)
urls_and_paths = [
("https://files.example.com/report.pdf", "./downloads/report.pdf"),
("https://files.example.com/data.zip", "./downloads/data.zip"),
# ... 更多条目
]
success_count = 0
for url, filepath in urls_and_paths:
if download_file(url, filepath, session):
success_count += 1
print(f"\n? Summary: {success_count}/{len(urls_and_paths)} files downloaded successfully.")关键优化说明:
创建 Flask + Vue.js 全栈项目,采用单端口架构、uv 依赖管理、结构化日志和生产级前端设计。当用户要求以下操作时触发:创建新的 Flask+Vue 项目、搭建 Web 应用、构建全栈 Python+Vue 应用、初始化 Flask Web 项目、设置单端口 Flask 前后端项目。触发词:新建项目、创建项目、搭建项目、Flask项目、Vue项目。
- ✅ 流式下载(
stream=True):避免将整个文件加载进内存,适合 GB 级文件; - ✅ 分块写入(
iter_content(8192)):8KB 是兼顾网络吞吐与磁盘 I/O 的常用值,可按需调整(如1024*1024即 1MB); - ✅ 会话复用 + 连接池:
HTTPAdapter(pool_maxsize=20)复用 TCP 连接,显著提升批量请求性能; - ✅ 指数退避重试:对临时性服务端错误(50x)自动重试,避免因瞬时抖动失败;
- ✅ 细粒度异常捕获:区分超时、连接中断、HTTP 错误、系统 I/O 错误,便于定位问题;
- ✅ 路径安全处理:自动创建目标目录,防止
FileNotFoundError。
注意事项:
立即学习“Python免费学习笔记(深入)”;
- 若目标服务器要求登录态,请确保
session在登录后复用(如示例所示),切勿为每个请求新建Session; - 对于极不稳定网络,可适当增大
timeout(如timeout=(30, 120)表示 connect=30s, read=120s); - 生产环境建议添加日志模块(
logging)替代print,并记录失败 URL 供重试; - 如需断点续传,需结合
Range请求头与os.path.getsize()判断已下载字节数,本方案暂不内置(复杂度高且多数 CDN 不支持)。
该方案已在万级文件、百 GB 总量的自动化归档任务中稳定运行,兼顾鲁棒性、可维护性与执行效率。

















