Python实现MySQL数据高效导出Excel的自动化方案
2026/8/1 17:02:41 网站建设 项目流程

1. 项目背景与核心需求

最近在整理客户历史订单数据时,遇到了一个典型的数据搬运需求——需要将MySQL数据库中近三年的交易记录导出到Excel文件,并按月份拆分存储。手工操作不仅效率低下,还容易出错。于是用Python写了个自动化脚本,没想到后来这个工具成了团队里的高频使用工具。

数据库到Excel的数据搬运是数据分析、报表生成等场景的基础操作。无论是财务对账、销售统计还是用户行为分析,都需要将数据库中的结构化数据导出为更易读的Excel格式。传统的手工导出存在三个痛点:

  1. 多表关联查询结果无法直接导出
  2. 大数据量导出时容易内存溢出
  3. 需要定期执行的导出任务缺乏自动化

2. 技术方案选型

2.1 数据库连接方式比较

常见的Python数据库连接库主要有三种:

  • MySQLdb:最原始的MySQL连接器,性能好但不支持Python3
  • PyMySQL:纯Python实现的替代方案,兼容性好
  • SQLAlchemy:ORM工具,支持多种数据库

对于简单的导出任务,PyMySQL足够轻量。如果项目已使用SQLAlchemy,也可以直接沿用。这里选择PyMySQL作为演示:

import pymysql conn = pymysql.connect( host='localhost', user='root', password='yourpassword', database='sales_db', charset='utf8mb4' )

2.2 Excel写入库选择

Python处理Excel的主流库对比:

库名称优点缺点适用场景
openpyxl功能全面,支持.xlsx内存占用大需要修改现有文件
xlwt/xlrd速度快只支持老版.xls兼容旧系统
pandas接口简单依赖其他库数据分析场景
xlsxwriter性能好只写不能读纯写入需求

考虑到我们只需要写入功能且要处理大量数据,选择xlsxwriter作为核心写入引擎。

3. 完整实现方案

3.1 基础版本实现

先实现单表导出的基础功能:

import pymysql import xlsxwriter from datetime import datetime def export_to_excel(host, user, password, db, table, output_file): # 建立数据库连接 connection = pymysql.connect( host=host, user=user, password=password, database=db, charset='utf8mb4' ) try: with connection.cursor() as cursor: # 获取数据 sql = f"SELECT * FROM {table}" cursor.execute(sql) results = cursor.fetchall() # 获取列名 desc = cursor.description column_names = [col[0] for col in desc] # 创建Excel文件 workbook = xlsxwriter.Workbook(output_file) worksheet = workbook.add_worksheet() # 写入表头 for col_num, name in enumerate(column_names): worksheet.write(0, col_num, name) # 写入数据 for row_num, row in enumerate(results, 1): for col_num, value in enumerate(row): worksheet.write(row_num, col_num, value) finally: connection.close() workbook.close()

3.2 分页查询优化

当数据量超过万条时,一次性读取会导致内存暴涨。改用分页查询:

def batch_export(host, user, password, db, table, output_file, batch_size=5000): connection = pymysql.connect(...) try: with connection.cursor() as cursor: # 获取总行数 count_sql = f"SELECT COUNT(*) FROM {table}" cursor.execute(count_sql) total = cursor.fetchone()[0] # 分页处理 workbook = xlsxwriter.Workbook(output_file) worksheet = workbook.add_worksheet() # 先写入表头 cursor.execute(f"SELECT * FROM {table} LIMIT 1") desc = cursor.description column_names = [col[0] for col in desc] for col_num, name in enumerate(column_names): worksheet.write(0, col_num, name) # 分批读取数据 for offset in range(0, total, batch_size): sql = f"SELECT * FROM {table} LIMIT {offset}, {batch_size}" cursor.execute(sql) batch = cursor.fetchall() # 写入当前批次 for row_num, row in enumerate(batch, offset+1): for col_num, value in enumerate(row): worksheet.write(row_num, col_num, value) finally: connection.close() workbook.close()

3.3 多表关联导出

实际业务中经常需要关联多表查询:

def export_join_tables(host, user, password, db, sql, output_file): connection = pymysql.connect(...) try: with connection.cursor() as cursor: cursor.execute(sql) results = cursor.fetchall() desc = cursor.description workbook = xlsxwriter.Workbook(output_file) worksheet = workbook.add_worksheet() # 写入动态列名 column_names = [col[0] for col in desc] for col_num, name in enumerate(column_names): worksheet.write(0, col_num, name) # 写入数据 for row_num, row in enumerate(results, 1): for col_num, value in enumerate(row): # 处理datetime类型 if isinstance(value, datetime): value = value.strftime('%Y-%m-%d %H:%M:%S') worksheet.write(row_num, col_num, value) finally: connection.close() workbook.close()

4. 高级功能实现

4.1 自动拆分多Sheet

当数据需要按类别分组导出时:

def export_by_category(host, user, password, db, table, category_column, output_file): connection = pymysql.connect(...) try: with connection.cursor() as cursor: # 获取所有分类 cursor.execute(f"SELECT DISTINCT {category_column} FROM {table}") categories = [row[0] for row in cursor.fetchall()] workbook = xlsxwriter.Workbook(output_file) for category in categories: # 为每个分类创建sheet sheet_name = str(category)[:31] # Excel sheet名最长31字符 worksheet = workbook.add_worksheet(sheet_name) # 查询该分类数据 sql = f"SELECT * FROM {table} WHERE {category_column} = %s" cursor.execute(sql, (category,)) results = cursor.fetchall() desc = cursor.description # 写入表头 column_names = [col[0] for col in desc] for col_num, name in enumerate(column_names): worksheet.write(0, col_num, name) # 写入数据 for row_num, row in enumerate(results, 1): for col_num, value in enumerate(row): worksheet.write(row_num, col_num, value) finally: connection.close() workbook.close()

4.2 添加样式和格式

使用xlsxwriter的样式功能提升可读性:

def export_with_styles(host, user, password, db, table, output_file): connection = pymysql.connect(...) try: with connection.cursor() as cursor: cursor.execute(f"SELECT * FROM {table}") results = cursor.fetchall() desc = cursor.description workbook = xlsxwriter.Workbook(output_file) worksheet = workbook.add_worksheet() # 定义样式 header_style = workbook.add_format({ 'bold': True, 'bg_color': '#4F81BD', 'font_color': 'white', 'border': 1 }) date_style = workbook.add_format({'num_format': 'yyyy-mm-dd'}) money_style = workbook.add_format({'num_format': '$#,##0.00'}) # 写入带样式的表头 column_names = [col[0] for col in desc] for col_num, name in enumerate(column_names): worksheet.write(0, col_num, name, header_style) # 自动调整列宽 worksheet.set_column(col_num, col_num, len(name)+2) # 写入数据并应用样式 for row_num, row in enumerate(results, 1): for col_num, (value, col) in enumerate(zip(row, desc)): col_type = col[1] # 获取字段类型 if isinstance(value, datetime): worksheet.write_datetime(row_num, col_num, value, date_style) elif col_type == pymysql.NUMBER and col[5] == 2: # 小数位为2的数值 worksheet.write_number(row_num, col_num, value, money_style) else: worksheet.write(row_num, col_num, value) finally: connection.close() workbook.close()

5. 性能优化技巧

5.1 内存优化方案

处理超大数据集时(超过10万行):

  1. 使用服务器端游标(SScursor)减少内存占用
  2. 分批写入磁盘,避免在内存中累积全部数据
  3. 禁用xlsxwriter的默认字符串缓存

优化后的实现:

def export_large_dataset(host, user, password, db, table, output_file, batch_size=10000): connection = pymysql.connect( host=host, user=user, password=password, database=db, charset='utf8mb4', cursorclass=pymysql.cursors.SSCursor # 服务器端游标 ) try: with connection.cursor() as cursor: # 获取列信息 cursor.execute(f"SELECT * FROM {table} LIMIT 1") desc = cursor.description column_names = [col[0] for col in desc] # 创建Excel文件并优化性能 workbook = xlsxwriter.Workbook( output_file, {'constant_memory': True, 'strings_to_urls': False} ) worksheet = workbook.add_worksheet() # 写入表头 for col_num, name in enumerate(column_names): worksheet.write(0, col_num, name) # 分批读取和写入 cursor.execute(f"SELECT * FROM {table}") row_num = 1 while True: batch = cursor.fetchmany(batch_size) if not batch: break for row in batch: for col_num, value in enumerate(row): worksheet.write(row_num, col_num, value) row_num += 1 finally: connection.close() workbook.close()

5.2 多线程导出

对于可以并行导出的多个表:

from concurrent.futures import ThreadPoolExecutor def parallel_export(config_list): """ config_list: [{ 'host': 'localhost', 'user': 'root', 'password': 'xxx', 'db': 'db1', 'table': 'table1', 'output': 'output1.xlsx' }, ...] """ with ThreadPoolExecutor(max_workers=4) as executor: futures = [] for config in config_list: future = executor.submit( export_to_excel, host=config['host'], user=config['user'], password=config['password'], db=config['db'], table=config['table'], output_file=config['output'] ) futures.append(future) # 等待所有任务完成 for future in futures: future.result()

6. 异常处理与日志记录

6.1 健壮性增强

完善的异常处理机制:

import logging from xlsxwriter.exceptions import FileCreateError logging.basicConfig( filename='export.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) def safe_export(host, user, password, db, table, output_file): try: # 验证输出路径 if not output_file.endswith('.xlsx'): raise ValueError("输出文件必须是.xlsx格式") # 记录开始时间 start_time = datetime.now() logging.info(f"开始导出 {table} 到 {output_file}") # 执行导出 export_to_excel(host, user, password, db, table, output_file) # 记录完成时间 duration = (datetime.now() - start_time).total_seconds() logging.info(f"导出完成,耗时 {duration:.2f} 秒") except pymysql.Error as e: logging.error(f"数据库错误: {e}") raise except FileCreateError as e: logging.error(f"文件创建失败: {e}") raise except Exception as e: logging.error(f"未知错误: {e}") raise

6.2 进度反馈

添加进度显示功能:

from tqdm import tqdm def export_with_progress(host, user, password, db, table, output_file): connection = pymysql.connect(...) try: with connection.cursor() as cursor: # 获取总行数 cursor.execute(f"SELECT COUNT(*) FROM {table}") total = cursor.fetchone()[0] workbook = xlsxwriter.Workbook(output_file) worksheet = workbook.add_worksheet() # 写入表头 cursor.execute(f"SELECT * FROM {table} LIMIT 1") desc = cursor.description column_names = [col[0] for col in desc] for col_num, name in enumerate(column_names): worksheet.write(0, col_num, name) # 使用tqdm显示进度 cursor.execute(f"SELECT * FROM {table}") with tqdm(total=total, unit='rows') as pbar: for row_num, row in enumerate(cursor, 1): for col_num, value in enumerate(row): worksheet.write(row_num, col_num, value) pbar.update(1) finally: connection.close() workbook.close()

7. 实际应用案例

7.1 电商订单导出系统

为电商平台设计的自动化导出方案:

def export_orders(start_date, end_date, output_dir): """导出指定日期范围内的订单数据""" sql = """ SELECT o.order_id, o.order_date, c.customer_name, p.product_name, oi.quantity, oi.unit_price, oi.quantity * oi.unit_price AS total FROM orders o JOIN customers c ON o.customer_id = c.customer_id JOIN order_items oi ON o.order_id = oi.order_id JOIN products p ON oi.product_id = p.product_id WHERE o.order_date BETWEEN %s AND %s ORDER BY o.order_date """ # 按月份拆分导出 current = start_date while current <= end_date: month_start = current.replace(day=1) month_end = (month_start + timedelta(days=32)).replace(day=1) - timedelta(days=1) month_end = min(month_end, end_date) output_file = os.path.join( output_dir, f"orders_{month_start.strftime('%Y%m')}.xlsx" ) conn = pymysql.connect(...) try: with conn.cursor() as cursor: cursor.execute(sql, (month_start, month_end)) results = cursor.fetchall() desc = cursor.description workbook = xlsxwriter.Workbook(output_file) worksheet = workbook.add_worksheet() # 写入表头 column_names = [col[0] for col in desc] for col_num, name in enumerate(column_names): worksheet.write(0, col_num, name) # 写入数据 for row_num, row in enumerate(results, 1): for col_num, value in enumerate(row): worksheet.write(row_num, col_num, value) finally: conn.close() workbook.close() current = month_end + timedelta(days=1)

7.2 定时自动导出任务

结合Windows任务计划或Linux cron实现自动化:

# auto_export.py import schedule import time def daily_export(): today = datetime.now().strftime('%Y%m%d') output_file = f'/data/exports/sales_{today}.xlsx' export_to_excel( host='prod-db.example.com', user='report_user', password='securepassword', db='sales', table='daily_sales', output_file=output_file ) # 每天凌晨1点执行 schedule.every().day.at("01:00").do(daily_export) while True: schedule.run_pending() time.sleep(60)

8. 常见问题与解决方案

8.1 编码问题处理

处理数据库中的特殊字符:

# 在连接配置中添加charset参数 conn = pymysql.connect( host='localhost', user='root', password='yourpassword', database='sales_db', charset='utf8mb4', # 支持emoji等特殊字符 cursorclass=pymysql.cursors.DictCursor ) # 写入Excel前对字符串进行清洗 def clean_string(value): if isinstance(value, str): return value.encode('unicode_escape').decode('ascii') return value

8.2 大数据量导出优化

当处理百万级数据时的建议:

  1. 使用服务器端游标(SSCursor)
  2. 增加batch_size到5000-10000
  3. 考虑先导出为CSV再转换
  4. 使用--quick参数连接MySQL

8.3 性能对比测试

不同方案的导出速度对比(测试数据:10万行,20列):

方案耗时(秒)内存峰值(MB)
基础版本45.2780
分页查询38.7120
服务器游标36.585
多线程(4线程)22.1210

9. 扩展功能思路

9.1 支持更多数据库类型

通过SQLAlchemy实现多数据库支持:

from sqlalchemy import create_engine def export_using_sqlalchemy(connection_str, sql, output_file): engine = create_engine(connection_str) with engine.connect() as conn: result = conn.execute(sql) workbook = xlsxwriter.Workbook(output_file) worksheet = workbook.add_worksheet() # 写入表头 column_names = result.keys() for col_num, name in enumerate(column_names): worksheet.write(0, col_num, name) # 写入数据 for row_num, row in enumerate(result, 1): for col_num, value in enumerate(row): worksheet.write(row_num, col_num, value) workbook.close()

9.2 添加数据验证

在Excel中添加下拉菜单等验证:

def export_with_validation(output_file): workbook = xlsxwriter.Workbook(output_file) worksheet = workbook.add_worksheet() # 添加数据验证 worksheet.data_validation( 'B2:B100', { 'validate': 'list', 'source': ['High', 'Medium', 'Low'] } ) # 添加条件格式 format_high = workbook.add_format({'bg_color': '#FFC7CE'}) worksheet.conditional_format( 'C2:C100', { 'type': 'cell', 'criteria': '>=', 'value': 1000, 'format': format_high } ) workbook.close()

9.3 与Pandas集成

利用Pandas简化数据处理:

import pandas as pd def export_with_pandas(host, user, password, db, sql, output_file): conn = pymysql.connect(...) try: df = pd.read_sql(sql, conn) # 使用Pandas的ExcelWriter with pd.ExcelWriter( output_file, engine='xlsxwriter', datetime_format='yyyy-mm-dd hh:mm:ss' ) as writer: df.to_excel(writer, index=False) # 获取xlsxwriter对象进行额外设置 workbook = writer.book worksheet = writer.sheets['Sheet1'] # 设置自动筛选 worksheet.autofilter(0, 0, 0, len(df.columns)-1) finally: conn.close()

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询