Python使用requests库下载文件时,展示自定义的进度条

前言

本来不打算将源码发出来的,但想着找了很久发现网上详细教程挺少的,还是总结分享一下吧,也希望该文章能帮到与我遇到相同问题的人。

注:该代码使用requests库即可完成,也仅对进度条部分实现逻辑进行说明。

1、源码展示

import time
import requests

def file_handle_speed(process, file_length):
    """
    显示文件处理速度
    :param process: 当前处理进度
    :param file_length: 文件内容总长度
    :return:
    """
    if process != file_length:
        num = process / file_length
        progress = '\r执行进度:%2.f%%【%s%s】' % (float(num * 100), '■' * round(num * 50), '□' * round((1 - num) * 50))
    else:
        progress = '\r执行完成:100%%【%s】' % ('■' * 50)
    print(progress, end='', flush=True)


def download_file(file_name, responses):
    """
    显示文件下载速度
    :param file_name: 保存的文件名称
    :param responses: response对象
    :return:
    """
    file_size = int(responses.headers['Content-Length'])        # 获取文件大小
    count_content = 0
    for content in responses.iter_content(chunk_size=DOWNLOAD_SPEED):    # 将要下载的文件分块进行下载
        start_time = time.time()

        for index in range(3):        # 用于控制出现访问权限异常时,重试的次数
            try:
                with open(file_name, mode='ab+') as f:
                    f.write(content)        # 将已下载的块文件保存至指定文档中
                    time.sleep(0.1)
                break
            except PermissionError:
                print('文件无法访问,下载完成前请勿访问正在下载中文件,5秒后将进行第{}次重试.....'.format(index + 1))
                time.sleep(5)
                continue

        end_time = time.time() - start_time     # 计算得到下载保存时间差
        count_content += len(content)       # 记录已下载的长度
        file_handle_speed(count_content, file_size)     # 打印进度
        if file_size / 1024 > 1024:     # 计算并打印出下载速度,剩余时间,文件大小的属性
            # 得到当前下载速度
            download_speed = round(file_size / 1048576 / end_time, 2)
            # 下载速度大于配置的下载速度时,将显示计算得到的值,否则显示配置的下载速度,因配置文件中下载速度的单位为byte(位),故需统一一下单位
            download_speed = download_speed if download_speed < (DOWNLOAD_SPEED / 1048576) else (DOWNLOAD_SPEED / 1048576)
            print('当前下载速度:{} MB/S,剩余下载时间:{} S,文件总大小:{}MB'.format(
                download_speed,
                int((file_size - count_content) / 1048576 / download_speed),
                round(file_size / 1048576, 2)
            ), flush=True, end='')
        else:
            download_speed = round(file_size / 1024 / end_time, 2)
            download_speed = download_speed if download_speed < (DOWNLOAD_SPEED / 1024) else (DOWNLOAD_SPEED / 1024)
            print('当前下载速度:{} KB/S,剩余下载时间:{} S,文件总大小:{}MB'.format(
                download_speed,
                int((file_size - count_content) / 1024 / download_speed),
                round(file_size / 1048576, 2)
            ), flush=True, end='')


if __name__ == '__main__':
    response = requests.get(url='xxxx', headers={})
    download_file('WY.exe', response)

注:代码中DOWNLOAD_SPEED参数值为:1048576

# 部分新代码,修改了各个值的计算方式,DOWNLOAD_SPEED的单位在配置文件中改成了MB,但还是存在一点小问题
def download_file(file_name, responses, folder=None):
    """
    显示文件下载速度
    :param folder: 存在多个文件时,可使用该参数将文件保存至指定文件夹中
    :param file_name: 保存的文件名称
    :param responses: response对象
    :return:
    """
    file_size = int(responses.headers['Content-Length'])        # 获取文件大小
    file_count_size = round(file_size / 1048576, 2)     # 单位 MB
    count_content = 0

    if folder is not None:
        if os.path.exists(os.getcwd() + '\\' + folder) is False:
            print('指定存储文件夹不存在,开始创建 文件夹.....')
            os.mkdir(os.getcwd() + '\\' + folder)
            file_name = (folder if folder[-1] != '/' else folder[:-1]) + '\\' + file_name
            print('文件夹创建成功,即将开始文件下载......')
        else:
            file_name = (folder if folder[-1] != '/' else folder[:-1]) + '\\' + file_name
    else:
        print('指定存在路径存在,即将开始下载......')

    for content in responses.iter_content(chunk_size=DOWNLOAD_SPEED * 1048576):    # 将要下载的文件分块进行下载
        print('\n文件正在下载中......', end='')
        start_time = time.time()

        for index in range(3):
            try:
                with open(file_name, mode='ab+') as f:
                    f.write(content)        # 将已下载的块文件保存至指定文档中
                    time.sleep(0.1)
                break
            except PermissionError:
                print('文件无法访问,下载完成前请勿访问正在下载中文件,5秒后将进行第{}次重试.....'.format(index + 1))
                time.sleep(5)
                if index == 2:
                    raise repr('DownloadError: {}{}'.format(file_name, '文件访问权限异常,无法继续写入数据,文件下载失败!!!'))
                else:
                    continue

        end_time = time.time() - start_time     # 计算得到下载保存时间差
        count_content += len(content)       # 记录已下载的长度
        file_handle_speed(count_content, file_size)     # 打印进度
        download_speed = round(len(content) / 1048576 / end_time, 2)      # 计算下载速度单位M/S
        download_speed = download_speed if download_speed < DOWNLOAD_SPEED else DOWNLOAD_SPEED
        remaining_time = int((file_size - count_content) / 1048576 / download_speed)        # 剩余时间计算

        if download_speed >= 1:     # 计算并打印出下载速度,剩余时间,文件大小的属性
            # 得到当前下载速度
            print('当前下载速度:{} MB/S,剩余下载时间:{} S,文件总大小:{}MB'.format(
                download_speed,
                remaining_time,
                file_count_size
            ), flush=True, end='')
        else:
            print('当前下载速度:{} KB/S,剩余下载时间:{} S,文件总大小:{}MB'.format(
                download_speed * 1024,
                remaining_time,
                file_count_size
            ), flush=True, end='')

    print('文件下载完成......')

2、部分函数简单说明

iter_content():该函数的作用主要是将请求的数据进行分块,功能与items()函数相似,其中chunk_size参数表示设置请求的数据块的大小【可理解为将一个蛋糕按规定的大小进行切块,这里切块的大小就是使用chunk_size参数进行设置】,块的单位为(byte),且数据必须为int类型

效果如下:

进度条样式一:

进度条样式二:

 图中为了提现效果,所以块大小(可理解为下载速度,至少我觉得可以这么理解)我限制为了1m/s,额,但图中好像有些小bug,算了剩下的大家就自己看注释信息吧,或者评论后面我回答吧,就这样了,其他的就靠各位大家自己去发现了。

备注

不好意思,上方的代码最终的展示效果实际为文件下载后的二进制数据写入文件中的速度并非实际的下载速度,下面这个才是真正的实现展示文件下载速度代码,该函数的实现主要参考了urllib中的urlretrieve函数:

def range_requests(responses, progress, save_file_function, file_name, folder_name=None):
    """
    利用断点续传功能分段获取文件内容
    :param file_name: 保存文件名称
    :param folder_name: 保存至指定文件夹
    :param save_file_function: 用于保存文件的回调函数
    :param progress: 需提供一个回调函数用于打印文件下载进度,将为函数需传入四个参数
        参数1:已下载内容长度
        参数2:当前下载块的长度
        参数3:响应时间
        参数4:所下载文件总长度
    :param responses: 使用requests第一次请求时所返回的response对象
    :return: None
    """
    header = responses.request.headers  # 获取上一次请求的请求头信息
    content_length = int(responses.headers['Content-Length'])  # 存储所需下载文件总大小
    download_size = 0  # 初始化已下载的内容长度记录参数
    download_speed = (DOWNLOAD_SPEED if DOWNLOAD_SPEED >= 1 else 1) * 1048576   # 初始化下载速度
    session = requests.Session()  # 实例化一个session对象,避免下方循环中每次均实例化requests对象
    first_execution = True      # 判断程序是否第一次执行

    if round(content_length / 1048576) <= 1:  # 当下载的文件小于1MB时不使用断点续传功能,将直接进行文件下载
        range_response = session.request(method='get', url=responses.url, headers=header, stream=True)  # 开始请求下载数据
        # 调用函数,将下载的数据保存至指定文件中
        file_name, first_execution = save_file_function(file_name, range_response.content, folder_name)
        progress(content_length, content_length, range_response.elapsed.total_seconds(), content_length)  # 打印下载进度

    else:
        header['Range'] = 'bytes=%s-%s' % (download_size, 1048575)      # 更新请求头并设置断点续传需进行下载的部分

        while download_size < content_length:  # 根据已下载内容长度判断是否已经完成下载
            range_response = session.request(method='get', url=responses.url, headers=header, stream=True)
            # 调用函数,将下载的数据保存至指定文件中
            start_time = time.time()
            file_name, first_execution = save_file_function(file_name, range_response.content, folder_name, first_execution)
            requests_time = time.time() - start_time + range_response.elapsed.total_seconds()  # 获取并保存响应及写入文件的总时间
            download_size += len(range_response.content)  # 更新记录已下载内容长度
            progress(download_size, download_speed / 1048576, requests_time, content_length)

            if requests_time < 1:  # 判断当前下载速度是否小于1秒,小于1秒时则自动增加下载速度
                download_speed += 1048576     # 下载速度将按照每次1m的速度进行递增
            else:  # 下载速度大于1秒时,将自动减小下载速度,且下载速度最低为1M/S
                download_speed = (download_speed - 1048576) if download_speed > 1048576 else 1048576

            header['Range'] = 'bytes=%s-%s' % (download_size, download_size + download_speed)       # 更新文件下载进度与下载速度

    print('\n下载完成,文件保存路径:{}!!!!'.format(file_name))

效果图:

该函数其实就是将上面的代码进行了一些更改,并且重新实现了iter_content()函数的功能,其他的不过多解释了,毕竟是第一次实现这个脚本,优化的地方肯定也还有很多地方。

大家有什么好的意见,或者代码中存在什么问题都可评论私信告诉我,先谢谢啦!!!!

YX9010_0@的第十四篇文章

                                                                                                             2022/8/3

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值