创建ZIP存档以进行即时下载

5
在我正在开发的 Web 应用程序中,用户可以创建一个包含文件夹中所有文件的 zip 归档文件。以下是代码:
files = torrent[0].files
    zipfile = z.ZipFile(zipname, 'w')
    output = ""

    for f in files:
        zipfile.write(settings.PYRAT_TRANSMISSION_DOWNLOAD_DIR + "/" + f.name, f.name)

downloadurl = settings.PYRAT_DOWNLOAD_BASE_URL + "/" + settings.PYRAT_ARCHIVE_DIR + "/" + filename
output = "Download <a href=\"" + downloadurl + "\">" + torrent_name + "</a>"
return HttpResponse(output)

但是这样做有一个不好的副作用,就是需要等待很长时间(10秒以上)才能下载zip归档文件。是否可以跳过这个步骤?而不是将归档文件保存到文件中,是否可以直接将其发送给用户?

我相信torrentflux提供了我所说的确切功能。能够压缩几GB的数据并在一秒内下载。

5个回答


9

正如mandrake所说,HttpResponse的构造函数接受可迭代对象。

幸运的是,ZIP格式可以在单个过程中创建归档文件,中央目录记录位于文件末尾:

enter image description here

(图片来自维基百科

幸运的是,只要您仅添加文件,zipfile确实不会进行任何搜索。

这是我想出的代码。一些说明:

  • 我正在使用此代码来压缩一堆JPEG图片。没有必要对它们进行压缩,我仅使用ZIP作为容器。
  • 内存使用量为O(最大文件大小),而不是O(存档大小)。对我来说已经足够好了:许多相对较小的文件加起来可能会形成巨大的存档
  • 此代码不设置Content-Length标题,因此用户无法获得良好的进度指示。如果已知所有文件的大小,则应该可以提前计算出这个值。
  • 像这样直接向用户提供ZIP意味着下载时无法恢复。

所以,这就是:

import zipfile

class ZipBuffer(object):
    """ A file-like object for zipfile.ZipFile to write into. """

    def __init__(self):
        self.data = []
        self.pos = 0

    def write(self, data):
        self.data.append(data)
        self.pos += len(data)

    def tell(self):
        # zipfile calls this so we need it
        return self.pos

    def flush(self):
        # zipfile calls this so we need it
        pass

    def get_and_clear(self):
        result = self.data
        self.data = []
        return result

def generate_zipped_stream():
    sink = ZipBuffer()
    archive = zipfile.ZipFile(sink, "w")
    for filename in ["file1.txt", "file2.txt"]:
        archive.writestr(filename, "contents of file here")
        for chunk in sink.get_and_clear():
            yield chunk

    archive.close()
    # close() generates some more data, so we yield that too
    for chunk in sink.get_and_clear():
        yield chunk

def my_django_view(request):
    response = HttpResponse(generate_zipped_stream(), mimetype="application/zip")
    response['Content-Disposition'] = 'attachment; filename=archive.zip'
    return response

5

这是一个简单的Django视图函数,它可以将/tmp目录中的所有可读文件压缩成zip文件并返回。

from django.http import HttpResponse
import zipfile
import os
from cStringIO import StringIO # caveats for Python 3.0 apply

def somezip(request):
    file = StringIO()
    zf = zipfile.ZipFile(file, mode='w', compression=zipfile.ZIP_DEFLATED)
    for fn in os.listdir("/tmp"):
        path = os.path.join("/tmp", fn)
        if os.path.isfile(path):
            try:
                zf.write(path)
            except IOError:
                pass
    zf.close()
    response = HttpResponse(file.getvalue(), mimetype="application/zip")
    response['Content-Disposition'] = 'attachment; filename=yourfiles.zip'
    return response

当然,这种方法只有在压缩文件可以方便地放入内存时才有效 - 如果不行,你就必须使用磁盘文件(这正是你想避免的)。在这种情况下,你只需将 file = StringIO() 替换为 file = open('/path/to/yourfiles.zip', 'wb') 并将 file.getvalue() 替换为读取磁盘文件内容的代码。


2

您正在使用的zip库是否允许输出到流中?您可以直接向用户流式传输,而不是先将其临时写入zip文件,然后再流式传输给用户。


我认为这可能是他在问什么。 - Travis
它允许使用类似文件的对象。一个可以拥有像缓冲流一样的文件对象——请看我的回答! - Pēteris Caune

0

可以将迭代器传递给HttpResponse的构造函数(请参阅文档)。这将允许您创建一个自定义迭代器,按请求生成数据。但是,我认为这不适用于zip文件(您必须在创建时发送部分zip)。

我认为正确的方法是在单独的进程中离线创建文件。然后用户可以监视进度,然后在准备好时下载文件(可能使用上述描述的迭代器方法)。这类似于像YouTube这样的网站在上传文件并等待其处理时使用的方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接