Django实现百万级数据Excel下载:解决MemoryError与Nginx超时

阿麦
阿麦
发布时间:2026-10-13| 5人浏览过
原创

本文详解在Django项目中实现Excel文件导出的完整流程,重点解决百万级数据下载时的MemoryError和Nginx超时问题。通过对比FileResponse与StreamingHttpResponse,深入解析使用SSDictCursor流式游标结合fetchone迭代生成的优化方案,确保大文件传输的稳定性和一致性。

Django导出Excel基础配置

依赖模块

安装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开发项目时,经常需要提供文件下载功能。Django官方提供了多种响应方式:FileResponse、StreamingHttpResponse和HttpResponse。其中,FileResponse和StreamingHttpResponse均基于迭代器机制生成数据,适合传输体积较大的文件;而HttpResponse则是将全部数据加载到内存后一次性返回,容易引发MemoryError或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万行,处理难度将显著增加。以剩余内存约1GB的环境为例,当数据行数超过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 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。