☰
Python操作Excel必备:openpyxl核心读写与自动化实战
2026/10/14 21:01:31 网站建设 项目流程

1. 先别急着写代码,搞清楚 openpyxl 到底解决什么问题

说实话,我在日常处理数据的时候,Excel 几乎是无处不在的。别人发来的业绩报表是 xlsx,领导要的周报模板是 xlsx,就连我自己的记账表、项目进度表也都是 xlsx。数据量小的时候鼠标点一点还好,但一旦到了几百行、上千行,或者需要一批一批地生成报告,光靠手工复制粘贴简直就是在折磨自己。所以我一直觉得,任何一个和数据打交道的人都应该会一个能操作 Excel 的 Python 库,而 openpyxl 就是其中最直接、最顺手的一个。

openpyxl 是一个纯 Python 编写的库,专门用来读写 Excel 2007 及以后版本的 xlsx/xlsm 文件。它能做的事情很具体:读取单元格的值、写入新的数据、设置单元格样式、合并单元格、插入图表、操作公式、筛选排序,甚至还能处理条件格式。它不像 pandas 那样上来就给你一个 DataFrame,而是更贴近 Excel 这个软件本身的逻辑:你有一个工作簿,工作簿里有多个工作表,每个工作表里有行列坐标对应的单元格。这种模型让很多熟悉 Excel 操作的人上手非常快,因为你脑子里想的“我在表格里填一个数”“我把这个格子标红加粗”,几乎都能直接翻译成 openpyxl 的代码。

什么人适合用这个库?我觉得主要有三类:第一类是经常做报表的职场人,你可能天天要处理销售明细、库存清单,与其每次都手动复制公式,不如写一个小脚本一劳永逸;第二类是测试和数据分析的初学者,你刚好需要批量准备测试数据或者做简单的数据清洗,又不想被 pandas 的很多概念绕晕;第三类是写自动化脚本的开发者,比如定时从某个系统导出数据后自动整理成固定格式的 Excel 文件。这篇文章我会从安装开始,把读写操作中最高频、最实用的部分完整过一遍,包括我踩过的坑和折腾出来的小技巧,尽量让你看完就能直接上手。

2. 为什么偏偏选 openpyxl,而不是 pandas、xlrd 或者 xlsxwriter

很多新手拿到“用 Python 操作 Excel”这个需求时,最先接触到的可能是 pandas,因为网上到处都在说 pandas 处理 Excel 很方便。pandas 确实方便——pd.read_excel()一行读进来,df.to_excel()一行写出去,但问题在于 pandas 的重点是“数据分析”,它把 Excel 当成一个数据流入流出的接口,而不是一个精细操作用的画布。你要做数据透视、分组聚合、统计分析,pandas 就是最优解;但如果你要打开一个已经有很多格式的模板文件,往里面指定位置填几个数,再调整某个单元格的背景色和边框,pandas 就很别扭了,它不擅长也不推荐做这种事情。

大家常听到的另外几个库也各有短板。xlrd 和 xlwt 是老牌库,操作的是 Excel 97-2003 的 xls 格式,放到今天来看,xls 格式本身已经不够主流,xlrd 的新版本甚至已经不再支持读取 xlsx 以外的格式了,维护状态也不乐观。xlsxwriter 主要用来“写入”新文件,功能上很强大,支持图表、图片、条件格式都有,但它不能读取已有的 xlsx 文件,也没办法修改一个现存的工作簿。所以如果你业务里既要读又要写,还希望保留原有的格式不被破坏,xlsxwriter 就不够用了。

openpyxl 目前是这几个选项里综合能力最均衡的:它既能读也能写,既能处理全新的文件也能处理已有的模板;它对 xlsx 格式的支持很完整,单元格样式、合并区域、冻结窗格、数据验证这些都能操作;而且它不依赖 pandas,随便配个 Python 环境就能跑,轻量干净。特别是在“批量修改报表”“按模板填充数据”这种场景里,openpyxl 几乎是不可替代的。

我这里给一张表,把几个常见库的适用情况说一下,方便你按需选型。

库名能读能写修改已有文件支持格式适合场景
openpyxl是是是xlsx / xlsm精细操作、模板填充、样式调整
pandas是是有限xlsx / xls 等数据分析、批量转表、聚合统计
xlrd是否否xls 为主只读老格式,注意新版限制
xlwt否是否xls写老格式
xlsxwriter否是否xlsx纯新建和写图表、写大文件

所以结论很简单:如果你要的是“把 Excel 当成一个可以被程序精细控制的对象”,用 openpyxl 就对了。

3. 环境准备和基础概念,10 分钟跑通第一个读写 Demo

3.1 安装 openpyxl,一条命令的事

openpyxl 的安装没有那么多花活,直接用 pip 安装就行。我个人建议你不管做什么项目,都先用虚拟环境隔离一下依赖,避免全局环境被搞成一团乱麻。具体操作如下:

python -m venv excel_env source excel_env/bin/activate # Windows 下执行 excel_env\Scripts\activate pip install openpyxl

装完以后在 Python 里执行import openpyxl不报错,就说明环境没问题了。如果你是在公司内网离线环境里,也可以先从官网下载 whl 文件,再用pip install xxx.whl安装。这一步实在没什么难度,我唯一要提醒的是:不要用sudo pip或者直接在系统全局环境里装,倒不是说会出多大的事,只是后续很容易出现依赖版本冲突,到时候排查起来很头疼。

3.2 一个能存活的读写 Demo

我先给一个最精简但是流程完整的示例,你把这个脚本跑通,就相当于已经知道了 openpyxl 80% 的日常工作模式。

import openpyxl # 1. 创建一个新工作簿 wb = openpyxl.Workbook() ws = wb.active ws.title = "成绩单" # 2. 写入数据 ws["A1"] = "姓名" ws["B1"] = "语文" ws["C1"] = "数学" students = [ ("张三", 88, 95), ("李四", 79, 92), ("王五", 93, 84), ] for row in students: ws.append(row) # 3. 保存文件 wb.save("成绩单.xlsx")

这段代码做完的事是:新建一个工作簿,把默认工作表改名为“成绩单”,写入标题行,再逐行追加学生数据,最后把整个工作簿保存为成绩单.xlsx。注意ws.append(row)这个方法很常用,它表示“从当前工作表的第一行还没有数据的空白位置开始,往下一行一行地追加数据”。如果你第一次运行,工作表是空的,那它就从第 1 行开始写,下一次再往同一个工作表追加,就自动跳到第 6 行,因为前面已经有 5 行数据了。

再来看一个读取的完整示例:

import openpyxl wb = openpyxl.load_workbook("成绩单.xlsx") ws = wb["成绩单"] # 读取单个单元格 print(ws["A1"].value) print(ws.cell(row=2, column=2).value) # 读取全部数据 for row in ws.iter_rows(min_row=1, max_row=ws.max_row, max_col=ws.max_column, values_only=True): print(row)

这个例子里你第一次接触到了两个读取姿势:一种是直接用坐标索引ws["A1"],和 Excel 里单元格的名称是一致的,符合直觉;另一种是用ws.cell(row=2, column=2),适合在循环里用变量方式动态指定行列。max_row和max_column是两个很重要的属性,表示当前工作表中有数据的最大行号和最大列号,遍历全表的时候经常拿它们来做边界。

3.3 关于 Workbook、Worksheet、Cell 这三个概念

把我刚才的代码拆开看,你会发现 openpyxl 的核心对象就是三层:最外层是 Workbook,对应一个 Excel 文件;中间是 Worksheet,对应文件里的一个表(就是左下角那些 tab);最里层是 Cell,对应一个单元格。日常操作基本就是这三层之间来回折腾。

Workbook 可以理解为整个文件的内存对象。你用openpyxl.Workbook()创建的是一个空白工作簿,里面会默认带一个名为 "Sheet" 的工作表;用openpyxl.load_workbook("xxx.xlsx")加载的是磁盘上已经存在的文件。操作完之后调用wb.save("xxx.xlsx")把内存里的内容写回磁盘。这里藏着一个容易踩的坑:save()是覆盖写。如果你加载了一个原来叫report.xlsx的文件,修改之后又直接save("report.xlsx"),原文件就被覆盖了,没有撤销的机会。所以我在处理重要文件的时候,习惯先另存成一个新文件名,确认没问题再替换。

Worksheet 就是你正在编辑的那个工作簿的某一个 sheet。wb.active表示当前默认激活的那个工作表;wb["成绩单"]是按名字取;wb.create_sheet(title="新表")是新建。如果你需要遍历所有工作表,wb.sheetnames能拿到一个包含所有表名的列表,这一招在合并多个 sheet 数据的时候特别好用。

Cell 是工作表里的最小单位,通过ws["A1"]或ws.cell(row=1, column=1)取到。一个单元格对象的核心属性就是.value,读取是拿它的值,赋值是设置它的值。一个让很多人不太适应的地方在于:就算你写ws["A1"] = "hello"之后再去读,也得通过.value才能拿到内容,直接打印ws["A1"]出来的是一坨对象的内存信息,不是 "hello"。

4. 核心读写操作:从单元格到区域,把常用操作全部吃透

4.1 单元格的读取和写入:两种写法都要熟练

读写单元格听起来很简单,但不同的写法背后对应着不同的编程场景。直接下标法ws["B2"] = 100是给人看的,代码清晰直白;ws.cell(row=2, column=2, value=100)是给程序看的,适合写在循环里,因为它接受的是整数参数,可以随变量变化。

如果你要批量给某一列填数据,不要一个一个地用ws["B1"]、ws["B2"]这种写法去赋值,尽量用append或者先构建好数据列表再统一写入。举个反例:我曾经见过一个把 3000 行数据通过for i in range(3000): ws.cell(row=i+1, column=2).value = data[i]写入的脚本,效率其实也不差,但代码读起来像上世纪的老式 BASIC,不好维护。后来我改成先把数据整理成列表,一次性操作,脚本清晰了,也方便后续加样式逻辑。

批量读取同样如此。你当然可以for row in range(1, ws.max_row + 1): for col in range(1, ws.max_column + 1): ...,但这种双重循环稍微复杂一点就容易把自己写晕。我更喜欢用iter_rows()和iter_cols()这两个方法,它们直接在行或者列的方向上做迭代,代码更短,也不容易差一。

# 按行读取,每个元素代表一行内的一组单元格 for row in ws.iter_rows(min_row=1, max_row=3, min_col=1, max_col=2): for cell in row: print(cell.coordinate, cell.value) # 按行读取,但只取单元格的值 for row in ws.iter_rows(min_row=1, max_row=3, min_col=1, max_col=2, values_only=True): print(row)

values_only=True这个参数很常用,它让迭代出来的东西不再是 Cell 对象,而是直接是值,在处理数据时更轻便。不过要注意,如果一行里有空单元格,values_only=True模式下读出来就是None,得自己处理空值。

4.2 批量写入的三种姿势:什么时候用 append,什么时候改单元格

我实际工作中用得最多的写法是append(),前面已经说过了,它自动找空白行往下追加,适合从零开始搭建一组数据。还有一种情况是你要往指定区域放数据,比如模板里已经规定好了 "B3:D3" 是合计区域,那你就用坐标直接赋值。第三种情况是读入了一个二维列表,想整体填到一个区域里,这时候可以用ws["A1:C3"] = list_of_lists这种区间赋值方式,它会按区间形状把数据填进去。注意区间赋值时如果数据和区间形状不匹配,openpyxl 会直接报ValueError,所以先assert一下行数列数最稳妥。

我个人的经验是:写新报表优先append(),填模板优先坐标赋值,区间赋值适合那种程序临时生成的大块数据。下面放一个区间赋值的小示例:

data = [ [1, 2, 3], [4, 5, 6], [7, 8, 9], ] ws["A1:C3"] = data

这段代码会把 1~9 九个数字分别填到 A1、B1、C1、A2、B2、C2 这样顺序的单元格里,矩阵结构和区域结构刚好对应,理解起来没有任何障碍。

4.3 读单元格的坑:data_only 参数必须搞清楚

load_workbook()有一个非常容易踩的坑,就是data_only参数。默认情况下它是False,此时你读取一个 Excel 里用公式算出来的单元格,拿到的不是结果显示,而是公式字符串本身。比如 Excel 里某个单元格显示 95,公式是=AVERAGE(B2:C2),你用默认方式读,得到的是"=AVERAGE(B2:C2)",而不是 95。这常常让第一次遇到的人一头雾水,以为数据丢了。

解决办法是load_workbook("xxx.xlsx", data_only=True)。这种模式下,openpyxl 会读取 Excel 在保存文件时缓存下来的公式计算结果。但这里面又有一个新的坑:如果这个文件是用 openpyxl 创建后直接保存的,Python 这一侧只保存了公式、并没有用 Excel 打开去算过,那data_only=True读出来的值就是None。因为这时候文件里根本没有可以被读取的公式结果缓存。

读取方式公式单元格读回来的是什么适用场景
data_only=False公式字符串,如=A1*2查看或修改公式本身
data_only=True公式的结果,如 88读取计算后的数据
都没有缓存None用 openpyxl 写公式后未用真实 Excel 打开过

所以我现在的习惯是:如果 Excel 文件是别人用 WPS 或者 Office 保存过的,我load_workbook(filename, data_only=True);如果这个文件是我自己脚本临时生成并且里面需要公式,那我就在写完公式之后,调用一个本地 Excel 应用打开再保存一次,或者干脆不依赖公式,直接在 Python 里把计算好的值写进去。追求自动化的时候就放弃公式,直接用结果,省得两头摸黑。

4.4 样式设置:字体、对齐、边框、填充色

很多人写 openpyxl 只填数据不加样式,然后拿出去的文件丑得没法看。其实设置样式也不复杂,核心就是几个类:Font管字体,PatternFill管背景填充,Alignment管对齐和换行,Border和Side管边框,openpyxl.styles模块里全都包含了。

我举一个实际场景:生成月度报表时给表头加个灰底、加粗、居中,再给所有数据单元格加上边框、自动换行。

from openpyxl.styles import Font, PatternFill, Alignment, Border, Side header_font = Font(bold=True, size=12) header_fill = PatternFill(start_color="D9D9D9", end_color="D9D9D9", fill_type="solid") center_alignment = Alignment(horizontal="center", vertical="center") thin_side = Side(style="thin", color="000000") border_all = Border(left=thin_side, right=thin_side, top=thin_side, bottom=thin_side) for col in range(1, ws.max_column + 1): cell = ws.cell(row=1, column=col) cell.font = header_font cell.fill = header_fill cell.alignment = center_alignment cell.border = border_all

这里有几个细节需要注意。PatternFill里的start_color和end_color对于solid类型的填充来说,只有start_color是有效颜色,end_color写不写都行,但很多人会在这里卡一下。Side(style="thin")的style还有medium、dashed等选项,如果你只是想要默认样式的细黑边框,thin就够了。边框必须四个方向都设置才完整,只设left的效果就是只有左边有框线。

还有一个小技巧:给整列设置宽度可以ws.column_dimensions["A"].width = 15,给整行设置高度可以ws.row_dimensions[1].height = 20。如果你数据里有长文本,记得配合Alignment(wrap_text=True)开启自动换行,不然表格里会直接撑出去。

4.5 合并单元格、冻结窗格和自动筛选

这三个功能在手工做 Excel 时很常用,openpyxl 也支持得很好。

合并单元格用的是ws.merge_cells("A1:D1"),合并之后左上角的那个单元格会保留值,其他被合并的单元格的值会变成None。取消合并用ws.unmerge_cells("A1:D1")。需要注意的是,合并单元格读取时只有左上角有值,你如果想按行遍历区域,被合并掉的单元格读出来的值是None,别觉得是程序 bug,这是 Excel 数据模型本身的逻辑。

冻结窗格是ws.freeze_panes = "A2",这句的意思是从 A2 这个位置之前的部分全部固定住。解释一下它的逻辑:你写freeze_panes = "B2",就表示从第二行开始向下滚动时第一行始终可见,从第二列开始向右滚动时第一列始终可见。如果你写freeze_panes = "A2",那第一行就被固定住了,表头滚不掉,这是做报表时最常用的一个设置。

自动筛选是ws.auto_filter.ref = ws.dimensions。这里有点绕:ws.dimensions是工作表的整个有效数据范围,比如A1:F120。把它指给auto_filter.ref,Excel 打开后第一行就会带上下拉筛选箭头,非常实用。注意ws.dimensions的获取时机要等你把数据都写完之后再设置,否则范围不准确。

4.6 读取和写入日期、公式、超链接

Excel 里日期是出了名的容易搞混。openpyxl 在写入日期时可以直接赋一个 Python 的datetime对象,它内部会存成数字值并设置日期格式,但为了显示效果,我一般会再显式设置一下单元格的number_format,比如yyyy-mm-dd。如果你不设置,用 Excel 打开时可能看到的是一串含义不明的数字,那其实是日期的底层序列值,不是乱码。

公式的写入也很直接,你只要把字符串赋值给单元格即可,比如ws["C2"] = "=SUM(A2:B2)"。前面已经提过,用 openpyxl 写进去的公式在文件里是没有计算结果缓存的,如果你后续用 pandas 或者其他工具读取这个文件,可能拿不到公式的值,这是由文件格式决定的,不是谁能改的。

超链接可以直接cell.hyperlink = "https://example.com",然后还可以顺便设置cell.style = "Hyperlink"让他有可点击的外观。不过说实话,超链接这个功能我日常用得少,如果你只是在做数据导出,这一项可以先跳过。

5. 进阶实操:批量整理 Excel,用脚本解决重复劳动

5.1 遍历工作簿里所有工作表,把数据汇总起来

我举个例子:某公司每个月的考勤明细表里分了 12 个 sheet,名字分别叫 1 月、2 月、一路到 12 月,每张表的结构完全一样,第一行是表头,后面是每个人的出勤天数、加班工时。领导要求把所有表合并成一张总表。手工复制 12 次也算能完成,但如果在脚本里遍历一次,以后每个月都不用再手动干这个活了。

import openpyxl wb = openpyxl.load_workbook("考勤明细.xlsx", data_only=True) all_rows = [] for name in wb.sheetnames: ws = wb[name] for row in ws.iter_rows(min_row=2, values_only=True): all_rows.append((name,) + row) new_wb = openpyxl.Workbook() new_ws = new_wb.active new_ws.append(("月份", "姓名", "出勤天数", "加班工时")) for row in all_rows: new_ws.append(row) new_wb.save("考勤总表.xlsx")

这里有几个地方值得注意:我在读取时用了data_only=True,因为考勤明细里的数据如果是公式生成的,没有这个参数读回来就是一堆公式字符串,根本没法汇总。iter_rows(min_row=2, values_only=True)直接从第二行开始,跳过标题行。合并时我在每一行前面插入了月份信息,这样汇总表里能看出每条数据来自哪个月。

5.2 怎么处理“模板文件太卡”的问题:只读模式和只写模式

openpyxl 默认加载一个 xlsx 文件会把所有单元格都读进内存,当你遇到一个几十 MB、几万行的文件时,加载过程会非常慢,甚至内存占用直接爆掉。这时候有两个优化手段:read_only=True和write_only=True。

load_workbook("big.xlsx", read_only=True)模式下,openpyxl 不会把所有工作表完整读入内存,而是采用按需读取的方式,遍历数据时速度很快、内存占用小。代价是这种模式只支持读取,不支持修改工作表结构,你也不能用它来改样式。适合的场景是:你只需要从一个大文件里把数据抽出来转存或者统计。

反过来,write_only=True是openpyxl.Workbook(write_only=True),它创建的工作簿不允许读取,但写入大数据时内存占用小很多。如果我要生成一个 10 万行的报表,我会优先考虑这个模式。

wb = openpyxl.Workbook(write_only=True) ws = wb.create_sheet(title="大表") ws.append(["序号", "数值"]) for i in range(1, 100001): ws.append([i, i * 2]) wb.save("大表.xlsx")

但注意,write_only模式下不能直接改单元格,也没有.cell()方法,只能append一行行加。你要提前把样式设置成整行也可能受到限制,所以它不是万能的,只是大数据场景下的一种取舍。

5.3 把样式模板复用起来:不要让代码变成一堆重复赋值

每次处理新的 Excel 都要写一遍表头加粗、设边框、调列宽吗?不用。我做法是写一个函数,接收一个 worksheet 对象,然后统一处理样式。以后所有脚本都用这个函数,改动一处,所有报表的样式都会跟着变。

def style_header_row(ws, row=1): for cell in ws[row]: cell.font = Font(bold=True, color="FFFFFF") cell.fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid") cell.alignment = Alignment(horizontal="center", vertical="center") cell.border = Border( left=Side(style="thin"), right=Side(style="thin"), top=Side(style="thin"), bottom=Side(style="thin"), )

这种封装的收益是长期的。你第一次写可能觉得多花了一点时间,但一个月后你要让表头从白字蓝底改成黑字黄底,只需要改这一处地方,而不是在几十个脚本里搜来搜去。

5.4 用 pandas 和 openpyxl 配合

很多人以为用了 openpyxl 就不能再用 pandas,这俩其实是互补关系。我的常用套路是:用 pandas 做复杂的数据清洗和统计,拿到最终结果后用 openpyxl 做格式化输出。或者反过来,用 openpyxl 从模板里读取配置信息,然后交给 pandas 去分析。

import pandas as pd from openpyxl.workbook import Workbook df = pd.DataFrame({"姓名": ["张三", "李四"], "成绩": [88, 95]}) wb = Workbook() ws = wb.active ws.append(df.columns.tolist()) for row in df.itertuples(index=False): ws.append(list(row)) wb.save("pandas_style.xlsx")

pd.read_excel()底层其实也支持openpyxl作为引擎。所以如果你项目中已经装了 openpyxl,那 pandas 读 xlsx 时默认用的引擎就是它。两者无缝衔接,没有谁取代谁的问题,只有什么时候用谁最合适的问题。

6. 常见报错与排查技巧:把这些坑提前填平

6.1 FileNotFoundError:路径不对是最大元凶

这个报错最常见的原因不是文件不存在,而是你给的路径错了。Python 在工作时相对路径是以当前工作目录为基准的,如果你脚本在 A 文件夹,Excel 文件在 B 文件夹,光写一个文件名当然找不到。我的建议是无论什么场合,文件路径都用os.path.join()或者pathlib.Path去拼,不要手写字符串。另外 I/O 操作时把路径打印一下,很多时候问题一眼就能看出来。

6.2 PermissionError:文件正被 Excel 占用

Windows 上这是第二个高频报错。你用代码读取或者保存文件时,如果那个 xlsx 正好被 Excel、WPS 打开着,系统会拒绝访问,抛PermissionError。这不是 openpyxl 的问题,是操作系统级别的文件锁。解决办法很简单:先关掉 Excel 再跑脚本。

我吃过很多次这个亏。以前有个脚本是每天早上批量生成报表,结果每过几天就有人反馈说报错,排查到最后发现是某个同事习惯性地把昨天的报表打开在那里看,锁住了文件。后来我在代码里加了异常捕获,遇到PermissionError就提示“请关闭已打开的 Excel 文件后重试”,这才算彻底把人从排查里解放出来。

6.3 NoneType 错误:公式缓存惹的祸

报错信息是AttributeError: 'NoneType' object has no attribute 'value'或者你也可能直接打印出None。原因我在前面已经说过:data_only=True读取一个没有计算结果缓存的公式单元格时,值就是None。所以排查思路是:先确认你的文件是谁生成的,再用data_only=False读一下看是不是公式字符串。如果确实需要公式计算结果,最好的办法是让数据源先把值算好再落到 Excel 里。

6.4 格式不支持:xls 和 xlsx 不是一回事

InvalidFileException这个报错一般是文件格式问题导致的。openpyxl 只支持 xlsx/xlsm,不支持老版 xls。如果你拿到一个 xls 文件,最好的办法是先用 Excel/WPS 另存为 xlsx 再让程序处理。如果这种操作太频繁,你可以用工具批量转换,或者干脆写个很小的函数,遇到 xls 就调用另一个库读出来再转存。我个人的建议是业务上尽量统一 xlsx 格式,大家沟通成本最低。

6.5 数据明明在表格里,循环读出来却少了行列

这个现象通常出在max_row和max_column不准确时:比如你的表格第一行和第二行之间有空行,max_row会停留在最后一个有数据的行,但如果空行出现在中间,遍历时就把空行也读进来了。反过来,如果表格最右边几列只有格式没有内容,max_column可能比实际数据列数大。我常用的做法是用iter_rows()时不依赖max_column,而是显式指定列范围,或者用values_only=True之后把None的列过滤掉。

6.6 常见问题速查表

报错信息可能原因解决思路
FileNotFoundError路径不对或文件不存在检查工作目录,用绝对路径
PermissionError文件被 Excel/WPS 打开关闭相关程序后重试
AttributeError: 'NoneType' object...公式值无缓存改data_only=False,或先用真实 Excel 保存
InvalidFileException文件不是 xlsx 格式转换为 xlsx 再处理
ValueError: cells of the openpyxl...区间和数据结构不匹配检查二维列表的行列数
zipfile.BadZipFile文件损坏或不是合法 xlsx用 Excel 重新打开另存

7. 几个提高效率的实操心得

我在实际使用 openpyxl 的这几年里,最大的体会是这个库的 API 设计基本遵循了 Excel 的操作直觉,所以学习成本很低,但想要真正用得顺,还是得靠多积累。这里分享几个我自己认为价值最高的心得。

第一,所有写入操作最后一定要记得wb.save()。这个听起来像废话,但有相当多的人写完了忘了保存,然后跑来问为什么数据没写进去。openpyxl 的所有变化都停留在内存对象里,不调用save()全部白搭。我记得第一次写脚本时也干过这种事,代码跑完没有任何报错,打开文件一看什么都没变,非常懵。

第二,调试时打印坐标和数据比什么都管用。比如你写了一个遍历脚本,先打印一下ws.max_row和ws.max_column,看看预期范围对不对。再打印一下ws["A1"].value,看读取对不对。很多问题不是逻辑错了,是数据源就和你想的不一样。

第三,随手设置number_format。我见过太多导出后的 Excel 日期列显示成一串数字、百分比列显示成小数、金额列没有千分位。这个问题不是 openpyxl 的功能缺陷,而是你没有告诉 Excel 这些单元格应该长什么样。一个number_format参数就能解决,比如ws["B2"].number_format = "0.00%"。

第四,如果要处理大量文件,脚本跑之前先在少量文件上试一遍。比如你有 100 个 xlsx 要合并,第一次可以只选 3 个跑,确认输出结果格式正确、行数对得上,再放全量跑。否则 100 个文件跑到第 90 个的时候突然报错,排查起来很难受。

8. 还能怎么继续扩展这个库的使用

openpyxl 能做的事远不止读写。我在做一些自动化报表时,还经常用到数据验证DataValidation来给单元格加下拉列表,用条件格式FormatRule给超过阈值的数值标红,甚至用图表模块往工作表里插入柱状图折线图。这些功能虽不算高频,但在某些特定业务场景下能省掉非常多手工操作。

如果你想系统地练习 openpyxl,我给一个建议:找一个自己手边真实存在的 Excel 表格,先尝试只用代码读一遍,再尝试生成一个格式好看的新表,最后再做一个合并或拆分的操作。做完这三个练习,openpyxl 的读写基础就算真正掌握了。之后你再看文档去探索样式、图表、数据验证等功能,会发现一切都很自然,因为你脑子里已经有了工作簿、工作表、单元格三者关系的清晰模型。

最后再分享一个小经验:写 openpyxl 脚本时,尽量把读和写拆成两个函数或者两个阶段。比如一个脚本里先load_workbook,把所有需要的数据读出来整理好,再新建一个 workbook 写入。这样代码结构清楚,也方便你中途打印数据排查问题。不要一边读一边写同一个文件,那样不仅容易出错,后面想复用这段代码也很麻烦。

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

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

立即咨询