在不写入磁盘的情况下下载和解压缩.zip文件


85

我设法使第一个python脚本起作用,该脚本从URL下载.ZIP文件的列表,然后继续提取ZIP文件并将其写入磁盘。

我现在无所适从,无法实现下一步。

我的主要目标是下载并提取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" ))

3
ZIP格式并非旨在流式传输。它使用页脚,这意味着您需要在文件的末尾弄清楚事物在其中的位置,这意味着您需要拥有整个文件,然后才能对文件的子集进行任何操作。
查尔斯·达菲

Answers:


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"

或更简单地(向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 2011年

1
完全没有-为什么您一次只能写入8个字节?相反,什么时候一次写入少于8位呢?
senderle'4

@ninjagecko:如果预期要写入的字节数不是8的倍数,您似乎会担心一个问题。StringIO的问题在于,当用户 unicode对象与str系统默认编码(通常为ascii)无法解码的对象混合在一起时。
约翰·马钦

1
对上面的代码做一些简短的评论:当您从.zip中读取多个文件时,请确保您逐一读取数据,因为两次调用zipfile.open会在第一次中删除引用。
scippie 2011年

15
请注意,从Python 3开始,您必须使用from io import StringIO
Jorge Leitao,2014年

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

我想提供一个使用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)

必要的更改:

注意:

  • 在Python 3中,打印的输出行将如下所示:b'some text'。这是预料之中的,因为它们不是字符串-请记住,我们正在读取字节流。看看Dan04的出色答案

我做了一些小改动:

  • 我使用with ... as而不是zipfile = ...根据Docs
  • 现在,脚本可.namelist()用于循环浏览zip中的所有文件并打印其内容。
  • 我不确定将ZipFile对象的创建移到with语句中,尽管不确定是否更好。
  • 为了响应NumenorForLife的评论,我添加了(并注释掉了)将字节流写入文件(zip中每个文件)的选项;它添加"unzipped_and_read_"到文件名和".file"扩展名的开头(我不建议不要".txt"用于带有字节串的文件)。当然,如果要使用代码,则需要对其缩进进行调整。
    • 这里需要小心-因为我们有一个字节字符串,所以我们使用二进制模式,因此"wb";我感觉写二进制文件反正会打开一罐蠕虫。
  • 我正在使用示例文件UN / LOCODE文本存档

我没有做的事:

  • NumenorForLife问有关将zip保存到磁盘的问题。我不确定他是什么意思-下载zip文件?那是另一回事。参见Oleh 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


18

写入驻留在RAM中的临时文件

事实证明,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()。就像普通文件一样,可以在with语句中使用类似文件的对象。

2.6版的新功能。

或者如果您很懒,并且/tmp在Linux上安装了tmpfs ,则可以在那里创建文件,但是您必须自己删除它并处理命名


3
+1-不知道SpooledTemporaryFile。我的倾向仍然是显式使用StringIO,但这很高兴。
senderle'4

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

使用help(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文件,我随后将其读入了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)

这是对我有用的确切解决方案。通过删除StringIO并添加IO库,我对Python 3版本进行了一些调整

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的答案。
鲍里斯

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语句的上下文管理器,以便正确关闭它们。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.