在Python中将多个.CSV文件发送到.ZIP而不存储到磁盘
我正在为我的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 个回答
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
,这意味着创建的压缩文件不会进行压缩。
标准库中有一个叫做 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
的用户,所以可能对 seek
和 read
的用法不太准确,但希望你能明白我的意思。
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)