如何在Python中检查空的gzip文件

时间:2016-06-17 06:50:07

标签: python python-2.7 file pandas gzip

我不想使用操作系统命令,因为它会依赖于操作系统。

这可以在tarfiletarfile.is_tarfile(filename)中使用,以检查文件是否是tar文件。

我无法在gzip模块中找到任何相关命令。

修改: 为什么我需要这个:我有gzip文件列表,这些文件大小不一(1-10 GB),有些是空的。在读取文件(使用pandas.read_csv)之前,我想检查文件是否为空,因为对于空文件,我在pandas.read_csv中收到错误。 (错误如:预期15列,找到-1)

带错误的示例命令:

import pandas as pd
pd.read_csv('C:\Users\...\File.txt.gz', compression='gzip', names={'a', 'b', 'c'}, header=False)
Too many columns specified: expected 3 and found -1

pandas版本是0.16.2

file用于测试,它只是一个空文件的gzip。

8 个答案:

答案 0 :(得分:6)

不幸的是,gzip模块没有公开任何与-l程序的gzip列表选项等效的功能。但是在Python 3中,通过使用.seek参数为2调用whence方法,可以轻松获得未压缩数据的大小,这表示相对于(未压缩)数据流的末尾的定位。

.seek返回新的字节位置,因此.seek(0, 2)返回未压缩文件末尾的字节偏移量,即文件大小。因此,如果未压缩文件为空,.seek调用将返回0.

import gzip

def gz_size(fname):
    with gzip.open(fname, 'rb') as f:
        return f.seek(0, whence=2)

这是一个适用于Python 2的函数,在Python 2.6.6上进行了测试。

def gz_size(fname):
    f = gzip.open(fname, 'rb')
    data = f.read()
    f.close()
    return len(data)

您可以使用.seek计划了解GzipFile课程的pydoc和其他方法。只需在shell中运行pydoc gzip

或者,如果您希望避免解压缩文件,可以直接从.gz文件中读取未压缩的数据大小。大小存储在文件的最后4个字节中,作为小尾数无符号长整数,因此它实际上是模2-5 32的大小,因此如果未压缩的数据大小为> = 4GB,它将不是真正的大小。

此代码适用于Python 2和Python 3。

import gzip
import struct

def gz_size(fname):
    with open(fname, 'rb') as f:
        f.seek(-4, 2)
        data = f.read(4)
    size = struct.unpack('<L', data)[0]
    return size

然而,这种方法并不可靠,正如Mark Adler( gzip 共同作者)在评论中提到的那样:

  

gzip文件末尾的长度还有其他原因   不代表未压缩数据的长度。 (级联   gzip流,在gzip文件末尾填充。)它不应该   用于此目的。它只是作为完整性检查   数据

这是另一种解决方案。它不解压缩整个文件。如果输入文件中的未压缩数据长度为零,则返回True,但如果输入文件本身长度为零,则返回True。如果输入文件的长度不是零并且不是gzip文件,则会引发OSError

import gzip

def gz_is_empty(fname):
    ''' Test if gzip file fname is empty
        Return True if the uncompressed data in fname has zero length
        or if fname itself has zero length
        Raises OSError if fname has non-zero length and is not a gzip file
    '''
    with gzip.open(fname, 'rb') as f:
        data = f.read(1)
    return len(data) == 0

答案 1 :(得分:3)

如果要检查文件是否是有效的Gzip文件,可以打开它并从中读取一个字节。如果成功,该文件很可能是一个gzip文件,但有一点需要注意: empty 文件也会成功完成此测试。

因此我们得到了

def is_gz_file(name):
    with gzip.open(name, 'rb') as f:
        try:
            file_content = f.read(1)
            return True
        except:
            return False

但是,如前所述,一个空(0字节)的文件仍然可以成功完成此测试,因此您可能希望确保该文件不为空:

def is_gz_file(name):
    if os.stat(name).ST_SIZE == 0:
        return False

    with gzip.open(name, 'rb') as f:
        try:
            file_content = f.read(1)
            return True
        except:
            return False

编辑:

因为问题现在改为&#34; gzip文件没有空内容&#34;,然后:

def is_nonempty_gz_file(name):
    with gzip.open(name, 'rb') as f:
        try:
            file_content = f.read(1)
            return len(file_content) > 0
        except:
            return False

答案 2 :(得分:3)

<强>更新

我强烈建议升级到pandas 0.18.1(目前是最新版本),因为每个新版本的pandas都会引入很好的新功能并修复大量的旧bug。实际版本(0.18.1)将立即处理您的空文件(参见下面的演示)。

如果您无法升级到更新版本,请使用@MartijnPieters建议 - 捕获异常,而不是检查(遵循Easier to ask for forgiveness than permission范例)

OLD回答:小型演示(使用pandas 0.18.1),它可以容忍空文件,不同列数等。

我尝试重现您的错误(尝试空CSV.gz,不同数量的列等),但我无法使用pandas v.1.18.1重现您的异常:

import os
import glob
import gzip
import pandas as pd

fmask = 'd:/temp/.data/37874936/*.csv.gz'

files = glob.glob(fmask)

cols = ['a','b','c']

for f in files:
    # actually there is no need to use `compression='gzip'` - pandas will guess it itself
    # i left it in order to be sure that we are using the same parameters ...
    df = pd.read_csv(f, header=None, names=cols, compression='gzip', sep=',')
    print('\nFILE: [{:^40}]'.format(f))
    print('{:-^60}'.format(' ORIGINAL contents '))
    print(gzip.open(f, 'rt').read())
    print('{:-^60}'.format(' parsed DF '))
    print(df) 

输出:

FILE: [    d:/temp/.data/37874936\1.csv.gz     ]
-------------------- ORIGINAL contents ---------------------
11,12,13
14,15,16


------------------------ parsed DF -------------------------
    a   b   c
0  11  12  13
1  14  15  16

FILE: [  d:/temp/.data/37874936\empty.csv.gz   ]
-------------------- ORIGINAL contents ---------------------

------------------------ parsed DF -------------------------
Empty DataFrame
Columns: [a, b, c]
Index: []

FILE: [d:/temp/.data/37874936\zz_5_columns.csv.gz]
-------------------- ORIGINAL contents ---------------------
1,2,3,4,5
11,22,33,44,55

------------------------ parsed DF -------------------------
        a   b   c
1  2    3   4   5
11 22  33  44  55

FILE: [d:/temp/.data/37874936\z_bad_CSV.csv.gz ]
-------------------- ORIGINAL contents ---------------------
1
5,6,7
1,2
8,9,10,5,6

------------------------ parsed DF -------------------------
   a    b     c
0  1  NaN   NaN
1  5  6.0   7.0
2  1  2.0   NaN
3  8  9.0  10.0

FILE: [d:/temp/.data/37874936\z_single_column.csv.gz]
-------------------- ORIGINAL contents ---------------------
1
2
3

------------------------ parsed DF -------------------------
   a   b   c
0  1 NaN NaN
1  2 NaN NaN
2  3 NaN NaN

您是否可以发布样本CSV,导致此错误或将其上传到某处并在此处发布链接?

答案 3 :(得分:1)

尝试这样的事情:

def is_empty(gzfile):
    size = gzfile.read().
    if len(size) > 0:
         gzfile.rewind()
         return False
    else:
         return True

答案 4 :(得分:1)

import gzip

with gzip.open("pCSV.csv.gz", 'r') as f:

    f.seek(3)
    couterA = f.tell()

    f.seek(2,0)
    counterB = f.tell()

    if(couterA > counterB):
        print "NOT EMPTY"
    else:
        print "EMPTY"

这应该在不读取文件的情况下进行。

答案 5 :(得分:1)

通过source code查看gzip模块的Python 2.7版本,它似乎立即返回EOF,不仅在gzip文件是零字节的情况下,而且在这种情况下gzip文件是零字节,这可能是一个错误。

但是,对于您的特定用例,我们可以做得更好,同时确认gzip压缩文件是有效的CSV文件。

此代码......

import csv
import gzip

# Returns true if the specified filename is a valid gzip'd CSV file
# If the optional 'columns' parameter is specified, also check that
# the first row has that many columns
def is_valid(filename, columns=None):

    try:

        # Chain a CSV reader onto a gzip reader
        csv_file = csv.reader(gzip.open(filename))

        # This will try to read the first line
        # If it's not a valid gzip, this will raise IOError
        for row in csv_file:

            # We got at least one row
            # Bail out here if we don't care how many columns we have
            if columns is None:
                return True

            # Check it has the right number of columns
            return len(row) == columns

        else:

            # There were no rows
            return False

    except IOError:

        # This is not a valid gzip file
        return False


# Example to check whether File.txt.gz is valid
result = is_valid('File.txt.gz')

# Example to check whether File.txt.gz is valid, and has three columns
result = is_valid('File.txt.gz', columns=3)

...应该正确处理以下错误情况...

  1. gzip文件为零字节
  2. gzip文件不是有效的gzip文件
  3. gzip压缩文件为零字节
  4. gzip压缩文件不是零字节,但不包含CSV数据
  5. (可选)gzip压缩文件包含CSV数据,但列数错误

答案 6 :(得分:1)

不幸的是,任何此类尝试都可能会产生相当大的开销,捕获异常可能会更便宜,例如用户在上面评论过。 gzip文件定义了几个固定大小的区域,如下所示:

固定区域

首先,Gzip幻数有2个字节,压缩方法有1个字节,标志有1个字节,MTIME有4个字节(文件创建时间),额外标志有2个字节,还有两个字节操作系统的字节数,到目前为止总共有12个字节。

这看起来如下(来自上面的链接):

+---+---+---+---+---+---+---+---+---+---+
|ID1|ID2|CM |FLG|     MTIME     |XFL|OS | (more-->)
+---+---+---+---+---+---+---+---+---+---+

可变区域

然而,这是事情变得棘手的地方(如果不使用gzip模块或其他平减器,则无法检查)。

如果设置了额外字段,则之后会设置一个XLEN字节的可变区域,如下所示:

(if FLG.FEXTRA set)
+---+---+=================================+
| XLEN  |...XLEN bytes of "extra field"...| (more-->)
+---+---+=================================+

此后,有一个N字节的区域,文件名的字符串以零结尾(默认情况下存储):

(if FLG.FNAME set)
+=========================================+
|...original file name, zero-terminated...| (more-->)
+=========================================+

然后我们发表评论:

(if FLG.FCOMMENT set)
+===================================+
|...file comment, zero-terminated...| (more-->)
+===================================+

最后,CRC16(循环冗余校验,以确保文件头然后工作,所有这些都在我们进入变量之前,压缩数据。

<强>解决方案

因此,任何类型的固定大小检查都取决于文件名,还是通过管道(gzip -c "Compress this data" > myfile.gz),其他字段和注释编写的,所有这些都可以为空文件定义。那么,我们如何解决这个问题呢?简单,使用gzip模块:

import gzip

def check_null(path):
    '''
    Returns an empty string for a null file, which is falsey, 
    and returns a non-empty string otherwise (which is truthey)
    '''

    with gzip.GzipFile(path, 'rb') as f:
        return f.read(1)

这将检查创建的文件中是否存在任何数据,而只读取一小部分数据。然而,这需要一段时间,要求宽恕比要求许可更容易。

import contextlib       # python3 only, use a try/except block for Py2
import pandas as pd

with contexlib.suppress(pd.parser.CParserError as error):
    df = pd.read_csv(path, compression='gzip', names={'a', 'b', 'c'}, header=False)
    # do something here

答案 7 :(得分:0)

我有几十万个gzip文件,其中只有几个大小为零,安装在网络共享上。我被迫使用以下优化。它很脆弱,但是(在非常频繁的情况下)您使用相同的方法生成了大量文件,除有效负载名称之外的所有字节的总和是一个常数。

然后您可以通过以下方式检查有效载荷为零的大小:

  1. 通过一个文件计算该常数。 You can code it up,但我发现仅使用命令行gzip更为简单(无论如何,整个答案还是很丑陋的。)
  2. 仅检查其余文件的索引节点,而不是打开每个文件,这可以快几个数量级:
from os import stat
from os.path import basename

# YMMV with len_minus_file_name
def is_gzip_empty(file_name, len_minus_file_name=23): 
    return os.stat(file_name).st_size - len(basename(file_name)) == len_minus_file_name

这可能会在很多方面破坏。买者自负。仅在其他方法不可行时才使用它。