请求使用Gzip压缩的HTTP下载并写入磁盘

5
我正在使用requests库和Python 2.7从Web API下载一个gzipped文本文件。使用下面的代码,我能够成功地发送一个get请求,并且从头部判断得出收到了一个gzip文件形式的响应。
我知道如果Requests检测到响应是gzip格式,它会自动解压缩这些文件。我想把这个下载以文件流的形式写入磁盘以供存储和未来分析。
然而,当我在我的工作目录中打开结果文件时,我得到像这样的字符:—}}¶— Q@Ï 'õ
为了参考,一些响应头包括'Content-Encoding': 'gzip','Content-Type': 'application/download','Accept-Encoding,User-Agent'
我用二进制写错了吗?我没有正确编码文本(例如,ASCII与utf-8之间的区别)吗?响应头中没有明显的字符编码。
try:
    response = requests.get(url, paramDict, stream=True)
except Exception as e:
    print(e)

with open(outName, 'wb') as out_file:
    for chunk in response.iter_content(chunk_size=1024):
        out_file.write(chunk)

2016年3月30日更新: 现在我已经稍微改了一下我的代码,使用了gzipstream库。我尝试使用该流读取响应内容中的整个压缩文本文件:

with open(outName, 'wb') as out_file, GzipStreamFile(response.content) as fileStream:
    streamContent = fileStream.read()
    out_file.write(streamContent)

我遇到了这个错误: out_file.write(streamContent) AttributeError: '_GzipStreamFile' object has no attribute 'close'

输出的结果是一个空文本文件,文件名如预期。我需要在with块外初始化streamContent变量,以便它不会在块结束时自动调用close方法吗?

2016年4月1日编辑 我想澄清一下,这不一定要是流,那只是我遇到的一个解决方案。我只是想每天请求这个压缩文件并将其保存为纯文本文件。

3个回答

8
try:
    response = requests.get(url, paramDict)
except Exception as e:
    print(e)

data = zlib.decompress(response.content, zlib.MAX_WBITS|32)

with open('outFileName.txt','w') as outFile:
    outFile.write(data)

这是我编写的代码,最终起作用了。正如sigmavirus所说:文件一开始就被压缩了。我知道这个事实,但显然没有清楚地描述它,因为我一直在读/写压缩字节。
使用zlib模块,我能够将响应内容一次性解压到数据变量中;然后我将包含解压数据的该变量写入文件。
我不确定这是否是最佳或最符合Python风格的方法,但它有效。如果有人能告诉我为什么我不能gzip.open此内容(也许我需要使用另一种方法,我尝试过gzipstream库但无济于事),我会感激任何解释,但我认为这个问题已经得到了回答。
感谢所有帮助过我的人,即使你们没有解决方案,你们也鼓励我坚持不懈!

当你写入数据时,也许需要在"data"后面添加".decode("utf-8")"。 - Save

3
这里stream=Trueiter_content 的组合导致了你的问题。你可能想要做的是类似于这样的操作(以保留流式传输的行为):
try:
    response = requests.get(url, params=paramDict, stream=True)
except Exception as e:
    print(e)

raw = response.raw
with open(outName, 'wb') as out_file
    while True:
        chunk = raw.read(1024, decode_content=True)
        if not chunk:
            break
        out_file.write(chunk)

注意,您仍然希望使用字节,因为您尚未确定内容的字符编码,所以您仍然具有字节,但您不再处理压缩的字节。

这个程序已经运行了,但它仍然只写了一个包含非人类可读字符的文件。我应该使用gzipstream库并尝试在if not chunk块之前读取流时解压缩流吗? - jaxas
你能检查一下 response.headers 是否有 Content-Encoding 头部,并告诉我它的内容吗? - Ian Stapleton Cordasco
{'Content-Length': '7811', 'Via': 'CONNECTION-INFO已被jaxas删除', 'Content-Disposition': 'attachment;filename=****.txt.gz', 'Content-Encoding': 'gzip', 'Vary': 'Accept-Encoding,User-Agent', 'Keep-Alive': 'timeout=3, max=100', 'Server': 'Apache', 'Connection': 'keep-alive', 'Date': 'Fri, 01 Apr ', 'X-Frame-Options': 'SAMEORIGIN', 'Content-Type': 'application/download'} 这是响应头。我想我应该将response.raw读入StringIO缓冲区,然后使用zlib从该对象解码。我一天只能进行几次此请求。 - jaxas
嗯,我认为问题出在他们一开始就发送给你一个gzipped文本文件。如果你查看Content-Disposition头部,文件名以.txt.gz结尾,这意味着文件已经被压缩了。所以你保存到磁盘上的文件也是一个gzipped文件。你可以使用我描述的方法来实现你想要的操作,这样你就能得到你期望的文件了。 - Ian Stapleton Cordasco

0

您正在请求原始套接字流,该流剥离了块传输编码,但保留了内容编码。换句话说:您拥有的很可能是经过gzip压缩的内容。 Content-Encoding: gzip头的存在是一个强烈的指示器,因为http客户端需要在删除内容编码时删除它。

消除这种情况的一种方法是在请求中发送一个空的Accept-Encoding头,表示不接受任何编码。如果API符合RFC标准,则应收到未压缩的响应。另一种方法是自己解压流。我认为gzip和zlib模块无法本地完成此操作。但是,gzipstream库应该可以帮助您入门。


我将尝试关闭流以查看是否访问响应而不是原始套接字将允许Requests解压缩响应内容。我还将尝试打开stream=True,然后使用gzipstream库处理内容。此外,我不确定此API是否符合RFC标准,因为它是针对一个鲜为人知的游戏而且文档和维护都很差。有没有办法进行检查? - jaxas
@jaxas 我知道有一个用于HTTP客户端的测试平台,但我不知道是否有任何API端点的验证服务,抱歉。在这方面检查的唯一方法是发出一个操纵请求并查看结果。后来我想到的一件事是:如果您在Linux / Unix系统上,则有“file”命令,可检查文件的类型和内容。如果它确实是gzipped,则会返回类似于“gzip压缩数据”的内容。哦,以二进制模式编写是绝对正确的做法。 - DaSourcerer

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