Excel 工作簿 (.xlsx)
XLSX 创建、编辑与分析
| 任务 | 方法 |
|---|---|
| 创建或编辑(含公式/格式) | openpyxl —— 见下文注意事项 |
| 批量数据导入/导出 | pandas (read_excel, to_excel) |
| 快速查看工作表 | markitdown file.xlsx —— 每个工作表对应一个 ## SheetName;支持 .xlsm。不提供单元格坐标,因此不要基于此结果规划编辑 |
| 读取模型(公式*及*数值) | 两次 load_workbook 遍历 —— 见注意事项 |
> openpyxl、pandas 和 markitdown 已预装 —— 请勿先运行 pip install;直接编写脚本并导入。仅在导入失败(或缺失 markitdown 命令)时,才 pip install 缺失的包。
> 以下脚本路径均相对于本技能目录。
每项输出的要求
- 专业字体:除非用户另有要求,否则全程使用专业字体(Arial, Times New Roman)。
- 零公式错误:只要
recalc.py报告errors_found,绝不能交付。如果你认为错误在之前就已存在,请证明这一点:使用data_only=True加载*原文件*并检查该单元格。你引入的错误与继承的错误在表现上完全一致。
- 使用公式,严禁硬编码结果:编写
sheet['B10'] = '=SUM(B2:B9)',而非 Python 计算出的总和。工作表必须在输入值更改时能自动重新计算。
- 严格遵循用户规范:精确的标签页名称、精确的列标题以及用户指定的公式。即使重新设计后的方案更优雅,但只要计算内容与要求不符,即视为失败。
- 记录所有假设和硬编码数字:在读者可见的地方进行标注 —— 如单元格批注或表格末尾的相邻单元格。若有真实来源请注明(例如:
来源:Company 10-K, FY2024, 第 45 页, 营收注释, [SEC EDGAR URL]);若数字来自用户,请直接说明。
- 创建供他人填写的空白工作簿:需要提供简短的图例,标明哪些单元格可编辑,并提供一行包含真实数值的示例行以展示预期格式。对于要求编辑的现有文件,切勿添加此类示例行。
- 编辑现有文件:完全匹配其既有约定。既有约定优先级高于本指南的所有准则。首先找到指定的输入单元格(通常由独特的字体颜色、填充或底纹标识)—— 仅在这些位置写入,且不得触动任何现有公式。
重新计算(只要文件包含公式,此步骤为强制要求)
openpyxl 将公式写入为字符串,不包含缓存值。在重新计算之前,任何读取缓存值的工具(如 pandas、load_workbook(data_only=True) 及大多数预览器)读取公式单元格时都会显示为 None。
python scripts/recalc.py output.xlsx [timeout_seconds] # 默认 30LibreOffice 会计算每个公式,文件将被原位重写,并返回 JSON 结果:status (success | errors_found)、total_formulas、total_errors 以及列出每种错误类型最多 100 个单元格的 error_summary(locations_truncated 表示省略的数量 —— 请信任 total_errors 而非列表长度)。修复指出的错误并重新运行。如果 JSON 返回的是 error 键而非 status 键,意味着没有任何内容被重新计算,且只有在这种情况下才会返回非零退出码 —— errors_found 仍会返回 0,因此绝不能将“正常退出”等同于“工作簿无误”。
**重新计算通过(绿色)仅证明你的公式可以*求值*,而非结果*正确*。范围偏差一个单元格或引用了错误的行,依然会生成一个无错误但数值错误的干净文件。在构建整个网格之前,请先编写 2-3 个公式并检查其获取的值是否符合预期。
链接到另一个工作簿的文件...
如果你使用 openpyxl 重新保存文件并随后进行重新计算,文件会丢失这些链接**。此类公式的形式为 ='[1]Returns Analysis'!$B$2 —— 其中 [1] 是工作簿外部引用列表的索引,指向的是*磁盘上的一个独立文件*,而非工作表。由于该文件通常不存在,单元格的缓存值是其数据的唯一来源。openpyxl 在保存时会清除该值;随后 LibreOffice 必须尝试真实解析该引用,解析失败后会写入 #NAME? 并删除所有链接。在这种状态下 recalc.py 将无法运行 —— 请在覆盖保存前将这些单元格的值复制出来(使用 --force 可强制覆盖并接受数据丢失)。
选择能通过验证的公式
LibreOffice 实现的函数比 Excel 少,任何它无法求值的函数都会在交付的文件中变成字面量 #NAME?。
- 优先使用 Excel 2007 时代的函数 —— 如
SUMIFS、INDEX、MATCH、IFERROR、SUMPRODUCT—— 这些函数不需要前缀。
- 六个 2007 年后的函数可以使用,但必须带有
_xlfn.前缀。因为 openpyxl 会原样将公式写入 XML,而 Excel 存储 2007 年后的函数名时会加上前缀(其 UI 界面隐藏了前缀):_xlfn.TEXTJOIN、_xlfn.CONCAT、_xlfn.IFS、_xlfn.SWITCH、_xlfn.MAXIFS、_xlfn.MINIFS。如果直接书写,每个都会产生#NAME?。
- 绝不要使用
XLOOKUP、XMATCH、SORT、FILTER、UNIQUE或SEQUENCE。运行环境中的 LibreOffice 在*任何*前缀下都无法求值这些函数。较新版本的 LibreOffice 虽然可以求值,但它们是溢出数组函数,而 openpyxl 写入的文件没有溢出元数据,因此只有范围的左上角单元格会获得值 —— 且recalc.py会对这种截断结果报告total_errors: 0。请使用INDEX/MATCH进行查找,并在写入单元格前使用 Python 进行排序、过滤和去重。
- LibreOffice 无法解析的公式在写回时会变为小写 —— 这是除
#NAME?之外的一个快速识别标志。
openpyxl 陷阱
- 读取模型需要加载两次。
data_only=True返回缓存值但公式丢失;默认设置返回公式字符串但没有值。一次加载无法同时获得两者。
- 如果保存,
data_only=True具有破坏性。该工作簿不再包含公式,因此保存时会将所有公式替换为字面量 —— 且不可逆。
- 对 openpyxl 刚写入的文件使用
data_only=True会导致所有位置返回None—— 请先运行recalc.py。(结果为""的公式在读回时也会显示为None。)
- 合并单元格:仅写入左上角的锚点单元格。范围内的其他所有单元格都是
MergedCell,其.value是只读的。
- 除非在
load_workbook中传递keep_vba=True,否则.xlsm会丢失宏。
- 跨表引用中包含空格的工作表名称必须加引号:
='Assumptions Inputs'!$B$5。如果不加引号,求值结果为#VALUE!。
财务模型
除非用户另有要求,或现有文件已有其他约定。
颜色: 硬编码输入和情景杠杆使用蓝色文字 (0,0,255) $\cdot$ 公式使用黑色 $\cdot$ 链接到另一工作表使用绿色 (0,128,0) $\cdot$ 链接到另一个文件使用红色 (255,0,0) $\cdot$ 关键假设和用户需填写的单元格使用黄色填充 (255,255,0)。
数字: 货币格式 $#,##0,单位在表头注明(如 Revenue ($mm))$\cdot$ 零显示为 -,包括百分比 ($#,##0;($#,##0);-) $\cdot$ 负数用括号表示 $\cdot$ 百分比 0.0%,以分数形式存储(0.15 显示为 15.0%;存储 15 则显示为 1500.0%)$\cdot$ 估值倍数 0.0x $\cdot$ 年份作为文本("2024",绝不要写成 2,024)。
结构: 始终
将假设值放在带有标签的独立单元格中,并在公式中引用该单元格(例如使用 =B5*(1+$B$6),而非 =B5*1.05)· 确保每个预测周期的公式保持一致,因为在行中间单独修改单元格是最常见的隐蔽错误 · 对可能为零的分母进行保护。
依赖项
openpyxl, pandas, markitdown(通过 pip 安装,已预装 —— 仅在导入失败或命令缺失时安装)· LibreOffice(soffice,通过 scripts/office/soffice.py 为沙箱环境自动配置)