在Python 2和Python 3中,ZipFile.testzip()返回的结果不同。

15

使用Python中的zipfile模块解压大数据文件在Python 2上能够正确工作,但在Python 3.6.0上会产生以下错误:

BadZipFile: Bad CRC-32 for file 'myfile.csv'

我追踪到这是由于错误处理代码检查CRC值引起的。

在Python 2上使用ZipFile.testzip()没有返回任何内容(所有文件都正常)。在Python 3上运行它会返回'myfile.csv',表示该文件存在问题。

可用于在Python 2和Python 3上复现此问题的代码(涉及300 MB下载,抱歉):

import zipfile
import urllib
import sys

url = "https://de.iplantcollaborative.org/anon-files//iplant/home/shared/commons_repo/curated/Vertnet_Amphibia_Sep2016/VertNet_Amphibia_Sept2016.zip"

if sys.version_info >= (3, 0, 0):
    urllib.request.urlretrieve(url, "vertnet_latest_amphibians.zip")
else:
    urllib.urlretrieve(url, "vertnet_latest_amphibians.zip")

archive = zipfile.ZipFile("vertnet_latest_amphibians.zip")
archive.testzip()

有人知道为什么会出现这种差异吗?是否有办法让Python 3正常提取文件,使用以下命令:

archive.extract("vertnet_latest_amphibians.csv")

看起来我的一些本地状态进入了示例。抱歉。现在应该可以顺利运行了。 - Ethan White
嗯,如果可以,请添加您正在使用的Python版本来运行此代码。 - Dimitris Fasarakis Hilliard
这是版本号 3.6.0。我刚刚把它添加到了问题中。 - Ethan White
2
我可以使用2.7.12(正常)和3.4.3(失败)重新创建这个。我现在必须离开,但我已经验证了ZipInfo中的CRC在两个Python版本中是相同的,所以我认为这是CRC32计算的差异。 另外,我的7-zip版本(自2010年以来我没有更新过)也认为这个文件是损坏的。 - Sam Mussmann
下载速度极慢,而且在完成之前就中止了。你能否将文件镜像到其他地方? - wim
@wim - 如果这个链接更好用的话,这里是Dropbox链接:https://www.dropbox.com/s/7kjg7sm0jgx9e7w/VertNet_Amphibia_Sept2016.zip?dl=0 - Ethan White
4个回答

6

我无法从档案中提取Python 3。以下是在Mac OS X上进行的一些调查结果,可能会有所帮助。

检查档案的健康状况

将文件设为只读,以防止意外更改:

$ chmod -w vertnet_latest_amphibians.zip 
$ ls -lh vertnet_latest_amphibians.zip 
-r--r--r-- 1 lawh 2045336417 296M Jan  6 10:10 vertnet_latest_amphibians.zip

使用 zipunzip 命令来检查归档文件:

$ zip -T vertnet_latest_amphibians.zip
test of vertnet_latest_amphibians.zip OK

$ unzip -t vertnet_latest_amphibians.zip
Archive:  vertnet_latest_amphibians.zip
    testing: VertNet_Amphibia_eml.xml   OK
    testing: __MACOSX/                OK
    testing: __MACOSX/._VertNet_Amphibia_eml.xml   OK
    testing: vertnet_latest_amphibians.csv   OK
    testing: __MACOSX/._vertnet_latest_amphibians.csv   OK
No errors detected in compressed data of vertnet_latest_amphibians.zip

正如@sam-mussmann所发现的那样,7z报告了一个CRC错误:

$ 7z t vertnet_latest_amphibians.zip 

7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=utf8,Utf16=on,HugeFiles=on,64 bits,4 CPUs x64)

Scanning the drive for archives:
1 file, 309726398 bytes (296 MiB)

Testing archive: vertnet_latest_amphibians.zip
--
Path = vertnet_latest_amphibians.zip
Type = zip
Physical Size = 309726398

ERROR: CRC Failed : vertnet_latest_amphibians.csv

Sub items Errors: 1

Archives with Errors: 1

Sub items Errors: 1

我的zipunzip都比较老旧;7z则是相当新的:

$ zip -v | head -2
Copyright (c) 1990-2008 Info-ZIP - Type 'zip "-L"' for software license.
This is Zip 3.0 (July 5th 2008), by Info-ZIP.

$ unzip -v | head -1
UnZip 6.00 of 20 April 2009, by Debian. Original by Info-ZIP.

$ 7z --help |head -3

7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=utf8,Utf16=on,HugeFiles=on,64 bits,4 CPUs x64)

提取

使用 unzip 命令:

$ time unzip vertnet_latest_amphibians.zip vertnet_latest_amphibians.csv
Archive:  vertnet_latest_amphibians.zip
  inflating: vertnet_latest_amphibians.csv  

real    0m17.201s
user    0m14.281s
sys 0m2.460s

为了简洁起见,使用Python 2.7.13版本的zipfile命令行接口进行提取:

$ time ~/local/python-2.7.13/bin/python2 -m zipfile -e vertnet_latest_amphibians.zip .

real    0m19.491s
user    0m12.996s
sys 0m5.897s

正如您发现的那样,Python 3.6.0(也包括3.4.5和3.5.2)报告了错误的CRC。
假设1:存档文件包含错误的CRC,而zip、unzip和Python 2.7.13未能检测到;7z和Python 3.4-3.6都做得很好。
假设2:存档文件没问题;7z和Python 3.4-3.6都出现了错误。
考虑到这些工具的相对年龄,我猜H1是正确的。
解决方法:
如果您不使用Windows并且信任存档中的内容,则可能更直接地使用常规shell命令。例如:
wget <the-long-url> -O /tmp/vertnet_latest_amphibians.zip
unzip /tmp/vertnet_latest_amphibians.zip vertnet_latest_amphibians.csv
rm -rf /tmp/vertnet_latest_amphibians.zip

或者你可以在Python中执行unzip

import os
os.system('unzip vertnet_latest_amphibians.zip vertnet_latest_amphibians.csv')

附带说明

捕获ImportError比检查Python解释器的版本略微更加简洁:

try:
    from urllib.request import urlretrieve
except ImportError:
    from urllib import urlretrieve

6
CRC值是正确的。在zip文件中记录的'vertnet_latest_amphibians.csv'文件的CRC值为0x87203305。解压后,文件的CRC确实是这个值。
但是,给定的未压缩大小是不正确的。zip文件记录了309,723,024字节的压缩大小和292,198,614字节的未压缩大小(比原来还小!)。实际上,未压缩文件大小为4,587,165,910字节(4.3 GiB)。这比32位计数器破裂的4 GiB阈值还要大。
你可以像这样修复它(在Python 3.5.2中至少可以工作):
archive = zipfile.ZipFile("vertnet_latest_amphibians.zip")
archive.getinfo("vertnet_latest_amphibians.csv").file_size += 2**32
archive.testzip() # now passes
archive.extract("vertnet_latest_amphibians.csv") # now works

1

像@Kundor所说,将file_size设置为最大值(2**32-1)可以工作,但对于任何大于4 GiB(4 GiB减1字节)的文件都会失败,因此请将其设置为ZIP64的最大大小(16 EiB减1字节)

测试结果(压缩后927MB,需要解压11GB的文件)

链接: https://de.iplantcollaborative.org/anon-files//iplant/home/shared/commons_repo/curated/Vertnet_Aves_Sep2016/VertNet_Aves_Sept2016.zip

文件名:vertnet_latest_birds.csv

import zipfile
import urllib
import sys

url = "https://de.iplantcollaborative.org/anon-files//iplant/home/shared/commons_repo/curated/Vertnet_Amphibia_Sep2016/VertNet_Amphibia_Sept2016.zip"
zip_path = "vertnet_latest_amphibians.zip"
file_to_extract = "vertnet_latest_amphibians.csv"

if sys.version_info >= (3, 0, 0):
    urllib.request.urlretrieve(url, zip_path)
else:
    urllib.urlretrieve(url, zip_path)

archive = zipfile.ZipFile(zip_path)
if archive.testzip():
    # reset uncompressed size header values to maximum
    archive.getinfo(file_to_extract).file_size += (2 ** 64) - 1
    
open_archive_file = archive.open(file_to_extract, 'r')
# or archive.extract(file_to_extract)

1
在我的情况下,问题是ZipInfo.file_size(Python 2.7)与提取后文件的实际大小不匹配(如@nick-matteo发现)。我发现导致文件大小不匹配的原因是将Unicode字符串传递给zipfile.writestr()函数。在我的情况下,解决方案是在传递给writestr()函数之前将Unicode编码为utf8:
zf = zipfile.ZipFile(...)
if isinstance(file_contents, unicode):
    file_contents = file_contents.encode("utf8")
zf.writestr("filename.txt", file_contents)
...

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