无需写入磁盘即可下载和解压缩.zip文件

时间:2011-04-19 02:13:24

标签: python unzip

我设法让我的第一个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" ))

10 个答案:

答案 0 :(得分:61)

下面是我用来获取压缩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']

答案 1 :(得分:49)

我的建议是使用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"

或者更简单(向Vishal道歉):

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

在Python 3中使用BytesIO而不是StringIO。

答案 2 :(得分:17)

我想提供一个更新的Python 3版本的Vishal的优秀答案,它使用的是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)

必要的变化:

  • Python 3中没有StringIO。我使用io,然后导入BytesIO,因为我们将处理字节流 - Docs,还this thread
  • 的urlopen:
    • “Python 2.6及更早版本中的旧版urllib.urlopen函数已停止使用; urllib.request.urlopen()对应旧版urllib2.urlopen。”,Docs
  • import urllib.request:

注意:

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

我做了一些小改动:

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

我没做的事:

  • NumenorForLife询问有关将zip保存到磁盘的问题。我不确定他的意思 - 下载zip文件?那是一项不同的任务;见Oleh Prypin's excellent answer

这是一种方式:

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)

答案 3 :(得分:15)

写入驻留在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,或直到   调用file的fileno()方法,at   内容写在哪一点   磁盘和操作继续进行   TemporaryFile()。

     

生成的文件还有一个   方法,翻转(),它导致   文件转到磁盘文件   不管它的大小。

     

返回的对象是类文件   _file属性为的对象   StringIO对象或真实文件   对象,取决于是否   已经调用了rollover()。这个   类文件对象可以在with中使用   声明,就像普通文件一样。

     

2.6版中的新功能。

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

答案 4 :(得分:12)

我想为完整性添加我的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]

答案 5 :(得分:9)

使用请求

添加其他答案
 # 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文件中的内容,以后可以与打开一起使用。

答案 6 :(得分:2)

在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

答案 7 :(得分:1)

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)

答案 8 :(得分:0)

使用zipfile模块。要从URL提取文件,您需要将urlopen调用的结果包装在BytesIO对象中。这是因为urlopen返回的Web请求的结果不支持搜索:

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 binary ('rb') mode中的文件,而不是文本文件,否则会出现zipfile.BadZipFile: File is not a zip file错误。

优良作法是将所有这些内容与with语句一起用作上下文管理器,以便将其正确关闭。

答案 9 :(得分:0)

所有这些答案似乎又大又长。使用requests来缩短代码,例如:

import requests, zipfile, io
r = requests.get(zip_file_url)
z = zipfile.ZipFile(io.BytesIO(r.content))
z.extractall("/path/to/directory")