2017-02-02 12 views
2

Я пытаюсь получить большой файл из Интернета и передать его прямо в zip-файл, предоставляемый модулем zipfile, что-то вроде:Передача файла-подобного объекта методу write() другого файлового объекта

from urllib.request import urlopen 
from zipfile import ZipFile 

zip_file = ZipFile("https://stackoverflow.com/a/certain/local/zip/file.zip","a") 
entry = zip_file.open("an.entry","w") 
entry.write(urlopen("http://a.certain.file/on?the=web")) 

Видимо, это не работает, потому что .write принимает bytes аргумент, а не считывающее устройство ввода/вывода. Однако, поскольку файл довольно большой, я не хочу загружать весь файл в ОЗУ, прежде чем сжимать его.

Простое решение заключается в использовании Баша (никогда не пробовал, может быть неправильно):

curl -s "http://a.certain.file/on?the=web" | zip -q /a/certain/local/zip/file.zip 

, но это будет не очень элегантно, и удобно, что нужно поставить одну строку Баша в скрипт Python.

Другим решением является использование urllib.request.urlretrieve, чтобы загрузить файл, а затем передать путь до zipfile.ZipFile.open, но в этом случае мне все равно придется дождаться завершения загрузки и, кроме того, также потреблять намного больше ресурсов ввода-вывода на диске ,

Есть ли способ в Python передать поток загрузки в zipfile-писатель, например, в bash-конвейер выше?

+0

У вас есть некоторые альтернативы, такие как 'gzopen' для создания файла .gz. –

+0

@ Jean-FrançoisFabre Мне нужен архив, поэтому в этом случае мне понадобится «tar», если я хочу использовать 'gzip'. – busukxuan

+0

вы можете использовать '.read (size)' для получения данных в chunk – furas

ответ

5

Вы можете использовать shutil.copyfileobj() эффективно копировать данные между файловыми объектами:

from shutil import copyfileobj 

with ZipFile("https://stackoverflow.com/a/certain/local/zip/file.zip", "w") as zip_file: 
    with zip_file.open("an.entry", "w") as entry: 
     with urlopen("http://a.certain.file/on?the=web") as response: 
      shutil.copyfileobj(response, entry) 

Это позовет .read() с заданной chunksize на объекте исходного файла, а затем передать этот кусок к методу .write() на целевой файл объект.

Если вы используете Python 3.5 или старше (где вы еще не можем непосредственно написать на ZipFile члена), единственным вариантом является поток во временный файл первого:

from shutil import copyfileobj 
from tempfile import NamedTemporaryFile 

with ZipFile("https://stackoverflow.com/a/certain/local/zip/file.zip", "w") as zip_file: 
    with NamedTemporaryFile() as cache: 
     with urlopen("http://a.certain.file/on?the=web") as response: 
      shutil.copyfileobj(response, cache) 
      cache.flush() 
      zipfile.write('an.entry', cache.name) 

Использование NamedTemporaryFile() как это работает только на POSIX-системах, в Windows вы не можете снова открыть одно и то же имя файла, поэтому вам нужно будет использовать tempfile.mkstemp() generated name, откройте файл оттуда и используйте try...finally для очистки после этого.

+0

проблема в том, что [zip_file.open()] (https://docs.python.org/3.5/library/zipfile.html#zipfile.ZipFile. open) можно использовать только для чтения - '' r "'. И [ZipFile.write()] (https://docs.python.org/3.5/library/zipfile.html#zipfile.ZipFile.write) ожидает только имя файла :( – furas

+0

@furas: Я смотрел на [Python 3.6 версия] (https://docs.python.org/3/library/zipfile.html#zipfile.ZipFile.open). –

+0

@furas: для Python <3.6 вам нужно добавить 'NamedTempFile()' в между тем, я боюсь –