Распаковка структуры каталогов с помощью python

У меня есть zip-файл, который содержит следующую структуру каталогов:

dir1\dir2\dir3a
dir1\dir2\dir3b

Я пытаюсь разархивировать его и сохранить структуру каталогов, но получаю сообщение об ошибке:

IOError: [Errno 2] No such file or directory: 'C:\\\projects\\\testFolder\\\subdir\\\unzip.exe'

где testFolder — это dir1 выше, а subdir — это dir2.

Есть ли быстрый способ разархивировать файл и сохранить структуру каталогов?


person Flyer1    schedule 12.03.2009    source источник
comment
Можете ли вы показать свой код?   -  person Joe Koberg    schedule 12.03.2009
comment
Также см.: вы распаковываете очень большие файлы в python"> stackoverflow.com/questions/339053/ (ответ Martijn)   -  person Christophe Roussy    schedule 29.02.2016


Ответы (9)


Методы Extract и ExtractAll отлично подходят, если вы используете Python 2.6. Пока я должен использовать Python 2.5, поэтому мне просто нужно создать каталоги, если они не существуют. Вы можете получить список каталогов с помощью метода namelist(). Каталоги всегда будут заканчиваться косой чертой (даже в Windows), например,

import os, zipfile

z = zipfile.ZipFile('myfile.zip')
for f in z.namelist():
    if f.endswith('/'):
        os.makedirs(f)

Вы, вероятно, не хотите делать это точно таким образом (т. е. вы, вероятно, захотите извлечь содержимое zip-файла при переборе списка имен), но вы поняли идею.

person Jeff    schedule 12.03.2009
comment
Возможно, вы захотите обернуть os.makedirs(f) в try: кроме (OSError, WindowsError): блокировать, если папки уже существуют. - person Christian Witts; 13.03.2009
comment
а как разархивировать файлы в директорию f(os.makedirs(f)) ?? - person BinqiangSun; 06.06.2016

Не доверяйте методам extract() или Extractall().

Эти методы слепо извлекают файлы по путям, указанным в их именах. Но имена ZIP-файлов могут быть любыми, включая опасные строки вроде «x/../../../etc/passwd». Извлеките такие файлы, и вы могли бы просто скомпрометировать весь свой сервер.

Возможно, это следует считать дырой в безопасности модуля Python zipfile, о которой следует сообщить, но любое количество zip-деархиваторов демонстрировало точно такое же поведение в прошлом. Чтобы безопасно разархивировать ZIP-файл со структурой папок, вам необходимо тщательно проверить каждый путь к файлу.

person bobince    schedule 13.03.2009
comment
Почему бы не считать это ошибкой? Тем более, что это новый код, он был добавлен для Python 2.6. Грузить с такой дырой просто глупо. - person u0b34a0f6ae; 23.01.2010
comment
Здесь нет причин беспокоиться о безопасности, вам нужно быть Халком Хоганом, чтобы запускать свое приложение с привилегиями root. - person skrat; 07.09.2011
comment
etc/passwd — это просто пример; существует множество способов, при которых файл, сброшенный в произвольное место файловой системы, может представлять угрозу безопасности без запуска от имени пользователя root. Классическое удаление файлов, таких как something.php, .htaccess и т. д., в исполняемые папки или перезапись данных времени выполнения. - person bobince; 07.09.2011
comment
Похоже, он изменился с версии 2.7.4: docs.python.org/ 2/library/zipfile#zipfile.ZipFile.extractall Модуль zipfile пытается предотвратить это. - person youri; 01.08.2013

Я попробовал это и могу воспроизвести. Метод Extractall, предложенный другими ответами, не решает проблему. Мне это кажется ошибкой в ​​​​модуле zipfile (возможно, только для Windows?), Если только я не понимаю, как устроены zip-файлы.

testa\
testa\testb\
testa\testb\test.log
> test.zip

>>> from zipfile import ZipFile
>>> zipTest = ZipFile("C:\\...\\test.zip")
>>> zipTest.extractall("C:\\...\\")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "...\zipfile.py", line 940, in extractall
  File "...\zipfile.py", line 928, in extract
  File "...\zipfile.py", line 965, in _extract_member
IOError: [Errno 2] No such file or directory: 'C:\\...\\testa\\testb\\test.log'

Если я сделаю printdir(), я получу это (первый столбец):

>>> zipTest.printdir()
File Name
testa/testb/
testa/testb/test.log

Если я попытаюсь извлечь только первую запись, например:

>>> zipTest.extract("testa/testb/")
'C:\\...\\testa\\testb'

На диске это приводит к созданию папки testa с файлом testb внутри. По-видимому, это является причиной того, что последующая попытка извлечения test.log не удалась; testa\testb — это файл, а не папка.

Изменить № 1: если вы извлекаете только файл, он работает:

>>> zipTest.extract("testa/testb/test.log")
'C:\\...\\testa\\testb\\test.log'

Редактировать № 2: код Джеффа - это путь; перебрать namelist; если это каталог, создайте каталог. В противном случае извлеките файл.

person DNS    schedule 12.03.2009

Я знаю, что может быть немного поздно говорить об этом, но Джефф прав. Это так же просто, как:

import os
from zipfile import ZipFile as zip

def extractAll(zipName):
    z = zip(zipName)
    for f in z.namelist():
        if f.endswith('/'):
            os.makedirs(f)
        else:
            z.extract(f)

if __name__ == '__main__':
    zipList = ['one.zip', 'two.zip', 'three.zip']
    for zip in zipList:
        extractAll(zipName)
person ki113d    schedule 25.06.2011
comment
кажется, что zipfile.ZipFile(zip_name).extractall() делает именно это. - person Erik Kaplun; 07.03.2012


Похоже, вы пытаетесь запустить unzip, чтобы распаковать zip.

Было бы лучше использовать модуль python zipfile и, следовательно, делать извлечение в python .

import zipfile

def extract(zipfilepath, extractiondir):
    zip = zipfile.ZipFile(zipfilepath)
    zip.extractall(path=extractiondir)
person Douglas Leeder    schedule 12.03.2009
comment
Обратите внимание, что pwd — это пароль к файлу; аргументом пути для извлечения является «путь». - person DNS; 12.03.2009
comment
Извините, мой плохой - вы можете сказать, что я написал код, не запуская его. :-) - person Douglas Leeder; 13.03.2009
comment
Также должно быть zip = zipfile.ZipFile(zipfilepath) - person Liam; 07.07.2010

Отфильтровать список имен, чтобы исключить папки

Все, что вам нужно сделать, это отфильтровать записи namelist(), заканчивающиеся на /, и проблема будет решена:

  z.extractall(dest, filter(lambda f: not f.endswith('/'), z.namelist()))

нРадость!

person nickl-    schedule 26.08.2012

Если, как и я, вам нужно извлечь полный zip-архив с более старой версией Python (в моем случае 2.4), вот что я придумал (на основе ответа Джеффа):

import zipfile
import os

def unzip(source_file_path, destination_dir):
    destination_dir += '/'
    z = zipfile.ZipFile(source_file_path, 'r')
    for file in z.namelist():
        outfile_path = destination_dir + file
        if file.endswith('/'):
            os.makedirs(outfile_path)
        else:
            outfile = open(outfile_path, 'wb')
            outfile.write(z.read(file))
            outfile.close()
    z.close()
person Apteryx    schedule 18.03.2015

Обратите внимание, что zip-файлы могут иметь записи как для каталогов, так и для файлов. При создании архивов с помощью команды zip передайте параметр -D, чтобы отключить явное добавление записей каталога в архив. Когда метод Python 2.6 ZipFile.extractall работает с записью каталога, создается впечатление, что вместо нее создается файл. Поскольку записи в архиве не всегда упорядочены, это часто приводит к сбою ZipFile.extractall, поскольку он пытается создать файл в подкаталоге файла. Если у вас есть архив, который вы хотите использовать с модулем Python, просто распакуйте его и повторно заархивируйте с параметром -D. Вот небольшой фрагмент, который я использовал некоторое время, чтобы сделать именно это:

P=`pwd` && 
Z=`mktemp -d -t zip` && 
pushd $Z && 
unzip $P/<busted>.zip && 
zip -r -D $P/<new>.zip . && 
popd && 
rm -rf $Z

Замените <busted>.zip и <new>.zip реальными именами файлов относительно текущего каталога. Затем просто скопируйте все это и вставьте в командную оболочку, и он создаст новый архив, готовый к работе с Python 2.6. Существует есть команда zip, которая удалит эти записи каталога без распаковки, но IIRC ведет себя странно в разных средах оболочки или конфигурациях zip.

person xdissent    schedule 27.02.2010