Python中bz2模块无法正确重新压缩的问题排查
问题场景
我用以下代码读取并解压BZ2压缩文件:
offset = 24 # 打开文件 fobj = open(filey,'rb') # 读取数据 buffer = fobj.read() # 执行BZ2解压 buffer_unbzip,places_to_bzip = bzip_blocks_decompress_all(buffer,offset)
解压函数定义:
def bzip_blocks_decompress_all(data,offset): import bz2 frames = bytearray() places_to_bzip = [] while offset < len(data): block_cmp_bytes = abs(int.from_bytes(data[offset:offset + 4], 'big', signed=True)) offset += 4 # 解压当前块并拼接到frames frames += bz2.decompress(data[offset:offset + block_cmp_bytes]) # 记录原buffer中该压缩块的位置 places_to_bzip.append([offset,offset+block_cmp_bytes]) offset += block_cmp_bytes return frames,places_to_bzip
之后尝试用places_to_bzip记录的位置重新压缩对应块:
# 提取原压缩块和解压后对应位置的数据 a1 = buffer[places_to_bzip[0][0]:places_to_bzip[0][1]] a2 = buffer_unbzip[places_to_bzip[0][0]:places_to_bzip[0][1]] # 重新压缩a2 a3 = bz2.compress(a2) print(len(a1)) print(len(a2)) print(len(a3))
输出结果:
104 104 70
显然重新压缩后的结果和原压缩块不一致,测试输出的a1和a2如下:
print(a1) b'BZh51AY&SY\xe6\xb1\xacS\x00\x00\x02_\xab\xfe(@\x00\x10\x00@\x04\x00@\x00@\x800\x02\x00\x00\x01\x00@\x08\x00\x00\x18 \x00T4\x8d\x004\x01\xa0\x91(\x01\x90\xd3\xd2\x14\xac\xd6v\x85\xf0\x0fD\x85\xc3A}\xe09\xbc\xe1\x8b\x04Y\xbfb$"\xcc\x13\xc0B\r\x99\xf1Qa%S\x00|]\xc9\x14\xe1BC\x9a\xc6\xb1L' print(a2) bytearray(b'\x00\x0b\x00\x02\x05z\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00X\x00\x00\x00\x00\x002\x04@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01h\x00\x00\x00\x00\x002\x04@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
问题原因
1. 核心错误:解压后数据的切片逻辑完全错误
places_to_bzip记录的是原压缩文件buffer中压缩块的字节位置,而buffer_unbzip是所有压缩块解压后拼接的全量数据。每个压缩块解压后的长度和原压缩块的长度没有对应关系(通常解压后长度远大于压缩前),用原压缩块的位置/长度去切解压后的全量buffer,得到的a2根本不是a1对应的解压内容。
比如在解压函数中,a1对应的解压内容是bz2.decompress(data[offset:offset + block_cmp_bytes]),这个内容的长度不一定等于原压缩块的104字节,现在用原压缩块的长度去切buffer_unbzip,拿到的不是对应a1的解压内容,重新压缩自然不可能得到a1。
2. BZ2压缩参数不匹配(次要原因)
即使拿到了正确的解压内容,直接用bz2.compress()默认参数压缩,也可能和原压缩块不一致。原压缩块可能使用了特定的压缩级别、块大小或其他参数(比如原压缩用了更低的压缩级别),而Python标准库的bz2.compress()默认使用压缩级别9,会导致压缩结果的字节长度、内容和原块不同。
3. BZ2块的元数据差异
原压缩块a1包含完整的BZ2块结构(比如开头的BZh5标识、CRC校验位、块大小标识等),而bz2.compress()生成的压缩数据可能在元数据格式上和原块有差异,即使解压后内容相同,压缩后的包装格式也会不一样。
解决思路
1. 修正解压时的记录逻辑
在解压函数中,同时记录每个压缩块对应的解压后内容,而不是只记录原压缩块的位置:
def bzip_blocks_decompress_all(data,offset): import bz2 frames = bytearray() # 修改为存储每个压缩块的原数据、解压后数据 block_records = [] while offset < len(data): block_cmp_bytes = abs(int.from_bytes(data[offset:offset + 4], 'big', signed=True)) offset += 4 cmp_block = data[offset:offset + block_cmp_bytes] decomp_block = bz2.decompress(cmp_block) frames += decomp_block # 保存原压缩块和解压后块的对应关系 block_records.append({ 'compressed': cmp_block, 'decompressed': decomp_block }) offset += block_cmp_bytes return frames, block_records
后续可以直接拿到每个原压缩块对应的解压内容,再尝试重新压缩对比:
# 取第一个块的记录 record = block_records[0] a1 = record['compressed'] a2 = record['decompressed'] a3 = bz2.compress(a2)
2. 匹配原压缩参数
如果修正后压缩结果还是和原块不一致,需要尝试调整bz2.compress()的参数,比如压缩级别(compresslevel参数,1-9),或者使用bz2.BZ2Compressor类自定义块大小等参数,尽可能匹配原压缩的参数。
3. 验证解压内容的正确性
先确认bz2.decompress(a1)是否等于正确的解压内容,如果不等,说明切片逻辑肯定错误;如果相等,再排查压缩参数的问题。
内容的提问来源于stack exchange,提问作者Miss_Orchid

