🔥
个人主页:
杨利杰YJlio
❄️
个人专栏:
《Sysinternals实战教程》
《Windows PowerShell 实战》
《WINDOWS教程》
《IOS教程》
《微信助手》
《锤子助手》
《Python》
《Kali Linux》
《那些年未解决的Windows疑难杂症》
🌟
让复杂的事情更简单,让重复的工作自动化

@[TOC](《超简单:用 Python 让 Excel 飞起来》读书笔记:3.3.2 保存工作簿:workbook.save()、close() 与 app.quit())

1. 写在前面:Excel 自动化不能只会写入,还要会正确收尾
本篇是我阅读《超简单:用 Python 让 Excel 飞起来》时整理的读书笔记,对应书中第 3 章 Python 模块里的 3.3.2 保存工作簿:workbook.save()、close() 与 app.quit()。
上一节如果已经能做到用 xlwings 启动 Excel、新建工作簿、往单元格写入数据,那么这一步还不能算真正完成自动化。真正的闭环应该是:生成文件 → 保存到指定位置 → 关闭工作簿 → 退出 Excel 进程。
很多 Excel 自动化脚本的问题,不是出在写入数据,而是出在最后收尾。文件没有保存到预期目录、Excel 后台进程残留、工作簿没有关闭、路径写错导致保存失败,这些问题在批量处理时非常常见。
这张图展示的是 xlwings 保存工作簿的完整闭环:生成文件、保存、关闭工作簿、退出 Excel。

从图中可以看出,save()、close()、quit() 不是三个可以随便省略的动作。save() 解决文件落盘问题,close() 解决工作簿关闭问题,app.quit() 解决 Excel 应用退出问题。三者都做完,自动化脚本才算收尾干净。
#mermaid-svg-oS1SSvnbrE5NMXqa{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-oS1SSvnbrE5NMXqa .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oS1SSvnbrE5NMXqa .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oS1SSvnbrE5NMXqa .error-icon{fill:#552222;}#mermaid-svg-oS1SSvnbrE5NMXqa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oS1SSvnbrE5NMXqa .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oS1SSvnbrE5NMXqa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oS1SSvnbrE5NMXqa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oS1SSvnbrE5NMXqa .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oS1SSvnbrE5NMXqa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oS1SSvnbrE5NMXqa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oS1SSvnbrE5NMXqa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oS1SSvnbrE5NMXqa .marker.cross{stroke:#333333;}#mermaid-svg-oS1SSvnbrE5NMXqa svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oS1SSvnbrE5NMXqa p{margin:0;}#mermaid-svg-oS1SSvnbrE5NMXqa .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-oS1SSvnbrE5NMXqa .cluster-label text{fill:#333;}#mermaid-svg-oS1SSvnbrE5NMXqa .cluster-label span{color:#333;}#mermaid-svg-oS1SSvnbrE5NMXqa .cluster-label span p{background-color:transparent;}#mermaid-svg-oS1SSvnbrE5NMXqa .label text,#mermaid-svg-oS1SSvnbrE5NMXqa span{fill:#333;color:#333;}#mermaid-svg-oS1SSvnbrE5NMXqa .node rect,#mermaid-svg-oS1SSvnbrE5NMXqa .node circle,#mermaid-svg-oS1SSvnbrE5NMXqa .node ellipse,#mermaid-svg-oS1SSvnbrE5NMXqa .node polygon,#mermaid-svg-oS1SSvnbrE5NMXqa .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oS1SSvnbrE5NMXqa .rough-node .label text,#mermaid-svg-oS1SSvnbrE5NMXqa .node .label text,#mermaid-svg-oS1SSvnbrE5NMXqa .image-shape .label,#mermaid-svg-oS1SSvnbrE5NMXqa .icon-shape .label{text-anchor:middle;}#mermaid-svg-oS1SSvnbrE5NMXqa .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-oS1SSvnbrE5NMXqa .rough-node .label,#mermaid-svg-oS1SSvnbrE5NMXqa .node .label,#mermaid-svg-oS1SSvnbrE5NMXqa .image-shape .label,#mermaid-svg-oS1SSvnbrE5NMXqa .icon-shape .label{text-align:center;}#mermaid-svg-oS1SSvnbrE5NMXqa .node.clickable{cursor:pointer;}#mermaid-svg-oS1SSvnbrE5NMXqa .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-oS1SSvnbrE5NMXqa .arrowheadPath{fill:#333333;}#mermaid-svg-oS1SSvnbrE5NMXqa .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oS1SSvnbrE5NMXqa .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oS1SSvnbrE5NMXqa .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oS1SSvnbrE5NMXqa .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-oS1SSvnbrE5NMXqa .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oS1SSvnbrE5NMXqa .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-oS1SSvnbrE5NMXqa .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oS1SSvnbrE5NMXqa .cluster text{fill:#333;}#mermaid-svg-oS1SSvnbrE5NMXqa .cluster span{color:#333;}#mermaid-svg-oS1SSvnbrE5NMXqa div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-oS1SSvnbrE5NMXqa .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-oS1SSvnbrE5NMXqa rect.text{fill:none;stroke-width:0;}#mermaid-svg-oS1SSvnbrE5NMXqa .icon-shape,#mermaid-svg-oS1SSvnbrE5NMXqa .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oS1SSvnbrE5NMXqa .icon-shape p,#mermaid-svg-oS1SSvnbrE5NMXqa .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-oS1SSvnbrE5NMXqa .icon-shape .label rect,#mermaid-svg-oS1SSvnbrE5NMXqa .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oS1SSvnbrE5NMXqa .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-oS1SSvnbrE5NMXqa .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-oS1SSvnbrE5NMXqa :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
xw.App 启动 Excel
新建或打开工作簿
写入/处理数据
wb.save 保存文件
wb.close 关闭工作簿
app.quit 退出 Excel
检查文件与进程
我建议把这套流程当成 xlwings 脚本的固定收尾习惯。尤其是做批量报表、资产清单、数据汇总这类自动化时,最后一步不收干净,后面很容易出现文件占用、进程堆积和运行变慢。

2. save、close、quit 三个动作分别做什么
先把三个动作分清楚。新手最容易把它们混成一件事,以为“保存了就等于结束了”,或者“关闭工作簿就等于 Excel 退出了”。这个理解不准确。
workbook.save(path) 负责保存工作簿。它解决的是文件是否写入磁盘、保存到哪个路径的问题。
workbook.close() 负责关闭当前工作簿。它关闭的是这个 Excel 文件本身,但不一定退出 Excel 应用。
app.quit() 负责退出 Excel 应用程序。它解决的是后台 EXCEL.EXE 进程是否残留的问题。
一句话总结:save 是保存文件,close 是关闭工作簿,quit 是退出 Excel 程序。
最标准的基础写法如下:
import xlwings as xw
app = xw.App(visible=False)
wb = app.books.add()
sht = wb.sheets[0]
sht.range("A1").value = "Hello xlwings!"
save_path = r"C:\\Temp\\输出文件.xlsx"
wb.save(save_path)
wb.close()
app.quit()
print("已保存并关闭:", save_path)
这段代码的优点很明确:保存位置可控,工作簿会被关闭,Excel 进程也会退出。对于办公自动化来说,这比“能生成文件”更重要。
如果只写 wb.save(),但不写 wb.close() 和 app.quit(),脚本短期可能看不出问题,长期批量运行就容易留下 Excel 后台进程。

3. 绝对路径 vs 相对路径:保存位置必须可控
保存 Excel 文件时,路径问题必须先讲清楚。因为很多人运行脚本后找不到文件,不是 xlwings 没保存,而是保存到了自己没注意到的位置。
绝对路径是从盘符开始写完整路径,例如:
save_path = r"C:\\Temp\\输出文件.xlsx"
相对路径则是不写完整目录,例如:
save_path = "输出文件.xlsx"
相对路径表示保存到当前工作目录下。问题在于,当前工作目录会受到运行方式影响。你在 PyCharm、VS Code、命令行、计划任务里运行脚本时,当前目录可能不一样。
这张图展示的是绝对路径和相对路径的区别,左侧是推荐的绝对路径,右侧是不推荐的相对路径。

从图中可以看出,绝对路径的保存位置固定,脚本运行后文件会明确保存到 C:\\Temp 目录。而相对路径只写了 输出文件.xlsx,具体保存位置取决于当前工作目录,容易出现“脚本跑完了,但文件不知道在哪”的问题。
做办公自动化时,我默认推荐使用绝对路径。尤其是给别人使用的脚本,路径越明确,后期沟通成本越低。
如果确实想使用相对目录结构,也不要直接让 wb.save("输出文件.xlsx") 乱跑,而是先把相对路径转成一个明确的完整路径。
import os
out_dir = os.path.join(os.getcwd(), "result")
if not os.path.exists(out_dir):
os.makedirs(out_dir)
save_path = os.path.join(out_dir, "输出文件.xlsx")
print("保存位置:", save_path)
这类写法既保留了相对目录的灵活性,又让最终保存路径变得可见、可控。脚本运行后先打印 save_path,排查时也更方便。

4. Windows 路径为什么建议使用 r""
在 Windows 里,路径通常使用反斜杠 \\。但在 Python 字符串中,反斜杠是转义符。比如 \\n 表示换行,\\t 表示制表符。
如果你直接这样写:
path = "C:\\new\\test.xlsx"
这条路径看起来像是 C:\\new\\test.xlsx,但 Python 可能会把里面的 \\n 当成换行,把 \\t 当成制表符。结果就是路径被悄悄破坏。
这张图展示的是 Windows 路径字符串的常见错误和推荐写法,重点是使用 r"…" 原始字符串避免转义问题。

从图中可以看出,普通字符串里的 \\n 和 \\t 可能被解释成特殊字符,而使用 r"C:\\new\\test.xlsx" 后,路径会按原样保留。这就是原始字符串的价值。
推荐写法如下:
save_path = r"C:\\new\\test.xlsx"
另一种写法是使用双反斜杠:
save_path = "C:\\\\new\\\\test.xlsx"
两种方式都可以,但我个人更推荐第一种。Windows 路径一律写成 r"…",简单、直观,也更不容易出错。
有一个小细节要注意:原始字符串不能以单个反斜杠结尾。比如 r"C:\\Temp\\" 这种写法会有问题。如果路径是目录,建议写成 r"C:\\Temp",或者使用 os.path.join() 拼接后续文件名。

5. 保存目录不存在时,要先创建目录
保存文件还有一个常见坑:保存路径写得没问题,但目标目录不存在。比如你想保存到 C:\\Temp\\result\\输出.xlsx,可是 result 文件夹还没有创建,保存就可能失败。
这类问题在批量自动化中很常见。因为我们通常会把处理结果单独放进 result、output、导出结果 这类目录里。如果脚本不负责创建目录,就要依赖人工提前建好文件夹,这不稳定。
这张图展示的是“先建目录,再保存文件”的流程:先检查 result 目录是否存在,不存在就创建,然后再执行保存。

从图中可以看出,保存动作前面多了一步目录检查。这个动作很小,但能避免很多路径类报错。批量脚本越多,越应该把这种前置条件写进代码里,而不是靠人工记忆。
推荐写法如下:
import os
out_dir = r"C:\\Temp\\result"
if not os.path.exists(out_dir):
os.makedirs(out_dir)
save_path = os.path.join(out_dir, "输出文件.xlsx")
再结合 xlwings 保存:
import os
import xlwings as xw
app = xw.App(visible=False)
wb = app.books.add()
sht = wb.sheets[0]
sht.range("A1").value = "Done"
out_dir = r"C:\\Temp\\result"
if not os.path.exists(out_dir):
os.makedirs(out_dir)
save_path = os.path.join(out_dir, "输出文件.xlsx")
wb.save(save_path)
wb.close()
app.quit()
print("已保存:", save_path)
推荐把输出目录创建逻辑写进脚本。这样脚本换电脑运行时,只要上级路径存在,输出目录就能自动准备好。
但不要把输出目录随便放到系统目录、Program Files 或无权限的共享目录里。如果目录本身没有写入权限,创建目录和保存文件都会失败。

6. try/finally:防止 EXCEL.EXE 残留
这一节是我认为最重要的实战点。因为很多 xlwings 脚本运行一段时间后,电脑越来越卡,任务管理器里一堆 EXCEL.EXE,根因往往就是脚本报错后没有执行 app.quit()。
如果代码正常跑到最后,手动写 app.quit() 当然没问题。但真实脚本可能在中途报错,比如路径不存在、文件被占用、工作表名称错误、单元格写入失败。只要中途报错,后面的 app.quit() 就可能没有机会执行。
这张图展示的是 try/finally 防止 Excel 进程残留的核心逻辑:即使中途报错,也要确保在 finally 中执行 app.quit()。

从图中可以看出,残留的 EXCEL.EXE 会占用内存、锁定文件,并影响后续脚本稳定性。正确做法是在 finally 中统一退出 Excel,保证无论成功还是失败,都尽量完成进程收尾。
推荐模板如下:
import xlwings as xw
app = xw.App(visible=False)
try:
wb = app.books.add()
# 这里写 Excel 处理逻辑
wb.sheets[0].range("A1").value = "Done"
wb.save(r"C:\\Temp\\输出.xlsx")
wb.close()
finally:
app.quit()
try 里放主要处理逻辑,finally 里放必须执行的清理动作。这不是为了让代码看起来高级,而是为了保证脚本异常时也能尽量收尾。
更严谨一点,还可以先定义 wb = None,避免工作簿还没创建成功就进入关闭逻辑:
import xlwings as xw
app = xw.App(visible=False)
wb = None
try:
wb = app.books.add()
wb.sheets[0].range("A1").value = "Done"
wb.save(r"C:\\Temp\\输出.xlsx")
finally:
if wb is not None:
wb.close()
app.quit()
这个模板更适合长期复用。尤其是后面写批量处理 Excel 文件时,建议优先采用这种结构,减少后台进程残留。
不要把 app.quit() 当成可有可无。对个人练习脚本来说,它只是一个收尾动作;对批量办公自动化来说,它是稳定性保障。

7. 完整模板:保存、关闭、退出一步到位
把前面的内容组合起来,可以得到一个更适合实战的保存模板。这个模板包含几个关键动作:指定输出目录、自动创建目录、保存文件、关闭工作簿、退出 Excel。
import os
import xlwings as xw
app = xw.App(visible=False)
wb = None
try:
wb = app.books.add()
# 示例:写入内容
sht = wb.sheets[0]
sht.range("A1").value = "Hello xlwings!"
sht.range("A2").value = "这是自动生成的 Excel 文件"
# 输出目录
out_dir = r"C:\\Temp\\result"
if not os.path.exists(out_dir):
os.makedirs(out_dir)
# 保存路径
save_path = os.path.join(out_dir, "输出文件.xlsx")
wb.save(save_path)
print("已保存:", save_path)
finally:
if wb is not None:
wb.close()
app.quit()
这段模板适合作为后续 Excel 自动化脚本的基础结构。真正写业务逻辑时,只需要替换中间的写入、清洗、汇总部分即可。
这类模板的价值在于,它把容易忘的收尾动作固定下来。以后无论是生成资产报表、整理维修清单,还是批量输出统计结果,都可以沿用这个结构。
如果是批量处理多个工作簿,则还要注意:每处理一个工作簿,最好对应关闭一个工作簿,不要把所有文件都长时间打开。
import os
import xlwings as xw
app = xw.App(visible=False)
try:
folder = r"C:\\Temp\\data"
out_dir = r"C:\\Temp\\result"
if not os.path.exists(out_dir):
os.makedirs(out_dir)
for name in os.listdir(folder):
if not name.lower().endswith(".xlsx"):
continue
src_file = os.path.join(folder, name)
out_file = os.path.join(out_dir, "处理后_" + name)
wb = app.books.open(src_file)
try:
# TODO:这里写处理逻辑
wb.save(out_file)
finally:
wb.close()
finally:
app.quit()
批量处理时,外层只保留一个 Excel App,内层逐个打开、保存、关闭工作簿,是比较稳的写法。这样既减少频繁启动 Excel 的开销,也能避免多个工作簿长期占用资源。

8. 效果验证:不要只看脚本有没有报错
保存工作簿以后,不能只看脚本有没有正常结束。真正的验证至少要看三件事:文件是否存在、文件内容是否正确、Excel 进程是否退出。
第一,检查目标路径下是否真的生成了文件。比如保存路径是 C:\\Temp\\result\\输出文件.xlsx,就要打开这个目录确认文件存在。
第二,打开生成的 Excel 文件,检查写入内容是否正确。比如 A1 是否写入了预期文本,数据格式是否正确,是否保存到了正确工作表。
第三,打开任务管理器,确认没有异常残留的 EXCEL.EXE。如果每运行一次脚本就多一个 Excel 进程,说明收尾逻辑有问题。
不要把“代码没有报错”误认为“自动化结果正确”。办公自动化最危险的情况不是脚本报错,而是脚本安静地生成了错误文件,用户还以为处理成功了。
可以在代码里增加简单验证:
import os
if os.path.exists(save_path):
print("文件已生成:", save_path)
else:
print("文件未生成,请检查保存路径")
推荐每次保存后都打印保存路径。这能减少很多沟通成本,尤其是把脚本发给同事运行时,对方可以直接复制输出路径去检查文件。

9. 常见问题与踩坑提醒
这一节整理几个实际写 xlwings 脚本时经常遇到的问题。它们不一定都是语法错误,但都会影响脚本稳定性。
9.1 文件保存到了哪里找不到
这种情况通常是使用了相对路径,但没有确认当前工作目录。解决办法是使用绝对路径,或者在保存前打印 os.getcwd() 和最终 save_path。
import os
print("当前工作目录:", os.getcwd())
print("保存路径:", save_path)
路径问题先打印出来,不要靠猜。
9.2 保存时报 PermissionError
常见原因有三个:目标文件正在被打开,目录没有写入权限,或者文件被同步软件、安全软件暂时占用。
如果目标 Excel 文件已经被手动打开,脚本保存时很可能失败。处理前先关闭目标文件,或者保存成新的文件名。
9.3 生成了很多 EXCEL.EXE
这通常说明 app.quit() 没有稳定执行。解决办法是使用 try/finally,把 app.quit() 放进 finally。
后台 Excel 进程残留,本质上是脚本资源释放不完整。这类问题一旦出现在批量脚本里,影响会被放大。
9.4 save 和 close 的顺序能不能反过来
不建议反过来。一般应该先 save(),再 close()。先关闭再保存,逻辑上就已经不成立了。
如果你打开的是已有文件,并且希望覆盖保存,可以使用 wb.save();如果希望另存到新路径,可以使用 wb.save(new_path)。
9.5 visible=False 是不是必须
不是必须,但办公自动化脚本通常推荐 visible=False。这样 Excel 在后台运行,不会打扰用户操作。
调试阶段可以设置 visible=True,正式批量处理时再改成 visible=False。这样既方便观察,也能保证最终脚本执行更安静。

10. 我的总结:保存工作簿是自动化闭环的最后一公里
这一节我最后记住的,不是单纯的 workbook.save() 语法,而是 Excel 自动化脚本的完整收尾逻辑。
一个真正可用的 Excel 自动化脚本,不能只负责生成数据,还要负责把文件保存到正确位置、关闭工作簿、退出 Excel 进程,并且让用户能验证结果。
本篇最核心的几个结论是:
第一,wb.save(path) 负责保存文件,建议传入明确的绝对路径。
第二,wb.close() 负责关闭工作簿,但不等于退出 Excel 应用。
第三,app.quit() 负责退出 Excel,是避免 EXCEL.EXE 残留的关键。
第四,Windows 路径推荐使用 r"…",保存目录不存在时要提前创建。
如果把这些动作固定成模板,后续写 Excel 自动化脚本会稳定很多。尤其是处理批量文件时,路径、保存、关闭、退出这几个细节越规范,脚本越不容易在真实办公环境里翻车。
对我来说,这一节真正有价值的地方,是把“保存文件”这件看似简单的小事,放进了完整自动化流程里理解。Excel 自动化不是把数据写进去就结束,而是要把整个流程收干净。
返回顶部




