我正在制作一个下载一个大文件的程序,我已经添加了一个功能,该功能可以确定程序的下载百分比,并在每次下载另一个10%的时候通知用户,当我在较小的文件上测试该程序时(即,print (str(percent) + " downloaded at " + str(time))),然而,我注意到它的准确性要低得多。下面是我做的一个示例程序:
import urllib.request
def printout(a, b, c):
print(str(a) + ", " + str(b) + ", " + str(c))
urllib.request.urlretrieve("http://downloadcenter.mcafee.com/products/tools/foundstone/fport.zip", r"C:\Users\Username\Downloads\fport.zip", reporthook = printout)这会下载Fport,这是我本来要下载的一个工具。不管怎样,我得到了这个输出:
0, 8192, 57843
1, 8192, 57843
2, 8192, 57843
3, 8192, 57843
4, 8192, 57843
5, 8192, 57843
6, 8192, 57843
7, 8192, 57843
8, 8192, 57843我想这正是我想要的。我正要把它放进去,这时我发现了一个小错误。8192与57843不符。不是8次。我把它插入到一个计算器中,发现,实际上,它大约有7次。这是一个相当大的差异,考虑到。这种断开对较大文件的影响较小,但它仍然存在。这是一种元数据还是报头?如果是这样的话,它是相当大的,不是吗?有没有办法解决这个问题(比如,它总是在16000字节左右)?
发布于 2014-01-16 06:33:08
因此,如果您查看Lib/urllib/request.py (CPython约2.7)代码,就会明白为什么会出现这种情况:
with tfp:
result = filename, headers
bs = 1024*8 # we read 8KB at a time.
size = -1
read = 0
blocknum = 0
if "content-length" in headers:
size = int(headers["Content-Length"])
if reporthook:
reporthook(blocknum, bs, size)
while True:
block = fp.read(bs) # here is where we do the read
if not block:
break
read += len(block)
tfp.write(block)
blocknum += 1
if reporthook:
reporthook(blocknum, bs, size)在最后一行中,reporthook被告知读取的是bs,而不是len(block),后者可能更准确。我不确定为什么会这样,也就是说,是否有一个很好的原因,或者是库中的一个小错误。当然,您可以在Python邮件程序上请求和/或提交一个bug。
注意:我认为在固定大小的块中读取数据是相当常见的,例如fread。在那里,返回值可能与遇到EOF (文件结尾)时请求读取的字节数不同,这与Python中的read类似。
发布于 2014-01-16 06:36:44
文档解释说,每个“分块”调用一次reporthook,并带有分块数量和总大小。
urllib.request不会试图使区块大小完全相等;它将尝试使区块大小成为2的一个很好的幂,比如8192,因为这通常是最快和最简单的。
因此,您要做的是使用实际的字节数来计算百分比,而不是区块数字。
urlretrieve接口不提供获取实际字节数的简单方法。只有当您假设每个socket.recv(n) (但最后一个)实际上返回n个字节时,计算块数才有效,这是不能保证的。只有假设urlretrieve在每次调用之前使用无缓冲的文件或刷新,os.stat(filename)才能工作(在大多数平台上),同样不能保证这一点。
这是不使用“遗留接口”的众多原因之一。
高级接口(只是调用urllib.request.urlopen并将Response用作文件对象)看起来可能比urlretrieve提供的信息少,但如果您阅读urllib.request Restrictions,就会清楚地看出这是一种错觉。因此,您可以只使用urlopen,在这种情况下,您只需从一个文件对象复制到另一个文件对象,而不是使用有限的回调接口,因此您可以使用任何您喜欢的文件对象复制函数,或者编写自己的函数:
def copy(fin, fout, flen=None):
sofar = 0
while True:
buf = fin.read(8192)
if not buf:
break
sofar += len(buf)
if flen:
print('{} / {} bytes'.format(sofar, flen))
fout.write(buf)
print('All done')
r = urllib.request.urlopen(url)
with open(path, 'wb') as f:
copy(r, f, r.headers.get('Content-Length'))如果您真的想要连接到urllib低级内部的东西,那么urlretrieve不是那种东西;它只是假装而已。您必须创建自己的opener子类以及随之而来的所有乱七八糟的东西。
如果您想要一个几乎与urlopen一样简单但提供与自定义opener…一样多的功能的接口好吧,urllib没有,这就是像requests这样的第三方模块存在的原因。
发布于 2014-01-17 03:01:25
urllib.request的高级接口真的不适合你正在尝试做的事情。你可以使用底层接口,但实际上,这是第三方库requests简化了一个数量级的事情之一。(例如,你不必使用requests-the各种curl包装器,这也比urllib更容易。但requests是最urllib的-like,也是最简单的第三方替代品)。
requests可以像urllib一样工作,自动下载所有内容,但只需添加stream=True,您就可以控制数据的提取。它有几个不同的接口(解码的Unicode行、字节行、套接字中的原始数据等),但iter_content可能是您想要的接口-它按需提供内容块、适当缓冲、透明地将分块传输模式映射为平面传输、处理100Continue、…基本上HTTP可以抛给你的所有东西。所以:
with open(path, 'wb') as f:
r = requests.get(url, stream=True)
for chunk in r.iter_content(8192):
f.write(chunk)添加进度仍然需要手动完成。但是,由于您提取的是块,而不是将它们保存到背后的文件中,因此您确切地知道您看到了多少字节。而且,只要服务器提供了Content-Length标头(有些服务器在某些情况下不会这样做,但除了处理它之外,您对此也无能为力),这很容易:
with open(path, 'wb') as f:
r = requests.get(url, stream=True)
total = r.headers.get('content-length')
sofar = 0
for chunk in r.iter_content(8192):
f.write(chunk)
sofar += len(chunk)
if total:
print('{} / {}: {}%'.format(sofar, total, sofar*100.0/total))
else:
print('{} / ???: ???%'.format(sofar))https://stackoverflow.com/questions/21149555
复制相似问题