【问题标题】:Uncompressed size of a webpage using chunked transfer encoding and gzip compression使用分块传输编码和 gzip 压缩的未压缩网页大小
【发布时间】:2016-11-10 08:20:35
【问题描述】:

我正在编写一个应用程序来计算在网页上使用 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

【问题讨论】:

    标签: python http gzip transfer-encoding


    【解决方案1】:

    如果您已经下载了 URL(使用不带 stream 选项的 requests GET 请求,则您已经可以使用两种尺寸,因为整个响应已下载并解压缩,并且原始长度可在标题:

    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 标头来避免必须先呈现内容。

    如果您请求的是 chunked transfer encoding 资源,则不会有 内容长度标头,在这种情况下,HEAD 请求可能也可能不会为您提供正确的信息。

    在这种情况下,您必须流式传输整个响应并从流的末尾提取解压缩的大小(GZIP 格式将其作为 little-endian 4-byte unsigned int 包含在最后)。在原始 urllib3 响应对象上使用 stream() method

    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
    

    【讨论】:

    • 我得到了没有内容长度的标题,在这种情况下我们该怎么办?
    • @BinuMathew:你的意思是你有Transfer-Encoding: chunked responses?请在您的问题中具体,举例说明您正在处理的内容类型可以帮助我们更好地为您提供帮助。请编辑您的问题以包含该信息。
    • @BinuMathew:那里有一个Content-Length 标头。
    • @Martijin :因为我没有使用 get 获取内容长度,所以我使用的是 request.head 方法
    【解决方案2】:

    在接受和不接受 gzip 压缩的情况下发送 HEAD 请求并比较结果之间的 Content-Length 标头。

    'accept-encoding' 标头可帮助您使用 gzip 压缩请求:

    'accept-encoding': 'gzip'
    

    在这种情况下,请求不使用 gzip 编码。

    'accept-encoding': ''
    

    requests library 可以轻松处理发送 HEAD 请求:

    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 压缩的情况下请求两次。
    • BeautifulSoup 是一个解析器,它对获取数据大小没有帮助。
    • 哦,我的意思是,使用“内容长度不会在 HEAD 和 GET 之间改变”和“没有 gzip 压缩的请求”,我们可以轻松获得未压缩的数据大小。 @MartijnPieters
    • 好吧,我明白你的意思了。不幸的是,我担心在 gzip 开启的情况下,并非所有 HTTP 服务器都能正确区分带有和不带有 Accept 的 HEAD 请求。
    • 例如,动态生成内容的网站最终可能会在未应用压缩的情况下向您发送 supposed 内容长度,因为实际内容不是针对 HEAD 请求生成的。
    猜你喜欢
    • 1970-01-01
    • 2012-12-02
    • 2011-07-13
    • 2021-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-21
    • 2015-07-14
    相关资源
    最近更新 更多