在django项目中导出数据到excel文件并实现下载的功能


Posted in Python onMarch 13, 2020

依赖模块

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超时!

总结

关于下载就分享到这了,还是比较简单的,谢谢观看~希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python之import机制详解
Jul 03 Python
Python中使用PIL库实现图片高斯模糊实例
Feb 08 Python
Python中的lstrip()方法使用简介
May 19 Python
python和flask中返回JSON数据的方法
Mar 26 Python
Python使用pickle模块储存对象操作示例
Aug 15 Python
python获取Pandas列名的几种方法
Aug 07 Python
Python使用import导入本地脚本及导入模块的技巧总结
Aug 07 Python
在Django下创建项目以及设置settings.py教程
Dec 03 Python
python输出数组中指定元素的所有索引示例
Dec 06 Python
使用tensorflow显示pb模型的所有网络结点方式
Jan 23 Python
Pycharm最常用的快捷键及使用技巧
Mar 05 Python
Python PIL库图片灰化处理
Apr 07 Python
Django choices下拉列表绑定实例
Mar 13 #Python
django model object序列化实例
Mar 13 #Python
浅析python标准库中的glob
Mar 13 #Python
Python3标准库glob文件名模式匹配的问题
Mar 13 #Python
python编写俄罗斯方块
Mar 13 #Python
探秘TensorFlow 和 NumPy 的 Broadcasting 机制
Mar 13 #Python
自定义Django Form中choicefield下拉菜单选取数据库内容实例
Mar 13 #Python
You might like
PHP上传图片进行等比缩放可增加水印功能
2014/01/13 PHP
PHP中文乱码解决方案
2015/03/05 PHP
php实现分页功能的详细实例方法
2019/09/29 PHP
combox改进版 页面原型参考dojo的,比网上jQuery的那些combox功能强,代码更小
2010/04/15 Javascript
JS 面向对象之神奇的prototype
2011/02/26 Javascript
jquery使用ajax实现微信自动回复插件
2014/04/28 Javascript
Javascript中浮点数相乘的一个解决方法
2014/06/03 Javascript
javascript实现类似java中getClass()得到对象类名的方法
2015/07/27 Javascript
JS利用cookie记忆当前位置的防刷新导航效果
2015/10/15 Javascript
浅析JS异步加载进度条
2016/05/05 Javascript
AngularJS中的按需加载ocLazyLoad示例
2017/01/11 Javascript
微信JSSDK调用微信扫一扫功能的方法
2017/07/25 Javascript
bootstrap响应式导航条模板使用详解(含下拉菜单,弹出框)
2017/11/17 Javascript
JavaScript DOM元素常见操作详解【添加、删除、修改等】
2018/05/09 Javascript
如何利用ES6进行Promise封装总结
2019/02/11 Javascript
深入分析jQuery.one() 函数
2020/06/03 jQuery
python递归打印某个目录的内容(实例讲解)
2017/08/30 Python
AI人工智能 Python实现人机对话
2017/11/13 Python
Python使用random.shuffle()打乱列表顺序的方法
2018/11/08 Python
python字符串替换re.sub()方法解析
2019/09/18 Python
python3.8.3安装教程及环境配置的详细教程(64-bit)
2020/11/28 Python
一款纯css3实现的圆形旋转分享按钮旋转角度可自己调整
2014/09/02 HTML / CSS
localStorage 设置过期时间的方法实现
2018/12/21 HTML / CSS
深入解析HTML5使用SVG图像时的viewBox属性用法
2015/09/02 HTML / CSS
俄罗斯美容和健康网上商店:Созвездие Красоты
2019/07/23 全球购物
网络安全类面试题
2015/08/01 面试题
EJB的角色和三个对象
2015/12/31 面试题
老师推荐信
2013/10/28 职场文书
小学三八妇女节活动方案
2014/03/16 职场文书
英文演讲稿
2014/05/15 职场文书
“六查”、“三学”、“三干”查摆问题整改措施
2014/09/27 职场文书
关于运动会广播稿50字
2014/10/18 职场文书
2015关爱留守儿童工作总结
2014/12/12 职场文书
工作违纪的检讨书范文
2019/07/09 职场文书
Pytorch DataLoader shuffle验证方式
2021/06/02 Python
宝塔更新Python及Flask项目的部署
2022/04/11 Python