在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单元测试unittest实例详解
May 11 Python
详解python发送各类邮件的主要方法
Dec 22 Python
初探TensorFLow从文件读取图片的四种方式
Feb 06 Python
使用requests库制作Python爬虫
Mar 25 Python
Python多进程multiprocessing.Pool类详解
Apr 27 Python
python实现linux下抓包并存库功能
Jul 18 Python
bluepy 一款python封装的BLE利器简单介绍
Jun 25 Python
Python字典中的值为列表或字典的构造实例
Dec 16 Python
python标识符命名规范原理解析
Jan 10 Python
Python MOCK SERVER moco模拟接口测试过程解析
Apr 13 Python
Python如何使用ConfigParser读取配置文件
Nov 12 Python
如何用PyPy让你的Python代码运行得更快
Dec 02 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实现的生成静态HTML速度快类库
2007/03/31 PHP
php中实现可以返回多个值的函数实例
2015/03/21 PHP
php+ajax实现的点击浏览量加1
2015/04/16 PHP
JS中获取数据库中的值的方法
2013/07/14 Javascript
jquery.qrcode在线生成二维码使用示例
2013/08/21 Javascript
JS实现固定在右下角可展开收缩DIV层的方法
2015/02/13 Javascript
JavaScript数组和循环详解
2015/04/27 Javascript
jQuery qrcode生成二维码的方法
2016/04/03 Javascript
vue-router 导航钩子的具体使用方法
2017/08/31 Javascript
基于 Vue.js 之 iView UI 框架非工程化实践记录(推荐)
2017/11/21 Javascript
webpack引入eslint配置详解
2018/01/22 Javascript
解决layui中table异步数据请求不支持自定义返回数据格式的问题
2018/08/19 Javascript
Vue项目中使用WebUploader实现文件上传的方法
2019/07/21 Javascript
layui富文本编辑器前端无法取值的解决方法
2019/09/18 Javascript
深入浅析python继承问题
2016/05/29 Python
python 容器总结整理
2017/04/04 Python
Python 基础教程之str和repr的详解
2017/08/20 Python
python数字图像处理之骨架提取与分水岭算法
2018/04/27 Python
python实现俄罗斯方块游戏
2020/03/25 Python
Python中Numpy包的安装与使用方法简明教程
2018/07/03 Python
python对html过滤处理的方法
2018/10/21 Python
Python设计模式之观察者模式原理与用法详解
2019/01/16 Python
Django 对象关系映射(ORM)源码详解
2019/08/06 Python
sklearn+python:线性回归案例
2020/02/24 Python
python3 os进行嵌套操作的实例讲解
2020/11/19 Python
怎样在程序里获得一个空指针
2015/01/24 面试题
口腔医学技术应届生求职信
2013/11/09 职场文书
国际商务专业职业生涯规划书范文
2014/01/17 职场文书
清明节演讲稿
2014/05/27 职场文书
计算机应用专业自荐信
2014/07/05 职场文书
通知函格式范文
2015/04/27 职场文书
结婚典礼主持词
2015/06/29 职场文书
幼儿园庆元旦主持词
2015/07/06 职场文书
战友聚会致辞
2015/07/28 职场文书
运动会跳远广播稿
2015/08/19 职场文书
oracle数据库去除重复数据
2022/05/20 Oracle