Python3进度条和gzip下载

4
我在Python进度条和下载的答案中遇到了一些问题。
如果下载的数据是gzip编码的,则在for data in response.iter_content():中连接它们后,内容长度和总长度不同,因为自动解压缩gzip编码响应。
因此,进度条会变得越来越长,一旦超过单行长度,就会开始在终端上泛滥。
以下是该问题的工作示例(这个网站是我在谷歌上找到的第一个同时具有content-length和gzip编码的网站)。
import requests,sys

def test(link):
    print("starting")
    response = requests.get(link, stream=True)
    total_length = response.headers.get('content-length')
    if total_length is None: # no content length header
        data = response.content
    else:
        dl = 0
        data = b""
        total_length = int(total_length)
        for byte in response.iter_content():
            dl += len(byte)
            data += (byte)
            done = int(50 * dl / total_length)
            sys.stdout.write("\r[%s%s]" % ('=' * done, ' ' * (50-done)))
            sys.stdout.flush()
    print("total data size: %s,  content length: %s" % (len(data),total_length))

test("http://www.pontikis.net/")

PS,我在使用Linux系统,但这应该不会影响其他操作系统(除了Windows,因为\r在Windows上不起作用,如果我没记错的话)

我正在使用requests.Session处理cookies(和gzip),所以我不想使用urllib和其他模块的解决方案


如果您的问题是requests自动解压缩数据,那么您可能不应该使用requests。由于您没有进行任何身份验证,标准的urllib.request可能会更好。然后,当文件完全下载时,您可以使用zlib模块检索带有工作进度条的数据并解压缩它。 - Kritzefitz
我需要在使用POST请求登录后持久化cookie,这就是为什么我提到了requests.Session,并且也是为什么我说urllib的解决方案不是我想要的原因。 - freeforall tousez
哦,抱歉。最后没看到那个。 - Kritzefitz
你可以使用 response.raw 来获取服务器的原始套接字响应,而不需要执行 iter_content 所有的处理。 - Michael Foukarakis
3个回答

0

也许你应该尝试禁用gzip压缩或者做出相应的调整。

关闭请求时(当你使用会话时),可以这样关闭:

import requests

s = requests.Session()
del s.headers['Accept-Encoding']

发送的头现在将是:Accept-Encoding: Identity,服务器不应尝试使用gzip压缩。如果您要下载gzip编码的文件,则不应遇到此问题。您将收到一个Content-Typeapplication/x-gzip-compressed的响应。如果网站被gzip压缩,则您将收到例如text/htmlContent-TypegzipContent-Encoding
如果服务器始终提供压缩内容,则您就没办法了,但是没有服务器应该这样做。
如果你想使用requests的函数式API来做一些事情:
import requests

r = requests.get('url', headers={'Accept-Encoding': None})

通过函数API(甚至在调用session.get时)将标题值设置为None会从请求中删除该标题。


这样做可以解决问题,但会使其成为更大的瓶颈。而且,我认为你不能仅仅使用del来删除普通requests.get中的头信息。 - freeforall tousez
@freeforalltousez,除非您正在下载几个千兆字节长的网页,否则这不应该引起太多麻烦。此外,您要求如何使用会话进行操作,但我已经更新了我的答案,告诉您如何在requests.get上执行完全相同的操作。如果您的问题不再符合您的要求,请更新它。 - Ian Stapleton Cordasco
我想我现在会标记这个答案,直到出现更好的答案。 - freeforall tousez

0
你可以用以下代码替换原来的:

dl = response.raw.tell()

而不是:

dl += len(byte)

文档 可以看到:

tell(): 获取已经从网络上获取的字节数。如果通过网络传输的字节被编码(例如压缩),可能与 :meth:HTTPResponse.read 返回的内容量不同。


0

这里是一个使用 tqdm 实现的简单进度条:

def _reader_generator(reader):
    b = reader(1024 * 1024)
    while b:
        yield b
        b = reader(1024 * 1024)

def raw_newline_count_gzip(fname):
    f = gzip.open(fname, 'rb')
    f_gen = _reader_generator(f.read)
    return sum(buf.count(b'\n') for buf in f_gen)


num = raw_newline_count_gzip(fname)
(loop a gzip file):
    with tqdm(total=num_ids) as pbar:
        # do whatever you want
        pbar.update(1)

进度条看起来像这样: 35%|███▌ | 26288/74418 [00:05<00:09, 5089.45it/s]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接