Скачивание и разархивирование .zip файла без записи на диск
Мне удалось заставить работать мой первый скрипт на 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" ))
Ответы
Ответ 1
Мое предложение было бы использовать объект 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"
Или проще (извинения Вишалу):
myzipfile = zipfile.ZipFile(StringIO(get_zip_data()))
for name in myzipfile.namelist():
[ ... ]
В Python 3 используйте BytesIO вместо StringIO.
Ответ 2
Ниже приведен фрагмент кода, который я использовал для получения заархивированного 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']
Ответ 3
Я бы хотел предложить обновленную версию 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)
Необходимые изменения:
- Там нет
StringIO
в Python 3. Вместо этого я использую io
, и из него я импортирую BytesIO
, потому что мы будем обрабатывать bytestream - Docs, также этот поток.
- urlopen:
- "Устаревшая функция urllib.urlopen из Python 2.6 и ранее была прекращена, urllib.request.urlopen() соответствует старому urllib2.urlopen.", Docs.
- import urllib.request:
Примечание:
- В Python 3 печатные выходные строки будут выглядеть так:
b'some text'
. Это ожидается, поскольку они не являются строками - помните, мы читаем байтовый поток. Посмотрите отличный ответ Dan04.
Несколько незначительных изменений, которые я сделал:
- Я использую
with ... as
вместо zipfile = ...
в соответствии с Docs.
- script теперь использует
namelist()
для циклического перемещения всех файлов в zip и печати их содержимого.
- Я переместил создание объекта
ZipFile
в оператор with-statement, хотя я не уверен, что это лучше.
- Я добавил (и закомментировал) возможность записать файл bytestream в файл (за файл в zip файле) в ответ на комментарий NumenorForLife; он добавляет
"unzipped_and_read_"
в начало имени файла и расширение ".file"
(я предпочитаю не использовать ".txt"
для файлов с bytestrings). Разумеется, отступы кода должны быть скорректированы, если вы хотите его использовать.
- Нужно быть осторожным здесь - потому что у нас есть байтовая строка, мы используем двоичный режим, поэтому
"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)
Ответ 4
записать во временный файл, который находится в оперативной памяти
Оказывается, модуль 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(), который вызывает файл для перехода к файлу на диске независимо от его размера.
Возвращаемый объект является файловым объект, атрибут _file которого объект StringIO или истинный файл объекта, в зависимости от того, вызывается rollover(). Эта файл-подобный объект можно использовать в с, как обычный файл.
Новое в версии 2.6.
или если вы ленивы и у вас есть tmpfs-установленный /tmp
на Linux, вы можете просто сделать там файл, но вы должны удалить его самостоятельно и иметь дело с именованием
Ответ 5
Я хотел бы добавить свой ответ 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]
Ответ 6
Добавление к другим ответам с использованием запросов:
# 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), чтобы получить дополнительные сведения о функциях, например, extractall(), который извлекает содержимое в zip файл, который позже можно использовать с open.
Ответ 7
В 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
Ответ 8
Пример 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])