在django项目中导出数据到excel文件并实现下载的功能
作者:matrix_theOne 发布时间:2023-09-10 08:31:43
依赖模块
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超时!
总结
关于下载就分享到这了,还是比较简单的,谢谢观看~希望能给大家一个参考,也希望大家多多支持脚本之家。
来源:https://blog.csdn.net/xdf19941224/article/details/93626524


猜你喜欢
- PyQ5已经自动定义了很多QT自建的信号。但是在实际的使用中为了灵活使用信号与槽机制,可以根据需要自定义信号。通过使用pyqtSignal(
- 简介模拟登录淘宝已经不是一件新鲜的事情了,过去我曾经使用get/post方式进行爬虫,同时也加入IP代理池进行跳过检验,但随着大型网站的升级
- 保存时代码如下:figure_corp = figure.crop( (32*rate/2, 32*rate/2, 32-32*rate/2
- 目的:设计一个应用GUI用于对比两个Excel文件思路1.参数同一个excel文件两个sheet页其中一个ODS(老数据),一个DWH(新数
- 前言在线演示地址:http://haiyong.site/age-calculatorJavaScript提供了一些内置的日期和时间函数,有
- 1、卓越亚马逊的首页轮换图片,每刷新一次,都是随机不同的顺序显示,这样的设计解决了对于较多图片轮换而靠后的图片信息很少被看到的问题,这点对于
- 120726 11:57:22 [Warning] 'user' entry 'root@localhost.loc
- 这篇文章主要介绍了Python如何使用Gitlab API实现批量的合并分支,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的
- declare @t varchar(255),@c varchar(255) declare table_cursor cursor fo
- 引子例如,一个人可能会在计算机上存储大量的照片、视频和文档文件,这些文件可能散落在不同的文件夹中,难以管理和查找。该程序可以根据文件类型将这
- 如何通过Kerberos认证.1.安装Kerberos客户端CentOS:yum install krb5-workstation使用whi
- 本文记录了RHEL7.5下mysql 8.0.11安装教程,具体内容如下首先去mysql官网下载mysql-8.0.11-el7-x86_6
- 视频观看视频敌人精灵这是我们“Shmup”项目的第2部分!在本课中,我们将添加一些敌人的精灵供玩家躲
- 一、连接Go语言中的database/sql包提供了保证SQL或类SQL数据库的泛用接口,并不提供具体的数据库驱动。使用database/s
- 1.INSERT INTO SELECT语句 语句形式为:Insert into Table2(field1,field2,...) sel
- 在我上一篇文章,我搭了一个框架,模拟了Flask网站上“@app.route(‘/')”第一条例子的行为。如果你错过了那篇“这不是魔
- 目录一、比较汽车性能二、比较不同城市近期天气状况雷达图是以从同一点开始的轴上表示的三个或更多个定量变量的二维图表的形式显示多变量数据的图形方
- 前几天在一本书上看到一篇可以利用字典破解zip文件密码的文章,觉得比较有意思于是研究了一番,在这里分享一下原理主要是利用python里自带的
- Mysql Binlog 简介Mysql Binlog是二进制格式的日志文件Binlog是用来记录Mysql内部对数据库的改动(只记录对数据
- Logminer是每个Dba都应熟悉的工具,当一天由于用户的误操作你需要做不完全的恢复时,由于你无法确定这个操作是哪个时间做的,所以这对你的