You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

requests.iter_content误判大文件下载完成未抛异常的问题咨询

根因说明

requests底层依赖urllib3库,默认配置下enforce_content_length参数为False,此时如果传输过程中出现网络波动、服务端主动断开长连接等情况,urllib3不会校验实际接收字节数和响应头Content-Length声明的大小是否匹配,会直接判定响应传输完成,因此不会抛出任何异常就提前退出iter_content循环。你添加的urllib3补丁已经解决了异常检测的问题,现在抛出的IncompleteRead类错误是符合预期的,本质是大文件长距离传输过程中的连接中断问题。

解决方案

1. 断点续传实现

大文件下载过程中连接中断是无法完全避免的,最可靠的方案是实现断点续传逻辑,步骤如下:

  • 提前通过HEAD请求确认目标服务端支持范围请求,即响应头包含Accept-Ranges: bytes
  • 持久化记录已成功接收的原始压缩文件字节数(注意是未解压的原始字节,Range头的偏移基于压缩文件的原始大小计算)
  • 每次重连时携带Range: bytes={已下载字节数}-请求头,服务端会返回206状态码,从指定偏移位置继续传输
  • 连接中断后不需要重置解压对象和残留行变量,直接用原有状态继续处理新传输的块即可

参考实现代码如下(已修正你原代码中的变量名笔误:原代码误用dns_chunk指代循环变量data_chunk):

import requests
import zlib
import urllib3

def patch_urllib3():
    """Set urllib3's enforce_content_length to True by default."""
    previous_init = urllib3.HTTPResponse.__init__
    def new_init(self, *args, **kwargs):
        previous_init(self, *args, enforce_content_length = True, **kwargs)
    urllib3.HTTPResponse.__init__ = new_init

def reader(target_url, data_queue, coordinator_queue, chunk_size=4096, max_retry=10):
    patch_urllib3()
    # 已下载的原始压缩字节数,可写入本地临时文件持久化,避免进程重启丢失进度
    downloaded_bytes = 0
    decompressor = zlib.decompressobj(zlib.MAX_WBITS|32)
    last_line = ""
    retry_cnt = 0

    while retry_cnt < max_retry:
        req_headers = {}
        if downloaded_bytes > 0:
            req_headers['Range'] = f'bytes={downloaded_bytes}-'
        try:
            with requests.get(
                target_url, 
                stream=True, 
                headers=req_headers,
                # 增加超时配置,避免长时间无响应卡住
                timeout=(30, 120)
            ) as remote_file:
                # 200为全量响应,说明服务端不支持续传,重置所有状态
                if remote_file.status_code == 200:
                    decompressor = zlib.decompressobj(zlib.MAX_WBITS|32)
                    last_line = ""
                    downloaded_bytes = 0
                elif remote_file.status_code != 206:
                    raise Exception(f"服务端不支持范围请求,响应码{remote_file.status_code}")
                
                for data_chunk in remote_file.iter_content(chunk_size=chunk_size):
                    if not data_chunk:
                        continue
                    # 累计已下载的压缩字节数
                    downloaded_bytes += len(data_chunk)
                    # 解压、解码、分行逻辑
                    decompressed_chunk = decompressor.decompress(data_chunk)
                    decompressed_chunk = decompressed_chunk.decode()
                    decompressed_chunk = last_line + decompressed_chunk
                    lines = decompressed_chunk.splitlines()
                    last_line = lines.pop()
                    data_queue.put(lines)
                    coordinator_queue.put('CHUNK_READ')
                # 正常下载完成,跳出重试循环
                break
        except (
            requests.exceptions.ChunkedEncodingError,
            urllib3.exceptions.IncompleteRead,
            ConnectionError,
            TimeoutError
        ) as e:
            retry_cnt += 1
            print(f"连接中断,第{retry_cnt}次重试,已下载字节数:{downloaded_bytes},错误信息:{str(e)}")
            continue
    else:
        # 超过最大重试次数
        coordinator_queue.put('READ_FAILED')
        raise Exception(f"下载失败,超过最大重试次数{max_retry}")
    
    # 推送最后残留行,通知任务完成
    print("Sending last line.")
    data_queue.put([last_line])
    coordinator_queue.put('READ_DONE')

2. 优化建议

  • 拆分下载和处理逻辑:不要在下载进程中执行解压、解码、分行这类CPU密集操作,直接将原始压缩块丢到队列中,由下游处理进程执行对应操作,可大幅降低下载进程阻塞概率,减少服务端主动断开连接的可能
  • 开启TCP Keepalive:通过requests适配器配置TCP长连接保活,避免中间网络设备因为长时间无数据传输断开连接
  • 若服务端不支持范围请求:可优先使用wget、curl等内置断点续传能力的工具提前将文件下载到本地磁盘,再通过脚本读取本地文件处理,稳定性更高

内容的提问来源于stack exchange,提问作者Shrout1

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 17:48:04