การดาวน์โหลดและคลายการบีบอัดไฟล์. zip โดยไม่ต้องเขียนลงดิสก์


86

ฉันจัดการเพื่อให้สคริปต์ python แรกของฉันทำงานได้ซึ่งจะดาวน์โหลดรายการไฟล์. ZIP จาก URL จากนั้นดำเนินการแยกไฟล์ ZIP และเขียนลงในดิสก์

ตอนนี้ฉันกำลังสูญเสียเพื่อบรรลุขั้นต่อไป

เป้าหมายหลักของฉันคือดาวน์โหลดและแตกไฟล์ zip และส่งเนื้อหา (ข้อมูล CSV) ผ่านสตรีม TCP ฉันไม่ต้องการที่จะเขียนไฟล์ 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 ใช้ BytesIO แทน StringIO:

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

1
ไม่เลย - ทำไมคุณถึงเขียนได้ครั้งละ 8 ไบต์เท่านั้น? ในทางกลับกันเมื่อใดที่คุณเคยเขียนน้อยกว่า 8 บิตต่อครั้ง
ส่ง

@ninjagecko: คุณดูเหมือนจะกลัวปัญหาหากจำนวนไบต์ที่คาดว่าจะเขียนไม่ใช่ผลคูณของ 8 ซึ่งไม่ได้มาจากคำสั่งเกี่ยวกับ StringIO และค่อนข้างไม่มีมูล ปัญหากับ StringIO คือเมื่อผู้ใช้ผสม unicodeวัตถุกับstrวัตถุที่ไม่สามารถถอดรหัสได้โดยการเข้ารหัสเริ่มต้นของระบบ (ซึ่งโดยทั่วไปascii)
John Machin

1
ความคิดเห็นเล็กน้อยเกี่ยวกับโค้ดด้านบน: เมื่อคุณอ่านไฟล์หลายไฟล์จาก. zip ตรวจสอบให้แน่ใจว่าคุณได้อ่านข้อมูลทีละไฟล์เนื่องจากการเรียก zipfile.open สองครั้งจะลบการอ้างอิงในครั้งแรก
scippie

15
สังเกตว่าใน Python 3 คุณต้องใช้from io import StringIO
Jorge Leitao

81

ด้านล่างนี้เป็นข้อมูลโค้ดที่ฉันใช้ในการดึงไฟล์ 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

ฉันต้องการเสนอคำตอบที่ยอดเยี่ยมของ Vishal เวอร์ชันปรับปรุงของ Python 3 ซึ่งใช้ Python 2 พร้อมกับคำอธิบายบางส่วนเกี่ยวกับการดัดแปลง / การเปลี่ยนแปลงซึ่งอาจมีการกล่าวถึงไปแล้ว

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)

การเปลี่ยนแปลงที่จำเป็น:

บันทึก:

  • ในหลาม 3, b'some text'พิมพ์สายออกจะมีลักษณะเช่นดังนั้น: นี่เป็นสิ่งที่คาดหวังเนื่องจากไม่ใช่สตริง - จำไว้ว่าเรากำลังอ่าน bytestream มีลักษณะที่เป็นคำตอบที่ดีเยี่ยมของ Dan04

ฉันได้ทำการเปลี่ยนแปลงเล็กน้อย:

  • ผมใช้with ... asแทนการzipfile = ...ตามเอกสาร
  • ตอนนี้สคริปต์ใช้.namelist()เพื่อวนรอบไฟล์ทั้งหมดใน zip และพิมพ์เนื้อหา
  • ฉันย้ายการสร้างZipFileวัตถุไปไว้ในwithคำสั่งแม้ว่าฉันจะไม่แน่ใจว่ามันดีกว่าหรือไม่
  • ฉันเพิ่ม (และแสดงความคิดเห็น) ตัวเลือกในการเขียน bytestream ไปยังไฟล์ (ต่อไฟล์ใน zip) เพื่อตอบสนองต่อความคิดเห็นของ NumenorForLife มันเพิ่ม"unzipped_and_read_"ที่จุดเริ่มต้นของชื่อไฟล์และ".file"นามสกุล (ฉันไม่ต้องการใช้".txt"สำหรับไฟล์ที่มี bytestrings) แน่นอนว่าการเยื้องรหัสจะต้องมีการปรับเปลี่ยนหากคุณต้องการใช้
    • จะต้องระมัดระวังที่นี่ - เพราะเรามีสตริงไบต์เราใช้โหมดไบนารีดังนั้น"wb"; ฉันมีความรู้สึกว่าการเขียนไบนารีเป็นการเปิดเวิร์มได้อยู่ดี ...
  • ฉันกำลังใช้ไฟล์ตัวอย่างที่เก็บข้อความ UN / LOCODE :

สิ่งที่ฉันไม่ได้ทำ:

  • NumenorForLife ถามเกี่ยวกับการบันทึก zip ลงในดิสก์ ฉันไม่แน่ใจว่าเขาหมายถึงอะไร - ดาวน์โหลดไฟล์ zip? นั่นเป็นงานที่แตกต่าง ดูคำตอบที่ดีโอ Prypin ของ

นี่คือวิธี:

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

คุณช่วยฉันด้วยคำถามนี้ได้ไหม: stackoverflow.com/questions/62417455/…
Harshit Kakkar

18

เขียนลงในไฟล์ชั่วคราวที่อยู่ใน RAM

ปรากฎว่าtempfileโมดูล ( http://docs.python.org/library/tempfile.html ) มีสิ่งที่:

tempfile.SpooledTem ContemporaryFile ([max_size = 0 [, mode = 'w + b' [, bufsize = -1 [, ต่อท้าย = '' [, คำนำหน้า = 'tmp' [, dir = None]]]]])

ฟังก์ชันนี้ทำงานเหมือนกับที่ TemporaryFile () ทำยกเว้นว่าข้อมูลจะถูกสพูลในหน่วยความจำจนกระทั่งขนาดไฟล์เกิน max_size หรือจนกว่าจะมีการเรียกเมธอด fileno () ของไฟล์เมื่อถึงจุดที่เนื้อหาจะถูกเขียนลงดิสก์และการดำเนินการจะดำเนินการเช่นเดียวกับ TemporaryFile ().

ไฟล์ที่ได้จะมีวิธีการเพิ่มเติมหนึ่งวิธีคือ rollover () ซึ่งทำให้ไฟล์ม้วนไปยังไฟล์บนดิสก์โดยไม่คำนึงถึงขนาดของไฟล์

อ็อบเจ็กต์ที่ส่งคืนเป็นอ็อบเจ็กต์คล้ายไฟล์ที่มีแอ็ตทริบิวต์ _file เป็นอ็อบเจ็กต์ StringIO หรืออ็อบเจ็กต์ไฟล์จริงขึ้นอยู่กับว่าโรลโอเวอร์ () ถูกเรียก อ็อบเจ็กต์ที่มีลักษณะคล้ายไฟล์นี้สามารถใช้ในคำสั่ง with ได้เช่นเดียวกับไฟล์ปกติ

ใหม่ในเวอร์ชัน 2.6

หรือถ้าคุณขี้เกียจและติดตั้ง tmpfs /tmpบน Linux คุณสามารถสร้างไฟล์ที่นั่นได้ แต่คุณต้องลบด้วยตัวเองและจัดการกับการตั้งชื่อ


3
+1 - ไม่รู้เกี่ยวกับ SpooledTem ContemporaryFile ความชอบของฉันยังคงใช้ StringIO อย่างชัดเจน แต่นี่เป็นสิ่งที่ดีที่จะรู้
ส่ง

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

ใช้วิธีใช้(f)เพื่อดูรายละเอียดฟังก์ชันเพิ่มเติมสำหรับเช่นextractall ()ซึ่งแยกเนื้อหาในไฟล์ zip ซึ่งสามารถใช้กับ openได้ในภายหลัง


หากต้องการอ่าน CSV ของคุณโปรดทำ:with f.open(f.namelist()[0], 'r') as g: df = pd.read_csv(g)
Corey Levinson

3

อย่างไรก็ตามตัวอย่างของ Vishal นั้นยอดเยี่ยมทำให้เกิดความสับสนเมื่อพูดถึงชื่อไฟล์และฉันไม่เห็นข้อดีของการกำหนด 'zipfile' ใหม่

นี่คือตัวอย่างของฉันที่ดาวน์โหลด zip ที่มีไฟล์บางไฟล์ซึ่งหนึ่งในนั้นเป็นไฟล์ csv ที่ฉันอ่านใน 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])

(หมายเหตุฉันใช้ Python 2.7.13)

นี่เป็นวิธีแก้ปัญหาที่เหมาะกับฉัน ฉันเพิ่งปรับแต่งเล็กน้อยสำหรับเวอร์ชัน Python 3 โดยการลบ StringIO และเพิ่มไลบรารี IO

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

ไม่ชัดเจนในคำตอบของ 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

นี่คือคำตอบของ Python 2
Boris

0

ใช้zipfileโมดูล ในการแตกไฟล์จาก URL คุณจะต้องรวมผลลัพธ์ของการurlopenเรียกในBytesIOออบเจ็กต์ เนื่องจากผลลัพธ์ของคำขอทางเว็บที่ส่งคืนโดย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.