Windows 本地 Excel 备用通道
什么时候用
需要读写本机 .xls / .xlsx / .csv,而常规通道走不通时:
- 在线表格 / 本地编辑服务(MCP 或 editor_sdk 类)连接失败,报连接超时、
UNOPENABLE_LOCAL_PATH之类 - 在线表格客户端监听端口正常,但本地回环连接被拦(
netstat有 LISTENING 却连不上,服务端日志收不到请求) - 装不了
pandas/xlrd/openpyxl(无外网、镜像不通、pip 被代理拒绝)
这时不要去杀宿主的表格服务进程,直接走本技能。
> 这是降级通道,启用前告知用户:为什么走备用方案、产物有什么差异(比如样式能力受限)。
三步流程
第 1 步:Excel COM 后台转换(.xls → .xlsx)
.xls(BIFF8 二进制)用标准库读不了,先转成 .xlsx(zip+XML)。用 PowerShell 内联执行(若本机执行策略是 Restricted,脚本文件不能直接跑,见坑表):
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force # 必须在同一条命令里
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false; $excel.DisplayAlerts = $false
$excel.AskToUpdateLinks = $false; $excel.EnableEvents = $false
$wb = $excel.Workbooks.Open($src, 0, $true) # 第 3 个参数 $true = 只读打开
$wb.SaveAs($dst, 51) # 51 = xlOpenXMLWorkbook
$wb.Close($false)
$excel.Quit()
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($excel) | Out-Null
- 不要写
Remove-Item做「先删后建」——部分环境有 safe-delete 保护会直接拦截。直接覆盖写即可(DisplayAlerts=$false下 SaveAs 会静默覆盖)。 .xlsx/.csv可以跳过这一步,直接用第 2 步的读取器。
第 2 步:纯标准库 Python 读取
用本技能附带的 scripts/xlsx_reader.py(只用 zipfile + xml.etree):
python scripts/xlsx_reader.py book.xlsx # dump 全部单元格
python scripts/dump_formulas.py book.xlsx sheet1.xml # dump 公式、合并区域、隐藏行列
读取器已处理:共享字符串、inlineStr、numFmt 索引、合并区域、多工作表顺序。
动手改数据前,先看公式和合并区域——公式揭示口径(谁=谁+谁),跨行合并说明那列是「整组」的数而非逐行的数,这是归类汇总最容易算错的地方。
第 3 步:生成结果
两种方式,按需要选:
- 只要数据、不要样式 → Python 直接写 CSV,交差。
- 要格式化 xlsx → Python 先生成布局 JSON,再让 Excel COM 写单元格和样式(保证文件一定合法)。
scripts/build_xlsx.ps1是可直接复用的模板:读 plan.json →Value2 = 二维数组一次写入 → 按行类型(header/subtotal/total/blank)套样式 → 冻结窗格 →SaveAs($dst, 51)。
踩过的坑
| 现象 | 原因 / 解法 |
|---|---|
不能设置类 Range 的 NumberFormat 属性 |
中文区域下 [Red] 非法。负值红色用 [红色];纯 ASCII 脚本里写 '#,##0.00;[' + [char]0x7EA2 + [char]0x8272 + ']-#,##0.00' |
无法加载文件…在此系统上禁止运行脚本 |
执行策略 Restricted。同一条命令里先 Set-ExecutionPolicy -Scope Process Bypass -Force |
safe-delete bulk delete guard blocked deletion |
脚本里的 Remove-Item 被拦。改成直接覆盖 |
New-Object -ComObject Excel.Application 后进程残留 |
finally 里必须 $excel.Quit() + ReleaseComObject + [GC]::Collect() |
| COM 写单元格很慢 | 用 Range.Value2 = [object[,]] 一次性写入,别逐格赋值 |
| PowerShell 工具拿不到 stdout | 把结果 Set-Content 到文件,再用 Read 读 |
交付前自查
- 读回生成的文件,逐表核对行数、关键合计
- 原表内部的口径矛盾(合并单元格覆盖范围与公式不一致、隐藏行、负数)如实列出,不擅自填补或删除
- 完成后确认没有遗留
EXCEL.EXE/ 临时服务进程