乐于分享
好东西不私藏

Excel 表格别再手点了,openpyxl 这几段代码够你干活

Excel 表格别再手点了,openpyxl 这几段代码够你干活
一个 Excel,三千多行数据,运营让我把空手机号找出来、金额格式统一、异常订单标红,最后再按部门做个汇总。

这种活我第一反应就不是打开 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 得来回点半天,换成代码其实就是几次循环和判断。

这种脚本我反而不建议写得特别花。

几十行,输入文件固定,规则写清楚,异常能看见,最后另存一份。

下个月运营再甩一个同格式的表过来,命令一跑,活就结束了。