在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通过自定义isnumber函数判断字符串是否为数字的方法
Apr 23 Python
python实现数值积分的Simpson方法实例分析
Jun 05 Python
深入flask之异步非堵塞实现代码示例
Jul 31 Python
python使用turtle绘制国际象棋棋盘
May 23 Python
python实战串口助手_解决8串口多个发送的问题
Jun 12 Python
python快速编写单行注释多行注释的方法
Jul 31 Python
Python socket非阻塞模块应用示例
Sep 12 Python
Python Gluon参数和模块命名操作教程
Dec 18 Python
python中return的返回和执行实例
Dec 24 Python
python实现横向拼接图片
Mar 23 Python
常用的10个Python实用小技巧
Aug 10 Python
如何利用Matlab制作一款真正的拼图小游戏
May 11 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
Bo-Blog专用的给Windows服务器的IIS Rewrite程序
2007/08/26 PHP
php模拟asp中的XmlHttpRequest实现http请求的代码
2011/03/24 PHP
PHP面向对象——访问修饰符介绍
2012/11/08 PHP
php生成N个不重复的随机数实例
2013/11/12 PHP
php 类自动载入的方法
2015/06/03 PHP
JavaScript 学习笔记之操作符
2015/01/14 Javascript
ajax+jQuery实现级联显示地址的方法
2015/05/06 Javascript
jquery通过name属性取值的简单实现方法
2016/06/20 Javascript
bootstrap table支持高度百分比的实例代码
2018/02/28 Javascript
微信小程序模拟cookie的实现
2018/06/20 Javascript
jquery ajax加载数据前台渲染方式 不用for遍历的方法
2018/08/09 jQuery
原生JS实现简单的倒计时功能示例
2018/08/30 Javascript
Vue源码解析之数组变异的实现
2018/12/04 Javascript
node基于async/await对mysql进行封装
2019/06/20 Javascript
layui 实现table翻页滚动条位置保持不变的例子
2019/09/05 Javascript
JS多个异步请求 按顺序执行next实现解析
2019/09/16 Javascript
全网小程序接口请求封装实例代码
2020/11/06 Javascript
[04:50]2019DOTA2高校联赛秋季赛四强集锦
2019/12/27 DOTA
本地文件上传到七牛云服务器示例(七牛云存储)
2014/01/11 Python
Python continue语句用法实例
2014/03/11 Python
在Python的while循环中使用else以及循环嵌套的用法
2015/10/14 Python
Python入门之三角函数sin()函数实例详解
2017/11/08 Python
python爬虫之xpath的基本使用详解
2018/04/18 Python
python多线程下信号处理程序示例
2019/05/31 Python
Python处理时间日期坐标轴过程详解
2019/06/25 Python
python实现BP神经网络回归预测模型
2019/08/09 Python
将python文件打包exe独立运行程序方法详解
2020/02/12 Python
巴西最大的体育用品商城:Netshoes巴西
2016/11/29 全球购物
亚洲最大的运动鞋寄售店:KicksCrew
2020/11/26 全球购物
自我鉴定标准格式
2014/03/19 职场文书
主要负责人任命书
2014/06/06 职场文书
暑假社会实践证明格式
2014/10/28 职场文书
学校党风廉政建设调研报告
2015/01/01 职场文书
2016年党员公开承诺书格式范文
2016/03/24 职场文书
PHP实现rar解压读取扩展包小结
2021/06/03 PHP
Github 使用python对copilot做些简单使用测试
2022/04/14 Python