如何读取用 7z 压缩的文本文件?

Posted

技术标签:

【中文标题】如何读取用 7z 压缩的文本文件?【英文标题】:How to read from a text file compressed with 7z? 【发布时间】:2013-11-20 18:52:05 【问题描述】:

我想从 7z 压缩的 csv(文本)文件中逐行读取(在 Python 2.7 中)。我不想解压缩整个(大)文件,而是流式传输。

我尝试pylzma.decompressobj() 失败。我收到数据错误。请注意,此代码尚未逐行读取:

input_filename = r"testing.csv.7z"
with open(input_filename, 'rb') as infile:
    obj = pylzma.decompressobj()
    o = open('decompressed.raw', 'wb')
    obj = pylzma.decompressobj()
    while True:
        tmp = infile.read(1)
        if not tmp: break
        o.write(obj.decompress(tmp))
    o.close()

输出:

    o.write(obj.decompress(tmp))
ValueError: data error during decompression

【问题讨论】:

为什么不发布您的代码和示例文件,以便我们重现您的错误并了解我们如何提供帮助? .7z 文件是可以包含多个文件的容器(档案),那么您要阅读的testing.7z 中的文件名称是什么? @martineau,testing.csv 【参考方案1】:

这将允许您迭代行。它部分源自我在 answer 中找到的一些代码,用于另一个问题。

在这个时间点 (pylzma-0.5.0),py7zlib 模块没有实现允许存档成员作为字节或字符流读取的 API — 它的 ArchiveFile 类仅提供 @987654325 @函数一次解压缩并返回成员中的未压缩数据。鉴于此,可以做的最好的事情是通过 Python 生成器使用它作为缓冲区迭代地返回字节或行。

以下是后者,但如果问题是存档 member 文件本身很大,则可能无济于事。

下面的代码应该可以在 Python 3.x 和 2.7 中运行。

import io
import os
import py7zlib


class SevenZFileError(py7zlib.ArchiveError):
    pass

class SevenZFile(object):
    @classmethod
    def is_7zfile(cls, filepath):
        """ Determine if filepath points to a valid 7z archive. """
        is7z = False
        fp = None
        try:
            fp = open(filepath, 'rb')
            archive = py7zlib.Archive7z(fp)
            _ = len(archive.getnames())
            is7z = True
        finally:
            if fp: fp.close()
        return is7z

    def __init__(self, filepath):
        fp = open(filepath, 'rb')
        self.filepath = filepath
        self.archive = py7zlib.Archive7z(fp)

    def __contains__(self, name):
        return name in self.archive.getnames()

    def readlines(self, name, newline=''):
        r""" Iterator of lines from named archive member.

        `newline` controls how line endings are handled.

        It can be None, '', '\n', '\r', and '\r\n' and works the same way as it does
        in StringIO. Note however that the default value is different and is to enable
        universal newlines mode, but line endings are returned untranslated.
        """
        archivefile = self.archive.getmember(name)
        if not archivefile:
            raise SevenZFileError('archive member %r not found in %r' %
                                  (name, self.filepath))

        # Decompress entire member and return its contents iteratively.
        data = archivefile.read().decode()
        for line in io.StringIO(data, newline=newline):
            yield line


if __name__ == '__main__':

    import csv

    if SevenZFile.is_7zfile('testing.csv.7z'):
        sevenZfile = SevenZFile('testing.csv.7z')

        if 'testing.csv' not in sevenZfile:
            print('testing.csv is not a member of testing.csv.7z')
        else:
            reader = csv.reader(sevenZfile.readlines('testing.csv'))
            for row in reader:
                print(', '.join(row))

【讨论】:

【参考方案2】:

如果您使用的是 Python 3.3+,则可以使用在该版本的标准库中添加的 lzma 模块来执行此操作。

见:lzmaExamples

【讨论】:

这个问题被标记为python-2.7,所以我们可以假设它不是这里是Python 3。 另外,你应该提到 python 3.3(来自文档链接)而不仅仅是 3。 @MartijnPieters 在我发表评论时没有那个标签。 即使 OP 使用的是 Python 3.3+,lzma 模块也仅提供使用 LZMA 压缩算法压缩和解压缩 原始数据 的函数——这并不相同处理可能包含多个文件/成员的 7zip 格式存档文件,就像 PyLZMA 第三方模块所做的那样。【参考方案3】:

如果你可以使用python 3,有一个有用的库py7zr,它支持部分 7zip解压如下:

import py7zr
import re
filter_pattern = re.compile(r'<your/target/file_and_directories/regex/expression>')
with SevenZipFile('archive.7z', 'r') as archive:
    allfiles = archive.getnames()
    selective_files = [f if filter_pattern.match(f) for f in allfiles]
    archive.extract(targets=selective_files)

【讨论】:

这并没有完成 OP 的要求,即 stream 单个输出文件。

以上是关于如何读取用 7z 压缩的文本文件?的主要内容,如果未能解决你的问题,请参考以下文章

如何打开7z文件?

C++程序编写压缩器/解压器(长度-游程编码的压缩/解压+霍夫曼编码压缩/解压 (霍夫曼树))

如何在Python中读取压缩文件夹中的文本文件

怎么将文件用7Z压缩到最小

7z.001,7z.002这样的文件如何解压

HP-UX - 如何在不解压缩的情况下从 tar 存档中读取文本文件?