Загрузка и разархивирование .zip файла без записи на диск - PullRequest
66 голосов
/ 19 апреля 2011

Мне удалось заставить работать мой первый скрипт на python, который загружает список файлов .ZIP с URL-адреса, а затем продолжает извлекать файлы ZIP и записывает их на диск.

Сейчас я нахожусь напотеря для достижения следующего шага.

Моя основная задача - загрузить и извлечь zip-файл и передать его содержимое (данные CSV) через поток TCP.Я бы предпочел не записывать какие-либо zip-файлы или извлеченные файлы на диск, если бы мне это удавалось.

Вот мой текущий скрипт, который работает, но, к сожалению, должен записывать файлы на диск.

import urllib, urllister
import zipfile
import urllib2
import os
import time
import pickle

# check for extraction directories existence
if not os.path.isdir('downloaded'):
    os.makedirs('downloaded')

if not os.path.isdir('extracted'):
    os.makedirs('extracted')

# open logfile for downloaded data and save to local variable
if os.path.isfile('downloaded.pickle'):
    downloadedLog = pickle.load(open('downloaded.pickle'))
else:
    downloadedLog = {'key':'value'}

# remove entries older than 5 days (to maintain speed)

# path of zip files
zipFileURL = "http://www.thewebserver.com/that/contains/a/directory/of/zip/files"

# retrieve list of URLs from the webservers
usock = urllib.urlopen(zipFileURL)
parser = urllister.URLLister()
parser.feed(usock.read())
usock.close()
parser.close()

# only parse urls
for url in parser.urls: 
    if "PUBLIC_P5MIN" in url:

        # download the file
        downloadURL = zipFileURL + url
        outputFilename = "downloaded/" + url

        # check if file already exists on disk
        if url in downloadedLog or os.path.isfile(outputFilename):
            print "Skipping " + downloadURL
            continue

        print "Downloading ",downloadURL
        response = urllib2.urlopen(downloadURL)
        zippedData = response.read()

        # save data to disk
        print "Saving to ",outputFilename
        output = open(outputFilename,'wb')
        output.write(zippedData)
        output.close()

        # extract the data
        zfobj = zipfile.ZipFile(outputFilename)
        for name in zfobj.namelist():
            uncompressed = zfobj.read(name)

            # save uncompressed data to disk
            outputFilename = "extracted/" + name
            print "Saving extracted file to ",outputFilename
            output = open(outputFilename,'wb')
            output.write(uncompressed)
            output.close()

            # send data via tcp stream

            # file successfully downloaded and extracted store into local log and filesystem log
            downloadedLog[url] = time.time();
            pickle.dump(downloadedLog, open('downloaded.pickle', "wb" ))

Ответы [ 8 ]

63 голосов
/ 19 апреля 2011

Ниже приведен фрагмент кода, который я использовал для извлечения ZIP-файла CSV, пожалуйста, посмотрите:

Python 2 :

from StringIO import StringIO
from zipfile import ZipFile
from urllib import urlopen

resp = urlopen("http://www.test.com/file.zip")
zipfile = ZipFile(StringIO(resp.read()))
for line in zipfile.open(file).readlines():
    print line

Python 3:

from io import BytesIO
from zipfile import ZipFile
from urllib.request import urlopen
# or: requests.get(url).content

resp = urlopen("http://www.test.com/file.zip")
zipfile = ZipFile(BytesIO(resp.read()))
for line in zipfile.open(file).readlines():
    print(line.decode('utf-8'))

Здесь file - строка.Чтобы получить фактическую строку, которую вы хотите передать, вы можете использовать zipfile.namelist().Например,

resp = urlopen('http://mlg.ucd.ie/files/datasets/bbc.zip')
zipfile = ZipFile(BytesIO(resp.read()))
zipfile.namelist()
# ['bbc.classes', 'bbc.docs', 'bbc.mtx', 'bbc.terms']
50 голосов
/ 19 апреля 2011

Я бы предложил использовать объект StringIO. Они эмулируют файлы, но находятся в памяти. Так что вы можете сделать что-то вроде этого:

# get_zip_data() gets a zip archive containing 'foo.txt', reading 'hey, foo'

from StringIO import StringIO
zipdata = StringIO()
zipdata.write(get_zip_data())
myzipfile = zipfile.ZipFile(zipdata)
foofile = myzipfile.open('foo.txt')
print foofile.read()

# output: "hey, foo"

Или проще (извинения Вишалу):

myzipfile = zipfile.ZipFile(StringIO(get_zip_data()))
for name in myzipfile.namelist():
    [ ... ]

В Python 3 используйте BytesIO вместо StringIO.

18 голосов
/ 08 февраля 2017

Я хотел бы предложить обновленную версию превосходного ответа Вишала на Python 3, в которой использовался Python 2, вместе с некоторыми объяснениями адаптаций / изменений, которые, возможно, уже упоминались.

from io import BytesIO
from zipfile import ZipFile
import urllib.request

    url = urllib.request.urlopen("http://www.unece.org/fileadmin/DAM/cefact/locode/loc162txt.zip")

    with ZipFile(BytesIO(url.read())) as my_zip_file:
        for contained_file in my_zip_file.namelist():
            # with open(("unzipped_and_read_" + contained_file + ".file"), "wb") as output:
            for line in my_zip_file.open(contained_file).readlines():
                print(line)
                # output.write(line)

Необходимые изменения:

  • В Python 3 нет StringIO. Вместо этого я использую io, и из него я импортирую BytesIO, потому что мы будем обрабатывать байтовый поток - Документы , также эта тема .
  • urlopen:
    • "Устаревшая функция urllib.urlopen из Python 2.6 и более ранних версий больше не поддерживается; urllib.request.urlopen () соответствует старому urllib2.urlopen.", Docs .
  • import urllib.request:

Примечание:

  • В Python 3 напечатанные выходные строки будут выглядеть так: b'some text'. Это ожидается, поскольку они не являются строками - помните, мы читаем поток байтов. Взгляните на отличный ответ Dan04 .

Несколько небольших изменений, которые я сделал:

  • Я использую with ... as вместо zipfile = ... в соответствии с Документами .
  • Сценарий теперь использует namelist() для циклического перебора всех файлов в zip-файле и печати их содержимого.
  • Я переместил создание объекта ZipFile в оператор with, хотя я не уверен, что это лучше.
  • Я добавил (и закомментировал) опцию записи байтового потока в файл (для каждого файла в zip) в ответ на комментарий NumenorForLife; он добавляет "unzipped_and_read_" к началу имени файла и расширению ".file" (я предпочитаю не использовать ".txt" для файлов с байтовыми строками). Отступ кода, конечно, нужно будет скорректировать, если вы хотите его использовать.
    • Здесь нужно быть осторожным - поскольку у нас есть строка байтов, мы используем двоичный режим, поэтому "wb"; У меня такое ощущение, что написание бинарного кода в любом случае открывает червячную банку
  • Я использую файл примера, текстовый архив UN / LOCODE :

Что я не делал:

Вот способ:

import urllib.request
import shutil

with urllib.request.urlopen("http://www.unece.org/fileadmin/DAM/cefact/locode/2015-2_UNLOCODE_SecretariatNotes.pdf") as response, open("downloaded_file.pdf", 'w') as out_file:
    shutil.copyfileobj(response, out_file)
15 голосов
/ 19 апреля 2011

запись во временный файл, который находится в оперативной памяти

, получается, что модуль tempfile (http://docs.python.org/library/tempfile.html) имеет только одну вещь:

tempfile.SpooledTeilitaryFile ([max_size = 0 [, mode = 'w + b' [, bufsize = -1 [, суффикс = '' [, prefix = 'tmp' [, dir = None]]]]]])

Эта функция работает точно так же, как и TemporaryFile (), за исключением того, что данные помещаются в буфер до тех пор, пока размер файла не превысит max_size, или пока не будет вызван метод fileno () файла, после чего содержимое записывается на диск, и операция продолжается какwith TemporaryFile ().

Полученный файл имеет один дополнительный метод, rollover (), который заставляет файл переноситься в файл на диске независимо от его размера.

Возвращаемый объектявляется файловым объектом, атрибут _file которого является либо объектом StringIO, либо истинным объектом файла, в зависимости от того, был ли вызван rollover ().Этот подобный файлу объект может использоваться в операторе with, как обычный файл.

Новое в версии 2.6.

или если вы ленивы и у вас есть tmpfs-монтированный /tmp в Linux, вы можете просто сделать файл там, но вы должны удалить его самостоятельно и иметь дело с именами

14 голосов
/ 18 января 2016

Я хотел бы добавить свой ответ Python3 для полноты:

from io import BytesIO
from zipfile import ZipFile
import requests

def get_zip(file_url):
    url = requests.get(file_url)
    zipfile = ZipFile(BytesIO(url.content))
    zip_names = zipfile.namelist()
    if len(zip_names) == 1:
        file_name = zip_names.pop()
        extracted_file = zipfile.open(file_name)
        return extracted_file
    return [zipfile.open(file_name) for file_name in zip_names]
10 голосов
/ 07 марта 2018

Добавление к другим ответам с помощью запросов :

 # download from web

 import requests
 url = 'http://mlg.ucd.ie/files/datasets/bbc.zip'
 content = requests.get(url)

 # unzip the content
 from io import BytesIO
 from zipfile import ZipFile
 f = ZipFile(BytesIO(content.content))
 print(f.namelist())

 # outputs ['bbc.classes', 'bbc.docs', 'bbc.mtx', 'bbc.terms']

Использование help (f) для получения дополнительных сведений о функциях, например, extractall () , который извлекает содержимое в zip-файл, который позже можно использовать с с открытым .

2 голосов
/ 07 июня 2015

В ответе Вишала не было очевидно, каким должно быть имя файла в тех случаях, когда на диске нет файла.Я изменил его ответ, чтобы работать без изменений для большинства нужд.

from StringIO import StringIO
from zipfile import ZipFile
from urllib import urlopen

def unzip_string(zipped_string):
    unzipped_string = ''
    zipfile = ZipFile(StringIO(zipped_string))
    for name in zipfile.namelist():
        unzipped_string += zipfile.open(name).read()
    return unzipped_string
1 голос
/ 11 октября 2017

Пример Вишала, каким бы великим он ни был, сбивает с толку, когда дело доходит до имени файла, и я не вижу смысла в переопределении 'zipfile'.

Вот мой пример, который загружает zip-файл, содержащий несколько файлов, одним из которых является файл csv, который я впоследствии читаю в панде DataFrame:

from StringIO import StringIO
from zipfile import ZipFile
from urllib import urlopen
import pandas

url = urlopen("https://www.federalreserve.gov/apps/mdrm/pdf/MDRM.zip")
zf = ZipFile(StringIO(url.read()))
for item in zf.namelist():
    print("File in zip: "+  item)
# find the first matching csv file in the zip:
match = [s for s in zf.namelist() if ".csv" in s][0]
# the first line of the file contains a string - that line shall de ignored, hence skiprows
df = pandas.read_csv(zf.open(match), low_memory=False, skiprows=[0])

(Обратите внимание, я использую Python2.7.13)

Это точное решение, которое сработало для меня.Я немного подправил его для версии Python 3, удалив StringIO и добавив библиотеку ввода-вывода

Версия Python 3

from io import BytesIO
from zipfile import ZipFile
import pandas
import requests

url = "https://www.nseindia.com/content/indices/mcwb_jun19.zip"
content = requests.get(url)
zf = ZipFile(BytesIO(content.content))

for item in zf.namelist():
    print("File in zip: "+  item)

# find the first matching csv file in the zip:
match = [s for s in zf.namelist() if ".csv" in s][0]
# the first line of the file contains a string - that line shall de     ignored, hence skiprows
df = pandas.read_csv(zf.open(match), low_memory=False, skiprows=[0])
...