重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
excel-automation by daymade/claude-code-skills
npx skills add https://github.com/daymade/claude-code-skills --skill excel-automation创建专业的 Excel 文件,解析复杂的财务模型,并在 macOS 上控制 Excel。
# 创建格式化的 Excel 报告
uv run --with openpyxl scripts/create_formatted_excel.py output.xlsx
# 解析 openpyxl 无法处理的复杂 xlsm 文件
uv run scripts/parse_complex_excel.py model.xlsm # 列出工作表
uv run scripts/parse_complex_excel.py model.xlsm "DCF" # 提取特定工作表
uv run scripts/parse_complex_excel.py model.xlsm --fix # 修复损坏的名称
# 通过 AppleScript 控制 Excel(使用超时防止挂起)
timeout 5 osascript -e 'tell application "Microsoft Excel" to activate'
三种能力:
| 能力 | 工具 | 使用场景 |
|---|---|---|
| 创建 格式化的 Excel | openpyxl | 报告、模型、仪表板 |
| 解析 复杂的 xlsm/xlsx | zipfile + |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
xml.etree| 财务模型、VBA 工作簿、>1MB 的文件 |
| 控制 Excel 窗口 | AppleScript (osascript) | 以编程方式缩放、滚动、选择单元格 |
Is the file simple (data export, no VBA, <1MB)?
├─ YES → openpyxl or pandas
└─ NO
├─ Is it .xlsm or from investment bank / >1MB?
│ └─ YES → zipfile + xml.etree.ElementTree (stdlib)
└─ Is it truly .xls (BIFF format)?
└─ YES → xlrd
"复杂" Excel 的信号:文件 >1MB、扩展名为 .xlsm、来自投资银行/经纪商、包含 VBA 宏。
重要提示:始终先运行 file <路径> — 扩展名可能不准确。一个 .xls 文件实际上可能是基于 ZIP 的 xlsx。
| 颜色 | RGB 代码 | 含义 |
|---|---|---|
| 蓝色 | 0000FF | 用户输入 / 假设 |
| 黑色 | 000000 | 计算值 |
| 绿色 | 008000 | 跨工作表引用 |
| 深蓝色背景上的白色 | FFFFFF 背景色 4472C4 | 章节标题 |
| 深蓝色文字 | 1F4E79 | 标题 |
from openpyxl.styles import Font, PatternFill, Border, Side, Alignment
# 字体
BLUE_FONT = Font(color="0000FF", size=10, name="Calibri")
BLACK_FONT_BOLD = Font(color="000000", size=10, name="Calibri", bold=True)
GREEN_FONT = Font(color="008000", size=10, name="Calibri")
HEADER_FONT = Font(color="FFFFFF", size=12, name="Calibri", bold=True)
# 填充
DARK_BLUE_FILL = PatternFill("solid", fgColor="4472C4")
LIGHT_BLUE_FILL = PatternFill("solid", fgColor="D9E1F2")
INPUT_GREEN_FILL = PatternFill("solid", fgColor="E2EFDA")
LIGHT_GRAY_FILL = PatternFill("solid", fgColor="F2F2F2")
# 边框
THIN_BORDER = Border(bottom=Side(style="thin", color="B2B2B2"))
BOTTOM_DOUBLE = Border(bottom=Side(style="double", color="000000"))
| 格式 | 代码 | 示例 |
|---|---|---|
| 货币 | '$#,##0' | $1,234 |
| 带小数的货币 | '$#,##0.00' | $1,234.56 |
| 百分比 | '0.0%' | 12.3% |
| 百分比 (2 位小数) | '0.00%' | 12.34% |
| 带逗号的数字 | '#,##0' | 1,234 |
| 乘数 | '0.0x' | 1.5x |
用于敏感性分析的红到绿渐变:
from openpyxl.formatting.rule import ColorScaleRule
rule = ColorScaleRule(
start_type="min", start_color="F8696B", # 红色 (低)
mid_type="percentile", mid_value=50, mid_color="FFEB84", # 黄色 (中)
end_type="max", end_color="63BE7B" # 绿色 (高)
)
ws.conditional_formatting.add(f"B2:F6", rule)
uv run --with openpyxl scripts/create_formatted_excel.py
完整模板脚本:参见 scripts/create_formatted_excel.py
当 openpyxl 在复杂的 xlsm 文件上失败时(损坏的 DefinedNames、复杂的 VBA),直接使用标准库。
file.xlsx (ZIP 存档)
├── [Content_Types].xml
├── xl/
│ ├── workbook.xml ← 工作表名称 + 顺序
│ ├── sharedStrings.xml ← 所有文本值 (查找表)
│ ├── worksheets/
│ │ ├── sheet1.xml ← 工作表 1 的单元格数据
│ │ ├── sheet2.xml ← 工作表 2 的单元格数据
│ │ └── ...
│ └── _rels/
│ └── workbook.xml.rels ← 映射 rId → sheetN.xml
└── _rels/.rels
workbook.xml 中的工作表名称通过 _rels/workbook.xml.rels 链接到物理文件:
import zipfile
import xml.etree.ElementTree as ET
MAIN_NS = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'
REL_NS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'
RELS_NS = 'http://schemas.openxmlformats.org/package/2006/relationships'
def get_sheet_path(zf, sheet_name):
"""将工作表名称解析为 ZIP 内的物理 XML 文件路径。"""
# 步骤 1: workbook.xml → 查找工作表名称对应的 rId
wb_xml = ET.fromstring(zf.read('xl/workbook.xml'))
sheets = wb_xml.findall(f'.//{{{MAIN_NS}}}sheet')
rid = None
for s in sheets:
if s.get('name') == sheet_name:
rid = s.get(f'{{{REL_NS}}}id')
break
if not rid:
raise ValueError(f"未找到工作表 '{sheet_name}'")
# 步骤 2: workbook.xml.rels → 将 rId 映射到文件路径
rels_xml = ET.fromstring(zf.read('xl/_rels/workbook.xml.rels'))
for rel in rels_xml.findall(f'{{{RELS_NS}}}Relationship'):
if rel.get('Id') == rid:
return 'xl/' + rel.get('Target')
raise ValueError(f"没有找到 {rid} 的文件映射")
def extract_cells(zf, sheet_path):
"""从工作表 XML 中提取所有单元格值。"""
# 构建共享字符串查找表
shared = []
try:
ss_xml = ET.fromstring(zf.read('xl/sharedStrings.xml'))
for si in ss_xml.findall(f'{{{MAIN_NS}}}si'):
texts = si.itertext()
shared.append(''.join(texts))
except KeyError:
pass # 没有共享字符串
# 解析工作表单元格
sheet_xml = ET.fromstring(zf.read(sheet_path))
rows = sheet_xml.findall(f'.//{{{MAIN_NS}}}row')
data = {}
for row in rows:
for cell in row.findall(f'{{{MAIN_NS}}}c'):
ref = cell.get('r') # 例如,"A1"
cell_type = cell.get('t') # "s" = 共享字符串,None = 数字
val_el = cell.find(f'{{{MAIN_NS}}}v')
if val_el is not None and val_el.text:
if cell_type == 's':
data[ref] = shared[int(val_el.text)]
else:
try:
data[ref] = float(val_el.text)
except ValueError:
data[ref] = val_el.text
return data
投资银行的 xlsm 文件通常包含损坏的 <definedName> 条目,其中包含 "Formula removed":
def fix_defined_names(zf_in_path, zf_out_path):
"""移除损坏的 DefinedNames 并重新打包。"""
import shutil, tempfile
with tempfile.TemporaryDirectory() as tmp:
tmp = Path(tmp)
with zipfile.ZipFile(zf_in_path, 'r') as zf:
zf.extractall(tmp)
wb_xml_path = tmp / 'xl' / 'workbook.xml'
tree = ET.parse(wb_xml_path)
root = tree.getroot()
ns = {'main': MAIN_NS}
defined_names = root.find('.//main:definedNames', ns)
if defined_names is not None:
for name in list(defined_names):
if name.text and "Formula removed" in name.text:
defined_names.remove(name)
tree.write(wb_xml_path, encoding='utf-8', xml_declaration=True)
with zipfile.ZipFile(zf_out_path, 'w', zipfile.ZIP_DEFLATED) as zf:
for fp in tmp.rglob('*'):
if fp.is_file():
zf.write(fp, fp.relative_to(tmp))
完整模板脚本:参见 scripts/parse_complex_excel.py
所有命令均在 macOS 上的 Microsoft Excel 中验证。
# 激活 Excel (置于前台)
osascript -e 'tell application "Microsoft Excel" to activate'
# 打开文件
osascript -e 'tell application "Microsoft Excel" to open POSIX file "/path/to/file.xlsx"'
# 设置缩放级别 (百分比)
osascript -e 'tell application "Microsoft Excel"
set zoom of active window to 120
end tell'
# 滚动到特定行
osascript -e 'tell application "Microsoft Excel"
set scroll row of active window to 45
end tell'
# 滚动到特定列
osascript -e 'tell application "Microsoft Excel"
set scroll column of active window to 3
end tell'
# 选择一个单元格范围
osascript -e 'tell application "Microsoft Excel"
select range "A1" of active sheet
end tell'
# 按名称选择特定工作表
osascript -e 'tell application "Microsoft Excel"
activate object sheet "DCF" of active workbook
end tell'
始终在 AppleScript 命令和后续操作(例如,截图)之间添加 sleep 1,以允许 UI 渲染。
重要提示:如果 Excel 未运行或无响应,osascript 将无限期挂起。始终使用 timeout 包装:
# 安全模式:5 秒超时
timeout 5 osascript -e 'tell application "Microsoft Excel" to activate'
# 检查退出代码:124 = 超时
if [ $? -eq 124 ]; then
echo "Excel 无响应 — 它正在运行吗?"
fi
| 错误 | 修正 |
|---|---|
| openpyxl 在复杂 xlsm 上失败 → 尝试猴子补丁 | 立即切换到 zipfile + xml.etree |
使用 wc -c 统计中文字符 | 使用 wc -m (字符数,而非字节数;中文 = 3 字节/字符) |
| 信任文件扩展名 | 先运行 file <路径> 以确认实际格式 |
openpyxl load_workbook 在大型 xlsm 上挂起 | 使用 zipfile 进行针对性提取,而非加载整个工作簿 |
uv run --with openpyxl 执行 Python 脚本 (切勿使用系统 Python)soffice --headless) 可以转换格式并重新计算公式references/formatting-reference.md每周安装数
66
代码仓库
GitHub 星标数
713
首次出现
2026年3月4日
安全审计
安装于
codex62
github-copilot61
amp61
cline61
kimi-cli61
gemini-cli61
Create professional Excel files, parse complex financial models, and control Excel on macOS.
# Create a formatted Excel report
uv run --with openpyxl scripts/create_formatted_excel.py output.xlsx
# Parse a complex xlsm that openpyxl can't handle
uv run scripts/parse_complex_excel.py model.xlsm # List sheets
uv run scripts/parse_complex_excel.py model.xlsm "DCF" # Extract a sheet
uv run scripts/parse_complex_excel.py model.xlsm --fix # Fix corrupted names
# Control Excel via AppleScript (with timeout to prevent hangs)
timeout 5 osascript -e 'tell application "Microsoft Excel" to activate'
Three capabilities:
| Capability | Tool | When to Use |
|---|---|---|
| Create formatted Excel | openpyxl | Reports, mockups, dashboards |
| Parse complex xlsm/xlsx | zipfile + xml.etree | Financial models, VBA workbooks, >1MB files |
| Control Excel window | AppleScript (osascript) | Zoom, scroll, select cells programmatically |
Is the file simple (data export, no VBA, <1MB)?
├─ YES → openpyxl or pandas
└─ NO
├─ Is it .xlsm or from investment bank / >1MB?
│ └─ YES → zipfile + xml.etree.ElementTree (stdlib)
└─ Is it truly .xls (BIFF format)?
└─ YES → xlrd
Signals of "complex" Excel : file >1MB, .xlsm extension, from investment bank/broker, contains VBA macros.
IMPORTANT : Always run file <path> first — extensions lie. A .xls file may actually be a ZIP-based xlsx.
| Color | RGB Code | Meaning |
|---|---|---|
| Blue | 0000FF | User input / assumption |
| Black | 000000 | Calculated value |
| Green | 008000 | Cross-sheet reference |
| White on dark blue | FFFFFF on 4472C4 | Section headers |
| Dark blue text | 1F4E79 |
from openpyxl.styles import Font, PatternFill, Border, Side, Alignment
# Fonts
BLUE_FONT = Font(color="0000FF", size=10, name="Calibri")
BLACK_FONT_BOLD = Font(color="000000", size=10, name="Calibri", bold=True)
GREEN_FONT = Font(color="008000", size=10, name="Calibri")
HEADER_FONT = Font(color="FFFFFF", size=12, name="Calibri", bold=True)
# Fills
DARK_BLUE_FILL = PatternFill("solid", fgColor="4472C4")
LIGHT_BLUE_FILL = PatternFill("solid", fgColor="D9E1F2")
INPUT_GREEN_FILL = PatternFill("solid", fgColor="E2EFDA")
LIGHT_GRAY_FILL = PatternFill("solid", fgColor="F2F2F2")
# Borders
THIN_BORDER = Border(bottom=Side(style="thin", color="B2B2B2"))
BOTTOM_DOUBLE = Border(bottom=Side(style="double", color="000000"))
| Format | Code | Example |
|---|---|---|
| Currency | '$#,##0' | $1,234 |
| Currency with decimals | '$#,##0.00' | $1,234.56 |
| Percentage | '0.0%' | 12.3% |
| Percentage (2 decimal) | '0.00%' | 12.34% |
| Number with commas | '#,##0' | 1,234 |
Red-to-green gradient for sensitivity analysis:
from openpyxl.formatting.rule import ColorScaleRule
rule = ColorScaleRule(
start_type="min", start_color="F8696B", # Red (low)
mid_type="percentile", mid_value=50, mid_color="FFEB84", # Yellow (mid)
end_type="max", end_color="63BE7B" # Green (high)
)
ws.conditional_formatting.add(f"B2:F6", rule)
uv run --with openpyxl scripts/create_formatted_excel.py
Full template script: See scripts/create_formatted_excel.py
When openpyxl fails on complex xlsm files (corrupted DefinedNames, complex VBA), use stdlib directly.
file.xlsx (ZIP archive)
├── [Content_Types].xml
├── xl/
│ ├── workbook.xml ← Sheet names + order
│ ├── sharedStrings.xml ← All text values (lookup table)
│ ├── worksheets/
│ │ ├── sheet1.xml ← Cell data for sheet 1
│ │ ├── sheet2.xml ← Cell data for sheet 2
│ │ └── ...
│ └── _rels/
│ └── workbook.xml.rels ← Maps rId → sheetN.xml
└── _rels/.rels
Sheet names in workbook.xml link to physical files via _rels/workbook.xml.rels:
import zipfile
import xml.etree.ElementTree as ET
MAIN_NS = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'
REL_NS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'
RELS_NS = 'http://schemas.openxmlformats.org/package/2006/relationships'
def get_sheet_path(zf, sheet_name):
"""Resolve sheet name to physical XML file path inside ZIP."""
# Step 1: workbook.xml → find rId for the sheet name
wb_xml = ET.fromstring(zf.read('xl/workbook.xml'))
sheets = wb_xml.findall(f'.//{{{MAIN_NS}}}sheet')
rid = None
for s in sheets:
if s.get('name') == sheet_name:
rid = s.get(f'{{{REL_NS}}}id')
break
if not rid:
raise ValueError(f"Sheet '{sheet_name}' not found")
# Step 2: workbook.xml.rels → map rId to file path
rels_xml = ET.fromstring(zf.read('xl/_rels/workbook.xml.rels'))
for rel in rels_xml.findall(f'{{{RELS_NS}}}Relationship'):
if rel.get('Id') == rid:
return 'xl/' + rel.get('Target')
raise ValueError(f"No file mapping for {rid}")
def extract_cells(zf, sheet_path):
"""Extract all cell values from a sheet XML."""
# Build shared strings lookup
shared = []
try:
ss_xml = ET.fromstring(zf.read('xl/sharedStrings.xml'))
for si in ss_xml.findall(f'{{{MAIN_NS}}}si'):
texts = si.itertext()
shared.append(''.join(texts))
except KeyError:
pass # No shared strings
# Parse sheet cells
sheet_xml = ET.fromstring(zf.read(sheet_path))
rows = sheet_xml.findall(f'.//{{{MAIN_NS}}}row')
data = {}
for row in rows:
for cell in row.findall(f'{{{MAIN_NS}}}c'):
ref = cell.get('r') # e.g., "A1"
cell_type = cell.get('t') # "s" = shared string, None = number
val_el = cell.find(f'{{{MAIN_NS}}}v')
if val_el is not None and val_el.text:
if cell_type == 's':
data[ref] = shared[int(val_el.text)]
else:
try:
data[ref] = float(val_el.text)
except ValueError:
data[ref] = val_el.text
return data
Investment bank xlsm files often have corrupted <definedName> entries containing "Formula removed":
def fix_defined_names(zf_in_path, zf_out_path):
"""Remove corrupted DefinedNames and repackage."""
import shutil, tempfile
with tempfile.TemporaryDirectory() as tmp:
tmp = Path(tmp)
with zipfile.ZipFile(zf_in_path, 'r') as zf:
zf.extractall(tmp)
wb_xml_path = tmp / 'xl' / 'workbook.xml'
tree = ET.parse(wb_xml_path)
root = tree.getroot()
ns = {'main': MAIN_NS}
defined_names = root.find('.//main:definedNames', ns)
if defined_names is not None:
for name in list(defined_names):
if name.text and "Formula removed" in name.text:
defined_names.remove(name)
tree.write(wb_xml_path, encoding='utf-8', xml_declaration=True)
with zipfile.ZipFile(zf_out_path, 'w', zipfile.ZIP_DEFLATED) as zf:
for fp in tmp.rglob('*'):
if fp.is_file():
zf.write(fp, fp.relative_to(tmp))
Full template script: See scripts/parse_complex_excel.py
All commands verified on macOS with Microsoft Excel.
# Activate Excel (bring to front)
osascript -e 'tell application "Microsoft Excel" to activate'
# Open a file
osascript -e 'tell application "Microsoft Excel" to open POSIX file "/path/to/file.xlsx"'
# Set zoom level (percentage)
osascript -e 'tell application "Microsoft Excel"
set zoom of active window to 120
end tell'
# Scroll to specific row
osascript -e 'tell application "Microsoft Excel"
set scroll row of active window to 45
end tell'
# Scroll to specific column
osascript -e 'tell application "Microsoft Excel"
set scroll column of active window to 3
end tell'
# Select a cell range
osascript -e 'tell application "Microsoft Excel"
select range "A1" of active sheet
end tell'
# Select a specific sheet by name
osascript -e 'tell application "Microsoft Excel"
activate object sheet "DCF" of active workbook
end tell'
Always add sleep 1 between AppleScript commands and subsequent operations (e.g., screenshot) to allow UI rendering.
IMPORTANT : osascript will hang indefinitely if Excel is not running or not responding. Always wrap with timeout:
# Safe pattern: 5-second timeout
timeout 5 osascript -e 'tell application "Microsoft Excel" to activate'
# Check exit code: 124 = timed out
if [ $? -eq 124 ]; then
echo "Excel not responding — is it running?"
fi
| Mistake | Correction |
|---|---|
| openpyxl fails on complex xlsm → try monkey-patching | Switch to zipfile + xml.etree immediately |
Count Chinese characters with wc -c | Use wc -m (chars, not bytes; Chinese = 3 bytes/char) |
| Trust file extension | Run file <path> first to confirm actual format |
openpyxl load_workbook hangs on large xlsm | Use zipfile for targeted extraction instead of loading entire workbook |
uv run --with openpyxl (never use system Python)soffice --headless) can convert formats and recalculate formulasreferences/formatting-reference.mdWeekly Installs
66
Repository
GitHub Stars
713
First Seen
Mar 4, 2026
Security Audits
Gen Agent Trust HubWarnSocketPassSnykPass
Installed on
codex62
github-copilot61
amp61
cline61
kimi-cli61
gemini-cli61
Skills CLI 使用指南:AI Agent 技能包管理器安装与管理教程
48,700 周安装
OpenClaw 指挥中心 - AI 工作团队任务控制面板,实时监控会话与系统状态
75 周安装
OpenZeppelin Stylus 合约设置指南:Rust 智能合约开发与部署教程
74 周安装
Notion自动化指南:通过Rube MCP与Composio实现页面、数据库与内容管理
72 周安装
Vercel自动化部署与监控指南 - 通过Rube MCP实现项目自动化管理
71 周安装
技术债务可视化工具 - 识别高息技术债务,优化代码重构优先级
78 周安装
Salesforce B2C Commerce 元数据开发指南:自定义属性、对象与站点偏好设置
71 周安装
| Title |
| Multiplier | '0.0x' | 1.5x |