使用请求通过 http 下载文件时的进度条
Posted
技术标签:
【中文标题】使用请求通过 http 下载文件时的进度条【英文标题】:Progress Bar while download file over http with Requests 【发布时间】:2016-10-01 02:32:51 【问题描述】:我需要下载一个相当大的 (~200MB) 文件。我想出了如何使用here 下载和保存文件。最好有一个进度条来知道已经下载了多少。我找到了ProgressBar,但我不确定如何将两者结合在一起。
这是我尝试过的代码,但它不起作用。
bar = progressbar.ProgressBar(max_value=progressbar.UnknownLength)
with closing(download_file()) as r:
for i in range(20):
bar.update(i)
【问题讨论】:
你得到的回溯错误是什么? 无,只是不更新。 有趣。我们可能使用不同的版本。当我复制/粘贴您的第一行时,我得到一个“意外的关键字参数'max_value'”。我使用的是 2.3 版。 【参考方案1】:我建议你试试tqdm
,它非常好用。
使用requests
库下载的示例代码:
from tqdm import tqdm
import requests
url = "http://www.ovh.net/files/10Mb.dat" #big file test
# Streaming, so we can iterate over the response.
response = requests.get(url, stream=True)
total_size_in_bytes= int(response.headers.get('content-length', 0))
block_size = 1024 #1 Kibibyte
progress_bar = tqdm(total=total_size_in_bytes, unit='iB', unit_scale=True)
with open('test.dat', 'wb') as file:
for data in response.iter_content(block_size):
progress_bar.update(len(data))
file.write(data)
progress_bar.close()
if total_size_in_bytes != 0 and progress_bar.n != total_size_in_bytes:
print("ERROR, something went wrong")
【讨论】:
不确定我是否遗漏了什么,但这并没有为我显示进度条,只显示数字(我认为是因为 tqdm 不知道总大小?) 是的,你需要获取总长度并将其作为参数传递:total = int(r.headers.get('content-length')); ...tqdm(r.iter_content(),total=total)...
这很安静!唯一的评论是 total_size = (int(r.headers.get('content-length', 0))/(32*1024))。这是因为请求一次获取 32*1024 字节而不是 1 字节。
我要加with open('output.bin', 'wb') as f: with tqdm(total=total_size / (32*1024.0), unit='B', unit_scale=True, unit_divisor=1024) as pbar: for data in r.iter_content(32*1024): f.write(data); pbar.update(len(data))
@HrvojeT 问题来自total_size//block_size
部分。看起来这是在迭代(并显示进度)块,而不是文件。【参考方案2】:
您似乎需要获取远程文件大小 (answered here) 来计算您的距离。
然后,您可以在处理每个块时更新进度条...如果您知道块的总大小和大小,您可以确定何时更新进度条。
【讨论】:
【参考方案3】:Progress Bar Usage 页面上的示例与代码实际需要的内容之间似乎存在脱节。
在以下示例中,请注意使用maxval
而不是max_value
。还要注意使用.start()
来初始化栏。这已在Issue 中注明。
n_chunk
参数表示在遍历请求迭代器时一次流式传输多少个 1024 kb 块。
import requests
import time
import numpy as np
import progressbar
url = "http://wikipedia.com/"
def download_file(url, n_chunk=1):
r = requests.get(url, stream=True)
# Estimates the number of bar updates
block_size = 1024
file_size = int(r.headers.get('Content-Length', None))
num_bars = np.ceil(file_size / (n_chunk * block_size))
bar = progressbar.ProgressBar(maxval=num_bars).start()
with open('test.html', 'wb') as f:
for i, chunk in enumerate(r.iter_content(chunk_size=n_chunk * block_size)):
f.write(chunk)
bar.update(i+1)
# Add a little sleep so you can see the bar progress
time.sleep(0.05)
return
download_file(url)
编辑:解决了关于代码清晰度的评论。 EDIT2:固定逻辑,因此栏在完成时报告 100%。感谢 leovp's answer 使用 1024 kb 块大小。
【讨论】:
跟我最初写的完全不一样!感谢您了解这个。【参考方案4】:tqdm 有答案。
def download(url, fname):
resp = requests.get(url, stream=True)
total = int(resp.headers.get('content-length', 0))
with open(fname, 'wb') as file, tqdm(
desc=fname,
total=total,
unit='iB',
unit_scale=True,
unit_divisor=1024,
) as bar:
for data in resp.iter_content(chunk_size=1024):
size = file.write(data)
bar.update(size)
Gits:https://gist.github.com/yanqd0/c13ed29e29432e3cf3e7c38467f42f51
【讨论】:
【参考方案5】:tqdm
包现在包含一个专门为这种情况设计的函数:wrapattr
。您只需包装对象的read
(或write
)属性,其余的由tqdm 处理;没有弄乱块大小或类似的东西。这是一个简单的下载功能,将所有内容与requests
放在一起:
def download(url, filename):
import functools
import pathlib
import shutil
import requests
from tqdm.auto import tqdm
r = requests.get(url, stream=True, allow_redirects=True)
if r.status_code != 200:
r.raise_for_status() # Will only raise for 4xx codes, so...
raise RuntimeError(f"Request to url returned status code r.status_code")
file_size = int(r.headers.get('Content-Length', 0))
path = pathlib.Path(filename).expanduser().resolve()
path.parent.mkdir(parents=True, exist_ok=True)
desc = "(Unknown total file size)" if file_size == 0 else ""
r.raw.read = functools.partial(r.raw.read, decode_content=True) # Decompress if needed
with tqdm.wrapattr(r.raw, "read", total=file_size, desc=desc) as r_raw:
with path.open("wb") as f:
shutil.copyfileobj(r_raw, f)
return path
【讨论】:
【参考方案6】:也可以使用python库enlighten,功能强大,提供彩色进度条,在Linux、Windows下都能正常运行。
以下是代码 + 现场截屏。这段代码可以运行here on repl.it。
import math
import requests, enlighten
url = 'https://upload.wikimedia.org/wikipedia/commons/a/ae/Arthur_Streeton_-_Fire%27s_on_-_Google_Art_Project.jpg?download'
fname = 'image.jpg'
# Should be one global variable
MANAGER = enlighten.get_manager()
r = requests.get(url, stream = True)
assert r.status_code == 200, r.status_code
dlen = int(r.headers.get('Content-Length', '0')) or None
with MANAGER.counter(color = 'green', total = dlen and math.ceil(dlen / 2 ** 20), unit = 'MiB', leave = False) as ctr, \
open(fname, 'wb', buffering = 2 ** 24) as f:
for chunk in r.iter_content(chunk_size = 2 ** 20):
print(chunk[-16:].hex().upper())
f.write(chunk)
ctr.update()
输出(+ascii-video)
【讨论】:
【参考方案7】:用您已经下载的大小计算文件大小会找到您的距离。或者你可以使用 tqdm。
【讨论】:
【参考方案8】:由于某种原因,我在处理 zip 文件时无法通过请求获取文件大小,所以我使用 urllib 来获取它
# A simple downloader with progress bar
import requests
from tqdm import tqdm
import zipfile
from urllib.request import urlopen
url = "https://web.cs.dal.ca/~juanr/downloads/malnis_dataset.zip"
block_size = 1024 #1 Kibibyte
filename = url.split("/")[-1]
print(f"Downloading filename...")
site = urlopen(url)
meta = site.info()
# Streaming, so we can iterate over the response.
response = requests.get(url, stream=True)
total_size_in_bytes = int(meta["Content-Length"])
progress_bar = tqdm(total = total_size_in_bytes, unit='iB', unit_scale=True)
with open('test.dat', 'wb') as file:
for data in response.iter_content(block_size):
progress_bar.update(len(data))
file.write(data)
progress_bar.close()
print("Download complete")
print(f"Extracting filename...")
zip = zipfile.ZipFile(filename, "r")
zip.extractall()
zip.close()
print("Extracting complete")
【讨论】:
以上是关于使用请求通过 http 下载文件时的进度条的主要内容,如果未能解决你的问题,请参考以下文章