web-dev-qa-db-fra.com

Téléchargement et décompression d'un fichier .zip sans écrire sur le disque

J'ai réussi à faire fonctionner mon premier script python) qui télécharge une liste de fichiers .Zip à partir d'une URL, puis procède à l'extraction des fichiers Zip et les écrit sur le disque.

Je suis maintenant perdu pour passer à l'étape suivante.

Mon objectif principal est de télécharger et d'extraire le fichier Zip et de passer le contenu (données CSV) via un flux TCP. Je préférerais ne pas réellement écrire l'un des fichiers Zip ou extraits sur le disque si Je pourrais m'en tirer.

Voici mon script actuel qui fonctionne mais doit malheureusement écrire les fichiers sur le disque.

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

Ma suggestion serait d'utiliser un objet StringIO . Ils émulent des fichiers, mais résident en mémoire. Vous pouvez donc faire quelque chose comme ceci:

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

Ou plus simplement (excuses à Vishal):

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

Dans Python 3 utilisez BytesIO au lieu de StringIO.

51
senderle

Ci-dessous un extrait de code que j'ai utilisé pour récupérer le fichier csv zippé, veuillez jeter un œil:

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

Ici file est une chaîne. Pour obtenir la chaîne réelle que vous souhaitez transmettre, vous pouvez utiliser zipfile.namelist(). Par exemple,

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

Je voudrais offrir une version mise à jour Python 3 de l'excellente réponse de Vishal, qui utilisait Python 2, avec une explication des adaptations/changements, qui ont peut-être déjà été mentionnés.

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)

Les changements nécessaires:

  • Il n'y a pas de StringIO dans Python 3. Au lieu de cela, j'utilise io, et à partir de là j'importe BytesIO, car nous allons gérer un bytestream - Docs , également ce fil .
  • urlopen:
    • "La fonction héritée urllib.urlopen de Python 2.6 et versions antérieures a été arrêtée; urllib.request.urlopen () correspond à l'ancienne urllib2.urlopen.", Docs .
  • import urllib.request:

Remarque:

  • Dans Python 3, les lignes de sortie imprimées ressembleront à ceci: b'some text'. Ceci est attendu, car ce ne sont pas des chaînes - rappelez-vous, nous lisons un flux-bytest. Have un coup d'oeil à excellente réponse de Dan04 .

Quelques modifications mineures que j'ai apportées:

  • J'utilise with ... as Au lieu de zipfile = ... Selon la documentation .
  • Le script utilise désormais namelist() pour parcourir tous les fichiers du Zip et imprimer leur contenu.
  • J'ai déplacé la création de l'objet ZipFile dans l'instruction with, mais je ne sais pas si c'est mieux.
  • J'ai ajouté (et commenté) une option pour écrire le bytestream dans un fichier (par fichier dans le Zip), en réponse au commentaire de NumenorForLife; il ajoute "unzipped_and_read_" au début du nom de fichier et une extension ".file" (je préfère ne pas utiliser ".txt" pour les fichiers avec des bytestrings). L'indentation du code devra, bien entendu, être ajustée si vous souhaitez l'utiliser.
    • Il faut être prudent ici - parce que nous avons une chaîne d'octets, nous utilisons le mode binaire, donc "wb"; J'ai le sentiment que l'écriture binaire ouvre une boîte de vers de toute façon ...
  • J'utilise un exemple de fichier, le archive de texte UN/LOCODE :

Ce que je n'ai pas fait:

  • NumenorForLife a demandé comment enregistrer le Zip sur le disque. Je ne sais pas ce qu'il voulait dire par là - télécharger le fichier Zip? C'est une tâche différente; voir excellente réponse d'Oleh Prypin .

Voici un moyen:

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

écrire dans un fichier temporaire qui réside dans la RAM

il s'avère que le module tempfile ( http://docs.python.org/library/tempfile.html ) a juste ce qu'il faut:

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

Cette fonction fonctionne exactement comme TemporaryFile (), sauf que les données sont mises en file d'attente jusqu'à ce que la taille du fichier dépasse max_size, ou jusqu'à ce que la méthode fileno () du fichier soit appelée, moment auquel le contenu est écrit sur le disque et l'opération se déroule comme avec TemporaryFile ().

Le fichier résultant a une méthode supplémentaire, rollover (), qui fait que le fichier est transféré vers un fichier sur disque quelle que soit sa taille.

L'objet renvoyé est un objet de type fichier dont l'attribut _file est soit un objet StringIO, soit un véritable objet fichier, selon que rollover () a été appelé. Cet objet de type fichier peut être utilisé dans une instruction with, tout comme un fichier normal.

Nouveau dans la version 2.6.

ou si vous êtes paresseux et que vous avez un _ monté sur tmpfs /tmp sous Linux, vous pouvez simplement y créer un fichier, mais vous devez le supprimer vous-même et gérer la dénomination

15
ninjagecko

Je voudrais ajouter ma réponse Python3 pour être complète:

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

Ajout aux autres réponses en utilisant requêtes:

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

Utilisez aide (f) pour obtenir plus de détails sur les fonctions, par ex. extractall () qui extrait le contenu du fichier Zip qui peut ensuite être utilisé avec avec open.

11
Akson

Il n'était pas évident dans la réponse de Vishal quel devait être le nom du fichier dans les cas où il n'y a pas de fichier sur le disque. J'ai modifié sa réponse pour travailler sans modification pour la plupart des besoins.

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

L'exemple de Vishal, si grand soit-il, est déroutant en ce qui concerne le nom du fichier, et je ne vois pas l'intérêt de redéfinir le "fichier zip".

Voici mon exemple qui télécharge un Zip qui contient certains fichiers, dont l'un est un fichier csv que j'ai ensuite lu dans un 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])

(Remarque, j'utilise Python 2.7.13)

C'est la solution exacte qui a fonctionné pour moi. Je l'ai juste légèrement modifié pour la version Python 3 en supprimant StringIO et en ajoutant IO bibliothèque

Version 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])
1
Martien Lubberink