正文
使用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')
输出类似于: