在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实现划词翻译
Apr 23 Python
Python中实现switch功能实例解析
Jan 11 Python
pandas数据预处理之dataframe的groupby操作方法
Apr 13 Python
解决pycharm无法调用pip安装的包问题
May 18 Python
pandas ix &amp;iloc &amp;loc的区别
Jan 10 Python
Python 绘制酷炫的三维图步骤详解
Jul 12 Python
python 字典的打印实现
Sep 26 Python
Python 3.10 的首个 PEP 诞生,内置类型 zip() 迎来新特性(推荐)
Jul 03 Python
查找适用于matplotlib的中文字体名称与实际文件名对应关系的方法
Jan 05 Python
scrapy-splash简单使用详解
Feb 21 Python
如何获取numpy array前N个最大值
May 14 Python
Python中glob库实现文件名的匹配
Jun 18 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图片加中文水印实现代码分享
2012/10/31 PHP
Symfony2创建基于域名的路由相关示例
2016/11/14 PHP
PHP正则判断一个变量是否为正整数的方法
2019/02/27 PHP
yii2 开发api接口时优雅的处理全局异常的方法
2019/05/14 PHP
二行代码解决全部网页木马
2008/03/28 Javascript
jQuery中:image选择器用法实例
2015/01/03 Javascript
jQuery实现hover合成事件的方法
2015/08/06 Javascript
JavaScript的jQuery库插件的简要开发指南
2015/08/12 Javascript
js捕捉键盘事件和按键键值的方法
2016/10/10 Javascript
ES6中Array.copyWithin()函数的用法实例详解
2017/09/16 Javascript
vue 中引用gojs绘制E-R图的方法示例
2018/08/24 Javascript
微信小程序使用map组件实现解析经纬度功能示例
2019/01/22 Javascript
webpack 如何解析代码模块路径的实现
2019/09/04 Javascript
vue遍历对象中的数组取值示例
2019/11/07 Javascript
js实现鼠标切换图片(无定时器)
2021/01/27 Javascript
python实现监控windows服务并自动启动服务示例
2014/04/17 Python
Python中用Descriptor实现类级属性(Property)详解
2014/09/18 Python
python采集百度百科的方法
2015/06/05 Python
深入解析Python设计模式编程中建造者模式的使用
2016/03/02 Python
tensorflow 获取模型所有参数总和数量的方法
2018/06/14 Python
PYQT5实现控制台显示功能的方法
2019/06/25 Python
python调用支付宝支付接口流程
2019/08/15 Python
使用python获取邮箱邮件的设置方法
2019/09/20 Python
Python中断多重循环的思路总结
2019/10/04 Python
python实现图片插入文字
2019/11/26 Python
利用CSS3的checked伪类实现OL的隐藏显示的方法
2010/12/18 HTML / CSS
突袭HTML5之Javascript API扩展2—地理信息服务及地理位置API学习
2013/01/31 HTML / CSS
StubHub西班牙:购买和出售全球活动门票
2017/06/05 全球购物
高校毕业生自我鉴定
2013/10/27 职场文书
关于元旦的广播稿
2014/02/16 职场文书
学习十八大标语
2014/10/09 职场文书
党的群众路线教育实践活动学习笔记范文
2014/11/06 职场文书
2015年幼儿园中班开学寄语
2015/05/27 职场文书
MySQL中存储时间的最佳实践指南
2021/07/01 MySQL
Python+腾讯云服务器实现每日自动健康打卡
2021/12/06 Python
我收到了德劲DE1107
2022/04/05 无线电