用途
此技能面向“编辑用户已经在用的 .xlsx 文件”这一场景,例如发票台账、记账簿。目标是在新增或修改工作表时,保留原有公式、样式与图片。资料明确指出:对于已有文件,不要用 openpyxl 的 load_workbook + save() 重写。以 Ventas2026.xlsx(40 个发票工作表,252 KB)为例,这种做法会清空所有公式的缓存值(摘要页合计列变空),并使 40 个 xl/drawings/drawingN.xml 丢失,属于静默丢数据。
相反,如果要从零新建工作簿(报表、映射、导出),openpyxl 是合适且方便的选择:多工作表、列宽、自动筛选、冻结窗格、超链接、方法说明页都可以用;因为没有原始文件需要保留,不存在上述丢失问题。资料提到该 Mac 默认 Python 中没有 openpyxl(uv 中有),可用 uv run --with openpyxl python3 build_xlsx.py 运行。
主要能力
正确路径是在 XML 层面克隆模板并缝合 ZIP,使已有工作表逐字节保持一致(资料称已用 sha256 逐部分验证)。新增一个工作表需要改动 3 个文件并新增 1 个文件:
- xl/workbook.xml:在 </sheets> 前插入 <sheet state="visible" name="…" sheetId="N" r:id="rIdM"/>。
- xl/_rels/workbook.xml.rels:在 </Relationships> 前加入 <Relationship Id="rIdM" Type="…/worksheet" Target="worksheets/sheetK.xml"/>。
- [Content_Types].xml:在 </Types> 前加入 <Override PartName="/xl/worksheets/sheetK.xml" ContentType="…spreadsheetml.worksheet+xml"/>。
- xl/worksheets/sheetK.xml:新工作表本体(可由模板复制后改单元格)。
资料特别提醒:sheetId(工作表唯一属性)与 rId(关系 ID)是两套独立命名空间;rels 中 Target 不带前导斜杠(worksheets/sheetK.xml),而 openpyxl 写法相反(/xl/worksheets/…),因此自行实现读取器时需要 .lstrip("/"),否则会拼成 xl/xl/worksheets/。
读取方面,资料提供 scripts/xlsx_read.py,可提取工作表、shared strings 和单元格,既能读取也能校验;它独立于写入器,因此不会“自己验证自己的错误”。
使用方式
写入单元格时只替换单元格内部内容、保留原有属性(s="…" 即样式索引)。文本使用 t="inlineStr",从而不必改动格式中最脆弱的 sharedStrings.xml。资料给出了 setnum 与 setstr 两个函数的实现思路:用正则匹配目标单元格,去掉继承的 t 属性,再写入 <v> 或 <is><t xml:space="preserve">。
资料指出单元格正则的风险:<c r="D1" 也会匹配到 <c r="D11" 内部;结果确定(取第一个匹配),但如果 D1 为空且自闭合,会吞掉其闭合标签,导致 XML 结构错乱、Excel 要求修复。因此须用 zipfile.testzip() 检查,并用独立解析器重新读取该文件。
公式处理:如果模板中是 <f>SUM(K27:K36)</f><v>252</v>,写入时若只留 <f> 而无 <v>,Excel 打开会重算,但按数据读取的工具(或 Drive 预览)可能看到空值;在发票场景中,没有缓存值的合计就是坏报表。因此应同时写入 <v>(或两者都写)。对于共享公式(t="shared" si="1"),子工作表只有不带文本的 <f t="shared" si="1"/>,最简单的做法是按普通值写入。
ZIP 写入常见问题:使用 zipfile.ZipFile(dest, "w", zipfile.ZIP_DEFLATED) 后把新建的 ZipInfo 传给 writestr,压缩方式会失效(实测 252 KB 变为 2,395 KB,131 个条目 compress_type 为 0)。应在新建 ZipInfo 时显式设置 compress_type = zipfile.ZIP_DEFLATED,并保留 external_attr 权限,再调用 writestr。
验证要求
在宣称完成之前必须验证:原有各 xl/worksheets/*.xml 与 xl/sharedStrings.xml 的 sha256 与原始文件一致,仅 workbook.xml、workbook.xml.rels 和 [Content_Types].xml 允许不同;zipfile.testzip() 返回 None;工作表数量符合预期且原有顺序不变;新单元格值正确(用独立解析器读取);若上传到 Drive,须从 Drive 下载回来验证,而不是验证本地文件。
上传到 Drive
Drive 中的 .xlsx 不能用 Sheets API 编辑,会返回 400:This operation is not supported for this document. The document must not be an Office file. 需通过 upload 端点整体覆盖,使用 PATCH 到 https://www.googleapis.com/upload/drive/v3/files/%s?uploadType=media&fields=id,version,Content-Type 为 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet。上传前应把原文件复制到 Drive 之外,并比较 version,避免覆盖期间他人改动;上传后提醒用户:若文件当时处于打开状态,需关闭并重新打开,否则其内存中的副本可能覆盖已上传内容。
适用场景与限制
适用于在用户已在使用的 .xlsx 中增加或编辑工作表,且需要保留公式、样式和图片的情况。以下情况不宜使用:用户想要 Google Sheets 原生工作表(应转换文件或使用 Sheets API);需要大量重排或删除工作表,或涉及复杂条件格式(手工改 XML 不如转成 Google Sheet 再用 API 处理);以及 computer use 不适合此任务(驱动可能捕获不到浏览器窗口,capture 返回 0x0 和 0 个元素,通过 UI 手工克隆工作表也无法验证)。如果驱动出现这类失败,不要反复尝试,改用上述 API 方式并说明原因。