ディスクに書き込まずに.zipファイルをダウンロードして解凍する


86

URLから.ZIPファイルのリストをダウンロードし、ZIPファイルを抽出してディスクに書き込む最初のPythonスクリプトを機能させることができました。

私は今、次のステップを達成するのに途方に暮れています。

私の主な目標は、zipファイルをダウンロードして抽出し、TCPストリームを介してコンテンツ(CSVデータ)を渡すことです。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" ))

3
ZIP形式はストリーミング用に設計されていません。フッターを使用します。つまり、ファイル内のどこにあるかを把握するためにファイルの終わりが必要です。つまり、ファイルのサブセットで何かを行う前に、ファイル全体を用意する必要があります。
Charles Duffy

回答:


66

私の提案は、StringIOオブジェクトを使用することです。それらはファイルをエミュレートしますが、メモリに常駐します。したがって、次のようなことができます。

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

またはもっと簡単に(Vishalへの謝罪):

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

Python 3では、StringIOの代わりにBytesIOを使用します。

import zipfile
from io import BytesIO

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

「StringIOオブジェクトはUnicodeまたは8ビットの文字列を受け入れることができます」これは、書き込む予定のバイト数が0 mod 8と一致しない場合、例外をスローするか、誤ったデータを書き込むことを意味しませんか?
ninjagecko 2011

1
まったくそうではありません-なぜ一度に8バイトしか書き込めないのでしょうか?逆に、一度に8ビット未満を書き込むのはいつですか。
センダール2011

@ninjagecko:書き込まれると予想されるバイト数が8の倍数でない場合、問題を恐れているようです。これはStringIOに関するステートメントから導き出すことはできず、まったく根拠がありません。StringIOの問題は、ユーザーがオブジェクトを、システムのデフォルトのエンコーディング(通常は)でデコードできないオブジェクトと混合 unicodeするstr場合ですascii
ジョンマチン2011

1
上記のコードに関する小さなコメント:.zipから複数のファイルを読み取る場合は、データを1つずつ読み取るようにしてください。zipfile.openを2回呼び出すと、最初の参照が削除されるためです。
scippie 2011年

15
Python 3以降、使用する必要があることに注意してくださいfrom io import StringIO
Jorge Leitao 2014年

81

以下は、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']

27

すでに述べたかもしれない適応/変更のいくつかの説明とともに、Python2を使用していたVishalの優れた答えの更新されたPython3バージョンを提供したいと思います。

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では、出力行は次のようになりますb'some text'。文字列ではないため、これは予想されることです。バイトストリームを読み取っていることを忘れないでください。Dan04の優れた答えを見てください

私が行ったいくつかの小さな変更:

  • ドキュメントに従ってwith ... asではなく使用しますzipfile = ...
  • スクリプトは現在、 .namelist()は、zip内のすべてのファイルを循環し、その内容を印刷ためにするようになりました。
  • ZipFileオブジェクトの作成をに移動しましたwithステートメントそれが優れているかどうかはわかりません。
  • NumenorForLifeのコメントに応えて、バイトストリームをファイルに(zip内のファイルごとに)書き込むオプションを追加(およびコメントアウト)しました。"unzipped_and_read_"ファイル名の先頭と拡張子を追加します".file"(私は使用したくない".txt"バイト文字列のあるファイルにはしたくない)。もちろん、コードを使用する場合は、コードのインデントを調整する必要があります。
    • ここでは注意する必要があります-バイト文字列があるため"wb"、バイナリモードを使用します。とにかくバイナリを書くとワームの缶が開くような気がします...
  • サンプルファイル、UN / LOCODEテキストアーカイブを使用しています

私がしなかったこと:

  • NumenorForLifeは、zipをディスクに保存することについて質問しました。彼がそれが何を意味するのかわかりません-zipファイルをダウンロードしますか?それは別のタスクです。OlehPrypinの優れた回答を参照してください。

方法は次のとおりです。

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)

すべてのファイルをディスクに書き込みたい場合は、ループする代わりにmy_zip_file.extractall( 'my_target') `を使用するのが簡単な方法です。しかし、それは素晴らしいことです!
MCMZL 2018年

:あなたはこの質問のヘルプ私を喜ばせることができstackoverflow.com/questions/62417455/...
Harshit Kakkar

18

RAMにある一時ファイルに書き込む

それが判明したtempfileモジュールは、(http://docs.python.org/library/tempfile.html)だけのものがあります。

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

この関数はTemporaryFile()とまったく同じように動作しますが、ファイルサイズがmax_sizeを超えるまで、またはファイルのfileno()メソッドが呼び出されるまでデータがメモリにスプールされ、その時点で内容がディスクに書き込まれ、TemporaryFileと同様に操作が続行されます。 ()。

結果のファイルには、rollover()という1つの追加メソッドがあります。これにより、ファイルは、サイズに関係なく、ディスク上のファイルにロールオーバーされます。

返されるオブジェクトは、rollover()が呼び出されたかどうかに応じて、_file属性がStringIOオブジェクトまたはtrueファイルオブジェクトのいずれかであるファイルのようなオブジェクトです。このファイルのようなオブジェクトは、通常のファイルと同じように、withステートメントで使用できます。

バージョン2.6の新機能。

または、怠惰/tmpでLinuxにtmpfsをマウントしている場合は、そこにファイルを作成するだけで済みますが、自分でファイルを削除して名前を付ける必要があります。


3
+ 1-SpooledTemporaryFileについて知りませんでした。私の傾向はまだStringIOを明示的に使用することですが、これは知っておくとよいでしょう。
センダール2011

16

完全を期すために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]

14

リクエストを使用して他の回答に追加する

 # 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)を使用し、たとえば、後でopenで使用できるzipファイルの内容を抽出するextractall()の関数の詳細を取得します


あなたのCSVを読み取るには、実行しますwith f.open(f.namelist()[0], 'r') as g: df = pd.read_csv(g)
コーリーレビンソン

3

Vishalの例は、すばらしいものですが、ファイル名に関しては混乱を招き、「zipfile」を再定義するメリットはわかりません。

これは、いくつかのファイルを含むzipをダウンロードする私の例です。そのうちの1つは、後でpandasDataFrameに読み込んだcsvファイルです。

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

(注:私はPython 2.7.13を使用しています)

これは私のために働いた正確な解決策です。StringIOを削除し、IOライブラリを追加することで、Python3バージョン用に少し調整しました。

Python3バージョン

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の回答では、ディスク上にファイルがない場合のファイル名が何であるかは明らかではありませんでした。私は彼の答えを修正して、ほとんどのニーズに合わせて修正せずに動作するようにしました。

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

これはPython2の答えです。
ボリス

0

zipfileモジュールを使用します。URLからファイルを抽出するには、urlopen呼び出しの結果をBytesIOオブジェクトでラップする必要があります。これは、によって返されたWebリクエストの結果がurlopenシークをサポートしていないためです。

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

すでにファイルをローカルにダウンロードしている場合は、必要ありません。BytesIOバイナリモードで開いて、ZipFile直接に渡します。

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

繰り返しますが、テキストとしてではなくopenバイナリ('rb')モードでファイルを作成する必要があることに注意してください。そうしないと、zipfile.BadZipFile: File is not a zip fileエラーが発生します。

これらすべてをwithステートメントのコンテキストマネージャーとして使用して、適切に閉じられるようにすることをお勧めします。


0

これらの答えはすべて、かさばって長く見える。リクエストを使用してコードを短縮します。例:

import requests, zipfile, io
r = requests.get(zip_file_url)
z = zipfile.ZipFile(io.BytesIO(r.content))
z.extractall("/path/to/directory")
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.