Завантаження та розпакування ZIP-файлу без запису на диск


85

Мені вдалося змусити працювати свій перший сценарій 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 не призначений для потокового передавання. Він використовує нижній колонтитул, тобто вам потрібен кінець файлу, щоб з’ясувати, де речі знаходяться всередині нього, це означає, що вам потрібно мати цілий файл, перш ніж ви зможете зробити що-небудь із його підмножиною.
Чарльз Даффі

Відповіді:


65

Моя пропозиція полягала б у використанні 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"

Або простіше (вибачення перед Вішалем):

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 біт за раз?
senderle

@ninjagecko: Ви, схоже, боїтеся проблеми, якщо кількість байтів, яку очікується записати, не кратна 8. Це не випливає із твердження про StringIO і є абсолютно безпідставним. Проблема з StringIO полягає в тому, що користувач змішує unicode об'єкти з strоб'єктами, які не можна декодувати за допомогою кодування за замовчуванням у системі (що зазвичай є ascii).
John Machin

1
Невеликий коментар до наведеного вище коду: коли ви читаєте кілька файлів із .zip-файлу, переконайтеся, що ви читаєте дані один за одним, оскільки дзвінок zipfile.open два рази видалить посилання в першому.
scippie

15
Зверніть увагу, що станом на Python 3 ви повинні використовуватиfrom io import StringIO
Хорхе Лейтао

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

26

Я хотів би запропонувати оновлену версію чудової відповіді 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)

Необхідні зміни:

  • У StringIOPython 3 немає модуля (його було переміщено io.StringIO). Натомість я використовую io.BytesIO] 2 , тому що ми будемо обробляти bytestream - Docs , також цей потік .
  • urlopen:

Примітка:

  • В Python 3, видрукувані вихідні лінії будуть виглядати так: b'some text'. Це очікується, оскільки вони не є рядками - пам’ятайте, ми читаємо bytestream. Погляньте на чудову відповідь Dan04 .

Кілька незначних змін, які я зробив:

  • Я використовую with ... asзамість zipfile = ...відповідно до Документів .
  • Тепер скрипт використовує .namelist()для перегляду всіх файлів у zip-файлі та друку їх вмісту.
  • Я перемістив створення ZipFileоб'єкта в withоператор, хоча я не впевнений, що це краще.
  • У відповідь на коментар NumenorForLife я додав (і прокоментував) опцію написання bytestream у файл (для кожного файлу в zip-файлі); це додає "unzipped_and_read_"до початку імені файлу та ".file"розширення (я вважаю за краще не використовувати ".txt"для файлів з bytestring). Відступ коду, звичайно, потрібно буде скоригувати, якщо ви хочете ним скористатися.
    • Тут потрібно бути обережним - оскільки ми маємо байтовий рядок, ми використовуємо двійковий режим, отже "wb"; У мене таке відчуття, що написання двійкового файлу все одно відкриває банку з хробаками ...
  • Я використовую прикладний файл, текстовий архів UN / LOCODE :

Що я не робив:

  • NumenorForLife запитав про збереження zip-файлу на диск. Я не впевнений, що він мав на увазі під цим - завантаження zip-файлу? Це інше завдання; див . чудову відповідь Олега Припіна .

Ось спосіб:

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

запис у тимчасовий файл, який знаходиться в оперативній пам'яті

виявляється, у tempfileмодулі ( http://docs.python.org/library/tempfile.html ) є просто така річ:

tempfile.SpooledTemporaryFile ([max_size = 0 [, mode = 'w + b' [, bufsize = -1 [, суфікс = '' [, префікс = 'tmp' [, dir = None]]]]]]]])

Ця функція працює точно так, як це робить TemporaryFile (), за винятком того, що дані буксуються в пам'яті до тих пір, поки розмір файлу не перевищить max_size, або до виклику методу fileno () файлу, після чого вміст записується на диск і операція продовжується, як і у TemporaryFile ().

Отриманий файл має один додатковий метод, rollover (), який змушує файл перекинутися на файл на диску, незалежно від його розміру.

Повернутий об'єкт є файлоподібним об'єктом, атрибут _file є або об'єктом StringIO, або справжнім об'єктом файлу, залежно від того, чи було викликано rollover (). Цей файлоподібний об'єкт можна використовувати в операторі with, як і звичайний файл.

Нове у версії 2.6.

або якщо ви ліниві і у вас /tmpна Linux встановлений tmpfs , ви можете просто створити там файл, але вам доведеться його самостійно видалити і займатися іменуванням


3
+1 - не знав про SpooledTemporaryFile. Я все ще маю намір використовувати явно StringIO, але це добре знати.
senderle

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)
Кори Левінсон

3

Приклад Вішала, яким би великим він не був, бентежить, коли справа стосується імені файлу, і я не бачу переваги перевизначення "zipfile".

Ось мій приклад, який завантажує zip-файл, який містить деякі файли, одним з яких є файл CSV, який я згодом прочитав у 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])

(Примітка, я використовую 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

У відповіді Вішаля не було очевидним, якою повинна бути назва файлу у випадках, коли на диску немає файлу. Я змінив його відповідь так, щоб він працював без змін для більшості потреб.

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.
Борис

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заявою, щоб вони були належним чином закриті.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.