从谷歌云存储中流式传输gzip压缩文件

3

我希望能够直接从Google Cloud Storage读取压缩文件,并使用Python csv包打开它们。 本地文件的代码如下:

def reader(self):
    print "reading local compressed file: ", self._filename
    self._localfile = gzip.open(self._filename, 'rb')
    csvReader = csv.reader(self._localfile, delimiter=',', quotechar='"')
    return csvReader

我已经使用过几个GCS API(基于JSON,cloud.storage),但是它们似乎都不能提供我可以通过gzip流式传输的内容。此外,即使文件未压缩,我也无法打开该文件并将其提供给cv.reader(Iterator类型)。
我的压缩CSV文件约为500MB,而未压缩的文件则使用多达几GB的空间。我认为这不是一个好主意:1-在打开它们之前本地下载文件(除非我可以同时下载和计算);或者2-在计算之前完全在内存中打开文件。
最后,我目前在本地机器上运行此代码,但最终,我将转移到AppEngine,因此它也必须在那里正常工作。
谢谢!

把你的文件分成多个部分怎么样? - Raito
这已经是一个超过1TB的数据集的多个部分了。将其进一步分解似乎是一个不必要的想法。我正在尝试Alex Martelli的建议。 - user1066293
2个回答

6
使用 GCScloudstorage.open(filename, 'r') 将给您一个只读文件对象(之前同样使用 'w' 创建),您可以一次读取一部分,并使用标准的 Python 库中的 zlib 模块,特别是 zlib.decompressobj。当然,如果 GS 对象最初是以互补方式创建的(使用 zlib.compressobj)。

或者,为了方便,您可以使用标准的 Python 库中的 gzip 模块,例如在读取阶段使用以下代码:

compressed_flo = cloudstorage.open('objname', 'r')
uncompressed_flo = gzip.GzipFile(fileobj=compressed_flo,mode='rb')
csvReader = csv.reader(uncompressed_flo)

当然,对于早期的编写阶段也是反过来。

请注意,当您在本地运行(使用dev_appserver)时,GCS客户端库使用本地磁盘文件来模拟GCS--在我的经验中,这对于开发目的很有用,当我需要从我的本地工作站与“真正”的GCS存储交互时,我可以使用gsutil或其他工具... GCS是为了让我从我的GAE应用程序中进行此类交互(以及首先在本地开发该GAE应用程序:-)。


“当然,前提是GS对象最初是以互补方式创建的(使用zlib.compressobj)”--为什么有这个限制?这应该适用于任何有效的gzipped文件内容,对吧?虽然我没有测试过,但zlib模块的标题是“zlib-与gzip兼容的压缩”。;-) - Dr. Jan-Philip Gehrcke
gzip 添加了 zlib 不知道也不关心的元数据,然后 gzip 使用 zlib 压缩数据主体,但仍然需要进行大量的元数据工作,例如 crc 维护。请参见 http://svn.python.org/projects/python/branches/py3k/Lib/gzip.py 以获取 gzip.py 的源代码 -- 在 zlib 之上有 500 多行代码!-) 当然与 GCS 无关 -- 自己将数据转储到/从本地磁盘文件中,您将遇到相同的问题。最好、最简单、最聪明的方法是:让 gzip 双向工作,或者如果元数据对您来说是无用的负担,则使用 zlib 双向工作(节省一些字节)。 - Alex Martelli
我同意,为了简单起见,应该双向使用相同的工具。然而,我现在很好奇为什么zlib模块声称“压缩与gzip兼容”,并查看了http://www.zlib.net上的文档。事实证明,“gzip添加了zlib不知道也不关心的元数据”并不完全正确。`zlib`可以自动检测`gzip`包装器(头+尾),并根据`windowBits`设置忽略它。在Python中,这个特性也是可用的,通过`zlib.decompressobj()`的`wbits`参数来公开。这很有趣。我已经在我的答案中添加了示例代码。 - Dr. Jan-Philip Gehrcke
@Jan-PhilipGehrcke 这在理论上很有趣,但它符合我“如果元数据对你没有用”的条件:是的,你可以比我想象的更容易地忽略它,但如果你无论如何都要忽略它,为什么要首先存储它并为其付费呢?-) - Alex Martelli
没错,我认为我们在这方面是一致的。我的收获只是zlib可以解压比我最初想象的更多的内容,并且这个功能在Python(2)文档中没有清晰地记录。 - Dr. Jan-Philip Gehrcke
1
@Jan-PhilipGehrcke,你说得对,我猜修改这些文档的补丁应该是受欢迎的! - Alex Martelli

4

所以,您在GCS上存储了gzip文件。您可以以类似流的方式处理存储在GCS上的数据。也就是说,您可以同时下载、解压缩和处理数据。这避免了

  • 必须将未压缩的文件存储到磁盘上
  • 必须等待下载完成后才能处理数据。

gzip文件有一个小的头部和尾部,主体是一个压缩流,由一系列块组成,每个块都可以单独解压缩。Python的zlib包可以帮助您实现这一点!

编辑: 这是一个示例代码,用于基于zlib分块逐步解压缩和分析zlib或gzip流:

import zlib
from collections import Counter


def stream(filename):
    with open(filename, "rb") as f:
        while True:
            chunk = f.read(1024)
            if not chunk:
                break
            yield chunk


def decompress(stream):
    # Generate decompression object. Auto-detect and ignore
    # gzip wrapper, if present.
    z = zlib.decompressobj(32+15)
    for chunk in stream:
        r = z.decompress(chunk)
        if r:
            yield r


c = Counter()
s = stream("data.gz")
for chunk in decompress(s):
    for byte in chunk:
        c[byte] += 1


print c

我用一个例子文件 data.gz 进行了代码测试,该文件是使用 GNU gzip 创建的。
引用来自http://www.zlib.net/manual.html

windowBits 也可以大于 15,用于可选的 gzip 解压缩。将 windowBits 加上 32 可以启用 zlib 和 gzip 解码,并进行自动头部检测,或者加上 16 仅解码 gzip 格式(zlib 格式将返回 Z_DATA_ERROR)。如果正在解码 gzip 流,则 strm->adler 是一个 crc32,而不是 adler32。

并且

gzip 头中包含的任何信息都不会被保留[...]


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