创建可立即下载的压缩文件

5 投票
5 回答
6363 浏览
提问于 2025-04-15 12:14

我正在开发一个网页应用,用户可以把一个装满文件的文件夹打包成一个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)

但是,这样做有个不太好的副作用,就是用户在下载这个zip文件的时候需要等很长时间(超过10秒)。有没有办法避免这个等待呢?比如,不把压缩文件保存到服务器上,而是直接把它发送给用户?

我觉得torrentflux就提供了我说的这种功能,能够把几GB的数据压缩后在一秒钟内下载到用户的电脑上。

5 个回答

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

当然,这种方法只有在zip文件可以方便地放进内存时才有效。如果文件太大,放不下,那你就得使用磁盘文件(虽然你可能不想这样)。在这种情况下,你只需要把file = StringIO()替换成file = open('/path/to/yourfiles.zip', 'wb'),然后把file.getvalue()替换成读取磁盘文件内容的代码。

9

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

幸运的是,ZIP格式的特点是可以在一次操作中创建归档文件,中央目录记录位于文件的最后:

在这里输入图片描述

(图片来自维基百科)

而且,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

撰写回答