【代码】Python使用requests下载文件时获取下载进度与实时速度

正文

使用requests, multiprocessing, time等库,代码如下,提供的注释供参考:

from contextlib import closing
import requests, os
import time, multiprocessing
from multiprocessing import Process


# 通过多进程实现实时速度显示
def download(download_url, download_path, headers):
    file_name = os.path.basename(download_path)
    with closing(requests.get(download_url, timeout=10, verify=False, stream=True, headers=headers)) as response:
        chunk_size = 1024                                       # 单次请求最大值
        content_size = int(response.headers['content-length'])  # 文件总大小

        with multiprocessing.Manager() as mng:  # 通过Manager向定时显示进程传递信息
            mdict = mng.dict()
            mdict['data_bytes'] = 0    # 当前下载字节数
            mdict['exit'] = False      # 进程是否继续
            process = Process(target=cron, args=(file_name, content_size, mdict))   # 生成进程对象,执行显示函数

            # 开始下载
            process.start()
            with open(download_path, "wb") as file:
                for data in response.iter_content(chunk_size=chunk_size):   # 每取满chunk_size字节即存储
                    file.write(data)
                    mdict['data_bytes'] += len(data)
            # 下载完毕
            mdict['exit'] = True
            process.join(3)     # 3秒超时时间,显示完全
            process.terminate() # 超时时直接终止


def cron(file_name, content_size, mdict):   # 显示任务函数
    interval = 0.5      # 执行间隔

    content_size_formated = format_bytes_num(content_size)  # 文件总大小转换为恰当显示格式
    data_bytes_prev = mdict['data_bytes']   # 计算字节增量所需
    time_prev = time_now = time.time()      # 初值
    while True:     # 显示循环
        data_bytes = mdict['data_bytes']            # 当前下载字节数
        # 速度计算
        try: speed_num = (data_bytes - data_bytes_prev) / (time_now - time_prev)
        except ZeroDivisionError: speed_num = 0
        data_bytes_prev = data_bytes    # 存储当前字节数作为下次计算的参照
        time_prev = time_now            # 存储当前时间作为下次计算的参照

        # 显示
        speed = format_bytes_num(speed_num)
        data_bytes_formated = format_bytes_num(data_bytes)
        persent = data_bytes / content_size * 100   # 当前下载百分比
        done_block = '█' * int(persent // 2)        # 共显示50块,故以2除百作五十,计为所下载的显示块数
        print(f"\r {file_name} ----> [{done_block:50}] {persent:.2f}%   {speed}/s   {data_bytes_formated}/{content_size_formated}", end=" ")

        # 收到信号时退出
        if mdict['exit']: break

        # 消磨剩余时间
        time_now = time.time()
        sleep_time = time_prev + interval - time_now
        if sleep_time > 0:
            time.sleep(sleep_time)
            time_now = time.time()  # 避免误差


def format_bytes_num(bytes_num):    # 格式化为合适的数值大小与单位
    i = 0
    while bytes_num > 1024 and i < 9 - 1:
        bytes_num /= 1024
        i += 1
    unit = ('B', 'kiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB')[i]
    return "%.2f" % bytes_num + unit

输出类似于:


2023.12.22 这几天又用命名管道重写了一份更好的显示进程,能够支持多行多项目的信息显示,更正了原延时处的一些bug,且可以在新的命令窗口上显示

因为使用管道传输数据,所以还可以与其他语言写的程序交互,甚至接收其他设备的信息,显示更多种项目的进度条

完整文件如下:

display.py

import msvcrt, os, time, win32pipe, pickle


def displayProcess(pfd):
    # infoList: [(cid, file_name, total_amount: int, unit, annotation), ...]
    # amountDict: {cid: current_amount: int, ...}

    # refacting: {cid: (file_name, total_amount, unit, annotation)|cur_amount, ...}
    # need: cid, (file_name, total_amount, unit, annotation)|cur_amount
    # data_format: frame_length:2, (file_name, total_amount, unit, annotation)(and cid:int4)|amountDict|Done(bool:pickle, and cid:int4):pickle

    fh = msvcrt.get_osfhandle(pfd.fileno())
    interval = 0.1  # 刷新的间隔
    window_length = 3   # 统计3秒内的内容
    infoDict = {}
    amountDictIs = [[{}, interval]]* int(window_length / interval)
    cur_time = time.time()
    while True:
        # 获取管道所有更新
        while win32pipe.PeekNamedPipe(fh, 0)[1] != 0:  # 有东西才读
            frame_length = int.from_bytes(pfd.read(2), byteorder='little')
            reads = pfd.read(frame_length)
            stuff = pickle.loads(reads)
            hisType = type(stuff)
            if hisType is tuple:
                cid = int.from_bytes(reads[-4:], byteorder='little')
                infoDict[cid] = stuff
                for di in amountDictIs:
                    di[0][cid] = 0
            elif hisType is dict:
                amountDictIs[0][0] = stuff
            else:
                cid = int.from_bytes(reads[-4:], byteorder='little')
                if cid in infoDict:
                    del infoDict[cid]
        # 计算窗口内时间
        delta_time = 0
        for i, di in enumerate(amountDictIs):
            delta_time += di[1]
            if delta_time >= window_length:
                prev_amountDict = amountDictIs.pop(i - 1 if i > 0 else 0)[0]
                break
        else:
            prev_amountDict = amountDictIs.pop()[0]
        cur_amountDict = amountDictIs[0][0]
        # 清空上一次的输出内容,比cls快
        print("\033[H\033[J", end='')
        #显示
        for n, (cid, (file_name, total_amount, unit, annotation)) in enumerate(infoDict.items(), 1):
            # 获取所需信息并计算速度
            if cid in cur_amountDict:
                cur_amount = cur_amountDict[cid]
                if cid in prev_amountDict:
                    prev_amount = prev_amountDict[cid]
                    speed = (cur_amount - prev_amount) / delta_time
                else:
                    speed = cur_amount / delta_time
            else:
                cur_amount = 0
                speed = 0
            percentage = cur_amount / total_amount * 100
            print(f'[{n}]:', file_name, '---> (')
            print('    [%-50s] %.2f%%\t%s/s\t%s/%s'%(
                '█' * int(percentage / 2),
                percentage,
                format_num(speed) + unit,
                format_num(cur_amount) + unit,
                format_num(total_amount) + unit
            ))
            print('  )', annotation)
        # # mean to delay some...
        # time.sleep(0.5)
        #休息时间
        prev_time = cur_time
        next_time = prev_time + interval
        cur_time = time.time()
        sleep_time = next_time - cur_time
        if sleep_time > 0:
            time.sleep(sleep_time)
            cur_time = next_time
        else:   # 万一前面产生了过长的卡顿
            amountDictIs[0][1] = cur_time - prev_time
        amountDictIs.insert(0, [amountDictIs[0][0].copy(), interval])


def format_num(num: int) -> str:    # 格式化为合适的数值大小与部分单位,完整单位后面加
    i = 0
    while num > 1024 and i < 9 - 1:
        num /= 1024
        i += 1
    unit = ('', 'ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi')[i]
    return "%.2f" % num + unit


# 需提前打开管道
with open(r'\\.\pipe\show_pipe', "rb") as pfd:
    displayProcess(pfd)
    os.system('pause')

输出类似于:

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值