在Python中将多个.CSV文件发送到.ZIP而不存储到磁盘

12 投票
3 回答
8621 浏览
提问于 2025-04-18 15:31

我正在为我的Django网站开发一个报告应用程序。我想运行几个报告,并让每个报告在内存中生成一个.csv文件,这样可以批量下载为一个.zip文件。我希望在这个过程中不把任何文件存储到硬盘上。目前,为了生成一个单独的.csv文件,我按照常见的操作进行:

mem_file = StringIO.StringIO()
writer = csv.writer(mem_file)
writer.writerow(["My content", my_value])
mem_file.seek(0)
response = HttpResponse(mem_file, content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename=my_file.csv'

这个方法很好用,但只适用于单个未压缩的.csv文件。如果我有一个用StringIO流创建的.csv文件列表,比如:

firstFile = StringIO.StringIO()
# write some data to the file

secondFile = StringIO.StringIO()
# write some data to the file

thirdFile = StringIO.StringIO()
# write some data to the file

myFiles = [firstFile, secondFile, thirdFile]

我该如何返回一个压缩文件,里面包含myFiles中的所有对象,并且可以正确解压出三个.csv文件呢?

3 个回答

1
def zipFiles(files):
    outfile = StringIO() # io.BytesIO() for python 3
    with zipfile.ZipFile(outfile, 'w') as zf:
        for n, f in enumarate(files):
            zf.writestr("{}.csv".format(n), f.getvalue())
    return outfile.getvalue()

zipped_file = zip_files(myfiles)
response = HttpResponse(zipped_file, content_type='application/octet-stream')
response['Content-Disposition'] = 'attachment; filename=my_file.zip'

StringIO 有一个叫做 getvalue 的方法,它可以返回里面的所有内容。你可以通过 zipfile.ZipFile(outfile, 'w', zipfile.ZIP_DEFLATED) 来压缩文件。默认的压缩方式是 ZIP_STORED,这意味着创建的压缩文件不会进行压缩。

2

标准库中有一个叫做 zipfile 的模块,里面有一个主要的类叫 ZipFile,这个类可以接收一个文件或者类似文件的对象:

from zipfile import ZipFile
temp_file = StringIO.StringIO()
zipped = ZipFile(temp_file, 'w')

# create temp csv_files = [(name1, data1), (name2, data2), ... ]

for name, data in csv_files:
    data.seek(0)
    zipped.writestr(name, data.read())

zipped.close()

temp_file.seek(0)

# etc. etc.

我不是 StringIO 的用户,所以可能对 seekread 的用法不太准确,但希望你能明白我的意思。

18

zipfile 是一个标准库模块,正好可以满足你的需求。对于你的情况,关键是一个叫做 "writestr" 的方法,它需要你提供一个文件名和你想要压缩的数据。

在下面的代码中,我使用了一种顺序命名的方式来处理解压后的文件,但你可以根据自己的需要更改这个命名方式。

import zipfile
import StringIO

zipped_file = StringIO.StringIO()
with zipfile.ZipFile(zipped_file, 'w') as zip:
    for i, file in enumerate(files):
        file.seek(0)
        zip.writestr("{}.csv".format(i), file.read())

zipped_file.seek(0)

如果你想让你的代码更具前瞻性(提示一下,Python 3),你可能想用 io.BytesIO 来替代 StringIO,因为 Python 3 更注重字节处理。另一个好处是,使用 io.BytesIO 时,在读取之前不需要明确地进行定位(我没有测试过这个在 Django 的 HttpResponse 中的表现,所以我还是把最后的定位保留在那儿,以防万一)。

import io
import zipfile

zipped_file = io.BytesIO()
with zipfile.ZipFile(zipped_file, 'w') as f:
    for i, file in enumerate(files):
        f.writestr("{}.csv".format(i), file.getvalue())

zipped_file.seek(0)

撰写回答