You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django临时目录存图生成zip下载报错排查及实现

Django实现图片打包ZIP下载的报错排查与实现方案

报错根因说明

  • IOError: [Errno 21] Is a directory: '/home/project/images':触发原因是代码直接调用open()操作目录路径。open()仅支持对文件执行读写,无法直接打开目录;同时初始代码存在两处本质逻辑错误:一是将图片URL字符串直接写入文件,并未存储实际图片二进制内容;二是直接将图片URL作为本地路径传入zipfile.write(),压缩模块无法读取不存在的本地文件。
  • OSError: [Errno 2] No such file or directory: '0':触发原因是遍历目录获取到的仅为文件名,未拼接目录的完整绝对路径时,压缩模块会在程序运行的当前工作目录下查找对应文件,无法定位到临时目录内的存储文件;同时该版本代码依然存在直接写入URL字符串、未保存实际图片内容的问题。
  • AttributeError: 'HttpResponse' object has no attribute 'seek':触发原因是Python内置zipfile.ZipFile默认要求传入支持随机读写(即实现seek方法)的类文件对象,而Django的HttpResponse是单向输出的流对象,不支持随机读写,直接传入作为压缩包写入目标就会抛出该错误。

正确实现逻辑

完整流程分为5步:

  1. 创建临时存储目录,配置目录已存在的兼容逻辑,避免重复创建报错
  2. 遍历图片列表,区分网络URL/本地文件路径,读取图片实际二进制内容写入临时目录,文件保留对应图片后缀
  3. 生成ZIP压缩包时,不要直接写入HttpResponse对象,选择临时磁盘文件或内存字节流作为压缩包写入载体
  4. 压缩完成后读取压缩包二进制内容,写入HttpResponse返回,同时配置正确的下载响应头
  5. 流程结束后自动清理临时目录、临时压缩包,避免服务器残留垃圾文件

磁盘临时文件版本实现(兼容大文件场景)

import os
import zipfile
import shutil
from django.http import HttpResponse
# 处理网络图片时需要引入requests,仅处理本地图片可注释该行
import requests

# 替换为项目实际的BASE_DIR配置
from django.conf import settings
BASE_DIR = settings.BASE_DIR

def download_images_zip(request):
    # 替换为实际的图片地址列表,支持本地绝对路径、http/https网络URL
    images_list = [image_absolute_url1, image_absolute_url2]
    temp_folder = os.path.join(BASE_DIR, "temp_images_store")
    # 创建临时目录,exist_ok=True兼容目录已存在的场景
    os.makedirs(temp_folder, exist_ok=True)
    temp_zip_path = os.path.join(BASE_DIR, "temp_pack.zip")

    try:
        # 写入所有图片到临时目录
        for idx, img_path in enumerate(images_list):
            # 提取图片后缀,过滤URL参数
            if "." in img_path:
                file_suffix = img_path.split(".")[-1].split("?")[0]
            else:
                file_suffix = "jpg"
            save_file_path = os.path.join(temp_folder, f"图片_{idx+1}.{file_suffix}")
            
            # 读取图片二进制内容
            if img_path.startswith(("http://", "https://")):
                img_resp = requests.get(img_path, timeout=15)
                img_content = img_resp.content
            else:
                with open(img_path, "rb") as img_f:
                    img_content = img_f.read()
            
            # 写入临时文件
            with open(save_file_path, "wb") as f:
                f.write(img_content)
        
        # 生成ZIP压缩包
        with zipfile.ZipFile(temp_zip_path, "w", zipfile.ZIP_DEFLATED) as zip_f:
            for filename in os.listdir(temp_folder):
                file_full_path = os.path.join(temp_folder, filename)
                # 第二个参数指定压缩包内的文件名,避免打包本地绝对路径
                zip_f.write(file_full_path, filename)
        
        # 读取压缩包返回响应
        with open(temp_zip_path, "rb") as zip_f:
            response = HttpResponse(zip_f.read(), content_type="application/zip")
        # 配置下载头,指定用户下载时的默认文件名
        response["Content-Disposition"] = 'attachment; filename="图片打包.zip"'
        return response
    finally:
        # 无论流程成功或报错,都清理临时文件
        if os.path.exists(temp_folder):
            shutil.rmtree(temp_folder)
        if os.path.exists(temp_zip_path):
            os.remove(temp_zip_path)

内存流版本实现(小文件场景性能更优)

如果图片总大小不超过100M,可直接在内存中完成压缩,减少磁盘IO开销,只需替换上述代码中生成ZIP和返回响应的部分即可:

from io import BytesIO

# 图片写入临时目录的逻辑和上述版本一致,省略重复部分
# 生成ZIP时使用内存字节流作为载体
buffer = BytesIO()
with zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED) as zip_f:
    for filename in os.listdir(temp_folder):
        file_full_path = os.path.join(temp_folder, filename)
        zip_f.write(file_full_path, filename)
# 将流指针移动到起始位置,才能读取完整内容
buffer.seek(0)
response = HttpResponse(buffer.read(), content_type="application/zip")
response["Content-Disposition"] = 'attachment; filename="图片打包.zip"'

注意事项

  • 所有图片、压缩包的读写操作都要使用二进制模式(rb/wb),避免文件内容编码错误导致图片损坏、压缩包无法打开
  • 写入压缩包时必须指定第二个参数(压缩包内的文件名),否则会将服务器本地的完整绝对路径打包进ZIP,用户解压后会看到多层嵌套的系统目录
  • 临时文件清理逻辑必须放在*finally块*中执行,避免接口报错时临时文件残留,长期运行占满服务器磁盘
  • 处理网络图片时要加超时时间、异常捕获,避免单个图片下载失败导致整个接口报错

内容的提问来源于stack exchange,提问作者D_P

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 03:57:23