线程不生效是因为共用Session导致锁等待,应每线程独立创建Session或用threading.local;下载大文件需stream=True+iter_content()流式写入+response.close();线程数宜从min(10, CPU数×2)起步,配合Semaphore控制并发;重试应捕获RequestException并指数退避,429/503需解析Retry-After头。

requests + threading 下载文件时线程不生效?检查是否用了 session
多线程下载卡在串行,大概率是因为所有线程共用了一个 requests.Session() 实例且没做线程隔离。Session 内部维护连接池和 cookie 等状态,跨线程复用会触发锁等待,实际变成“伪并发”。
- 每个线程应创建独立的
session(轻量,开销可接受),或至少用threading.local()绑定 session - 不要在全局定义一个
session = requests.Session()然后传给所有线程 - 若必须复用连接池,改用
concurrent.futures.ThreadPoolExecutor配合局部 session 更稳妥
下载大文件时内存爆掉?必须流式写入 + 手动关闭响应
requests.get(url) 默认把整个响应体加载进内存,几 GB 的文件直接 OOM。关键不是加 stream=True,而是后续必须手动迭代 response.iter_content() 并写入文件。
- 漏掉
response.close()或未消费完流,会导致连接无法释放、fd 耗尽 - 写文件要用二进制模式:
open(filename, "wb"),不能用文本模式 - 建议加
timeout=(3, 30):第一个数字是连接超时,第二个是读取超时,避免单个卡死拖垮全部线程
def download_file(url, filename):
session = requests.Session()
try:
with session.get(url, stream=True, timeout=(3, 30)) as r:
r.raise_for_status()
with open(filename, "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
if chunk: # 过滤 keep-alive 空块
f.write(chunk)
finally:
session.close() # 显式关闭 session,释放连接池
线程数设多少才合适?别盲目调高
HTTP 下载是 I/O 密集型任务,但受制于网络带宽、目标服务器并发限制、本地端口耗尽等因素。设 20 个线程不一定比 5 个快,还可能被目标站限速或封 IP。
- 从
min(10, os.cpu_count() * 2)开始试,再根据实际吞吐调整 - 加随机延时(如
time.sleep(random.uniform(0.1, 0.5)))可降低被识别为爬虫的风险 - 用
threading.Semaphore控制并发上限,比 raw thread 更可控
遇到 ConnectionError / SSLError 怎么稳定重试?
网络请求失败太常见,但 requests 默认不重试。直接包一层带指数退避的逻辑比依赖第三方库更轻量、更可控。
立即学习“Python免费学习笔记(深入)”;
- 捕获
requests.exceptions.RequestException(基类,覆盖超时、连接拒绝、SSL 错误等) - 重试次数建议 ≤ 3,间隔用
2 ** attempt秒,避免雪崩 - 注意:重试时要重建
session和重新打开文件句柄,否则可能复用已损坏的状态
真正难处理的是服务端返回 429(Too Many Requests)或 503,这类需要解析响应头 r.headers.get("Retry-After"),而不是简单 sleep 固定时长。


















