在写 flask 后端的时候,特别是在作数据相关的操做的时候,产品每每须要咱们作一个导出数据的需求,通常都是导出 excel 格式的文件。前端
那在 flask 上,如何实现请求链接便可让浏览器下载呢?有两种思路。python
一:flask
文件在本地磁盘,这时候咱们只须要发送相应的地址过去便可。后端
二:浏览器
经过 io 中的 BytesIO, 把文件以二进制的形式发送过去,这里咱们须要使用 flask 自带的 send_file。app
第一种的坏处在于不便于权限控制,拿到下载连接在哪都能下载,第二种方法的缺陷在于只能接收 get 请求,post 请求发送的文件浏览器是不能识别的。异步
要实现 send_file, 是很容易的, 代码以下(适用于 python 3):post
import xlsxwriter from io import BytesIO from flask import Flask, send_file app = Flask(__name__) @app.route("/download", methods=["GET"]) def download(): out = BytesIO() workbook = xlsxwriter.Workbook(out) table = workbook.add_worksheet() table.write(0, 0, "name") table.write(0, 1, "age") workbook.close() out.seek(0) return send_file(out, as_attachment=True, attachment_filename="dream.xlsx") if __name__ == "__main__": app.run(debug=True)
这是一个完整的后端程序,可以直接跑起来。测试
其中咱们用到了 xlsxwriter 这个库,用来生成一个 excel 文件, 直接传给 BytesIO() 成数据流的形式发出去,浏览器接收到这些数据流,回自动进行下载,文件名便是 send_file 参数中的 attachment_filename, 在咱们这里即是 dream.xlsx 。编码
启动程序,在浏览器中输入 127.0.0.1:5000/download, 便可下载名为 dream.xlsx 的文件。
咱们打开看看:
的确是咱们生成的一个 excel 表格。
如今问题来了,这里的文件名是英文的,那咱们须要中文怎么办?直接把 attachment_filename 参数改为 attachment_filename="测试表格.xlsx"能够么?
咱们来试试:
return send_file(out, as_attachment=True, attachment_filename="测试表格.xlsx")
其他代码不变,仅有此处发成改变。
运行代码,浏览器访问下载试试。
浏览器没有任何反应,表明咱们没有把数据流传给它,看程序,也报错了,报错信息:
UnicodeEncodeError: 'latin-1' codec can't encode characters in position 43-46: ordinal not in range(256)
编码问题。
解决办法以下:
import xlsxwriter from io import BytesIO from flask import Flask, send_file from urllib.parse import quote app = Flask(__name__) @app.route("/download", methods=["GET"]) def download(): out = BytesIO() workbook = xlsxwriter.Workbook(out) table = workbook.add_worksheet() table.write(0, 0, "name") table.write(0, 1, "age") workbook.close() out.seek(0) filename = quote("测试表格.xlsx") rv = send_file(out, as_attachment=True, attachment_filename=filename) rv.headers['Content-Disposition'] += "; filename*=utf-8''{}".format(filename) return rv if __name__ == "__main__": app.run(debug=True)
咱们从 urllib.parse 引入 quote, 首先对文件名进行编码,而后 send_file 中 做为 attachment_filename 的参数,这时候能成功下载文件,可是文件名是编码后的名字,要解码的话,咱们须要在 headers 里面声明编码格式,即:
rv.headers['Content-Disposition'] += "; filename*=utf-8''{}".format(filename)
这样的话,对文件名进行 UTF-8 解码,咱们的文件名就是中文了。
如图:
打开文件,也是咱们想要的,如图:
大功告成!
固然实际生产工做中,数据量是是很是大的,生成 excel 文件将会特别耗时,咱们固然不但愿咱们的程序在此堵塞, 这时候咱们可使用 celery 异步任务,返回前端一个任务 ID, 前端去轮询这个任务 ID,当文件生成好了,便可开始下载。
因为有些时候咱们 get 请求没法知足咱们的参数传递,好比有多个嵌套对象做为参数传递,咱们必须使用 post 请求,这时候一样能够采用 celery 异步任务的方式,返回任务 ID, 轮询任务状态,下载文件。
以后我会写一篇教程,celery 异步任务。