这种活我第一反应就不是打开 Excel 点筛选。
能重复两遍的操作,我一般就想办法让 Python 干。
openpyxl 我平时用得不少,它不适合搞什么复杂数据分析,但处理日常的 .xlsx 文件,改值、加列、套格式、做汇总,够用了。
先装:
pip install openpyxl
拿到 Excel,我一般先确认表头,不会一上来就写死第几列。
from openpyxl import load_workbook
book = load_workbook("订单明细.xlsx")
sheet = book["订单数据"]
headers = {
cell.value: cell.column
for cell in sheet[1]
if cell.value
}
print(headers)
假设表头是:
订单号 | 部门 | 手机号 | 金额 | 状态
得到列位置以后,后面的代码就好写了。
比如把手机号为空、金额不合法的数据找出来。
我见过不少脚本直接写:
sheet.cell(row=i, column=3)
这种代码当时能跑,Excel 一旦有人在前面插一列,脚本直接处理错数据,而且还不一定报错。
我更愿意按表头取列。
phone_col = headers["手机号"]
amount_col = headers["金额"]
bad_rows = []
for row_no in range(2, sheet.max_row + 1):
phone = sheet.cell(row_no, phone_col).value
amount = sheet.cell(row_no, amount_col).value
ifnot phone:
bad_rows.append((row_no, "手机号为空"))
continue
try:
sheet.cell(row_no, amount_col).value = round(float(amount), 2)
except (TypeError, ValueError):
bad_rows.append((row_no, "金额格式错误"))
这里有个习惯:永远别默认 Excel 里的数字真的是数字。
财务或者运营手工维护的表格里,128.5、"128.5"、"128.5元" 都可能出现。
脚本不做校验,迟早翻车。
发现异常以后,别光打印日志。我一般直接把问题标回 Excel,对方打开文件就能看见。
from openpyxl.styles import PatternFill
warn_fill = PatternFill(
fill_type="solid",
fgColor="FFF2CC"
)
for row_no, reason in bad_rows:
sheet.cell(row_no, 1).fill = warn_fill
note_col = sheet.max_column + 1
sheet.cell(1, note_col).value = "校验结果"
sheet.cell(row_no, note_col).value = reason
不过这段还有个小问题。
note_col 没必要每次循环都算,我实际写的时候会提前定下来:
note_col = sheet.max_column + 1
sheet.cell(1, note_col).value = "校验结果"
for row_no, reason in bad_rows:
sheet.cell(row_no, 1).fill = warn_fill
sheet.cell(row_no, note_col).value = reason
这种细节不影响功能,但批处理脚本我不喜欢把没必要的动作塞进循环里。
接下来是办公室里另一个高频需求:汇总。
比如按部门统计订单金额。
没必要再开 Excel 透视表,直接扫一遍。
from collections import defaultdict
dept_col = headers["部门"]
status_col = headers["状态"]
summary = defaultdict(float)
for row_no in range(2, sheet.max_row + 1):
dept = sheet.cell(row_no, dept_col).value
status = sheet.cell(row_no, status_col).value
amount = sheet.cell(row_no, amount_col).value
ifnot dept or status == "已取消":
continue
try:
summary[dept] += float(amount)
except (TypeError, ValueError):
pass
然后单独生成一个汇总页。
if"部门汇总"in book.sheetnames:
del book["部门汇总"]
report = book.create_sheet("部门汇总")
report.append(["部门", "有效订单金额"])
for dept, total in summary.items():
report.append([dept, round(total, 2)])
append() 这个方法我挺常用。
生成报表时,一行一行往后塞,比自己维护行号省事。
最后再顺手调一下列宽。
report.column_dimensions["A"].width = 20
report.column_dimensions["B"].width = 18
for cell in report["B"][1:]:
cell.number_format = '#,##0.00'
处理完千万别覆盖原文件。
尤其是别人甩给你的财务表、库存表、人事表,原件先留着。
book.save("订单明细_已检查.xlsx")
到这里,openpyxl 日常最常用的东西其实已经过了一遍:
打开 Excel:
load_workbook()
取工作表:
book["订单数据"]
读写单元格:
sheet.cell(row, col).value
遍历行:
for row_no in range(2, sheet.max_row + 1):
新增工作表:
book.create_sheet()
写一整行:
sheet.append()
最后保存:
book.save()
真正在办公自动化里,难的通常不是记住 openpyxl 有多少 API。
难的是先把人工操作拆明白。
“筛选空值、改格式、标异常、再汇总”,人操作 Excel 得来回点半天,换成代码其实就是几次循环和判断。
这种脚本我反而不建议写得特别花。
几十行,输入文件固定,规则写清楚,异常能看见,最后另存一份。
下个月运营再甩一个同格式的表过来,命令一跑,活就结束了。
夜雨聆风