依賴子產品
xlwt下載下傳:pip install xlwt
背景子產品
view.py
# 導出Excel檔案
def export_excel(request):
city = request.POST.get('city')
print(city)
list_obj=place.objects.filter(city=city)
# 設定HTTPResponse的類型
response = HttpResponse(content_type='application/vnd.ms-excel')
response['Content-Disposition'] = 'attachment;filename='+city+'.xls'
"""導出excel表"""
if list_obj:
# 建立工作簿
ws = xlwt.Workbook(encoding='utf-8')
# 添加第一頁資料表
w = ws.add_sheet('sheet1') # 建立sheet(sheet的名稱為"sheet1")
# 寫入表頭
w.write(0, 0, u'地名')
w.write(0, 1, u'次數')
w.write(0, 2, u'經度')
w.write(0, 3, u'緯度')
# 寫入資料
excel_row = 1
for obj in list_obj:
name = obj.place
sum = obj.sum
lng = obj.lng
lat = obj.lat
# 寫入每一行對應的資料
w.write(excel_row, 0, name)
w.write(excel_row, 1, sum)
w.write(excel_row, 2, lng)
w.write(excel_row, 3, lat)
excel_row += 1
# 寫出到IO
output = BytesIO()
ws.save(output)
# 重新定位到開始
output.seek(0)
response.write(output.getvalue())
return response
複制
前端子產品
<button id=”export_excel” type=”button” class=”btn btn-primary col-sm-5″ style=”margin-left: 10px” 導出excel</button
$("#export_excel").click(function () {
var csrf=$('input[name="csrfmiddlewaretoken"]').val();
const req = new XMLHttpRequest();
req.open('POST', '/export_excel/', true);
req.responseType = 'blob';
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); //設定請求頭
req.send('city='+$('#city').val()+"&&csrfmiddlewaretoken="+csrf); //輸入參數
req.onload = function() {
const data = req.response;
const a = document.createElement('a');
const blob = new Blob([data]);
const blobUrl = window.URL.createObjectURL(blob);
download(blobUrl) ;
};
});
複制
function download(blobUrl) {
var city = $("input[name='city']").val();
const a = document.createElement('a');
a.style.display = 'none';
a.download = '<檔案命名 ';
a.href = blobUrl;
a.click();
document.body.removeChild(a);
}
複制
補充知識:Python Django實作MySQL百萬、千萬級的資料量下載下傳:解決memoryerror、nginx time out
前文
在用Django寫項目的時候時常需要提供檔案下載下傳的功能,而Django也是貼心提供了幾種方法:FileResponse、StreamingHttpResponse、HttpResponse,其中FileResponse和StreamingHttpResponse都是使用疊代器疊代生成資料的方法,是以适合傳輸檔案比較大的情況;而HttpResponse則是直接取得資料傳回給使用者,是以容易造成memoryerror和nginx time out(一次性取得資料和傳回的資料過多,導緻nginx逾時或者記憶體不足),關于這三者,DJango的官網也是寫的非常清楚,連接配接如下:https://docs.djangoproject.com/en/1.11/ref/request-response/
那正常我們使用的是FileResponse和StreamingHttpResponse,因為它們流式傳輸(疊代器)的特點,可以使得資料一條條的傳回給用戶端,檔案随時中斷和複傳,并且保持檔案的一緻性。
FileResponse和StreamingHttpResponse
FileResponse顧名思義,就是打開檔案然後進行傳輸,并且可以指定一次能夠傳輸的資料chunk。是以适用場景:從服務端傳回大檔案。缺點是無法實時擷取資料庫的内容并傳輸給用戶端。舉例如下:
def download(request):
file=open('path/demo.py','rb')
response =FileResponse(file)
response['Content-Type']='application/octet-stream'
response['Content-Disposition']='attachment;filename="demo.py"'
return response
複制
從上可以發現,檔案打開後作為參數傳入FileResponse,随後指定傳輸頭即可,但是很明顯用這個來傳輸資料庫就不太友善了,是以這邊推介用StreamingHttpResponse的方式來傳輸。
這裡就用PyMysql來取得資料,然後指定為csv的格式傳回,具體代碼如下:
# 通過pymysql取得資料
import pymysql
field_types = {
1: 'tinyint',
2: 'smallint',
3: 'int'} #用于後面的字段名比對,這裡省略了大多數
conn = pymysql.connect(host='127.0.0.1',port=3306,database='demo',user='root',password='root')
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
cursor.execute(sql)
#擷取所有資料
data = cursor.fetchall()
cols = {}
#擷取所有字段
for i,row in enumerate(self.cursor.description):
if row[0] in cols:
cols[str(i)+row[0]] = field_types.get(row[1], str(row[1])) #這裡的field_type是類型和數字的比對
cols[row[0]] = field_types.get(row[1], str(row[1]))
cursor.close()
conn.close()
#通過StreamingHttpResponse指定傳回格式為csv
response = StreamingHttpResponse(get_result_fromat(data, cols))
response['Content-Type'] = 'application/octet-stream'
response['Content-Disposition'] = 'attachment;filename="{0}"'.format(out_file_name)
return response
#循環所有資料,然後加到字段上傳回,注意的是要用疊代器來控制
def get_result_fromat(data, cols):
tmp_str = ""
# 傳回檔案的每一列列名
for col in cols:
tmp_str += '"%s",' % (col)
yield tmp_str.strip(",") + "\n"
for row in data:
tmp_str = ""
for col in cols:
tmp_str += '"%s",' % (str(row[col]))
yield tmp_str.strip(',') + "\n"
複制
整個代碼如上,大緻分為三部分:從mysql取資料,格式化成我們想要的格式:excel、csv、txt等等,這邊指定的是csv,如果對其他格式也有興趣的可以留言,最後就是用StreamingHttpResponse指定傳回的格式傳回。
實作百萬級資料量下載下傳
上面的代碼下載下傳可以支援幾萬行甚至十幾萬行的資料,但是如果超過20萬行以上的資料,那就比較困難了,我這邊的剩餘記憶體大概是1G的樣子,當超過15萬行資料(大概)的時候,就報memoryerror了,問題就是因為fetchall,雖然我們StreamingHttpResponse是一條條的傳回,但是我們的資料時一次性批量的取得!
如何解決?以下是我的解決方法和思路:
用fetchone來代替fetchall,疊代生成fetchone
發現還是memoryerror,因為execute是一次性執行,後來發現可以用流式遊标來代替原來的普通遊标,即SSDictCursor代替DictCursor
于是整個代碼需要修改的地方如下:
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) ===
cursor = conn.cursor(cursor=pymysql.cursors.SSDictCursor)
data = cursor.fetchall() ===
row = cursor.fetchone()
def get_result_fromat(data, cols):
tmp_str = ""
# 傳回檔案的每一列列名
for col in cols:
tmp_str += '"%s",' % (col)
yield tmp_str.strip(",") + "\n"
for row in data:
tmp_str = ""
for col in cols:
tmp_str += '"%s",' % (str(row[col]))
yield tmp_str.strip(',') + "\n"
=====
def get_result_fromat(data, cols):
tmp_str = ""
for col in cols:
tmp_str += '"%s",' % (col)
yield tmp_str.strip(",") + "\n"
while True:
tmp_str = ""
for col in cols:
tmp_str += '"%s",' % (str(row[col]))
yield tmp_str.strip(',') + "\n"
row = db.cursor.fetchone()
if row is None:
break
複制
可以看到就是通過while True來實作不斷地取資料下載下傳,有效避免一次性從MySQL取出記憶體不足報錯,又或者取得過久導緻nginx逾時!
總結
關于下載下傳就分享到這了,還是比較簡單的,謝謝觀看~希望能給大家一個參考。