2011-04-11 26 views
4

此代碼是在通過HTTP多部分POST接收上傳的zip文件並不會只讀內部的數據的處理的Django應用程序的代碼簡化:奇怪「BadZipfile:壞CRC-32」的問題

#!/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),並有它比一個普通的文件名以外的StringIO.StringIOio.BytesIO(也許任何實例化ZipFile?我在Django的類似問題嘗試通過TemporaryUploadedFile創建ZipFile或通過調用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模塊中的錯誤?

回答

8

我可能剛剛發現了問題和解決方案,但不幸的是我必須用我自己的一個黑客(這裏稱爲myzipfile)替換Python的zipfile模塊。

$ 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會打開一個新的文件對象。我的解決方案適用於傳入文件對象的情況,而不是直接使用該文件對象,我創建了它的副本。

這種變化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,它說:

注:如果zip文件是由一個類文件對象傳遞作爲第一個參數 構造函數創建,那麼對象通過open()股zip文件的文件指針返回。在這些 的情況下,在對ZipFile對象執行任何其他操作 之後,不應使用open()返回的對象。如果ZipFile是通過向構造函數傳入一個字符串( 文件名)作爲第一個參數創建的,那麼open()將創建一個新的文件 對象,該對象將由ZipExtFile保存,允許它獨立於ZipFile進行操作。

+0

我剛剛發現的這一提的我在某種程度上忽略了之前的Python文檔。 – 2011-04-11 21:49:37

+0

我遇到了同樣的問題。相同的代碼在Python2中產生了錯誤,但在Python3中產生了錯誤。所以我根據這裏的解決方案嘗試,傳遞一個文件名而不是文件對象。然後我遇到了'打開太多文件'的錯誤,如果zip有數百個成員,就會發生這種情況。然後我意識到,如果我在初始化ZipFile之前對文件對象執行seek(0),錯誤就會消失。不明白爲什麼,因爲我之前在文件對象上什麼也沒做... – deeenes 2016-06-29 19:53:38