development

디스크에 쓰지 않고 .zip 파일 다운로드 및 압축 해제

big-blog 2020. 11. 6. 21:00
반응형

디스크에 쓰지 않고 .zip 파일 다운로드 및 압축 해제


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

내 제안은 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"

또는 더 간단하게 (Vishal에게 사과) :

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

Python 3에서는 StringIO 대신 BytesIO를 사용합니다.


다음은 압축 된 csv 파일을 가져 오는 데 사용한 코드 스 니펫입니다. 한 번보세요.

파이썬 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

파이썬 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']

이미 언급되었을 수있는 적응 / 변경 사항에 대한 설명과 함께 Python 2를 사용하는 Vishal의 탁월한 답변의 업데이트 된 Python 3 버전을 제공하고 싶습니다.

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)

필요한 변경 :

  • StringIOPython 3 에는 없습니다 . 대신를 사용 io하고 가져옵니다 BytesIO. 왜냐하면 바이트 스트림 ( 문서 , 이 스레드처리 할 것이기 때문) 입니다.
  • urlopen :
    • "Python 2.6 이하의 레거시 urllib.urlopen 함수는 중단되었습니다. urllib.request.urlopen ()은 이전 urllib2.urlopen에 해당합니다.", Docs .
  • import urllib.request :

노트 :

  • Python 3에서 인쇄 된 출력 행은 다음과 같습니다 b'some text'.. 이것은 문자열이 아니기 때문에 예상됩니다. 우리는 바이트 스트림을 읽고 있다는 것을 기억하십시오. Dan04의 탁월한 답변을 살펴보십시오 .

몇 가지 사소한 변경 사항 :

  • with ... as대신 문서zipfile = ...따라 사용 합니다 .
  • 이제 스크립트 namelist()는 zip에있는 모든 파일을 순환하고 내용을 인쇄하는 데 사용합니다.
  • 나는 ZipFile그것이 더 나은지 확실하지 않지만 객체 의 생성을 with-statement로 옮겼습니다 .
  • NumenorForLife의 의견에 대한 응답으로 파일 (zip의 파일 당)에 바이트 스트림을 쓰는 옵션을 추가 (및 주석 처리)했습니다. "unzipped_and_read_"파일 이름과 ".file"확장자 의 시작 부분에 추가 됩니다 ( ".txt"바이트 문자열이있는 파일에는 사용하지 않는 것을 선호합니다 ). 물론 코드의 들여 쓰기는 사용하려는 경우 조정해야합니다.
    • 여기서주의해야합니다. 바이트 문자열이 있으므로 바이너리 모드를 사용하므로 "wb"; 바이너리를 작성하면 어쨌든 웜 캔이 열리는 느낌이 듭니다 ...
  • 예제 파일 인 UN / LOCODE 텍스트 아카이브를 사용하고 있습니다 .

내가하지 않은 것 :

  • NumenorForLife asked about saving the zip to disk. I'm not sure what he meant by it -- downloading the zip file? That's a different task; see Oleh Prypin's excellent answer.

Here's a way:

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)

write to a temporary file which resides in RAM

it turns out the tempfile module ( http://docs.python.org/library/tempfile.html ) has just the thing:

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

This function operates exactly as TemporaryFile() does, except that data is spooled in memory until the file size exceeds max_size, or until the file’s fileno() method is called, at which point the contents are written to disk and operation proceeds as with TemporaryFile().

The resulting file has one additional method, rollover(), which causes the file to roll over to an on-disk file regardless of its size.

The returned object is a file-like object whose _file attribute is either a StringIO object or a true file object, depending on whether rollover() has been called. This file-like object can be used in a with statement, just like a normal file.

New in version 2.6.

or if you're lazy and you have a tmpfs-mounted /tmp on Linux, you can just make a file there, but you have to delete it yourself and deal with naming


I'd like to add my Python3 answer for completeness:

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]

Adding on to the other answers using requests:

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

Use help(f) to get more functions details for e.g. extractall() which extracts the contents in zip file which later can be used with with open.


It wasn't obvious in Vishal's answer what the file name was supposed to be in cases where there is no file on disk. I've modified his answer to work without modification for most needs.

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

Vishal's example, however great, confuses when it comes to the file name, and I do not see the merit of redefing 'zipfile'.

Here is my example that downloads a zip that contains some files, one of which is a csv file that I subsequently read into a 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])

(Note, I use Python 2.7.13)

This is the exact solution that worked for me. I just tweaked it a little bit for Python 3 version by removing StringIO and adding IO library

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

참고URL : https://stackoverflow.com/questions/5710867/downloading-and-unzipping-a-zip-file-without-writing-to-disk

반응형