11.8. 处理被压缩的数据

优质
小牛编辑
124浏览
2023-12-01

11.8. 处理被压缩的数据

你要支持的最后一个重要的 HTTP 特性是压缩。 许多 web 服务具有发送压缩数据的能力, 这可以将网络线路上传输的大量数据消减 60% 以上。 尤其适用于 XML web 服务, 因为 XML 数据 的压缩率可以很高。

服务器不会为你发送押送数据,除非你告诉服务器你可以处理压缩数据。

例 11.14. 告诉服务器你想获得压缩数据

>>> import urllib2, httplib
>>> httplib.HTTPConnection.debuglevel = 1
>>> request = urllib2.Request('http://diveintomark.org/xml/atom.xml')
>>> request.add_header('Accept-encoding', 'gzip')        1
>>> opener = urllib2.build_opener()
>>> f = opener.open(request)
connect: (diveintomark.org, 80)
send: '
GET /xml/atom.xml HTTP/1.0
Host: diveintomark.org
User-agent: Python-urllib/2.1
Accept-encoding: gzip                                    2
'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Thu, 15 Apr 2004 22:24:39 GMT
header: Server: Apache/2.0.49 (Debian GNU/Linux)
header: Last-Modified: Thu, 15 Apr 2004 19:45:21 GMT
header: ETag: "e842a-3e53-55d97640"
header: Accept-Ranges: bytes
header: Vary: Accept-Encoding
header: Content-Encoding: gzip                           3
header: Content-Length: 6289                             4
header: Connection: close
header: Content-Type: application/atom+xml
1这是关键: 一旦你已经创建了你的 Request 对象, 就添加一个 Accept-encoding 头信息告诉服务器你能接受 gzip 压缩数据。 gzip 是你使用的压缩算法的名称。 理论上你可以使用其它的压缩算法, 但是 gzip 是 web 服务器上使用率高达 99% 的一种。
2这是你的头信息传越网络线路的过程。
3这是服务器的返回信息: Content-Encoding: gzip 头信息意谓着你要回得的数据已经被 gzip 压缩了。
4Content-Length 头信息是已压缩数据的长度, 并非解压缩数据的长度。 一会儿你会看到, 实际的解压缩数据长度为 15955, 因此 gzip 压缩节省了 60% 以上的网络带宽!

例 11.15. 解压缩数据

>>> compresseddata = f.read()                              1
>>> len(compresseddata)
6289
>>> import StringIO
>>> compressedstream = StringIO.StringIO(compresseddata)   2
>>> import gzip
>>> gzipper = gzip.GzipFile(fileobj=compressedstream)      3
>>> data = gzipper.read()                                  4
>>> print data                                             5
<?xml version="1.0" encoding="iso-8859-1"?>
<feed version="0.3"
  xmlns="http://purl.org/atom/ns#"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xml:lang="en">
  <title mode="escaped">dive into mark</title>
  <link rel="alternate" type="text/html" href="http://diveintomark.org/"/>
  <-- rest of feed omitted for brevity -->
>>> len(data)
15955
1继续上面的例子, f 是一个从 URL 开启器返回的类似文件的对象。 使用它的 read() 方法将正常地获得非压缩数据, 但是因为这个数据已经被 gzip 压缩过, 所以这只是获得你想要的最终数据的第一步。
2OK, 只是先得有点儿凌乱的步骤。 Python 有 gzip 模块, 它读取 (并实际写入) 磁盘上的 gzip 压缩文件。 但是磁盘上还没有文件, 只在内存里有一个 gzip 压缩缓冲区, 并且 你不想仅仅为了解压缩而写出一个临时文件。 那么怎么做来创建一个内存数据 (compresseddata) 之外的类似文件的对象呢, 需要使用 StringIO 模块。 你首次看到 StringIO 模块是在 上一章, 但现在你会发现它的另一种用法。
3现在你可以创建 GzipFile 的一个实例, 并且告诉它其中的 “file” 是一个类似文件的对象 compressedstream。
4这是做所有工作的一行: 从 GzipFile 中 “读取” 将会解压缩数据。 感到奇妙吗? 是的, 它确实解压缩了数据。 gzipper 是一个类似文件的对象, 它代表一个 gzip 压缩文件。 尽管这个 “file” 并非一个磁盘上的真实文件; 但 gzipper 还是真正的从你用 StringIO 包装了压缩数据所创建的类似文件的对象中 “读取” 数据, 它仅仅是内存中的变量 compresseddata。 压缩的数据来自哪呢? 你通常从远程 HTTP 服务器下载, 然后从你用 urllib2.build_opener 创建的类似文件的对象中 “读取”。 令人吃惊吧, 这就是所有的步骤。 链条上的每一步都完全不知道上一步在造假。
5看看吧, 实际的数据 (实际为 15955 bytes)。

“等等!” 我听见你在叫。 “还能更简单吗!” 我知道你在想什么。 你在想那个 opener.open 返回一个类似文件的对象, 那么为什么不抛弃中间件 StringIO 而通过 f 直接访问 GzipFile 呢? OK, 或许你没想到, 但是别为此担心, 因为那样无法工作。

例 11.16. 从服务器直接解压缩数据

>>> f = opener.open(request)                  1
>>> f.headers.get('Content-Encoding')         2
'gzip'
>>> data = gzip.GzipFile(fileobj=f).read()    3
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "c:\python23\lib\gzip.py", line 217, in read
    self._read(readsize)
  File "c:\python23\lib\gzip.py", line 252, in _read
    pos = self.fileobj.tell()   # Save current position
AttributeError: addinfourl instance has no attribute 'tell'
1继续前面的例子, 你已经有一个用 Accept-encoding: gzip 头信息设置的 Request 对象。
2简单地打开请求将获得你的头信息 (虽然还没下载任何数据)。 正如你从 Content-Encoding 头信息所看到的, 这个数据已经被 gzip 压缩发送了。
3从 opener.open 返回了一个类似文件的对象, 并且阅读头信息你可以获知, 你将获得 gzip 压缩数据, 为什么不简单地通过那个类似文件的对象直接访问 GzipFile 呢? 因为你从 GzipFile 实例 “读取” , 他将从远程 HTTP 服务器 “读取” 被压缩的数据并且立即解压缩。 这是个好主意, 但是不行的是它无法工作。 由 gzip 压缩的工作方式所致, GzipFile 需要存储其位置并在压缩文件上往返游走。当 “file” 是来自远程服务器的字节流时无法工作; 你能用它做的所有工作就是一次返回一个字节流, 而不是在字节流上往返。 所以使用 StringIO 这种看上去不雅的手段是最好的解决方案: 下载压缩的数据, 除此之外用 StringIO 创建一个类似文件的对象, 并从中解压缩数据。