Странная проблема "BadZipfile: Bad CRC-32" - PullRequest
5 голосов
/ 11 апреля 2011

Этот код является упрощением кода в приложении Django, которое получает выгруженный zip-файл через HTTP, состоящий из нескольких частей POST, и выполняет обработку данных только для чтения внутри:

#!/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-файл и один или два CSV-файла внутри zip-файла.

Что странного в том, что если я запускаю это с большим zip-файлом (~ 13 МБ) и создаю для него экземпляр ZipFile из StringIO.StringIO или io.BytesIO (возможно, из-за простого имени файла? У меня было аналогичные проблемы в приложении Django при попытке создать ZipFile из TemporaryUploadedFile или даже файлового объекта, созданного путем вызова os.tmpfile() и shutil.copyfileobj()) и заставить его открывать ДВА файла 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?

Ответы [ 3 ]

10 голосов
/ 11 апреля 2011

Я, возможно, только что нашел проблему и решение, но, к сожалению, мне пришлось заменить модуль 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. Это означает, что tell и seek вызываются в одном и том же файле, и поэтому попытка открыть несколько файлов в 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 голос
/ 20 апреля 2017

Что я сделал, так это обновил инструменты настройки, затем снова загрузил, и теперь это работает

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

0 голосов
/ 27 июня 2019

В моем случае это решило проблему:

pip uninstall pillow
...