某些情况下,我们希望利用Python在Excel中批量且自动地调整格式。这种时候,比起查阅复杂的 win32api,直接在 Python 中向 Excel 的 VBA 宏发起调用显然是一个更加简单且直观的方法。
下文会简单介绍一下我常用的一种方法,我对于它的使用多半基于经验,而非严谨的理论,因此它可能并不是最好的一种方法,甚至都算不上一种好方法,并且在我没有试验过的硬件环境下存在出错的可能。请务必以批判的眼光阅读,如有问题可随时与我探讨。
环境
- Windows (因为需要用到win32api)
- Python3 (我用的是3.12,但是版本应当并不重要,只要能够安装下面的依赖)
- Microsoft Office (在Python中调用VBA需要创建一个Excel进程,因此你的机器上一定要安装Office)
准备工作
安装 pywin32 库
pip install pypiwin32
确保在Excel中启用并信任宏
打开 Excel,在文件-选项-信任中心-信任中心设置-宏设置
中,勾选信任对 VBA 工程对象模型的访问
。
录制并编辑你的宏模板
如果在此之前你有VBA和宏的使用经验,那么这一部分对你来说应当小菜一碟。
首先要明白,录制宏是为了帮助我们简化宏和VBA的编写,让你省去大量查询VBA对象文档的时间,它不一定是万能的,但相当有效。
录制宏的时机也很有讲究,比如,
我需要利用程序向Sheet中填入n行,而对于这n行中的任意行N,我都希望把第N行A列的单元格格式设置为加粗居中,同时把第1行从B到E列的所有单元格的格式全部刷到第N行从B到E列的单元格上。
那么我们需要做的便是,
在Excel顶上的
视图-宏
的下拉框中选择录制宏
,宏名随意,快捷键不设,然后点击确定开始录制。回到Sheet,选中A2单元格(或者是A3、A4、A2000,这个不重要),在开始
选项卡中依次点击加粗
和居中
;然后选中B1到E1的单元格,在开始
选项卡中点击格式刷
,然后点击B2单元格将格式刷上去。最后回到视图-宏
,在下拉框中停止录制
。
💡 发现了吗?在录制宏的整个流程中,我们所扮演的是“指导者”的角色,我们并不需要告诉机器每一步应当具体怎么做(实际上我们也做不到),我们只需要给机器演示其中一步,之后再通过编程的手段让机器“举一反三”即可。
好,到此为止,我们已经录制了我们的第一个宏。让我们在视图-宏
的下拉框中找到查看宏
,选中我们的宏名,点击编辑
。在这里,我们可以看到刚刚录制的宏的 VB 代码:
1 | Sub 宏1() |
对于这段代码,我们大可不必弄懂它每一步说的是啥,干了什么。我们唯一需要做的,就是把那些可变的量标记出来。
还记得我们刚刚做了什么吗?我们希望为任意的AN单元格设置加粗和居中,但在录制的时候我们仅仅为A2一个单元格设置了加粗和居中。因此,我们要把A2替换成变量AN,并在之后的编程中进一步替换。
1 | Range("AN").Select Selection.Font.Bold = True |
以此类推,我们希望把B1:E1的单元格刷到任意BN:EN单元格中,那么我们应当把代码中的B2:E2标记为变量……吗?你可能已经发现了,代码中并没有出现B2:E2的字眼,只有B2出现过一次。不过不用担心,这是格式刷的特性,在这里我们也作特殊处理,只要把B2替换为变量BN即可。
1 | Range("B1:E1").Select |
最后,让我们把宏的名字从宏1改成我的宏,然后删除多余的注释行,一个 VBA 模板的雏形就此诞生!
1 | Sub 我的宏() |
Python 代码部分
首先引入pywin32:
1 | from win32com.client import DispatchEx |
还记得我们在刚刚录制宏的最后产生了一个 VB 代码模板吗?将它复制过来,同时利用 Python 的字符串 Formatter 功能实现变量替换。
让我们创建一个根据模板生成 VB 代码的函数:
1 | def gen_script(script_id: int, row_index: int) -> str: |
我们这个函数读取了一个行索引row_index
作为参数,并用其替换我们刚刚的变量AN和BN,最后以字符串的形式返回生成的VBA代码。
我想你可能注意到了,我们还有一个形参script_id
,这个参数改变了我们的宏名,如果script_id
取1,那么生成的宏名则为我的宏1;如果取2000,那么宏名则为我的宏2000……
理由很简单,这么做是为了保证在一个 Excel 文件中执行多个宏时不会出现冲突。就像同一命名空间下 Python 的函数名不能重复(类里面对于成员方法的重写除外)一样,同一文件下的 VBA 宏名也不能重复。
好了,最复杂的部分搞定了,让我们“八股文”式地编写 Python 代码来调用这个宏吧!
首先创建 Excel 进程,将其设置为“无头”模式:
1 | client = DispatchEx('Excel.Application') |
初始化script_id
,我们准备让它从1开始迭代,每次的增量为1:
1 | script_id = 1 |
然后打开工作表、打开 Sheet、打开……
1 | workbook = client.Workbooks.Open(os.path.abspath('MyWorkbook.xlsx'), ReadOnly=False) |
注意:在Open时请务必使用绝对路径,使用相对路径一定会报错!
接下来我用一个简单的 for 循环,对 Sheet 的 3 到 23 行生成并执行宏:
1 | for row_index in range(3, 24): |
最后,让我们保存并退出:
1 | workbook.SaveAs(os.path.abspath('MyWorkbookNEW.xlsx')) |
同样,SaveAs 也要使用绝对路径。
结尾
内容到此告一段落。
我在 GitHub 上有一个应用实例,包含了本文所述的基于 Python 的 VBA 调用方法,代码可随时查看,不过使用的数据,由于涉及隐私不便公开。