Tải xuống và giải nén tệp .zip mà không cần ghi vào đĩa


85

Tôi đã quản lý để tập lệnh python đầu tiên của mình hoạt động, tải xuống danh sách các tệp .ZIP từ một URL và sau đó tiến hành giải nén các tệp ZIP và ghi chúng vào đĩa.

Bây giờ tôi đang mất khả năng đạt được bước tiếp theo.

Mục tiêu chính của tôi là tải xuống và giải nén tệp zip và chuyển nội dung (dữ liệu CSV) qua luồng TCP. Tôi không muốn thực sự ghi bất kỳ tệp zip hoặc tệp trích xuất nào vào đĩa nếu tôi có thể thoát khỏi nó.

Đây là tập lệnh hiện tại của tôi hoạt động nhưng tiếc là phải ghi các tệp vào đĩa.

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
Định dạng ZIP không được thiết kế để truyền trực tuyến. Nó sử dụng footer, nghĩa là bạn cần phần cuối của tệp để tìm ra nơi mọi thứ thuộc về bên trong nó, nghĩa là bạn cần có toàn bộ tệp trước khi có thể làm bất cứ điều gì với một tập con của nó.
Charles Duffy

Câu trả lời:


65

Đề xuất của tôi là sử dụng một StringIOđối tượng. Chúng mô phỏng các tệp, nhưng nằm trong bộ nhớ. Vì vậy, bạn có thể làm điều gì đó như sau:

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

Hoặc đơn giản hơn (xin lỗi Vishal):

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

Trong Python 3, sử dụng BytesIO thay vì StringIO:

import zipfile
from io import BytesIO

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

"Đối tượng StringIO có thể chấp nhận Unicode hoặc chuỗi 8-bit" Điều này không có nghĩa là nếu số byte bạn muốn ghi không tương ứng với 0 mod 8, thì bạn sẽ ném một ngoại lệ hoặc ghi dữ liệu sai?
ninjagecko

1
Không hề - tại sao bạn chỉ có thể viết 8 byte một lúc? Ngược lại, khi nào bạn viết ít hơn 8 bit cùng một lúc?
gửi

@ninjagecko: Bạn có vẻ sợ một vấn đề nếu số byte dự kiến ​​sẽ được viết không phải là bội số của 8. Điều đó không có được từ tuyên bố về StringIO và khá vô căn cứ. Vấn đề với StringIO là khi người dùng trộn unicode các đối tượng với strcác đối tượng không thể giải mã theo mã hóa mặc định của hệ thống (thường là ascii).
John Machin

1
Nhận xét nhỏ về đoạn mã trên: khi bạn đọc nhiều tệp ra khỏi .zip, hãy đảm bảo bạn đọc dữ liệu ra từng cái một, vì gọi zipfile.open hai lần sẽ xóa tham chiếu trong lần đầu tiên.
scippie

15
Lưu ý rằng kể từ Python 3 bạn phải sử dụngfrom io import StringIO
Jorge Leitao

81

Dưới đây là đoạn mã tôi đã sử dụng để tìm nạp tệp csv đã nén, vui lòng xem:

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

Đây filelà một chuỗi. Để có được chuỗi thực mà bạn muốn chuyển, bạn có thể sử dụng zipfile.namelist(). Ví dụ,

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

26

Tôi muốn cung cấp một phiên bản Python 3 cập nhật cho câu trả lời tuyệt vời của Vishal, đang sử dụng Python 2, cùng với một số giải thích về các điều chỉnh / thay đổi, có thể đã được đề cập.

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)

Sự thay đổi cần thiết:

  • Không có StringIOmô-đun nào trong Python 3 (nó đã được chuyển sang io.StringIO). Thay vào đó, tôi sử dụng io.BytesIO] 2 , bởi vì chúng tôi sẽ xử lý một luồng bytest - Tài liệu , cũng là chuỗi này .
  • urlopen:
    • " urllib.urlopenChức năng kế thừa từ Python 2.6 trở về trước đã bị ngừng; urllib.request.urlopen()tương ứng với chức năng cũ urllib2.urlopen.", Docschuỗi này .

Ghi chú:

  • Trong Python 3, dòng sản lượng in sẽ trông giống như vậy: b'some text'. Điều này được mong đợi, vì chúng không phải là chuỗi - hãy nhớ rằng, chúng tôi đang đọc một dòng bytest. Chúc các bạn xem câu trả lời xuất sắc của Dan04 .

Một vài thay đổi nhỏ tôi đã thực hiện:

  • Tôi sử dụng with ... asthay vì zipfile = ...theo Tài liệu .
  • Tập lệnh hiện sử dụng .namelist() để chuyển qua tất cả các tệp trong zip và in nội dung của chúng.
  • Tôi đã chuyển việc tạo ZipFileđối tượng vàowith câu lệnh, mặc dù tôi không chắc liệu điều đó có tốt hơn không.
  • Tôi đã thêm (và nhận xét) một tùy chọn để ghi bytestream vào tệp (mỗi tệp trong zip), để đáp lại nhận xét của NumenorForLife; nó thêm "unzipped_and_read_"vào phần đầu của tên tệp và một ".file"phần mở rộng (tôi không muốn sử dụng ".txt"cho các tệp có bytestrings). Tất nhiên, việc thụt lề của mã sẽ cần được điều chỉnh nếu bạn muốn sử dụng nó.
    • Cần phải cẩn thận ở đây - bởi vì chúng tôi có một chuỗi byte, chúng tôi sử dụng chế độ nhị phân, vì vậy "wb"; Tôi có cảm giác rằng dù sao việc viết mã nhị phân cũng mở ra một thùng sâu ...
  • Tôi đang sử dụng tệp ví dụ, tệp lưu trữ văn bản UN / LOCODE :

Những gì tôi đã không làm:

Đây là một cách:

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)

Nếu bạn muốn ghi tất cả các tệp vào đĩa, cách dễ dàng hơn là sử dụng my_zip_file.extractall ('my_target') 'thay vì lặp lại. Nhưng điều đó thật tuyệt!
MCMZL

bạn có thể vui lòng giúp tôi với câu hỏi này không: stackoverflow.com/questions/62417455/…
Harshit Kakkar

18

ghi vào một tệp tạm thời nằm trong RAM

hóa ra tempfilemô-đun ( http://docs.python.org/library/tempfile.html ) chỉ có điều:

tempfile.SpooledTemporaryFile ([max_size = 0 [, mode = 'w + b' [, bufsize = -1 [, hậu tố = '' [, prefix = 'tmp' [, dir = None]]]]]])

Hàm này hoạt động chính xác như TemporaryFile (), ngoại trừ việc dữ liệu được lưu vào bộ nhớ cho đến khi kích thước tệp vượt quá max_size hoặc cho đến khi phương thức fileno () của tệp được gọi, tại thời điểm này nội dung được ghi vào đĩa và hoạt động tiến hành như với TemporaryFile ().

Tệp kết quả có một phương thức bổ sung, rollover (), làm cho tệp chuyển sang tệp trên đĩa bất kể kích thước của nó.

Đối tượng trả về là một đối tượng giống tệp có thuộc tính _file là đối tượng StringIO hoặc đối tượng tệp đúng, tùy thuộc vào việc rollover () có được gọi hay không. Đối tượng giống tệp này có thể được sử dụng trong câu lệnh with, giống như một tệp bình thường.

Mới trong phiên bản 2.6.

hoặc nếu bạn lười biếng và bạn có một tmpfs gắn /tmptrên Linux, bạn có thể tạo một tệp ở đó, nhưng bạn phải tự xóa nó và xử lý việc đặt tên


3
+1 - không biết về SpooledTemporaryFile. Xu hướng của tôi vẫn là sử dụng StringIO một cách rõ ràng, nhưng điều này là tốt để biết.
gửi

16

Tôi muốn thêm câu trả lời Python3 của mình cho hoàn chỉnh:

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

Thêm vào các câu trả lời khác bằng yêu cầu :

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

Sử dụng trợ giúp (f) để biết thêm chi tiết về chức năng, ví dụ: extractall () trích xuất nội dung trong tệp zip mà sau này có thể được sử dụng khi mở .


Để đọc CSV của bạn, làm:with f.open(f.namelist()[0], 'r') as g: df = pd.read_csv(g)
Corey Levinson

3

Ví dụ của Vishal, tuy nhiên tuyệt vời, gây nhầm lẫn khi nói đến tên tệp và tôi không thấy lợi ích của việc định nghĩa lại 'zipfile'.

Đây là ví dụ của tôi tải xuống một tệp zip chứa một số tệp, một trong số đó là tệp csv mà sau đó tôi đã đọc thành DataFrame của gấu trúc:

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

(Lưu ý, tôi sử dụng Python 2.7.13)

Đây là giải pháp chính xác đã làm việc cho tôi. Tôi vừa chỉnh sửa một chút cho phiên bản Python 3 bằng cách xóa StringIO và thêm thư viện IO

Phiên bản 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

Không rõ ràng trong câu trả lời của Vishal tên tệp được cho là gì trong trường hợp không có tệp trên đĩa. Tôi đã sửa đổi câu trả lời của anh ấy để hoạt động mà không cần sửa đổi cho hầu hết các nhu cầu.

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

Đây là câu trả lời trong Python 2.
Boris

0

Sử dụng zipfilemô-đun. Để trích xuất một tệp từ một URL, bạn sẽ cần bao bọc kết quả của một urlopencuộc gọi trong một BytesIOđối tượng. Điều này là do kết quả của một yêu cầu web được trả lại bởi urlopenkhông hỗ trợ tìm kiếm:

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

Nếu bạn đã tải xuống tệp cục bộ, bạn không cần BytesIO, chỉ cần mở tệp ở chế độ nhị phân và chuyển ZipFiletrực tiếp đến :

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

Một lần nữa, lưu ý rằng bạn phải open tệp ở chế độ binary ( 'rb') , không phải ở dạng văn bản, nếu không bạn sẽ nhận đượczipfile.BadZipFile: File is not a zip file lỗi.

Bạn nên sử dụng tất cả những thứ này làm trình quản lý ngữ cảnh với withcâu lệnh, để chúng được đóng đúng cách.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.