Requests: 流式压缩响应

创建于 2014-07-31  ·  10评论  ·  资料来源: psf/requests

我需要将大型 XML 响应作为流处理。 未压缩的响应大小可能有数百兆字节,因此在将它们交给 XML 解析器之前将它们完全加载到内存中是不可行的。

我正在使用 lxml 进行解析,我只是将response.raw交给它的iterparse()函数,如请求文档中某处所述。 这适用于未压缩的响应。

不幸的是,我调用的 API 并不是特别好。 因此,即使我明确要求未压缩数据,它有时也会返回Content-Encoding: gzip 。 此外,这些极其重复和冗长的 XML 文件的压缩率非常好(10 倍以上),所以我真的很想使用压缩响应。

这可以通过请求实现吗? 我在文档中找不到它。 深入研究 urllib3,它的HTTPResponse.read()方法似乎支持decode_content参数。 如果未设置,urllib3 将回退到构造函数中设置的内容。 当请求调用requests.adapters.HTTPAdapter.send() 中的构造函数时,它显式地将decode_content为 False。

请求这样做有什么理由吗?

奇怪的是, iter_content()实际上是在阅读时设置了decode_content=True 。 为什么在这里? 这一切都显得有些随意。 我真的不明白在这里以一种方式在那里以另一种方式这样做的动机。
就个人而言,我当然不能真正使用iter_content() ,因为我需要一个类似文件的对象用于 lxml。

我以前写过我自己的类似文件的对象,我可以在请求和 lxml 之间挂钩,但是缓冲当然很难,而且我觉得比我以前写过的人更聪明,所以我宁愿不必自己动手.

你对如何处理这个问题有什么建议? 请求是否应该更改为默认设置decode_content=True在 urllib3 中设置

Contributor Friendly Documentation Planned

最有用的评论

我过去做过这个

r = requests.get('url', stream=True)
r.raw.decode_content = True
...

所有10条评论

不,出于各种原因,它不应该默认设置。 您应该做的是使用functools.partial替换响应中的read方法(或者只是以另一种方式包装它),以便您执行以下操作:

response.raw.read = functools.partial(response.raw.read, decode_content=True)

然后将response.raw传递给您的解析器。

@sigmavirus24谢谢,这绝对是我上面概述的问题的优雅解决方案!

我建议将其添加到请求的文档中,例如在常见问题解答中: http ://docs.python-requests.org/en/latest/community/faq/#encoded -data
目前,声明“请求自动解压缩 gzip 编码的响应”对于stream=True情况不正确,可能会导致意外。

至于我的问题,正如你在urllib3 问题上所读到的,gzip 解压的 urllib3 实现有它自己的小怪癖,我必须在我的代码中解决,但这不再是请求的问题。

但这不再是请求的问题。

就像你觉得这可以关闭?

@sigmavirus24我认为应该记录下来,因为当前的文档不正确。

但是,如果您不同意这一点,是的,请关闭!

文档可能更清楚。 对我来说(这完全是因为我是核心开发人员)第一段是对 90% 永远不会接触原始响应的用户说的,而第二段与第一段相矛盾说“但是如果你需要访问原始数据,它就在你身边”。 就像我说的,这对我来说很明显,但我可以看到如何更清楚地说明这一点。 今晚我会解决这个问题。

对我来说,更多的是我将“原始数据”解释为“原始有效载荷”,即解压缩流。 我只需要按我需要的任何块阅读它。 与.content ,后者是一个解压缩的 blob(也是有效载荷,但形式不同)。

实际的解压对我来说就像是 HTTP 库的一个问题——如果你愿意的话,这是 HTTP 的一个实现细节,我希望请求能够抽象出来。 无论我是从请求中读取负载作为流还是作为预取的数据块都没有区别。 无论哪种方式,请求都会抽象实现细节“压缩”。

(这个假设也是我最初要求默认decode_contentTrue 。当然,现在我看到这是一个多么有漏洞的抽象,我不再建议这样做。)

但是,是的,我绝对同意您 99% 的用户永远不会受到这个细节的影响。

随意关闭这个问题。

所以这实际上导致了我脑子里一直萦绕的东西,我还没有提出,因为这将是一个重大的 API 变化。

我不喜欢我们建议人们使用r.raw的事实,因为它是一个我们没有记录的对象,它是由urllib3提供的对象(我们过去声称它是更多的实现细节)。 考虑到这一点,我一直在考虑在Response对象上提供方法的想法,该对象只是代理urllib3方法( read只会代理raw.read等)。 这为我们在urllib3提供了额外的灵活性,并允许我们(代表用户)处理urllib3的 API 更改(这在历史上几乎从来没有出现过问题,因此没有任何问题)紧迫性)。

话虽如此,在我看来,我们已经在 Response 对象上拥有足够的方法,并且扩展我们的 API 并不理想。 最好的 API 是无需删除任何内容的 API。 所以我一直对此持观望态度。


这个假设也是我最初请求默认 decode_content 为 True 的核心。 当然,现在我看到这是一个多么有漏洞的抽象,我不再建议这样做。

对于发现这一点但可能不确定为什么这是真的其他人,请允许我解释一下。

有几个请求用户关闭自动解压来验证响应的长度,或者用它做其他重要的事情。 前一种消费者是 OpenStack。 许多 OpenStack 客户端验证发送给客户端的Content-Length标头和接收到的正文的实际长度。 对他们来说,处理减压是一种公平的权衡,以确保他们正在接收和处理有效的响应。

另一个消费者是 Betamax(或实际上任何(重新)构造 Response 对象的工具),因为当它处理做出完全有效响应的整个过程时,它需要内容为压缩格式。

我敢肯定还有其他@Lukasa或我都不知道的人也严重依赖这种行为。

今天遇到了同样的问题,最终做出了同样的假设,因为目前没有其他方法可以流式传输响应。

而不是 Response 上的多个新方法,为什么没有一个新属性,例如response.stream ,它可以扮演与.raw相同的代理角色? 它还可以很好地反映stream=True设置/参数,并且不会影响需要当前.raw行为的用户。

我过去做过这个

r = requests.get('url', stream=True)
r.raw.decode_content = True
...

请注意, @sigmavirus24解决方法破坏了tell方法的语义,这将返回不正确的偏移量。

我在将响应作为可恢复上传流式传输到 Google Cloud Storage API 时遇到了这个问题,该 API 使用tell()来计算刚刚读取的字节数(此处)。

此页面是否有帮助?
0 / 5 - 0 等级