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

如何基于Requests会话实现多分片并发大文件下载并显示进度条

多分片并发下载实现(requests Session + ThreadPool + 进度条)

针对你的需求,这里给出基于ThreadPoolExecutor的多分片并发下载实现,完美适配你的现有代码逻辑,保留requests.Session会话和enlighten进度条,同时保证下载高效无错误:

完整代码

import lxml.html as htmlparser
import regex
import math
import enlighten
import requests
from concurrent.futures import ThreadPoolExecutor, as_completed

def download_chunk(session, url, start, end, file_obj, counter):
    """下载单个分片并写入文件对应位置"""
    headers = {"Range": f"bytes={start}-{end}"}
    # 复制原会话的cookies到当前会话,保持登录状态
    session.cookies.update(original_session.cookies)
    response = session.get(url, stream=True, headers=headers)
    response.raise_for_status()
    
    # 写入分片到文件指定位置
    file_obj.seek(start)
    for chunk in response.iter_content(chunk_size=2**20):
        if chunk:
            file_obj.write(chunk)
            counter.update(len(chunk) // 2**20)  # 按MiB更新进度

if __name__ == "__main__":
    # 初始化原会话,获取必要的认证信息和下载链接
    original_session = requests.Session()
    url = 'https://serverlink.com/vid/XXXYYY'
    URL = "https://serverhomepage.com/"

    CONTENT_ID = regex.compile(r"/vid/([^?#&/]+)")
    
    # 获取csrf-token
    csrf_token = (
        htmlparser.fromstring(original_session.get(url).text)
        .cssselect("meta[name='csrf-token']")[0]
        .get("content")
    )

    # 获取内容ID和下载流链接
    content_id = CONTENT_ID.search(url).group(1)
    stream_response = original_session.post(
        URL + "api/vid/get&user=xxx&pass=yyy",
        headers={
            "x-requested-with": "XMLHttpRequest",
            "x-csrf-token": csrf_token,
        },
        json={"id": content_id},
    )
    stream_response.raise_for_status()
    stream_url = stream_response.json().get("result").get("Link")

    # 获取最终下载链接和文件总大小
    head_response = original_session.head(stream_url)
    head_response.raise_for_status()
    final_url = head_response.headers.get("Location")
    
    # 获取文件总大小
    size_response = original_session.head(final_url)
    size_response.raise_for_status()
    total_size = int(size_response.headers.get("Content-Length", 0))
    assert total_size > 0, "无法获取文件大小"

    fname = 'vid.mp4'
    num_chunks = 10  # 分片数量,可根据网络情况调整(建议5-20)
    chunk_size = total_size // num_chunks

    # 初始化进度条
    MANAGER = enlighten.get_manager()
    total_mib = math.ceil(total_size / 2**20)
    counter = MANAGER.counter(
        color='green',
        total=total_mib,
        unit='MiB',
        leave=False,
        desc="下载进度"
    )

    # 打开文件,准备写入(先创建空文件并设置大小)
    with open(fname, 'wb') as f:
        # 预分配文件空间(可选,提升写入效率)
        f.truncate(total_size)
        
        # 使用线程池下载分片
        with ThreadPoolExecutor(max_workers=num_chunks) as executor:
            futures = []
            for i in range(num_chunks):
                start = i * chunk_size
                # 最后一个分片覆盖到文件末尾
                end = start + chunk_size - 1 if i != num_chunks - 1 else total_size - 1
                # 每个线程创建独立的Session,避免线程安全问题
                session = requests.Session()
                futures.append(executor.submit(download_chunk, session, final_url, start, end, f, counter))
            
            # 等待所有任务完成,处理异常
            for future in as_completed(futures):
                try:
                    future.result()
                except Exception as e:
                    print(f"分片下载失败: {str(e)}")
                    raise  # 抛出异常终止整个下载

    print(f"文件 {fname} 下载完成,总大小: {total_mib} MiB")
    MANAGER.stop()

核心要点说明

  • 会话安全:requests.Session不是线程安全的,因此每个下载线程创建独立的Session,并复制原Session的cookies,确保认证状态一致。
  • 分片下载逻辑:通过HTTP的Range请求头实现分块下载,服务器需支持断点续传(大部分主流服务器都支持)。
  • 文件写入:用seek()定位到分片起始位置写入,避免多线程写入冲突,保证文件完整性。
  • 进度条同步:enlighten的counter是线程安全的,每个分片下载时按MiB单位更新进度,总进度实时准确。
  • 异常处理:捕获每个分片的下载异常,一旦出错立即终止整个下载,避免生成损坏文件。
  • 性能优化:预分配文件空间减少磁盘碎片,chunk_size设为1MiB平衡网络IO和磁盘写入效率。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 18:07:03