上次同事扔给我一个需求:把 200 个 Excel 文件里的销售数据汇总到一张表。
我问他之前怎么做的,他说手动复制粘贴。
200 个文件。手动。
我打开编辑器,写了不到 30 行代码,10 秒跑完。他看我的眼神像看外星人。
今天就把这套方法讲透,你也能做到。
为什么选 openpyxl?
Python 操作 Excel 的库有好几个,我直接说结论:选 openpyxl,别犹豫。
- •
xlrd:只读,而且从 2.0 版本开始不再支持 .xlsx 格式,基本废了 - •
pandas:适合做数据分析,但精细操作单元格格式很别扭 - •
openpyxl:读写都行,支持 .xlsx,能控制样式、公式、图表
安装就一行:
pip install openpyxl
装完就能用,不需要额外配置。
先搞懂 Excel 的"术语"
操作 Excel 之前,得先知道代码里的东西对应表格里的哪个位置。很多人上来就写代码,结果被 Workbook、Worksheet 搞晕了,其实就是这么回事:
| | |
|---|
Workbook | | |
Worksheet | | |
Cell | | |
Row | | |
Column | | |
记住这个对应关系,后面看代码就不会懵。
读写基础:5 分钟上手
创建并写入一个 Excel
这是最简单的例子——新建一个文件,往里面填点东西:
from openpyxl import Workbookwb = Workbook() # 新建工作簿ws = wb.active # 拿到默认的 Sheetws['A1'] = '姓名' # 直接往单元格里塞值ws['B1'] = '销售额'ws['A2'] = '张三'ws['B2'] = 15000wb.save('demo.xlsx') # 保存,完事
跑完之后,当前目录下会多一个 demo.xlsx,打开就能看到数据。
读取已有的 Excel
from openpyxl import load_workbookwb = load_workbook('demo.xlsx') # 打开文件ws = wb.active # 拿到当前活动 Sheet# 逐行读取,values_only=True 直接拿值,不加的话拿到的是 Cell 对象for row in ws.iter_rows(min_row=1, max_row=2, values_only=True): print(row)# 输出:# ('姓名', '销售额')# ('张三', 15000)
values_only=True 这个参数很关键。不加它,你拿到的是一堆 Cell 对象,还得再调 .value 才能拿到值。加了省一半代码。
筛选数据:只拿你要的行
真实场景下,表格里几百上千行数据,你不可能全要。
比如有一张销售表,只要销售额超过 10000 的记录:
from openpyxl import load_workbookwb = load_workbook('sales.xlsx')ws = wb.activeresults = []for row in ws.iter_rows(min_row=2, values_only=True): # 跳过表头 name, amount = row[0], row[1] if amount > 10000: # 条件筛选 results.append((name, amount))print(f'符合条件的记录:{len(results)} 条')for name, amount in results: print(f' {name}: {amount}')
注意 min_row=2——跳过第一行表头,不然表头也会被当成数据参与筛选。这个坑我踩过,当时 name 列的值全是 "姓名",查了半天。
如果你想更灵活地筛选,可以封装一个通用函数,以后各种筛选条件都能复用:
def filter_rows(ws, condition, min_row=2): """传入一个条件函数,返回满足条件的所有行""" return [row for row in ws.iter_rows(min_row=min_row, values_only=True) if condition(row)]# 用法:筛选销售额 > 10000 的行high_sales = filter_rows(ws, lambda r: r[1] and r[1] > 10000)
数据批量转换:改完直接写回去
另一个高频需求:批量修改数据。
比如所有销售额要换成美元(假设汇率 7.2),直接在原表上改:
from openpyxl import load_workbookwb = load_workbook('sales.xlsx')ws = wb.activerate = 7.2for row in ws.iter_rows(min_row=2, max_col=2): # 遍历每行的前两列 cell = row[1] # B 列(销售额) if cell.value and isinstance(cell.value, (int, float)): cell.value = round(cell.value / rate, 2) # 换算并保留两位小数wb.save('sales_usd.xlsx') # 另存一份,别覆盖原文件
踩坑提醒:iter_rows() 返回的是 Cell 对象,直接改 cell.value 就能修改内容。但一定要先判断 cell.value 不是 None,不然会报 TypeError。
还有个常见场景——日期格式统一。不同人填的日期五花八门,"2024/6/15"、"2024-6-15"、"6月15日" 都有,批量统一一下:
from datetime import datetimefor row in ws.iter_rows(min_row=2, max_col=3): cell = row[2] # C 列是日期 if isinstance(cell.value, str): cell.value = datetime.strptime(cell.value, '%Y/%m/%d') cell.number_format = 'YYYY-MM-DD' # 统一显示格式
常用函数与公式
Excel 最强的地方是公式,openpyxl 也能往单元格里写公式,打开文件时 Excel 会自动计算:
ws['C1'] = '合计'ws['C2'] = '=SUM(B2:B100)' # 求和ws['D1'] = '平均值'ws['D2'] = '=AVERAGE(B2:B100)' # 平均值ws['E1'] = '最大值'ws['E2'] = '=MAX(B2:B100)' # 最大值
但有个坑:openpyxl 自己不会算公式。你读取一个带公式的单元格,拿到的是公式字符串,不是计算结果。
解决方案有两种:
# 方法一:用 data_only 模式读取(需要先用 Excel 打开并保存过一次)wb = load_workbook('sales.xlsx', data_only=True)print(wb.active['C2'].value) # 拿到的是上次保存时的计算结果# 方法二:让 Python 自己算amounts = [row[1] for row in ws.iter_rows(min_row=2, max_col=2, values_only=True)]total = sum(x for x in amounts if isinstance(x, (int, float)))print(f'合计: {total}')
如果公式计算是刚需而且很复杂,别用 openpyxl 硬扛,用 xlwings 调用 Excel 自己的计算引擎更靠谱。
完整实战:汇总多个 Excel 文件
回到开头的需求——200 个文件汇总。核心代码就这些:
from openpyxl import Workbook, load_workbookfrom pathlib import Pathdef merge_excels(folder, output='汇总.xlsx'): """把文件夹里所有 Excel 的数据汇总到一张表""" wb_out = Workbook() ws_out = wb_out.active ws_out.append(['文件名', '姓名', '销售额', '日期']) # 写表头 files = list(Path(folder).glob('*.xlsx')) for f in files: if f.name == output: # 跳过输出文件本身 continue wb = load_workbook(f) ws = wb.active for row in ws.iter_rows(min_row=2, values_only=True): ws_out.append([f.name] + list(row)) # 每行前面加个文件名 wb_out.save(output) print(f'搞定,共汇总 {len(files)} 个文件')merge_excels('./销售数据/')
扔到文件夹上跑一下,几秒钟的事。
写在最后
Python 操作 Excel 这件事,会了就是生产力翻倍,不会就是加班到头秃。
openpyxl 覆盖了 90% 的日常需求,剩下 10% 的复杂场景(比如需要调用 Excel 宏、处理超大数据量),后面再单独讲。
留个问题给你:如果你要处理的不是.xlsx,而是.xls甚至.csv,该怎么办? 评论区聊聊。