在django项目中导出数据到excel文件并实现下载的功能
依赖模块
xlwt下载:pipinstallxlwt
后台模块
view.py
#导出Excel文件
defexport_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表"""
iflist_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
forobjinlist_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())
returnresponse
前端模块
导出excel
$("#export_excel").click(function(){
varcsrf=$('input[name="csrfmiddlewaretoken"]').val();
constreq=newXMLHttpRequest();
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(){
constdata=req.response;
consta=document.createElement('a');
constblob=newBlob([data]);
constblobUrl=window.URL.createObjectURL(blob);
download(blobUrl);
};
});
functiondownload(blobUrl){
varcity=$("input[name='city']").val();
consta=document.createElement('a');
a.style.display='none';
a.download='<文件命名>';
a.href=blobUrl;
a.click();
document.body.removeChild(a);
}
补充知识:PythonDjango实现MySQL百万、千万级的数据量下载:解决memoryerror、nginxtimeout
前文
在用Django写项目的时候时常需要提供文件下载的功能,而Django也是贴心提供了几种方法:FileResponse、StreamingHttpResponse、HttpResponse,其中FileResponse和StreamingHttpResponse都是使用迭代器迭代生成数据的方法,所以适合传输文件比较大的情况;而HttpResponse则是直接取得数据返回给用户,所以容易造成memoryerror和nginxtimeout(一次性取得数据和返回的数据过多,导致nginx超时或者内存不足),关于这三者,DJango的官网也是写的非常清楚,连接如下:https://docs.djangoproject.com/en/1.11/ref/request-response/
那正常我们使用的是FileResponse和StreamingHttpResponse,因为它们流式传输(迭代器)的特点,可以使得数据一条条的返回给客户端,文件随时中断和复传,并且保持文件的一致性。
FileResponse和StreamingHttpResponse
FileResponse顾名思义,就是打开文件然后进行传输,并且可以指定一次能够传输的数据chunk。所以适用场景:从服务端返回大文件。缺点是无法实时获取数据库的内容并传输给客户端。举例如下:
defdownload(request):
file=open('path/demo.py','rb')
response=FileResponse(file)
response['Content-Type']='application/octet-stream'
response['Content-Disposition']='attachment;filename="demo.py"'
returnresponse
从上可以发现,文件打开后作为参数传入FileResponse,随后指定传输头即可,但是很明显用这个来传输数据库就不太方便了,所以这边推介用StreamingHttpResponse的方式来传输。
这里就用PyMysql来取得数据,然后指定为csv的格式返回,具体代码如下:
#通过pymysql取得数据
importpymysql
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={}
#获取所有字段
fori,rowinenumerate(self.cursor.description):
ifrow[0]incols:
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)
returnresponse
#循环所有数据,然后加到字段上返回,注意的是要用迭代器来控制
defget_result_fromat(data,cols):
tmp_str=""
#返回文件的每一列列名
forcolincols:
tmp_str+='"%s",'%(col)
yieldtmp_str.strip(",")+"\n"
forrowindata:
tmp_str=""
forcolincols:
tmp_str+='"%s",'%(str(row[col]))
yieldtmp_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()
defget_result_fromat(data,cols):
tmp_str=""
#返回文件的每一列列名
forcolincols:
tmp_str+='"%s",'%(col)
yieldtmp_str.strip(",")+"\n"
forrowindata:
tmp_str=""
forcolincols:
tmp_str+='"%s",'%(str(row[col]))
yieldtmp_str.strip(',')+"\n"
=====>
defget_result_fromat(data,cols):
tmp_str=""
forcolincols:
tmp_str+='"%s",'%(col)
yieldtmp_str.strip(",")+"\n"
whileTrue:
tmp_str=""
forcolincols:
tmp_str+='"%s",'%(str(row[col]))
yieldtmp_str.strip(',')+"\n"
row=db.cursor.fetchone()
ifrowisNone:
break
可以看到就是通过whileTrue来实现不断地取数据下载,有效避免一次性从MySQL取出内存不足报错,又或者取得过久导致nginx超时!
总结
关于下载就分享到这了,还是比较简单的,谢谢观看~希望能给大家一个参考,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。