奇怪的“BadZipfile:错误的CRC-32”问题

时间:2011-04-11 16:47:34

标签: zipfile stringio bytesio

此代码简化了Django应用程序中的代码,该应用程序通过HTTP多部分POST接收上传的zip文件,并对内部数据进行只读处理:

#!/usr/bin/env python

import csv, sys, StringIO, traceback, zipfile
try:
    import io
except ImportError:
    sys.stderr.write('Could not import the `io` module.\n')

def get_zip_file(filename, method):
    if method == 'direct':
        return zipfile.ZipFile(filename)
    elif method == 'StringIO':
        data = file(filename).read()
        return zipfile.ZipFile(StringIO.StringIO(data))
    elif method == 'BytesIO':
        data = file(filename).read()
        return zipfile.ZipFile(io.BytesIO(data))


def process_zip_file(filename, method, open_defaults_file):
    zip_file    = get_zip_file(filename, method)
    items_file  = zip_file.open('items.csv')
    csv_file    = csv.DictReader(items_file)

    try:
        for idx, row in enumerate(csv_file):
            image_filename = row['image1']

            if open_defaults_file:
                z = zip_file.open('defaults.csv')
                z.close()

        sys.stdout.write('Processed %d items.\n' % idx)
    except zipfile.BadZipfile:
        sys.stderr.write('Processing failed on item %d\n\n%s' 
                         % (idx, traceback.format_exc()))


process_zip_file(sys.argv[1], sys.argv[2], int(sys.argv[3]))

非常简单。我们在zip文件中打开zip文件和一个或两个CSV文件。

奇怪的是,如果我使用一个大型zip文件(~13 MB)运行它并让它从ZipFileStringIO.StringIO实例化io.BytesIO(也许除了一个简单的文件名?当我尝试从ZipFile或甚至通过调用TemporaryUploadedFileos.tmpfile()创建的文件对象创建shutil.copyfileobj()时,我在Django应用程序中遇到了类似的问题让它打开两个csv文件而不是一个,然后它在处理结束时失败。这是我在Linux系统上看到的输出:

$ ./test_zip_file.py ~/data.zip direct 1
Processed 250 items.

$ ./test_zip_file.py ~/data.zip StringIO 1
Processing failed on item 242

Traceback (most recent call last):
  File "./test_zip_file.py", line 26, in process_zip_file
    for idx, row in enumerate(csv_file):
  File ".../python2.7/csv.py", line 104, in next
    row = self.reader.next()
  File ".../python2.7/zipfile.py", line 523, in readline
    return io.BufferedIOBase.readline(self, limit)
  File ".../python2.7/zipfile.py", line 561, in peek
    chunk = self.read(n)
  File ".../python2.7/zipfile.py", line 581, in read
    data = self.read1(n - len(buf))
  File ".../python2.7/zipfile.py", line 641, in read1
    self._update_crc(data, eof=eof)
  File ".../python2.7/zipfile.py", line 596, in _update_crc
    raise BadZipfile("Bad CRC-32 for file %r" % self.name)
BadZipfile: Bad CRC-32 for file 'items.csv'

$ ./test_zip_file.py ~/data.zip BytesIO 1
Processing failed on item 242

Traceback (most recent call last):
  File "./test_zip_file.py", line 26, in process_zip_file
    for idx, row in enumerate(csv_file):
  File ".../python2.7/csv.py", line 104, in next
    row = self.reader.next()
  File ".../python2.7/zipfile.py", line 523, in readline
    return io.BufferedIOBase.readline(self, limit)
  File ".../python2.7/zipfile.py", line 561, in peek
    chunk = self.read(n)
  File ".../python2.7/zipfile.py", line 581, in read
    data = self.read1(n - len(buf))
  File ".../python2.7/zipfile.py", line 641, in read1
    self._update_crc(data, eof=eof)
  File ".../python2.7/zipfile.py", line 596, in _update_crc
    raise BadZipfile("Bad CRC-32 for file %r" % self.name)
BadZipfile: Bad CRC-32 for file 'items.csv'

$ ./test_zip_file.py ~/data.zip StringIO 0
Processed 250 items.

$ ./test_zip_file.py ~/data.zip BytesIO 0
Processed 250 items.

顺便提一下,代码在相同的条件下失败但在我的OS X系统上以不同的方式失败。它不是BadZipfile例外,而是读取损坏的数据并且非常困惑。

这一切都告诉我,我在这段代码中做了一些你不应该做的事情 - 例如:在一个文件上调用zipfile.open,同时已经在同一个zip文件对象中打开了另一个文件?使用ZipFile(filename)时这似乎不是问题,但是在传递ZipFile类似文件的对象时可能会出现问题,因为zipfile模块中有一些实现细节?

也许我错过了zipfile文档中的内容?或许它还没有记录?或者(最不可能),zipfile模块中的错误?

4 个答案:

答案 0 :(得分:9)

我可能刚刚发现了问题和解决方案,但不幸的是我不得不用我自己的一个(zipfile模块替换Python的myzipfile模块。(

}。

$ diff -u ~/run/lib/python2.7/zipfile.py myzipfile.py
--- /home/msabramo/run/lib/python2.7/zipfile.py 2010-12-22 17:02:34.000000000 -0800
+++ myzipfile.py        2011-04-11 11:51:59.000000000 -0700
@@ -5,6 +5,7 @@
 import binascii, cStringIO, stat
 import io
 import re
+import copy

 try:
     import zlib # We may need its compression method
@@ -877,7 +878,7 @@
         # Only open a new file for instances where we were not
         # given a file object in the constructor
         if self._filePassed:
-            zef_file = self.fp
+            zef_file = copy.copy(self.fp)
         else:
             zef_file = open(self.filename, 'rb')

标准zipfile模块中的问题是,当传递文件对象(不是文件名)时,它会对open方法的每次调用使用相同的传入文件对象。这意味着tellseek在同一个文件上被调用,因此尝试在zip文件中打开多个文件会导致文件位置被共享,因此多个open调用结果在他们身边踩着对方。相反,当传递文件名时,open会打开一个新的文件对象。我的解决方案适用于传入文件对象而不是直接使用该文件对象的情况,我创建了它的副本。

zipfile的此更改修复了我看到的问题:

$ ./test_zip_file.py ~/data.zip StringIO 1
Processed 250 items.

$ ./test_zip_file.py ~/data.zip BytesIO 1
Processed 250 items.

$ ./test_zip_file.py ~/data.zip direct 1
Processed 250 items.

但我不知道它是否会对zipfile ...

产生其他负面影响

编辑:我刚才在Python文档中发现了这一点,我之前曾忽略过这一点。在http://docs.python.org/library/zipfile.html#zipfile.ZipFile.open,它说:

  

注意:如果ZipFile是通过传入类似文件的对象作为第一个参数创建的   构造函数,然后由open()返回的对象共享ZipFile的文件指针。根据这些   在任何其他操作之后,open()返回的对象不应该使用   在ZipFile对象上执行。如果ZipFile是通过传入一个字符串创建的(   filename)作为构造函数的第一个参数,然后open()将创建一个新文件   ZipExtFile将保留的对象,允许它独立于ZipFile运行。

答案 1 :(得分:1)

我所做的是更新设置工具,然后重新下载,现在可以使用

https://pypi.python.org/pypi/setuptools/35.0.1

答案 2 :(得分:0)

就我而言,这解决了问题:

pip uninstall pillow

答案 3 :(得分:0)

是否可以在桌面上打开它?有时候这对我来说是发生了,解决方法是只运行代码而不在python会话外部打开文件。