【问题标题】:Serving dynamically generated ZIP archives in Django在 Django 中提供动态生成的 ZIP 档案
【发布时间】:2010-09-09 05:00:23
【问题描述】:

如何在 Django 中为用户提供动态生成的 ZIP 存档?

我正在创建一个网站,用户可以在其中选择可用书籍的任意组合并将其下载为 ZIP 存档。我担心为每个请求生成这样的档案会减慢我的服务器速度。我还听说 Django 目前没有很好的解决方案来提供动态生成的文件。

【问题讨论】:

    标签: python django


    【解决方案1】:

    解决方法如下。

    使用 Python 模块 zipfile 创建 zip 存档,但作为文件指定 StringIO 对象(ZipFile 构造函数需要类似文件的对象)。添加要压缩的文件。然后在您的 Django 应用程序中返回 HttpResponse 中 StringIO 对象的内容,并将 mimetype 设置为 application/x-zip-compressed(或至少 application/octet-stream)。如果你愿意,你可以设置content-disposition 标头,但这不是必须的。

    但请注意,为每个请求创建 zip 存档是个坏主意,这可能会导致您的服务器崩溃(如果存档很大,则不计算超时)。性能方面的方法是将生成的输出缓存在文件系统中的某处,并仅在源文件发生更改时重新生成它。更好的办法是提前准备存档(例如,通过 cron 作业)并让您的网络服务器像往常一样为它们提供静态服务。

    【讨论】:

    • StringIO 将在 Python 3.0 中消失,因此您可能需要相应地将代码括起来。
    • 并没有消失,只是移到了io模块。 docs.python.org/3.0/library/io.html#io.StringIO
    • 想一想,既然你已经手动创建了一个HttpResponse,你能不能把它用作缓冲区?我的意思是把回复传递给zipfile,让它直接写进去。我已经用其他东西做了。如果您正在处理大量流,它可能会更快,内存效率更高。
    • @Oli 会很好,但 ZipFile 需要 f.seek() 而 HttpResponse 不支持
    【解决方案2】:

    这是执行此操作的 Django 视图:

    import os
    import zipfile
    import StringIO
    
    from django.http import HttpResponse
    
    
    def getfiles(request):
        # Files (local path) to put in the .zip
        # FIXME: Change this (get paths from DB etc)
        filenames = ["/tmp/file1.txt", "/tmp/file2.txt"]
    
        # Folder name in ZIP archive which contains the above files
        # E.g [thearchive.zip]/somefiles/file2.txt
        # FIXME: Set this to something better
        zip_subdir = "somefiles"
        zip_filename = "%s.zip" % zip_subdir
    
        # Open StringIO to grab in-memory ZIP contents
        s = StringIO.StringIO()
    
        # The zip compressor
        zf = zipfile.ZipFile(s, "w")
    
        for fpath in filenames:
            # Calculate path for file in zip
            fdir, fname = os.path.split(fpath)
            zip_path = os.path.join(zip_subdir, fname)
    
            # Add file, at correct path
            zf.write(fpath, zip_path)
    
        # Must close zip for all contents to be written
        zf.close()
    
        # Grab ZIP file from in-memory, make response with correct MIME-type
        resp = HttpResponse(s.getvalue(), mimetype = "application/x-zip-compressed")
        # ..and correct content-disposition
        resp['Content-Disposition'] = 'attachment; filename=%s' % zip_filename
    
        return resp
    

    【讨论】:

    • 在本例中不需要,但一般情况下,请确保根据需要引用和转义 content-disposition 标头中的文件名。例如,如果文件名中有空格,大多数浏览器将只使用文件名空格之前的部分(例如,attachment; filename=Test File.zip 保存为Test。)
    • @MikeDeSimone 好点。有没有一种好方法可以为这种上下文转义文件名?
    • 对于 django 版本 > 1.7 使用 content_type 而不是 mimetype
    • 对于二进制文件,我可以用b = BytesIO.BytesIO() 替换它吗?
    【解决方案3】:

    这里的许多答案建议使用StringIO 或BytesIO 缓冲区。但是这不是必需的,因为 HttpResponse 已经是一个类似文件的对象:

    response = HttpResponse(content_type='application/zip')
    zip_file = zipfile.ZipFile(response, 'w')
    for filename in filenames:
        zip_file.write(filename)
    response['Content-Disposition'] = 'attachment; filename={}'.format(zipfile_name)
    return response
    

    【讨论】:

    • 简单多了!
    • 记得拨打zip_file.close()
    • 是的,这好多了!
    • 我认为的最佳答案,
    • 还有FileResponse
    【解决方案4】:

    我使用过 Django 2.0 和 Python 3.6。

    import zipfile
    import os
    from io import BytesIO
    
    def download_zip_file(request):
        filelist = ["path/to/file-11.txt", "path/to/file-22.txt"]
    
        byte_data = BytesIO()
        zip_file = zipfile.ZipFile(byte_data, "w")
    
        for file in filelist:
            filename = os.path.basename(os.path.normpath(file))
            zip_file.write(file, filename)
        zip_file.close()
    
        response = HttpResponse(byte_data.getvalue(), content_type='application/zip')
        response['Content-Disposition'] = 'attachment; filename=files.zip'
    
        # Print list files in zip_file
        zip_file.printdir()
    
        return response
    

    【讨论】:

    • 嘿,我有相同的目标要做,但不是文件列表,我有多个图像 url,需要下载和压缩,然后作为响应给出,知道如何流式传输,我的意思是我有一个工作代码,我需要做的就是使用请求来获取图像并将其写入 BytesIO,然后写入 zip_file,但是如果图像很大,则下载需要花费太多时间然后超时.任何帮助都很好。 TY
    • 这是一个糟糕的答案。您正在将整个 zipfile 加载到内存中。想象一个 10GB 的文件
    • 如果您正在处理少量文件,这是一个很好的答案
    【解决方案5】:

    对于 python3,我使用 io.ByteIO,因为不推荐使用 StringIO 来实现这一点。希望对您有所帮助。

    import io
    
    def my_downloadable_zip(request):
        zip_io = io.BytesIO()
        with zipfile.ZipFile(zip_io, mode='w', compression=zipfile.ZIP_DEFLATED) as backup_zip:
            backup_zip.write('file_name_loc_to_zip') # u can also make use of list of filename location
                                                     # and do some iteration over it
         response = HttpResponse(zip_io.getvalue(), content_type='application/x-zip-compressed')
         response['Content-Disposition'] = 'attachment; filename=%s' % 'your_zipfilename' + ".zip"
         response['Content-Length'] = zip_io.tell()
         return response
    

    【讨论】:

    • 使用这样的代码,我无法正确命名文件。目前,它只是一个看起来像 UUID 的随机字符串。
    【解决方案6】:

    Django 不直接处理动态内容(特别是 Zip 文件)的生成。这项工作将由 Python 的标准库完成。大家可以看看如何在Python中动态创建Zip文件here。

    如果您担心它会降低您的服务器速度,如果您希望有许多相同的请求,您可以缓存这些请求。你可以使用 Django 的cache framework 来帮助你。

    总体而言,压缩文件可能会占用大量 CPU,但 Django 不应该比其他 Python Web 框架慢。

    【讨论】:

      【解决方案7】:

      无耻插件:您可以将django-zipview用于相同目的。

      pip install django-zipview之后:

      from zipview.views import BaseZipView
      
      from reviews import Review
      
      
      class CommentsArchiveView(BaseZipView):
          """Download at once all comments for a review."""
      
          def get_files(self):
              document_key = self.kwargs.get('document_key')
              reviews = Review.objects \
                  .filter(document__document_key=document_key) \
                  .exclude(comments__isnull=True)
      
              return [review.comments.file for review in reviews if review.comments.name]
      

      【讨论】:

        【解决方案8】:

        我建议使用单独的模型来存储这些临时 zip 文件。您可以即时创建 zip,使用文件字段保存到模型,最后将 url 发送给用户。

        优点:

        • 使用 django 媒体机制提供静态 zip 文件(就像通常的上传一样)。
        • 能够通过定期执行 cron 脚本来清理陈旧的 zip 文件(可以使用 zip 文件模型中的日期字段)。

        【讨论】:

          【解决方案9】:

          已经对这个主题做出了很多贡献,但是由于我在第一次研究这个问题时遇到了这个帖子,所以我想我应该添加自己的两分钱。

          集成您自己的 zip 创建可能不如 Web 服务器级解决方案强大和优化。同时,我们使用的是 Nginx,它没有开箱即用的模块。

          但是,您可以使用 mod_zip 模块编译 Nginx(请参阅 here 获取具有最新稳定 Nginx 版本的 docker 映像,以及使其小于默认 Nginx 映像的 alpine 基础)。这会添加 zip 流功能。

          然后 Django 只需要提供一个要压缩的文件列表,就完成了! 为这个文件列表响应使用一个库更可重用,而 django-zip-stream 提供了这一点。

          遗憾的是,它从来没有真正对我有用,所以我创建了一个 fork 并进行了修复和改进。

          您可以在几行中使用它:

          def download_view(request, name=""):
              from django_zip_stream.responses import FolderZipResponse
              path = settings.STATIC_ROOT
              path = os.path.join(path, name)
          
              return FolderZipResponse(path)
          

          您需要一种方法让 Nginx 为您要归档的所有文件提供服务,但仅此而已。

          【讨论】:

            【解决方案10】:

            你不能只写一个“压缩服务器”的链接或诸如此类的东西吗?为什么需要从 Django 提供 zip 存档本身?一个 90 年代时代的 CGI 脚本来生成一个 zip 并将其发送到 stdout 确实是这里所需要的,至少据我所见。

            【讨论】:

              猜你喜欢
              • 2010-11-07
              • 1970-01-01
              • 2016-08-24
              • 1970-01-01
              • 2014-01-17
              • 1970-01-01
              • 1970-01-01
              • 2015-05-29
              • 1970-01-01
              相关资源
              最近更新 更多