0

0

Python怎样实现文件断点续传?HTTP请求控制

雪夜

雪夜

发布时间:2025-08-13 10:30:02

|

807人浏览过

|

来源于php中文网

原创

python实现文件断点续传的关键在于利用http协议的range请求头,通过1.检查本地文件大小确定下载起点;2.在请求头中添加range字段如bytes=1024-以请求指定字节范围;3.发送请求后根据状态码判断服务器支持情况,206表示支持断点续传,200则需重新下载;4.解析content-range响应头获取文件总大小并校验续传一致性;5.以追加模式写入数据并实时更新进度;6.结合head请求预判服务器是否支持accept-ranges: bytes及content-length;7.加入异常处理、重试机制与网络中断恢复策略;8.通过etag或last-modified配合if-range防止服务器文件变更导致的下载错乱;9.确保流式下载stream=true和分块读取iter_content以节省内存;10.最终完成下载后建议进行文件完整性校验,整个过程需兼顾服务器兼容性与系统健壮性,从而实现高效可靠的断点续传功能。

Python怎样实现文件断点续传?HTTP请求控制

Python实现文件断点续传,主要依赖于HTTP协议的

Range
请求头。这个机制允许客户端请求文件的一部分内容,而不是下载整个文件。当下载中断后,我们可以根据已下载的文件大小,向服务器发起新的请求,从中断处继续获取数据,从而避免重复下载已有的部分,极大提升下载效率和用户体验。

解决方案

要实现断点续传,我们首先得理解HTTP的几个关键点。最核心的就是

Range
请求头,比如
Range: bytes=1024-
就表示从文件的第1024字节开始下载到文件末尾。服务器如果支持,会返回
206 Partial Content
状态码,并在响应头中包含
Content-Range
,告诉你这次传输的是哪一部分,以及文件的总大小。如果服务器返回
200 OK
,那就说明它不鸟你的
Range
请求,直接把整个文件又发过来了,这时候你就得做好重新下载的准备了。

具体来说,实现这个功能,我们需要:

立即学习Python免费学习笔记(深入)”;

  1. 检查本地文件状态: 在开始下载前,先看看本地是不是已经有这个文件了?如果有,它有多大?这个大小就是我们下次请求的起点。
  2. 构建HTTP请求: 使用Python的
    requests
    库(或者其他HTTP客户端库),在请求头里加上
    Range
    ,例如
    headers={'Range': f'bytes={已下载大小}-'}
  3. 处理服务器响应:
    • 如果状态码是
      206 Partial Content
      ,恭喜你,服务器支持,你可以把这次收到的数据追加到现有文件末尾。
    • 如果状态码是
      200 OK
      ,这意味着服务器不接受
      Range
      请求,或者干脆忽略了它,直接把整个文件又发过来了。这时候,你可能需要决定是覆盖现有文件,还是放弃断点续传,重新下载。
    • 如果遇到其他错误状态码,比如
      404 Not Found
      500 Internal Server Error
      ,那就得处理异常了。
  4. 写入文件: 将接收到的数据块以追加模式写入本地文件。
  5. 循环与重试: 网络下载嘛,总会遇到各种幺蛾子。所以,需要一个循环来持续下载,直到文件完整。同时,加入适当的重试机制,比如网络瞬断了,等几秒再试。

说实话,我最初接触这个的时候,觉得概念挺简单,不就是加个头嘛。但实际写起来,你会发现各种边缘情况和服务器行为差异才是真正的挑战。

如何判断服务器是否支持断点续传?

这是个很实际的问题,毕竟不是所有服务器都那么“友好”。最直接的办法是发送一个

HEAD
请求。
HEAD
请求和
GET
请求很像,但它只返回响应头,不返回实际内容,这对于我们判断服务器能力来说非常高效。

ima.copilot
ima.copilot

腾讯大混元模型推出的智能工作台产品,提供知识库管理、AI问答、智能写作等功能

下载

发送

HEAD
请求后,你需要关注响应头中的两个关键信息:

  1. Accept-Ranges
    头部:
    如果响应头中包含
    Accept-Ranges: bytes
    ,那恭喜你,服务器很可能支持字节范围请求。这意味着它能理解并处理你的
    Range
    头。如果这个头是
    none
    ,或者干脆没有,那基本上就别指望它能断点续传了。
  2. Content-Length
    头部:
    这个头部会告诉你文件的总大小。即使服务器支持
    Range
    ,如果你不知道文件总大小,也无从判断下载是否完成。所以,这个信息也很重要。

我个人经验是,光看

Accept-Ranges
还不够保险。有些服务器虽然宣称支持,但实际行为却很怪异。更稳妥的办法是,在真正开始下载前,尝试发送一个极小的
Range
请求,比如
Range: bytes=0-0
,然后检查返回的状态码是不是
206 Partial Content
。如果是,那才真正说明它能干活。如果返回
200 OK
,或者干脆报错,那说明这个服务器可能在
Range
支持上有点“水分”。

Python实现断点续传的关键代码逻辑是什么?

使用

requests
库来实现断点续传,核心逻辑其实并不复杂,但细节决定成败。

import requests
import os
import time

def download_file_resumable(url, local_filename, chunk_size=8192):
    """
    实现文件的断点续传下载
    """
    if os.path.exists(local_filename):
        # 检查本地文件大小,作为续传的起点
        downloaded_size = os.path.getsize(local_filename)
        mode = 'ab'  # 追加模式
        headers = {'Range': f'bytes={downloaded_size}-'}
        print(f"检测到本地文件 {local_filename},大小 {downloaded_size} 字节,尝试从此处续传。")
    else:
        downloaded_size = 0
        mode = 'wb'  # 写入模式
        headers = {}
        print(f"本地无文件 {local_filename},将从头开始下载。")

    try:
        # 第一次请求,检查服务器是否支持Range
        # 实际下载时,可以先发HEAD请求判断,这里直接用GET并处理200/206
        with requests.get(url, headers=headers, stream=True, timeout=30) as r:
            # 检查服务器是否支持断点续传,以及响应状态码
            if r.status_code == 206:
                print("服务器支持断点续传 (206 Partial Content)。")
                # 确保Content-Range头存在且格式正确
                content_range = r.headers.get('Content-Range')
                if content_range:
                    # Content-Range: bytes 0-1023/10240
                    total_size_str = content_range.split('/')[-1]
                    try:
                        total_size = int(total_size_str)
                    except ValueError:
                        print("警告: 无法解析Content-Range中的总大小。")
                        total_size = None
                else:
                    print("警告: 206响应中缺少Content-Range头部。")
                    total_size = None # 无法确定总大小,可能需要重新下载

                # 如果本地文件大小与服务器响应的起始字节不符,可能需要重新下载
                if total_size is not None and downloaded_size > 0 and downloaded_size != int(content_range.split(' ')[1].split('-')[0]):
                    print("警告: 本地文件大小与服务器续传起始点不符,可能需要重新下载。")
                    # 这里可以加入逻辑,比如删除本地文件,重新下载
                    # os.remove(local_filename)
                    # return download_file_resumable(url, local_filename) # 重新调用
                    # 为了简化,这里选择继续,但用户需注意
                    pass

            elif r.status_code == 200:
                print("服务器不支持断点续传或忽略了Range请求 (200 OK),将重新下载整个文件。")
                # 如果是200,说明服务器发了整个文件,需要从头开始写入
                mode = 'wb'
                downloaded_size = 0
                total_size = int(r.headers.get('Content-Length', 0))
            else:
                print(f"下载失败: 服务器返回状态码 {r.status_code}")
                return False

            # 获取文件总大小,用于进度显示
            if total_size is None:
                # 尝试从Content-Length获取,如果206没有Content-Range
                total_size = int(r.headers.get('Content-Length', 0)) + downloaded_size # 对于206,Content-Length是剩余部分的大小

            # 写入文件
            with open(local_filename, mode) as f:
                for chunk in r.iter_content(chunk_size=chunk_size):
                    if chunk: # 过滤掉保持连接的空数据块
                        f.write(chunk)
                        downloaded_size += len(chunk)
                        # 简单的进度显示
                        if total_size:
                            progress = (downloaded_size / total_size) * 100
                            print(f"\r下载进度: {progress:.2f}% ({downloaded_size}/{total_size} 字节)", end='')
                        else:
                            print(f"\r已下载: {downloaded_size} 字节", end='')
            print(f"\n文件下载完成: {local_filename}")
            return True

    except requests.exceptions.RequestException as e:
        print(f"网络请求错误: {e}")
        return False
    except Exception as e:
        print(f"发生未知错误: {e}")
        return False

# 示例使用
if __name__ == "__main__":
    # 找一个支持断点续传的URL,比如一些大型文件下载地址
    # 注意:请替换为实际可用的URL
    test_url = "http://speedtest.tele2.net/1MB.zip" # 示例,可能会失效
    # test_url = "https://example.com/some_large_file.zip" # 替换为你的文件URL
    output_file = "downloaded_file.zip"

    print(f"尝试下载: {test_url} 到 {output_file}")
    success = download_file_resumable(test_url, output_file)
    if success:
        print("下载任务成功完成。")
    else:
        print("下载任务失败。")

    # 模拟中断后再次运行,看是否能续传
    # 可以手动中断程序后再次运行,或者在函数内部模拟中断(比如下载一部分后raise Exception)
    # print("\n模拟中断后再次尝试下载...")
    # time.sleep(2) # 模拟中断
    # success_resume = download_file_resumable(test_url, output_file)
    # if success_resume:
    #     print("续传任务成功完成。")
    # else:
    #     print("续传任务失败。")

这段代码里,我尝试把可能遇到的状态都考虑进去。

stream=True
是关键,它让
requests
不会一次性把所有内容都下载到内存,而是流式传输,这对于大文件下载至关重要。
iter_content
则允许我们分块处理数据,边下载边写入,进一步节省内存。我特别加了对
Content-Range
的解析,因为
206
响应的
Content-Length
只表示本次传输的数据量,而不是文件总大小。

断点续传中可能遇到的挑战与应对策略

说实话,断点续传听起来美好,但实际应用中总会碰到一些“坑”,这就像你以为代码写完了,结果测试一跑,各种意想不到的边界条件就冒出来了。

  1. 服务器不支持
    Range
    请求:
    这是最常见也是最无奈的情况。前面提过,通过
    HEAD
    请求或小范围
    GET
    请求可以判断。如果服务器确实不支持,那你的策略就得变成“全量下载”。这意味着,如果下载中断,就得从头再来。所以,你的下载器需要有这种降级能力,不能因为不支持续传就直接报错。
  2. 网络中断与重试: 网络波动是家常便饭。下载过程中,连接可能会断开。你需要实现一个健壮的重试机制,比如使用指数退避(Exponential Backoff)策略。第一次失败等1秒,第二次等2秒,第三次等4秒,以此类推,但要设定最大重试次数和最大等待时间,避免无限等待。
    requests
    库的
    Retry
    适配器就能很好地处理这类问题。
  3. 文件完整性校验: 下载完成的文件,真的完整无损吗?或者说,续传过程中文件是否被意外截断或损坏?最可靠的方法是服务器提供文件的哈希值(如MD5、SHA256)。下载完成后,计算本地文件的哈希值并与服务器提供的进行比对。如果服务器不提供,至少也要检查下载的文件大小是否与
    Content-Length
    (如果知道总大小的话)匹配。
  4. 文件在服务器端发生变化: 你正在下载一个文件,结果服务器上的文件更新了。这时候,如果你继续续传,就会得到一个“拼接”后的文件,前半部分是旧的,后半部分是新的,这显然不是你想要的。HTTP协议提供了
    ETag
    Last-Modified
    头部来解决这个问题。在续传前,可以带上
    If-Range
    请求头,值为上次下载时服务器返回的
    ETag
    Last-Modified
    。如果文件在服务器端没有变化,服务器会返回
    206
    ;如果变化了,则返回
    200
    ,表示需要重新下载整个文件。这个细节,很多人在实现断点续传时容易忽略。
  5. 并发写入问题: 如果你的应用有多个线程或进程可能同时操作同一个下载文件,那就需要引入文件锁(如
    threading.Lock
    fcntl
    )来避免数据冲突和文件损坏。当然,更常见的做法是让一个下载任务只由一个线程/进程负责。
  6. 磁盘空间不足: 如果下载的是一个非常大的文件,最好在开始下载前检查一下目标磁盘是否有足够的空间。
    Content-Length
    头可以帮助你预估所需空间。

处理这些挑战,就像在修补一个漏水的桶,你得找到所有的洞,并用合适的材料堵上。这不仅仅是技术问题,更是一种对系统健壮性和用户体验的思考。

相关专题

更多
python开发工具
python开发工具

php中文网为大家提供各种python开发工具,好的开发工具,可帮助开发者攻克编程学习中的基础障碍,理解每一行源代码在程序执行时在计算机中的过程。php中文网还为大家带来python相关课程以及相关文章等内容,供大家免费下载使用。

715

2023.06.15

python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

625

2023.07.20

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

739

2023.07.25

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

617

2023.07.31

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

1235

2023.08.03

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

547

2023.08.04

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

575

2023.08.04

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

697

2023.08.11

桌面文件位置介绍
桌面文件位置介绍

本专题整合了桌面文件相关教程,阅读专题下面的文章了解更多内容。

0

2025.12.30

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 0.6万人学习

Django 教程
Django 教程

共28课时 | 2.6万人学习

SciPy 教程
SciPy 教程

共10课时 | 0.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号