如何判断文件是否为gzip压缩格式?

40

我有一个Python程序,它需要读取文本文件作为输入。但是,其中一些文件可能是gzip压缩的。

是否有一种可跨平台、可从Python使用的方法来确定文件是否已经gzip压缩?

以下方法可靠吗?或者一般的文本文件会不会“意外地”看起来像gzip文件,导致误报呢?

try:
    gzip.GzipFile(filename, 'r')
    # compressed
    # ...
except:
    # not compressed
    # ...

2
只是一个小提示...永远不要依赖文件扩展名。看看hop的回答如何做到这一点。 - helpermethod
@助手:我不确定(请看我的编辑)。你仍然需要处理可能的IOError,但是没有后缀名的压缩文件是有问题的,这是我的意见... 这很棘手 :) - user3850
6个回答

46

gzip压缩文件的魔数是1f 8b。虽然测试不是100%可靠,但“普通文本文件”以这两个字节开头的可能性非常小——在UTF-8中甚至不合法。

通常,gzip压缩文件的后缀名为.gz。即使是gzip(1)本身也不会解压缩没有该后缀名的文件,除非你使用--force选项。理论上你可以这样做,但你仍然必须处理可能出现的IOError(无论如何都必须这样做)。

你的方法有一个问题,即gzip.GzipFile()如果输入的是未压缩的文件,则不会抛出异常。只有稍后的read()才会抛出异常。这意味着你可能需要两次实现程序逻辑。很丑陋。


gzip压缩文件通常具有.gz文件扩展名(事实上,我认为我从未见过.gzip扩展名),但是通常不安全依赖文件扩展名来测试文件类型。 - CanSpice
它会吗?- gzip C库将透明地读取未压缩的文件。虽然它会以未压缩的方式写入文件,但会通过它们放置CRC代码以允许“gzip -t”(有一次抓住了我)。 - Martin Beckett
@Martin:它确实可以:$ gunzip foo gzip: foo: 未知的后缀 -- 已忽略 - user3850
C语言的“库”gzip,即gzopen/gzread等,可以透明地读取未压缩的文件。它们有一个打开compression=none模式,不会写入未更改的平面文件。 - Martin Beckett
关于扩展。您还需要检查相对常见的.tgz扩展名。 - mxmlnkn

44

有没有一种跨平台的、可用于 Python 的方式来确定文件是否经过 gzip 压缩?

接受的答案 解释了如何通常检测 gzip 压缩的文件: 检测前两个字节是否为 1f 8b。然而,它并没有展示如何在 Python 中实现。

下面是其中一种方法:

def is_gz_file(filepath):
    with open(filepath, 'rb') as test_f:
        return test_f.read(2) == b'\x1f\x8b'

4
也可以不使用binascii实现:test_f.read(2) == b'\x1f\x8b' - nemetroid
4
为了降低误报率,您可以检测前三个字节是否为 1f 8b 08 - Mark Adler
1
如果文件不是 .gz 文件,那么 test_f.read(2) 在第一次调用时会抛出 OSError 异常,还需要使用 test_f.read(2) == b'\x1f\x8b' 检查字节吗?编辑:这似乎仅适用于 Python 3.7 及以上版本。 - Blade

16

测试gzip文件的魔术数字是唯一可靠的方式。但是,从python3.7开始,不再需要自己比较字节了。gzip模块将为您比较字节,并在不匹配时引发异常!

从python3.7开始,这样做就行了

import gzip
with gzip.open(input_file, 'r') as fh:
    try:
        fh.read(1)
    except OSError:
        print('input_file is not a valid gzip file by OSError')

从Python 3.8开始,这也是有效的:

import gzip
with gzip.open(input_file, 'r') as fh:
    try:
        fh.read(1)
    except gzip.BadGzipFile:
        print('input_file is not a valid gzip file by BadGzipFile')

2

gzip自身会在不是gzip文件时引发OSError

>>> with gzip.open('README.md', 'rb') as f:
...     f.read()
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "/Users/dennis/.asdf/installs/python/3.6.6/lib/python3.6/gzip.py", line 276, in read
    return self._buffer.read(size)
  File "/Users/dennis/.asdf/installs/python/3.6.6/lib/python3.6/gzip.py", line 463, in read
    if not self._read_gzip_header():
  File "/Users/dennis/.asdf/installs/python/3.6.6/lib/python3.6/gzip.py", line 411, in _read_gzip_header
    raise OSError('Not a gzipped file (%r)' % magic)
OSError: Not a gzipped file (b'# ')

可以结合其他方法来增加信心,例如检查MIME类型或在文件头中查找魔数(请参见其他答案的示例)并检查扩展名。

import pathlib

if '.gz' in pathlib.Path(filepath).suffixes:
   # some more inexpensive checks until confident we can attempt to decompress
   # ...
   try ...
     ...
   except OSError as e:
     ...

1
Python 3.8现在增加了一个更具体的错误称为gzip.BadGzipFile以此来实现。该错误仍然继承自OSError - winni2k

0

在Python3中似乎不能很好地工作...

import mimetypes
filename = "./datasets/test"

def file_type(filename):
    type = mimetypes.guess_type(filename)
    return type
print(file_type(filename))

返回 (无,无) 但来自 Unix 命令 "File"

:~> file datasets/test datasets/test: gzip 压缩数据,文件名为 "iostat_collection",来自 Unix,最后修改时间:2015 年 1 月 29 日,07:09:34


3
mimetypes仅使用文件名来猜测类型。要从原始文件中检测文件类型,您需要使用“magic”模块。 - Brice M. Dempsey

0

导入mimetypes模块。 它可以自动猜测你拥有的文件类型,以及是否压缩。

例如:

mimetypes.guess_type('blabla.txt.gz')

返回:

('text/plain','gzip')


25
mimetypes仅检查文件名的结尾,实际上不会根据文件内容进行猜测。 - Odinulf

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