it-swarm.com.de

Herunterladen und Entpacken einer ZIP-Datei, ohne auf die Festplatte zu schreiben

Ich habe es geschafft, mein erstes python Skript zum Funktionieren zu bringen, das eine Liste von .Zip-Dateien von einer URL herunterlädt und dann fortfährt, die Zip-Dateien zu extrahieren und sie auf die Festplatte zu schreiben.

Ich bin jetzt ratlos, den nächsten Schritt zu erreichen.

Mein primäres Ziel ist das Herunterladen und Extrahieren der Zip-Datei und die Weitergabe der Inhalte (CSV-Daten) über einen TCP) - Stream. Ich würde es vorziehen, keine der Zip- oder extrahierten Dateien auf die Festplatte zu schreiben, wenn Ich könnte damit durchkommen.

Hier ist mein aktuelles Skript, das funktioniert, aber leider die Dateien auf die Festplatte schreiben muss.

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

Mein Vorschlag wäre, ein StringIO -Objekt zu verwenden. Sie emulieren Dateien, befinden sich jedoch im Speicher. Sie könnten also so etwas tun:

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

Oder einfacher (entschuldigt sich bei Vishal):

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

Verwenden Sie in Python 3 BytesIO anstelle von StringIO.

51
senderle

Unten ist ein Code-Snippet, mit dem ich eine gezippte CSV-Datei abgerufen habe:

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:

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

Hier ist file eine Zeichenkette. Um die tatsächliche Zeichenfolge abzurufen, die Sie übergeben möchten, können Sie zipfile.namelist() verwenden. Zum Beispiel,

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']
66
Vishal

Ich möchte eine aktualisierte Python 3-Version von Vishals exzellenter Antwort anbieten, die Python 2 verwendet, zusammen mit einer Erklärung der Anpassungen/Änderungen, was vielleicht schon erwähnt wurde.

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)

Notwendige Änderungen:

  • Es gibt kein StringIO in Python 3. Stattdessen verwende ich io, und daraus importiere ich BytesIO, weil wir a behandeln werden bytestream - Docs , auch dieser Thread .
  • urlopen:
    • "Die alte urllib.urlopen-Funktion von Python 2.6 und früher wurde eingestellt; urllib.request.urlopen () entspricht der alten urllib2.urlopen.", Dokumente .
  • import urllib.request:

Hinweis:

  • In Python 3 sehen die gedruckten Ausgabezeilen folgendermaßen aus: b'some text' Dies wird erwartet, da es sich nicht um Zeichenfolgen handelt - denken Sie daran, wir lesen einen Bytestream Ein Blick auf Dan04's ausgezeichnete Antwort .

Ein paar kleine Änderungen, die ich vorgenommen habe:

  • Ich verwende with ... as Anstelle von zipfile = ... Gemäß der Dokumentation .
  • Das Skript verwendet jetzt namelist(), um alle Dateien in der Zip-Datei zu durchlaufen und ihren Inhalt zu drucken.
  • Ich habe die Erstellung des Objekts ZipFile in die with-Anweisung verschoben, obwohl ich nicht sicher bin, ob das besser ist.
  • Als Reaktion auf NumenorForLifes Kommentar habe ich eine Option hinzugefügt (und auskommentiert), um den Bytestream in eine Datei zu schreiben (pro Datei im Zip). es fügt "unzipped_and_read_" am Anfang des Dateinamens und eine ".file" - Erweiterung hinzu (ich bevorzuge die Verwendung von ".txt" für Dateien mit Bytestrings). Der Einzug des Codes muss natürlich angepasst werden, wenn Sie ihn verwenden möchten.
    • Hier ist Vorsicht geboten - da wir einen Byte-String haben, verwenden wir den Binärmodus, also "wb"; Ich habe das Gefühl, dass das Schreiben von Binärdateien sowieso eine Dose Würmer öffnet ...
  • Ich verwende eine Beispieldatei, das UN/LOCODE-Textarchiv :

Was ich nicht gemacht habe:

  • NumenorForLife fragte nach dem Speichern der Zip-Datei auf der Festplatte. Ich bin mir nicht sicher, was er damit gemeint hat - die Zip-Datei herunterladen? Das ist eine andere Aufgabe. siehe Oleh Prypins ausgezeichnete Antwort .

Hier ist ein Weg:

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)
19
Zubo

in eine temporäre Datei schreiben, die sich im RAM befindet

es stellt sich heraus, dass das tempfile -Modul ( http://docs.python.org/library/tempfile.html ) genau das Richtige hat:

tempfile.SpooledTemporaryFile ([max_size = 0 [ mode = 'w + b' [ bufsize = -1 [ suffix = '' [ prefix = 'tmp' [ dir = None]]]]])

Diese Funktion funktioniert genauso wie TemporaryFile (), mit der Ausnahme, dass Daten im Speicher gespoolt werden, bis die Dateigröße max_size überschreitet oder bis die fileno () -Methode der Datei aufgerufen wird. Anschließend wird der Inhalt auf die Festplatte geschrieben und der Vorgang wie bei TemporaryFile fortgesetzt ().

Die resultierende Datei verfügt über eine zusätzliche Methode, rollover (), mit der die Datei unabhängig von ihrer Größe in eine Datei auf der Festplatte verschoben wird.

Das zurückgegebene Objekt ist ein dateiähnliches Objekt, dessen _file-Attribut entweder ein StringIO-Objekt oder ein echtes Dateiobjekt ist, je nachdem, ob rollover () aufgerufen wurde. Dieses dateiähnliche Objekt kann wie eine normale Datei in einer with-Anweisung verwendet werden.

Neu in Version 2.6.

oder wenn du faul bist und ein tmpfs-mounted hast /tmp Unter Linux können Sie dort einfach eine Datei erstellen, müssen diese jedoch selbst löschen und die Benennung vornehmen

15
ninjagecko

Ich möchte der Vollständigkeit halber meine Python3-Antwort hinzufügen:

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]
14
lababidi

Hinzufügen zu den anderen Antworten mit Anfragen:

 # 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']

Verwenden Sie help (f), um weitere Funktionsdetails für z. extractall () extrahiert den Inhalt der Zip-Datei, der später mit with open verwendet werden kann.

11
Akson

In Vishals Antwort war nicht ersichtlich, wie der Dateiname lauten sollte, wenn sich keine Datei auf der Festplatte befindet. Ich habe seine Antwort so geändert, dass sie für die meisten Anforderungen unverändert bleibt.

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
2
plowman

Vishals Beispiel, so großartig es auch sein mag, ist verwirrend, wenn es um den Dateinamen geht, und ich sehe keinen Vorteil darin, 'zipfile' neu zu definieren.

In meinem Beispiel wird eine Zip-Datei heruntergeladen, die einige Dateien enthält. Eine davon ist eine CSV-Datei, die ich anschließend in einen pandas 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])

(Beachte, ich benutze Python 2.7.13)

Dies ist die genaue Lösung, die für mich funktioniert hat. Ich habe es nur ein wenig für die Python 3-Version optimiert, indem ich StringIO entfernt und IO library hinzugefügt habe

Python 3-Version

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])
1