繁体   English   中英

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

[英]Downloading and unzipping a .zip file without writing to disk

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

下面是我用来获取压缩的 csv 文件的代码片段,请看:

蟒蛇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

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

我的建议是使用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():
    [ ... ]

我想提供 Vishal 使用 Python 2 的优秀答案的更新 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 中没有StringIO模块(它已移至io.StringIO )。 相反,我使用io.BytesIO ] 2 ,因为我们将处理一个字节流-文档,也该线程
  • 打开网址:

笔记:

  • 在 Python 3 中,打印的输出行看起来像这样: b'some text' 这是意料之中的,因为它们不是字符串——记住,我们正在读取一个字节流。 看看Dan04 的优秀答案

我做了一些小改动:

  • 根据文档,我使用with ... as而不是zipfile = ...
  • 该脚本现在使用.namelist()循环浏览 zip 中的所有文件并打印它们的内容。
  • 我将ZipFile对象的创建移到with语句中,尽管我不确定这是否更好。
  • 我添加(并注释掉)将字节流写入文件(zip 中的每个文件)的选项,以响应 NumenorForLife 的评论; 它将"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)

写入驻留在 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 版中的新功能。

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

为了完整起见,我想添加我的 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]

使用requests添加其他答案:

 # 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一起使用

所有这些答案都显得过于庞大和冗长。 使用请求来缩短代码,例如:

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

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

在 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

使用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'))

再次注意,您必须以二进制 ( 'rb' ) 模式open文件,而不是以文本形式open ,否则您将收到zipfile.BadZipFile: File is not a zip file错误。

将所有这些东西用作with语句的上下文管理器是一种很好的做法,以便它们可以正确关闭。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM