ZipFile.testzip() возвращает разные результаты на Python 2 и Python 3

Использование модуля zipfile для распаковки большого файла данных в Python корректно работает на Python 2, но на Python 3.6.0 появляется следующая ошибка:

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

Я проследил это с кодом обработки ошибок, проверяющим значения CRC.

Использование ZipFile.testzip() на Python 2 ничего не возвращает (все файлы в порядке). Запуск его на Python 3 возвращает 'myfile.csv', указывающий на проблему с этим файлом.

Код для воспроизведения как на Python 2, так и на Python 3 (включает загрузку 300 МБ, извините):

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")

Ответы

Ответ 1

Значение CRC в порядке. CRC из 'vertnet_latest_amphibians.csv', записанный в zip, равен 0x87203305. После извлечения это действительно CRC файла.

Однако данный несжатый размер неверен. Почтовый файл записывает сжатый размер 309 723 024 байта и несжатый размер 292,198,614 байта (что меньше!). В действительности, несжатый файл составляет 4 587 165 910 байт (4.3 гигабайта). Это больше, чем порог 4 GiB, где сбой 32-битных счетчиков.

Вы можете это исправить (это работает, по крайней мере, в 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

Ответ 2

Мне не удалось извлечь 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

Проверьте архив с помощью zip и unzip:

$ 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

Мои zip и unzip оба довольно старые; 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 и не доверяете содержимому архива, может быть проще использовать обычные команды оболочки. Что-то вроде:

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

Или вы можете выполнить unzip из Python:

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

Ответ 3

Как @Kundor, установка максимального размера файла (2 ** 32 - 1) будет работать, но сбой для файла размером более 4 ГБ (4 ГБ минус 1 байт), поэтому установите его максимальный размер для ZIP64 (16 EiB минус 1 байт)

Протестировано (927 МБ сжато и 11 ГБ файла_от_экстракта)

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

file: 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)