Python中的zlib解压缩

12 投票
4 回答
91273 浏览
提问于 2025-04-15 13:48

我有一些数据流是用Python(2.6)的zlib.compress()函数压缩的。当我尝试解压这些数据时,有些解压不出来(出现了zlib错误-5,这似乎是个“缓冲区错误”,我也不知道该怎么理解)。一开始我以为自己搞定了,但后来我发现所有解压不出来的文件都是以0x78DA开头的,而能正常解压的文件是以0x789C开头的。我查了一下,发现这可能是另一种类型的zlib压缩——这个“魔法数字”会根据使用的压缩方式而变化。我该用什么来解压这些文件呢?我是不是没救了?

4 个回答

-1

抱歉,我之前没有说清楚。这是关于win32和python 2.6.2的内容。我找不到zlib文件,但它是win32二进制版本里包含的东西。而且我没有原始数据——我一直在压缩我的日志文件,现在想把它们恢复回来。至于其他软件,我天真地试过7zip,但当然失败了,因为它是zlib格式,不是gzip格式(我找不到可以直接解压zlib流的软件)。我现在不能给你完整的错误信息,但大概是(追溯到zlib.decompress(data))zlib.error: 错误: -3。另外,想说明的是,这些是静态文件,而不是我之前说的流文件(所以没有传输错误)。我再次强调,我没有代码,但我知道我用的是zlib.compress(data, 9)(也就是最高压缩级别——有趣的是,似乎并不是所有的zlib输出都是你预期的78da,因为我用了最高级别)然后就是zlib.decompress()。

4

我在寻找

python -c 'import sys,zlib;sys.stdout.write(zlib.decompress(sys.stdin.read()))'

我自己写的;是根据在Python中使用zlib解压缩的回答写的

36

根据RFC 1950,"OK"的0x789C和"bad"的0x78DA之间的区别在于FLEVEL位字段:

  FLEVEL (Compression level)
     These flags are available for use by specific compression
     methods.  The "deflate" method (CM = 8) sets these flags as
     follows:

        0 - compressor used fastest algorithm
        1 - compressor used fast algorithm
        2 - compressor used default algorithm
        3 - compressor used maximum compression, slowest algorithm

     The information in FLEVEL is not needed for decompression; it
     is there to indicate if recompression might be worthwhile.

"OK"使用的是2,而"bad"使用的是3。所以这个区别本身并不是问题。

要进一步了解,你可能需要提供一些信息,包括压缩和(尝试)解压缩时使用的平台、Python的版本、zlib库的版本,以及调用zlib模块时使用的实际代码。同时提供失败解压缩尝试的完整错误信息和追踪记录。你有没有尝试用其他zlib读取软件解压缩失败的文件?结果如何?请明确你所拥有的信息:“我完蛋了吗?”是否意味着你无法访问原始数据?数据是如何从流转变为文件的?你有什么保证数据在传输过程中没有被损坏?

更新 根据你自答中发布的部分澄清,以下是一些观察:

你正在使用Windows。Windows在读取和写入文件时区分二进制模式和文本模式。在文本模式下读取时,Python 2.x会将'\r\n'转换为'\n',而在写入时则将'\n'转换为'\r\n'。处理非文本数据时,这并不是个好主意。更糟的是,在文本模式下读取时,'\x1a'(也就是Ctrl-Z)会被视为文件结束。

压缩文件的方法:

# imports and other superstructure left as a exercise
str_object1 = open('my_log_file', 'rb').read()
str_object2 = zlib.compress(str_object1, 9)
f = open('compressed_file', 'wb')
f.write(str_object2)
f.close()

解压文件的方法:

str_object1 = open('compressed_file', 'rb').read()
str_object2 = zlib.decompress(str_object1)
f = open('my_recovered_log_file', 'wb')
f.write(str_object2)
f.close()

顺便提一下,最好使用gzip模块,这样你就不用考虑像文本模式这样的麻烦,虽然会多占用几个字节来存储额外的头信息。

如果你在压缩代码中使用了'rb'和'wb',但在解压缩代码中没有使用这些模式[不太可能?],你并没有完蛋,只需要完善上述解压缩代码就可以了。

请仔细注意以下未经过测试的想法中使用的“可能”、“应该”等词。

如果你在压缩代码中没有使用'rb'和'wb',那么你出问题的可能性就相当高。

如果你的原始文件中有任何'\x1a'的实例,那么在第一个这样的字节之后的所有数据都会丢失——但在这种情况下,解压缩时不应该失败(换句话说,这种情况并不符合你的症状)。

如果Ctrl-Z是由zlib本身生成的,这应该会在尝试解压缩时导致提前结束文件,这当然会引发异常。在这种情况下,你可以小心地通过以二进制模式读取压缩文件,然后将'\r\n'替换为'\n'(即模拟文本模式而不使用Ctrl-Z -> EOF的技巧)来逆转这个过程。解压缩结果。编辑 将结果以文本模式写出。结束编辑

更新2 我可以用以下脚本重现你的症状——无论是1到9的任何级别:

import zlib, sys
fn = sys.argv[1]
level = int(sys.argv[2])
s1 = open(fn).read() # TEXT mode
s2 = zlib.compress(s1, level)
f = open(fn + '-ct', 'w') # TEXT mode
f.write(s2)
f.close()
# try to decompress in text mode
s1 = open(fn + '-ct').read() # TEXT mode
s2 = zlib.decompress(s1) # error -5
f = open(fn + '-dtt', 'w')
f.write(s2)
f.close()

注意:你需要使用一个相对较大的文本文件(我使用了一个80kb的源文件),以确保解压缩结果中会包含'\x1a'。

我可以用这个脚本恢复:

import zlib, sys
fn = sys.argv[1]
# (1) reverse the text-mode write
# can't use text-mode read as it will stop at Ctrl-Z
s1 = open(fn, 'rb').read() # BINARY mode
s1 = s1.replace('\r\n', '\n')
# (2) reverse the compression
s2 = zlib.decompress(s1)
# (3) reverse the text mode read
f = open(fn + '-fixed', 'w') # TEXT mode
f.write(s2)
f.close()

注意:如果原始文件中有'\x1a'(也就是Ctrl-Z)字节,并且文件是以文本模式读取的,那么该字节及其后面的所有字节将不会被包含在压缩文件中,因此无法恢复。对于文本文件(例如源代码),这并没有损失。对于二进制文件,你很可能就完蛋了。

更新3 [在晚些时候发现问题涉及加密/解密层后]:

“错误 -5”消息表明你尝试解压缩的数据在压缩后已经被损坏。如果不是因为在文件上使用了文本模式,显然应该怀疑你的解密和加密包装。如果你想要帮助,你需要透露这些包装的来源。实际上,你应该尝试(像我一样)编写一个小脚本,在多个输入文件上重现这个问题。其次(像我一样)看看在什么条件下你可以逆转这个过程。如果你想要第二阶段的帮助,你需要透露问题重现的脚本。

撰写回答