使用分块传输编码和gzip压缩的网页的未压缩大小

3

我正在编写一款应用程序,计算在网页使用gzip之后节省的大小。当用户输入使用gzip的网页的URL时,应用程序应该输出由于gzip而节省的大小。

我应该如何解决这个问题?

这是我在页面上获取GET请求的标头:

{
    'X-Powered-By': 'PHP/5.5.9-1ubuntu4.19',
    'Transfer-Encoding': 'chunked',
    'Content-Encoding': 'gzip',
    'Vary': 'Accept-Encoding', 
    'Server': 'nginx/1.4.6 (Ubuntu)',
    'Connection': 'keep-alive',
    'Date': 'Thu, 10 Nov 2016 09:49:58 GMT',
    'Content-Type': 'text/html'
}

我正在使用requests获取网页:

r  = requests.get(url, headers)
data = r.text
print "Webpage size : " , len(data)/1024
2个回答

5
如果您已经下载了URL(使用requestsGET请求而没有使用stream选项),则已经拥有两个可用大小,因为整个响应已经被下载并解压缩,原始长度在标头中可用:
from __future__ import division

r = requests.get(url, headers=headers)
compressed_length = int(r.headers['content-length'])
decompressed_length = len(r.content)

ratio = compressed_length / decompressed_length

你可以将使用 Accept-Encoding: identity 的 HEAD 请求的内容长度头与使用 Accept-Encoding: gzip 的请求进行比较:
no_gzip = {'Accept-Encoding': 'identity'}
no_gzip.update(headers)
uncompressed_length = int(requests.get(url, headers=no_gzip).headers['content-length'])
force_gzip = {'Accept-Encoding': 'gzip'}
force_gzip.update(headers)
compressed_length = int(requests.get(url, headers=force_gzip).headers['content-length'])

然而,这种方法可能并不适用于所有服务器,因为动态生成内容的服务器通常会在这种情况下破坏Content-Length头部,以避免首先渲染内容。

如果您请求的是分块传输编码资源,则不会有content-length头部,在这种情况下,HEAD请求可能会或可能不会提供正确的信息。

在这种情况下,您必须流式传输整个响应,并从流的末尾提取解压后的大小(GZIP格式在末尾包括一个小端4字节无符号整数)。使用原始urllib3响应对象上的stream()方法

import requests
from collections import deque

if hasattr(int, 'from_bytes'):
    # Python 3.2 and up
    _extract_size = lambda q: int.from_bytes(bytes(q), 'little')
else:
    import struct
    _le_int = struct.Struct('<I').unpack
    _extract_size = lambda q: _le_int(b''.join(q))[0]

def get_content_lengths(url, headers=None, chunk_size=2048):
    """Return the compressed and uncompressed lengths for a given URL

    Works for all resources accessible by GET, regardless of transfer-encoding
    and discrepancies between HEAD and GET responses. This does have
    to download the full request (streamed) to determine sizes.

    """
    only_gzip = {'Accept-Encoding': 'gzip'}
    only_gzip.update(headers or {})
    # Set `stream=True` to ensure we can access the original stream:
    r = requests.get(url, headers=only_gzip, stream=True)
    r.raise_for_status()
    if r.headers.get('Content-Encoding') != 'gzip':
        raise ValueError('Response not gzip-compressed')
    # we only need the very last 4 bytes of the data stream
    last_data = deque(maxlen=4)
    compressed_length = 0
    # stream directly from the urllib3 response so we can ensure the
    # data is not decompressed as we iterate
    for chunk in r.raw.stream(chunk_size, decode_content=False):
        compressed_length += len(chunk)
        last_data.extend(chunk)
    if compressed_length < 4:
        raise ValueError('Not enough data loaded to determine uncompressed size')
    return compressed_length, _extract_size(last_data)

演示:

>>> compressed_length, decompressed_length = get_content_lengths('http://httpbin.org/gzip')
>>> compressed_length
179
>>> decompressed_length
226
>>> compressed_length / decompressed_length
0.7920353982300885

1
我收到了没有Content-Length的头信息,这种情况下我们该怎么办? - Binu Mathew
1
@BinuMathew:你的意思是你有“Transfer-Encoding: chunked”响应吗?请在提问时要非常具体,给出你正在处理的内容类型的示例可以帮助我们更好地帮助你。请编辑你的问题以包含这些信息。 - Martijn Pieters
1
@BinuMathew:那里有一个“Content-Length”头。 - Martijn Pieters
1
@Martijin:由于使用get方法无法获取内容长度,我正在使用request.head方法。 - Binu Mathew

1
发送 HEAD 请求时,可以选择是否接受 gzip 压缩,并比较结果中的 Content-Length 头部。
'accept-encoding' 头部可帮助您请求 gzip 压缩。
'accept-encoding': 'gzip'

在这种情况下,请求不使用gzip编码。
'accept-encoding': ''

发送 HEAD 请求可以很容易地通过 requests 库处理:
import requests
r = requests.head("http://stackoverflow.com/", headers={'Accept-Encoding': 'gzip'})
print(r.headers['content-length'])

41450

r = requests.head("http://stackoverflow.com/", headers={'Accept-Encoding': ''})
print(r.headers['content-length'])

250243


@MartijnPieters这就是为什么你需要使用gzip压缩和不使用gzip压缩两次请求。 - Ryang MinHo
BeautifulSoup是一个解析器,它对于获取数据大小没有帮助。 - Ryang MinHo
哦,我的意思是,使用“在HEAD和GET之间内容长度不会改变”和“没有gzip压缩的请求”,我们可以轻松地获取未压缩的数据大小。@MartijnPieters - Ryang MinHo
好的,我明白你的意思。不幸的是,我担心并非所有的HTTP服务器都能正确区分开启gzip的带有和不带有“Accept”的HEAD请求。 - Martijn Pieters
1
例如,动态生成内容的网站可能会在HEAD请求中发送假定的内容长度(未应用压缩),因为实际内容并未生成。 - Martijn Pieters

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