Bir .zip dosyasını diske yazmadan indirme ve açma


86

Bir URL'den .ZIP dosyalarının bir listesini indiren ve ardından ZIP dosyalarını ayıklayıp diske yazan ilk python betiğimi çalıştırmayı başardım.

Şimdi bir sonraki adıma ulaşmak için bir kayıp yaşıyorum.

Birincil hedefim, zip dosyasını indirip çıkarmak ve içeriği (CSV verileri) bir TCP akışı aracılığıyla iletmektir. Bundan kurtulabilirsem, zip veya çıkarılan dosyaların hiçbirini diske yazmamayı tercih ederim.

İşte çalışan ama maalesef dosyaları diske yazması gereken mevcut betiğim.

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

3
ZIP biçimi akış için tasarlanmamıştır. Altbilgileri kullanır, yani içinde nesnelerin nereye ait olduğunu bulmak için dosyanın sonuna ihtiyacınız vardır , yani bir alt kümesiyle herhangi bir şey yapmadan önce dosyanın tamamına sahip olmanız gerekir.
Charles Duffy

Yanıtlar:


66

Benim önerim bir StringIOnesne kullanmak olacaktır . Dosyaları taklit ederler, ancak bellekte bulunurlar. Böylece şöyle bir şey yapabilirsiniz:

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

import zipfile
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"

Veya daha basitçe (Vishal'dan özür dilerim):

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

Python 3'te StringIO yerine BytesIO kullanın:

import zipfile
from io import BytesIO

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

"StringIO nesnesi, Unicode veya 8-bit dizeleri kabul edebilir" Bu, yazmayı beklediğiniz bayt sayısı 0 mod 8 ile uyumlu değilse, o zaman bir istisna atacağınız veya yanlış veri yazacağınız anlamına gelmiyor mu?
ninjagecko

1
Hiç de değil - neden bir seferde yalnızca 8 bayt yazabiliyorsunuz? Tersine, ne zaman bir seferde 8 bitten daha az yazarsınız?
gönderen

@ninjagecko: Yazılması beklenen bayt sayısı 8'in katı değilse bir sorundan korkuyorsunuz. Bu, StringIO ile ilgili ifadeden türetilemez ve oldukça temelsizdir. StringIO ile ilgili sorun, kullanıcının nesneleri sistem varsayılan kodlamasıyla (tipik olarak ) kodu çözülemeyen nesnelerle karıştırmasıdır . unicodestrascii
John Machin

1
Yukarıdaki kodla ilgili küçük bir yorum: .zip'ten birden fazla dosya okuduğunuzda, verileri tek tek okuduğunuzdan emin olun, çünkü zipfile.open'ı iki kez çağırmak ilkindeki referansı kaldıracaktır.
scippie

15
Python 3 itibariyle kullanmanız gerektiğine dikkat edinfrom io import StringIO
Jorge Leitao

81

Aşağıda sıkıştırılmış csv dosyasını getirmek için kullandığım bir kod parçası var, lütfen bir göz atın:

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

İşte filebir dizedir. Geçmek istediğiniz gerçek dizeyi almak için kullanabilirsiniz zipfile.namelist(). Örneğin,

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

27

Vishal'ın mükemmel cevabının güncellenmiş bir Python 3 versiyonunu sunmak istiyorum, Python 2'yi kullanıyordu ve daha önce bahsedilmiş olabilecek uyarlamalar / değişiklikler hakkında bazı açıklamalarla birlikte.

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)

Gerekli değişiklikler:

Not:

  • Python 3, baskılı çıkış hatları şöyle görünecektir: b'some text'. Dizge olmadıkları için bu beklenen bir durumdur - unutmayın, bir yan test okuyuyoruz. Dan04'ün mükemmel cevabına bir göz atın .

Yaptığım birkaç küçük değişiklik:

  • Dokümanlara göre with ... asyerine kullanıyorum .zipfile = ...
  • Komut dosyası artık .namelist()zip içindeki tüm dosyalar arasında gezinmek ve içeriklerini yazdırmak için kullanıyor.
  • Daha iyi olup olmadığından emin olmasam da ZipFilenesnenin yaratılışını withifadeye taşıdım .
  • NumenorForLife'ın yorumuna yanıt olarak, bytestream'i dosyaya (zip dosyasındaki dosya başına) yazma seçeneğini ekledim (ve yorum yaptım); "unzipped_and_read_"dosya adının başına ve bir ".file"uzantı ekler ( ".txt"yan testlere sahip dosyalar için kullanmayı tercih etmem). Elbette, kullanmak istiyorsanız, kodun girintisinin ayarlanması gerekecektir.
    • Burada dikkatli olmamız gerekiyor - çünkü bir bayt dizgimiz var, ikili kip kullanıyoruz, bu yüzden "wb"; İkili yazmanın bir kutu solucan açtığını hissediyorum ...
  • Örnek bir dosya, UN / LOCODE metin arşivi kullanıyorum :

Ben ne yapmadım:

  • NumenorForLife zip dosyasını diske kaydetmeyi sordu. Bununla ne demek istediğinden emin değilim - zip dosyasını indirmek mi? Bu farklı bir görev; bkz Oleh Prypin mükemmel cevap .

İşte bir yol:

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)

Tüm dosyaları diske yazmak istiyorsanız, daha kolay yol, döngü yerine my_zip_file.extractall ('my_target') `kullanmaktır. Ama bu harika!
MCMZL

lütfen bu soru için bana yardım eder misin: stackoverflow.com/questions/62417455/…
Harshit Kakkar

18

RAM'de bulunan geçici bir dosyaya yaz

tempfilemodülün ( http://docs.python.org/library/tempfile.html ) sadece şeye sahip olduğu ortaya çıktı :

tempfile.SpooledTporaryFile ([max_size = 0 [, mode = 'w + b' [, bufsize = -1 [, sonek = '' [, önek = 'tmp' [, dir = Yok]]]]])

Bu işlev, tam olarak TemporaryFile () işlevinin yaptığı gibi çalışır, ancak veri, dosya boyutu max_size değerini geçene kadar veya dosyanın fileno () yöntemi çağrılana kadar bellekte biriktirilir, bu noktada içerikler diske yazılır ve işlem TemporaryFile'da olduğu gibi devam eder. ().

Elde edilen dosyanın, boyutuna bakılmaksızın dosyanın disk üzerindeki bir dosyaya dönmesine neden olan rollover () adlı ek bir yöntemi vardır.

Döndürülen nesne, rollover () 'ın çağrılıp çağrılmadığına bağlı olarak _file niteliği bir StringIO nesnesi veya bir gerçek dosya nesnesi olan dosya benzeri bir nesnedir. Bu dosya benzeri nesne, normal bir dosya gibi bir with ifadesinde kullanılabilir.

2.6 sürümünde yeni.

ya da tembelseniz /tmpve Linux'a bağlı bir tmpfs'niz varsa, orada bir dosya oluşturabilirsiniz, ancak onu kendiniz silmeniz ve adlandırma ile ilgilenmeniz gerekir.


3
+1 - SpooledTporaryFile hakkında bilmiyordum. Eğilim yine de açıkça StringIO'yu kullanmak olacaktır, ancak bunu bilmek güzel.
02:47

16

Tamlık için Python3 cevabımı eklemek istiyorum:

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

İstekleri kullanarak diğer cevaplara ekleme :

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

Daha sonra açık ile birlikte kullanılabilen zip dosyasındaki içerikleri çıkaran, örneğin extractall () için daha fazla işlev ayrıntısı almak için yardım (f) kullanın .


CSV'nizi okumak için şunları yapın:with f.open(f.namelist()[0], 'r') as g: df = pd.read_csv(g)
Corey Levinson

3

Vishal'ın örneği, ne kadar harika olursa olsun, dosya adı söz konusu olduğunda kafa karıştırıyor ve 'zipfile'ı yeniden tanımlamanın değerini görmüyorum.

Aşağıda, bir tanesi daha sonra pandalar DataFrame'e okuduğum bir csv dosyası olan bazı dosyaları içeren bir zip dosyası indiren örneğim var:

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

(Not, Python 2.7.13 kullanıyorum)

Benim için işe yarayan kesin çözüm budur. StringIO'yu kaldırıp IO kitaplığı ekleyerek Python 3 sürümü için biraz değiştirdim.

Python 3 Sürümü

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

Vishal'ın cevabında diskte dosya olmadığı durumlarda dosya adının ne olacağı açık değildi. Yanıtını çoğu ihtiyaç için değişiklik yapmadan çalışacak şekilde değiştirdim.

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

Bu bir Python 2 cevabıdır.
Boris

0

zipfileModülü kullanın . Bir URL'den bir dosya çıkarmak için, bir urlopençağrının sonucunu bir BytesIOnesneye kaydırmanız gerekir . Bunun nedeni, tarafından döndürülen bir web isteğinin sonucunun urlopenaramayı desteklememesidir:

from urllib.request import urlopen

from io import BytesIO
from zipfile import ZipFile

zip_url = 'http://example.com/my_file.zip'

with urlopen(zip_url) as f:
    with BytesIO(f.read()) as b, ZipFile(b) as myzipfile:
        foofile = myzipfile.open('foo.txt')
        print(foofile.read())

Dosyayı zaten yerel olarak indirdiyseniz, ihtiyacınız yoktur BytesIO, sadece ikili modda açın ve ZipFiledoğrudan şu adrese geçin :

from zipfile import ZipFile

zip_filename = 'my_file.zip'

with open(zip_filename, 'rb') as f:
    with ZipFile(f) as myzipfile:
        foofile = myzipfile.open('foo.txt')
        print(foofile.read().decode('utf-8'))

Yine, opendosyayı metin olarak değil ikili ( 'rb') kipinde yapmanız gerektiğini unutmayın, yoksa bir zipfile.BadZipFile: File is not a zip filehata alırsınız .

Tüm bunları withifadeyle birlikte bağlam yöneticisi olarak kullanmak iyi bir uygulamadır , böylece düzgün bir şekilde kapatılırlar.


0

Tüm bu cevaplar hantal ve uzun görünüyor. Kodu kısaltmak için istekleri kullanın , örneğin:

import requests, zipfile, io
r = requests.get(zip_file_url)
z = zipfile.ZipFile(io.BytesIO(r.content))
z.extractall("/path/to/directory")
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.