ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

xlwings操作WPS表格全攻略:解决NoneType报错与COM接口对接

xlwings操作WPS表格全攻略:解决NoneType报错与COM接口对接 说来挺魔幻的同事给我投过来一个Python批量报表脚本在开发机上一路绿灯结果拿到现场一跑全都是NoneType在报错。排查到最后发现一个共同点那家公司的办公电脑全员装的是WPS表格压根没有装Office。也就是从那一刻起我才认真研究起“Python xlwings到底能不能操作WPS表格”这个问题并且把从报错到正常运行的完整路径摸了一遍。如果你也是只有WPS没有Excel、又恰好要用Python做表格自动化的那批人这篇文章应该能帮你少走几个星期的弯路。先说结论xlwings本身是个非常成熟的库但它默认只认识Excel的COM接口也就是Excel.Application而WPS表格对外暴露的COM接口是KET.Application。两边对不上轻则返回空对象重则直接抛出com_error表现出来就是各种NoneType。所以问题的关键不在于“报错”而在于怎么让xlwings和WPS的接口正确对接。这篇文章我会从原理拆到实操再列出我实际踩过的坑照着做基本都能跑通。1. 先弄清NoneType到底从哪来很多教程一上来就让你改注册表、改源码但根本没解释清楚为什么要这么干。在动手之前我建议你先花五分钟搞清楚报错链条不然今天改对了明天换个机器又不知道错在哪。1.1 xlwings的“眼睛”只认识Excelxlwings在Windows上不是靠什么黑魔法工作的它底层就是win32com通过COM组件去调用Excel或WPS的进程。你可以把COM理解成一个电话簿程序要打电话给某个软件就查这个电话簿找到对应的号码ProgID。Excel的号码是Excel.ApplicationWPS表格的号码则是KET.Application两者都是“打开工作簿”这个服务的入口但号码各不相同。问题就出在这xlwings在初始化App的时候默认只会去拨打Excel.Application这个号码。如果你的电脑里没有安装真正的Excel或者Excel的COM组件没有正确注册那么这个电话就打不通。打不通的时候不是说直接报“找不到应用”这么友好而是返回一个空对象None或者抛出com_error。后续代码一访问空对象的属性比如app.books、wb.sheetsPython就只会告诉你NoneType object has no attribute ...。所以大多数NoneType报错根子都在“找不到正确的COM实例”上而不是你的Python代码逻辑写错了。1.2 NoneType报错的五种典型姿势我整理了一下xlwings结合WPS时报NoneType相关错误基本逃不出下面这几种场景错误现象根本原因常见环境xw.Book(文件.xlsx)直接抛com_error默认找Excel.Application但系统只有WPS只安装WPS的机器app.books.open(path)返回NoneCOM对象创建失败或文件路径/扩展名不受支持WPS识别不了xlsm等格式sheet.range(A1).value返回None单元格原本就是空或工作表未激活/选中读取前未检查工作表状态程序运行完et.exe进程卡在后台app.quit()没执行成功或COM对象未释放WPS弹窗导致进程挂起Python是64位WPS是32位位数不同导致COM注册表重定向找不到对象默认安装WPS时多为32位这几种情况我都遇到过而且往往不是单个出现而是连环触发先是因为找不到COM报了None代码中断后WPS进程没退干净第二次再跑就彻底连不上了。1.3 为什么重装WPS能“暂时解决”但治标不治本很多人遇到这个报错第一反应是重装WPS装完发现确实能跑了。原因很简单WPS安装包里自带COM组件注册重装会把KET.Application这个接口重新写进注册表。但问题是WPS后续如果更新、或者系统清理工具扫了一遍COM注册很容易又丢。而且哪怕COM注册是好的xlwings默认找的还是Excel.Application所以重装通常只能缓解“COM找不到”这一类报错真正要让xlwings彻底认上WPS还是得在代码层面做文章。2. 环境体检确认WPS的COM还能用在写任何代码之前先给当前机器的WPS做个“体检”。这一步很快但能帮你把问题范围缩小一半。2.1 三步检查COM组件是否在册第一步按Win R输入regedit打开注册表编辑器在顶部输入HKEY_CLASSES_ROOT之后用查找功能搜KET.Application。如果能看到这个键说明WPS表格的COM接口已经注册了如果搜不到说明COM注册缺失。第二步用Python直接调一下这个接口。打开你的Python环境先确保装了pywin32然后执行import win32com.client app win32com.client.Dispatch(KET.Application) print(app.Version)如果正常会打印出WPS的版本号同时可能在任务栏闪过一个WPS窗口。如果抛pywintypes.com_error: (-2147221005, 无效的类字符串, None, None)说明系统里根本没有可用的WPS COM类就需要先修复WPS安装。第三步确认当前Python解释器是多少位的。在交互环境里执行import platform print(platform.architecture())这一步很关键因为WPS默认安装的往往是32位版本注册表里的COM信息会落在WOW6432Node目录下而64位Python在默认情况下不一定能正确找到32位COM组件。我曾经在一台机器上折腾了很久最后把Python换成32位版本就一切正常了。2.2 WPS的COM没注册怎么办如果第一步就发现KET.Application不存在最稳妥的办法不是手动去注册表里乱建而是打开Windows的“控制面板 - 程序和功能”找到WPS Office选择“修复”。修复完成后重新检查注册表。另外一个常见操作是打开WPS自带的“配置工具”路径一般是“开始菜单 - WPS Office工具 - 配置工具”在里面把“WPS Office兼容第三方系统和软件”相关的选项勾上然后重新启动电脑。不要自己去regsvr32注册某个dllWPS的组件结构很复杂手动注册dll不仅解决不了问题还可能把其他功能搞坏。2.3 给环境装上必要的“翻译官”xlwings本身依赖pywin32所以环境里这两个包都得有。直接执行pip install xlwings pywin32这里有一个很多人忽略的细节如果之前安装过xlwings的旧版本建议顺便升级到最新版因为新版才对App(spec...)这类参数支持得更好。我在实操中发现老版本xlwings对WPS的兼容性明显差一些很多新版才有的API在老版本里根本不存在。提示如果公司的Python环境是离线内网安装包不方便最低要求是可以先跑通pywin32再用我下一节的“win32com直连”方案同样能处理WPS表格。3. 改造方案让xlwings认准WPS引擎环境确认没问题之后下面这几招就是解决“xlwings不认WPS”的核心了。我按推荐程度从高到低排列你选一种适合自己的就行。3.1 最推荐的一招App(spec“KET.Application”)xlwings的App类其实提供了一个spec参数用来指定要启动的COM ProgID只是大多数教程没提。默认值是Excel.Application但你可以显式传入WPS的KET.Application让xlwings直接操作WPS表格。代码长这样import xlwings as xw app xw.App(specKET.Application, visibleTrue, add_bookFalse) wb app.books.open(rD:\test\demo.xlsx) sheet wb.sheets[0] print(sheet.range(A1).value) sheet.range(B1).value 写入成功 wb.save() wb.close() app.quit()这段代码的核心就在于specKET.Application。加上之后xlwings底层创建的就不是Excel应用而是WPS表格应用。这一步做完绝大多数“NoneType错误”都会消失因为对象创建成功了后续操作自然就顺着链条走通了。需要注意add_bookFalse我建议每次都带上。如果不带xlwings会默认创建一个空工作簿对WPS来说这反而会拖慢启动速度偶尔还会导致后续app.books.open()返回莫名其妙的结果。3.2 如果xlwings版本太老源码小改有些老项目或者老环境里的xlwings版本比较旧spec参数可能不支持或者有Bug。此时可以改xlwings源码里默认的ProgID——找到你的Python安装路径下的site-packages/xlwings/main.py搜索Excel.Application把默认值改成KET.Application。注意改之前先备份原文件。这种改法只影响当前Python环境比改注册表安全得多。我建议直接搜到类似这样的代码spec spec or Excel.Application把它改成spec spec or KET.Application之后重启Python进程直接使用xw.App(...)不用再传spec也能默认走WPS。坏处是如果哪一天你又要操作真正的Excel需要再改回来或者显式传specExcel.Application。所以只适合那种“机器上永远只有WPS”的场景。3.3 绕开xlwings用win32com直连如果你觉得xlwings对WPS的兼容性还是不够可靠可以选择直接用win32com操作WPS。这也是一开始很多工程师走的路子完全不依赖xlwingsimport win32com.client as win32 app win32.Dispatch(KET.Application) app.Visible True wb app.Workbooks.Open(rD:\test\demo.xlsx) sheet wb.Worksheets(1) print(sheet.Cells(1, 1).Value) sheet.Cells(2, 1).Value 来自win32com的写入 wb.Save() wb.Close(False) app.Quit()这种方式的优点是绝对可控Workbooks、Worksheets、Cells这些接口和VBA很接近熟悉Excel VBA的人上手极其容易。缺点是代码写起来比xlwings啰嗦尤其是单元格区域操作、格式设置等需要自己封装不少辅助函数。从最终效果看xlwings和win32com直连都能和WPS正常通信关键区别就是开发效率。我个人建议如果只是写一次性脚本用win32com没问题如果要长期维护一个报表自动化系统还是优先xlwings加spec的方案后续代码可读性高很多。4. 实战案例几段能直接抄的代码原理讲得多不如直接跑通一个案例。下面这几个场景都是我实际做过的基本覆盖了办公自动化的高频需求。4.1 批量把多个CSV汇总到一张WPS表格这应该是很多财务、运营同学的刚需每天收到一堆CSV要汇总到一个工作簿里。用xlwings加WPS引擎可以这么写import xlwings as xw import csv from pathlib import Path def csv_to_wps(csv_dir, output_path): app xw.App(specKET.Application, visibleFalse, add_bookFalse) wb app.books.add() ws wb.sheets[0] for idx, csv_file in enumerate(Path(csv_dir).glob(*.csv), start1): rows [] with open(csv_file, encodingutf-8) as f: reader csv.reader(f) rows list(reader) # 每个文件放到独立的列组中中间空一列 col (idx - 1) * 3 1 for r_i, row in enumerate(rows, start1): for c_i, cell in enumerate(row, start1): ws.cells(r_i, col c_i - 1).value cell wb.save(output_path) wb.close() app.quit() csv_to_wps(rD:\data\daily, rD:\data\汇总.xlsx)这里有个细节批量写入时千万别一个单元格一个单元格直接ws.range(A1).value xxxWPS的COM通信开销不低量大了会非常慢。更好的做法是把数据先组装成二维列表再用sheet.range(A1).value 二维列表一次性写入我这里为了演示清晰用了嵌套循环实际生产环境建议改成批量写入。4.2 在WPS表格里做条件匹配别折腾VLOOKUP热门搜索词里有“xlwings匹配单元格”其实就是假设你有一张主表和一张价格表想根据产品编号把价格匹配过去。很多人第一反应是调用WPS的VLOOKUP公式但公式容易因格式不统一出乱子。更稳的是拿到Python里用pandas做匹配再写回去import pandas as pd import xlwings as xw app xw.App(specKET.Application, visibleFalse, add_bookFalse) wb app.books.open(rD:\data\订单.xlsx) sheet wb.sheets[订单明细] # 读成DataFrame df sheet.range(A1).expand(table).options(pd.DataFrame).value price_df pd.DataFrame({ 产品编号: [A001, A002, A003], 价格: [12.5, 20.0, 35.0] }) merged df.merge(price_df, on产品编号, howleft) sheet.range(A1).value merged.values.tolist() sheet.range(A1).expand(table).columns.width 15 wb.save() wb.close() app.quit()这种方式会把列头也当成数据读进来实际用的时候可以用indexFalse之类的参数微调但大方向是能读成结构化DataFrame处理的就不要在表格里写公式。4.3 把网页HTML表格转成WPS表格文件热词里还有一个“html格式转换wps表格”这个用Python处理特别快。核心是用pandas.read_html把网页里的表格解析出来再借助xlwings的WPS引擎写入import pandas as pd import xlwings as xw tables pd.read_html(http://example.com/data.html, encodingutf-8) df tables[0] app xw.App(specKET.Application, visibleFalse, add_bookFalse) wb app.books.add() sheet wb.sheets[0] sheet.range(A1).value df.values.tolist() wb.save(rD:\data\网页数据.xlsx) wb.close() app.quit()这里要注意read_html对网页编码的依赖很强如果解析出来是乱码先单独用requests库把网页内容下载下来指定正确的编码再丢给read_html处理。别一股脑把整个URL塞进去到时候定位问题会很难受。4.4 页面设置A3调成A4后表格没变是怎么回事“wps从a3调成a4之后表格没变”这个关键词我猜很多人搜过因为它和Python自动化输出打印文件密切相关。页面尺寸从来不是简单设置一下PaperSize就结束的还得考虑缩放比例。在xlwings里可以这样设置# xlPaperA4 9, xlPaperA3 8 sheet.api.PageSetup.PaperSize 9 sheet.api.PageSetup.Zoom False sheet.api.PageSetup.FitToPagesWide 1 sheet.api.PageSetup.FitToPagesTall False为什么设置了PaperSize表格看起来没变因为WPS表格的“视图缩放比例”和“打印纸张大小”是两码事你在界面上把纸张从A3调成A4屏幕上的网格线不会立刻跟着变按Ctrl 鼠标滚轮调整视图放大倍数之后就会看到变化。如果再讲究一点用上面的代码把FitToPagesWide设成1打印时就会自动把横向内容压缩到一页宽输出效果立刻专业很多。5. 踩坑实录这些坑每个都是实打实踩出来的最后一个部分分享我在实际项目中遇到的几个典型问题。这些问题不写在官方文档里但能救你的命。5.1 运行完脚本WPS进程一直赖在后台这是最多人问的问题。代码结尾明明写了app.quit()但打开任务管理器et.exeWPS表格的进程名还是好几个久而久之内存越占越多甚至导致下一次脚本跑不起来。原因是WPS在通过COM接口打开文件时会弹一些窗口或加载项这些窗口阻止了正常的quit()动作。解决办法是在代码里做好兜底import xlwings as xw import psutil app xw.App(specKET.Application, visibleFalse, add_bookFalse) try: wb app.books.open(rD:\data\demo.xlsx) # 业务操作... finally: wb.close() app.quit() # 如果还残留强制清理进程 for proc in psutil.process_iter([pid, name]): if proc.info[name] and proc.info[name].lower() in (et.exe, wps.exe): proc.kill()这个兜底逻辑能有效避免因为弹窗僵死导致的进程残留。生产环境里我建议把它封装成独立的工具函数每次调用完自动清理。5.2 中文路径和带空格的文件名频繁失败在Windows环境下xlwings和win32com对路径很敏感尤其是使用相对路径或文件名里有中英文混合的空格时经常出现“找不到文件”或者“返回None但没有报错”的情况。我的习惯是一律用绝对路径路径字符串前加r转义文件路径里尽量不要有中文、括号、特殊符号如果避免不了先用os.path.normpath规范化一遍。比如这段代码就有坑path D:\数据\订单 明细.xlsx # 反斜杠会被当成转义符正确写法是from pathlib import Path path str(Path(rD:\数据\订单 明细.xlsx))5.3 WPS兼容模式下的公式和数字格式问题WPS打开xlsx文件后很多在Excel里正常的单元格格式会变成“兼容模式”导致数据看起来对但类型不对。典型的就是数字被存成文本日期格式错乱。这种问题在用xlwings写入时尤其明显。解决方法是显式设置单元格格式。比如把某列设置成数值格式sheet.range(C2:C100).number_format 0.00设置成日期格式sheet.range(D2:D100).number_format yyyy-mm-dd经验之谈写入数据时先统一转成符合目标格式的Python类型再写进表格不要指望WPS自动帮你识别。比如日期就传datetime.date对象不要传字符串“2025-01-01”否则后续排序、透视会非常混乱。5.4 环境问题Python版本、位数和包的版本有时候代码本身没毛病报错却五花八门这时候就要检查环境。我遇到过最严重的一次是WPS是32位Python是64位xlwings怎么都调不通查了三天最后在官方社区看到“在Windows上xlsx文件的COM调用必须保证Python和Office/WPS的位数一致”于是我把Python换成32位版本问题当场消失。所以在排查NoneType之前先确认几个版本检查项期望结果Python位数与WPS位数一致xlwings版本0.20及以上pywin32版本最新版WPS是否注册COM注册表存在KET.Application这台机器如果是其他同事用过的也可能装了多个Python版本导致pip装包和实际运行的Python不是同一个。用python -m pip list确认一下xlwings在哪个解释器下比什么都有用。当然这种问题不一定和WPS自动化直接相关但排查效率越高越不耽误正事。从第一次被NoneType卡到怀疑人生到现在写WPS自动化脚本一气呵成我最大的体会是遇到这种“同一个报错千奇百怪的原因”的问题一定不要急着在网上复制粘贴改注册表先把COM接口确认清楚再决定用xlwings还是win32com。毕竟xlwings或者WPS本身都没什么错错的是它们俩还不熟。如果你手头正好有一台只有WPS的机器按这篇文章的顺序检查一遍大概率能直接跑通。
返回列表