首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Urllib进度抖动不准确?

Urllib进度抖动不准确?
EN

Stack Overflow用户
提问于 2014-01-16 06:23:46
回答 3查看 170关注 0票数 3

我正在制作一个下载一个大文件的程序,我已经添加了一个功能,该功能可以确定程序的下载百分比,并在每次下载另一个10%的时候通知用户,当我在较小的文件上测试该程序时(即,print (str(percent) + " downloaded at " + str(time))),然而,我注意到它的准确性要低得多。下面是我做的一个示例程序:

代码语言:javascript
运行
复制
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,这是我本来要下载的一个工具。不管怎样,我得到了这个输出:

代码语言:javascript
运行
复制
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字节左右)?

EN

回答 3

Stack Overflow用户

发布于 2014-01-16 06:33:08

因此,如果您查看Lib/urllib/request.py (CPython约2.7)代码,就会明白为什么会出现这种情况:

代码语言:javascript
运行
复制
    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类似。

票数 1
EN

Stack Overflow用户

发布于 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,在这种情况下,您只需从一个文件对象复制到另一个文件对象,而不是使用有限的回调接口,因此您可以使用任何您喜欢的文件对象复制函数,或者编写自己的函数:

代码语言:javascript
运行
复制
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这样的第三方模块存在的原因。

票数 1
EN

Stack Overflow用户

发布于 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可以抛给你的所有东西。所以:

代码语言:javascript
运行
复制
with open(path, 'wb') as f:
    r = requests.get(url, stream=True)
    for chunk in r.iter_content(8192):
        f.write(chunk)

添加进度仍然需要手动完成。但是,由于您提取的是块,而不是将它们保存到背后的文件中,因此您确切地知道您看到了多少字节。而且,只要服务器提供了Content-Length标头(有些服务器在某些情况下不会这样做,但除了处理它之外,您对此也无能为力),这很容易:

代码语言:javascript
运行
复制
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))
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/21149555

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档