使用Python将多个CSV文件发送到ZIP而无需存储到磁盘

11

我正在为我的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个回答

16

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)

2
非常完整和全面,感谢您包含BytesIO信息以备将来之需!这种方法曾经在我脑海中出现过,但由于我认为content_type是将文件标识为.csv的方式,所以我认为这是不可能的。我猜您以您的方式编写扩展名就可以解决问题了。谢谢!我还需要等待几个小时才能授予赏金。 - Jamie Counsell
1
很高兴能够帮助! :) - dwlz
@DanLoewenherz,请问"files"在这里是什么意思,i, file in enumerate(files),它包含了什么? - snehil singh

2

stdlib 包含模块 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可能有些混淆,但希望您能理解。


1
我建议使用cStringIO来提高性能,因为它完全是用C语言编写的,而不是Python。它已经包含在Python本地库中,因此它应该使用更少的内存开销。 - denisvm

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文件。默认压缩值为ZIP_STORED,它将创建未经压缩的zip文件。


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