使用Python解压缩.gz文件的一部分

9
所以问题来了。我有一个大约60KB大小的sample.gz文件。我想要解压这个文件的前2000个字节。但是我遇到了CRC校验失败的错误,我猜测是因为gzip的CRC字段出现在文件末尾,需要整个gzipped文件才能解压缩。有没有办法绕过这个问题呢?我不关心CRC校验。即使由于坏的CRC而无法解压缩,也没关系。有没有办法绕过这个问题并解压部分的.gz文件?
我目前的代码如下:
import gzip
import time
import StringIO

file = open('sample.gz', 'rb')
mybuf = MyBuffer(file)
mybuf = StringIO.StringIO(file.read(2000))
f = gzip.GzipFile(fileobj=mybuf)
data = f.read()
print data

遇到的错误是:
File "gunzip.py", line 27, in ?
    data = f.read()
File "/usr/local/lib/python2.4/gzip.py", line 218, in read
  self._read(readsize)
File "/usr/local/lib/python2.4/gzip.py", line 273, in _read
  self._read_eof()
File "/usr/local/lib/python2.4/gzip.py", line 309, in _read_eof
  raise IOError, "CRC check failed"
IOError: CRC check failed

同时,有没有办法使用zlib模块来实现此操作并忽略gzip头部信息?


因为我对压缩数据的前面大约4K部分很感兴趣。 - user210126
4个回答

14
gzip模块的问题不在于无法解压部分文件,错误只会在尝试验证解压后内容的校验和时发生。(原始校验和存储在压缩文件的末尾,因此使用部分文件进行校验将永远不起作用。)
关键是要欺骗gzip跳过验证。由caesar0301提供的答案通过修改gzip源代码来实现这一点,但没有必要走得那么远,简单的猴子补丁就可以了。我编写了这个上下文管理器来在我解压部分文件时暂时替换gzip.GzipFile._read_eof
import contextlib

@contextlib.contextmanager
def patch_gzip_for_partial():
    """
    Context manager that replaces gzip.GzipFile._read_eof with a no-op.

    This is useful when decompressing partial files, something that won't
    work if GzipFile does it's checksum comparison.

    """
    _read_eof = gzip.GzipFile._read_eof
    gzip.GzipFile._read_eof = lambda *args, **kwargs: None
    yield
    gzip.GzipFile._read_eof = _read_eof

一个示例用法:

from cStringIO import StringIO

with patch_gzip_for_partial():
    decompressed = gzip.GzipFile(StringIO(compressed)).read()

1
属性错误:类型对象'GzipFile'没有属性'_read_eof'。 - Marlon Teixeira

12

看起来你需要研究一下Python zlib库。

GZIP格式依赖于zlib,但是它引入了文件级别的压缩概念以及CRC检查,这似乎不是你目前想要或需要的。

例如,可以参考Dough Hellman的这些代码片段

Edit: Dough Hellman网站上的代码仅展示如何使用zlib进行压缩或解压缩。如上所述,GZIP是“带信封的zlib”,因此在处理zlib压缩数据之前,您需要先解码信封。以下提供更多信息,这实际上并不复杂:

  • 详见RFC 1952,了解有关GZIP格式的详细信息。
  • 该格式以10个字节的标题开始,后跟可选的、非压缩元素(例如文件名或注释),然后是zlib压缩数据,其后是CRC-32(精确地说是“Adler32” CRC)。
  • 通过使用Python的struct模块,解析标题应该相对简单。
  • 然后可以使用Python的zlib模块解压缩zlib序列(或其前几千个字节,因为这是你想要做的)。
  • 需要处理的可能问题:如果GZip归档文件中有多个文件,并且第二个文件始于我们希望解压缩的几千个字节之内。

很抱歉没有提供简单的过程或现成的代码片段,但按照以上说明解码文件应该相对快速和简单。


哪个代码片段适用于上面的示例?我浏览了链接并阅读了“使用流”部分。它没有说明它正在使用gzip流。我假设这适用于zlib流(已经测试过zlib流)。 - user210126
@unknown:请检查我的编辑;代码片段涉及到纯zlib的压缩/解压缩。 GZip格式意味着首先解析一个小的、未压缩的头部,然后找到其zlip“有效载荷”,可以像展示的那样进行解压缩。 - mjv
Doug Hellmann的代码片段似乎已经移动到这里 - Ben

11

我无法想象为什么您要解压缩前2000个压缩字节。根据数据不同,它可能会解压缩成任意数量的输出字节。

您肯定是想解压文件,并在解压所需部分后停止,类似于:

f = gzip.GzipFile(fileobj=open('postcode-code.tar.gz', 'rb'))
data = f.read(4000)
print data

据我所知,这不会导致整个文件被读取,而只会读取必要的部分以获取前4000个字节。


f.read(2000) 这里将读取解压后数据的前 2000 字节。我对压缩数据的前 2000 字节感兴趣。 - user210126
为什么?你的应用程序到底是做什么的? - rjmunro
我正在尝试在前4k的数据中查找字符串“xyz”。假设我解压缩了2K的gzip数据并得到了4K的解压缩数据,我可以在这4k中搜索/ grep该字符串。所有搜索代码已经就位... - user210126
假设我只能从一个60K的.gz文件中获取前2k个压缩数据。之后什么都没有了。我需要在这2k的解压缩部分中查找我的字符串。 - user210126
4
如果您想搜索未压缩数据的前4k,就像我在答案中所做的那样搜索前4k未压缩数据(也许将4000更改为4096)。不要试图猜测2k会解压缩为4k。可能不是这样的情况。它可能只能解压缩到2k,或者可能解压缩到几兆字节。 - rjmunro
1
这太完美了。非常感谢!不需要使用肮脏的黑科技。 - Marco Roy

2
当我使用我的Python脚本读取在Linux下由gzip工具生成的压缩文件并且原始文件已经丢失时,我也遇到了这个问题。
通过阅读Python的gzip.py实现,我发现gzip.GzipFile有类似于File类的方法,并利用Python zip模块来处理数据的解/压缩。同时,_read_eof()方法也存在于检查每个文件的CRC。
但在某些情况下,比如处理流或没有正确CRC的.gz文件(我的问题),_read_eof()会引发IOError("CRC check failed")。因此,我尝试修改gzip模块以禁用CRC检查,最终这个问题消失了。
def _read_eof(self):
    pass

https://github.com/caesar0301/PcapEx/blob/master/live-scripts/gzip_mod.py

我知道这是一种暴力解决方法,但使用zip模块重写一些低级方法会节省很多时间,例如逐块从压缩文件中读取数据并逐行提取数据,其中大部分已经存在于gzip模块中。
Jamin

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