在Flask/Django中增加下載Excel的功能


又好久沒寫博客了,因為公司在做的東西涉及到業務方面的比較多,沒法寫。
最近在做下載功能,在網上能找到很多例子,但是都不太好用,自己半研究半照抄,終於搞出來了能用的東西。所以覺得應該記錄一下。
 
下載什么呢?下載Excel。我所維護的幾個系統里,有一些數據,需要在頁面上導出。以前的做法,我都是用定時任務提前把要下載的Excel生成好,保存在服務器的某個位置,但是這樣做似乎太傻了。
於是我現在用的是這樣一種方法:在生成Excel的時候,最后保存為字節流,而不是一個文件;然后,在框架的response中,設置header,使返回的數據直接是下載類型的。
這樣,前端只要直接調用這個接口,就能返回一個可以下載的字節流數據,而下載完成后,保存下來的就是一個Excel了。
 
那么具體要怎么做呢?
首先還是生成Excel。這里我使用的是openpyxl。我之前試了一下用xlwt,其實使用起來也是蠻方便的,並不比openpyxl難用,但是有個致命的弱點:生成的xls文件,最大支持的行數為65535行。而我業務上要生成的Excel,動輒就是十萬行起(攤手。xlsx格式的Excel是能夠支持到一百萬行的,所以也就沒什么好說的了。
 
下面我就分成Flask和Django兩個版本來介紹一下,下載功能應該怎么做。
 
 
1、Flask版本
 
生成Excel的部分:
 
wb = Workbook()
ws = wb.active
# 首行列名寫入excel
for i, t in enumerate(title):
    ws.cell(row=1, column=(i + 1)).value = t[1]
# 數據部分寫入excel
title_fields = [t[0] for t in title]
for i, _data in enumerate(data):
    one_row = [_data[t] for t in title_fields]
    for j, d in enumerate(one_row):
        ws.cell(row=(i + 2), column=(j + 1)).value = d

 

這里我是做成了一個通用的寫Excel的方法:
data是一個字典,格式  {"key1": "value1", "key2": "value2", ...}
title是一個二維數組,格式  [("key1": "第一列"), ("key2": "第二列"), ...]
這樣能保證title寫入excel的時候保證跟想要的順序一致。
 
接下來就到了重點了:一般在保存Excel的時候,我們會用
 
wb.save(filename)

 

而這里我不是這樣用的,我是將它保存為一個字節流:
 
sio = BytesIO()
wb.save(sio)

 

接下來就是將這個流返回到瀏覽器端下載:
 
response = Response()
response.headers.add("Content-Type", "application/vnd.ms-excel")
response.headers.add('Content-Disposition', 'attachment', filename=filename.encode("utf-8").decode("latin1"))
sio.seek(0)
response.data = sio.getvalue()
return response

 

要注意的是,這里面的filename有一個小小的尬點:在flask框架中,header里面的文件名會用latin1編碼(flask框架的代碼是這么寫的):
 
 
這就有點尷尬,我正常的filename如果有中文,並且不經編碼,在這里就會報錯——沒錯,就算是python3,中文編碼一樣能惡心你。
而解決方法,就像上面寫的那樣,先encode成UTF-8編碼,然后再decode成latin1。后面的事情就讓框架去做吧。
這樣就可以了。當我們去下載的時候,在前端調用這個鏈接,就能自動下載Excel了,而我們的服務器上,也用不着傻傻地保存一份。
 
 
完整的代碼是這樣的:
 
app.py
from flask import Flask
from excel import generate_excel

app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello World!'


@app.route('/excel')
def download():
    data = [
        {"key1": 1, "key2": 2, "key3": 3},
        {"key1": 11, "key2": 22, "key3": 33},
        {"key1": 111, "key2": 222, "key3": 333},
        {"key1": 1111, "key2": 2222, "key3": 3333},
        {"key1": 11111, "key2": 22222, "key3": 33333},
    ]
    title = [("key1", "第一列"), ("key2", "第二列"), ("key3", "第三列")]
    filename = "測試Excel.xlsx"
    return generate_excel(title, data, filename)


if __name__ == '__main__':
    app.run()

 

excel.py
from io import BytesIO
from openpyxl import Workbook
from flask import Response


def generate_excel(title, data, filename):
    wb = Workbook()
    ws = wb.active

    # 首行列名寫入excel
    for i, t in enumerate(title):
        ws.cell(row=1, column=(i + 1)).value = t[1]
    # 數據部分寫入excel
    title_fields = [t[0] for t in title]
    for i, _data in enumerate(data):
        one_row = [_data[t] for t in title_fields]
        for j, d in enumerate(one_row):
            ws.cell(row=(i + 2), column=(j + 1)).value = d

    # 傳給save函數的不是保存文件名,而是BytesIO流
    sio = BytesIO()
    wb.save(sio)

    response = Response()
    response.headers.add("Content-Type", "application/vnd.ms-excel")
    response.headers.add('Content-Disposition', 'attachment', filename=filename.encode("utf-8").decode("latin1"))
    sio.seek(0)
    response.data = sio.getvalue()
    return response

 

 

2、Django版本

 
如果用django的話,思路是一致的,只不過在實現方面有點出入。在返回流到瀏覽器下載這部分,django的寫法是這樣的:
 
from django.http import HttpResponse
from django.utils.encoding import escape_uri_path
 
# ...從生成excel到wb.save(sio)都是一樣的
 
response = HttpResponse()
response["Content-Type"] = "application/vnd.ms-excel"
response["Content-Disposition"] = "attachment; filename*=UTF-8''%s" % escape_uri_path(filename)
# 保存流
sio.seek(0)
response.write(sio.getvalue())
return response

 

其他部分就不貼了。django代碼比較繁瑣,全貼出來也沒什么意義。
使用django的話,其實還有專門的StreamingHttpResponse和FileResponse模塊(FileResponse還是從StreamingHttpResponse繼承來的),理論上來說應該能更方便,不過我沒有嘗試。畢竟我懶。
 
OK那就這樣。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM