mcpField
E
excel-automation

excel-automation

github.com/daymadeドキュメント1.5k1.5k 回インストール今日更新
リポジトリ

excel-automation について

ワークブックの作成、解析、macOSでのウィンドウ操作を行うExcel自動化スキルです。ファイルの複雑さに応じて処理方法を選び、複雑なファイルにはXMLベースの抽出も使います。

主な機能

  1. 01書式付きExcelレポートの作成
  2. 02複雑なxlsxとxlsmの解析
  3. 03破損した定義名の修復
  4. 04AppleScriptによるExcel操作
  5. 05ファイル特性に応じた解析手段の選択

活用シーン

  1. 01財務レポートやダッシュボードの作成
  2. 02大容量またはVBA付きブックからの抽出
  3. 03定義名が破損したxlsmの処理
  4. 04macOSでのExcel表示調整

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/daymade/claude-code-skills --skill excel-automation

インストール後、Claude Code で/excel-automationを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
formatting-reference.md
printable-forms.md
scripts/
create_formatted_excel.py
parse_complex_excel.py

詳細

Excel の自動化

プロフェッショナルな Excel ファイルを作成し、複雑な財務モデルを解析し、macOS 上で Excel を操作します。

編集可能なセルと印刷可能なレイアウトが必要な紙のフォームの画像やスキャンを扱う場合は、ブックを作成する前に印刷用フォームを読み込んでください。ホストで指定されているスプレッドシート作成ツールに従ってください。以下の openpyxl の例は、そのツールの使用が許可されている場合にのみ使用してください。

クイックスタート

コマンドによって Excel を前面に表示したり、表示中のウィンドウを変更したりする前に、ユーザーの操作状況と他のエージェントの使用状況を確認してください。現在のタスクで許可されているフォアグラウンド操作のみを使用してください。画面が使用中の場合、使用状況が不明な場合、または予期しない入力があった場合は、入力を停止し、独立して行えるバックグラウンド作業を続けてください。

# 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'

概要

機能ツール用途
書式設定された Excel ファイルの作成openpyxlレポート、モックアップ、ダッシュボード
複雑な xlsm/xlsx の解析zipfile + 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 <path> を実行してください。拡張子は実際の形式と異なる場合があります。.xls ファイルが実際には ZIP ベースの xlsx であることもあります。

Excel ファイルの作成(openpyxl)

プロフェッショナルな色の慣例(投資銀行標準)

この慣例に従って、財務レポートに以下の色を適用してください。紙のフォームでは、意図された印刷時の外観を維持し、既定で入力色を追加しないでください。

色RGB コード意味
青0000FFユーザー入力/前提条件
黒000000計算値
緑008000他シートへの参照
濃い青の背景に白FFFFFF(4472C4 上)セクション見出し
濃い青の文字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"))
数値書式コード
書式コード例
通貨'$#,##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",   # 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

テンプレートスクリプト全体:scripts/create_formatted_excel.py を参照してください。

複雑な Excel の解析(zipfile + xml)

複雑な xlsm ファイル(破損した DefinedNames、複雑な VBA など)で openpyxl が失敗する場合は、標準ライブラリを直接使用してください。

XLSX 内部の ZIP 構造
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
シート名の解決

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):
    """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
破損した DefinedNames の修正

投資銀行の xlsm ファイルでは、「Formula removed」を含む破損した <definedName> エントリがよく見られます。

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))

テンプレートスクリプト全体:scripts/parse_complex_excel.py を参照してください。

macOS 上での Excel の操作(AppleScript)

すべてのコマンドは、Microsoft Excel を使用する macOS 上で検証済みです。

検証済みのコマンド
# 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'
タイミングとタイムアウト

UI の描画を待つため、AppleScript コマンドと後続の操作(スクリーンショットなど)の間には、必ず sleep 1 を入れてください。

重要:Excel が起動していない、または応答していない場合、osascript は無期限に停止します。必ず 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

よくある間違い

間違い修正方法
複雑な xlsm で openpyxl が失敗したため、モンキーパッチを試すすぐに zipfile + xml.etree に切り替える
wc -c で中国語の文字数を数えるwc -m を使用する(バイト数ではなく文字数。中国語は 1 文字あたり 3 バイト)
ファイルの拡張子を信用する最初に file <path> を実行し、実際の形式を確認する
大きな xlsm で openpyxl の load_workbook が停止するブック全体を読み込むのではなく、zipfile で対象を絞って抽出する

重要な注意事項

  • Python スクリプトは uv run --with openpyxl で実行してください(システムの Python は使用しないでください)
  • LibreOffice(soffice --headless)で、形式の変換や数式の再計算ができます
  • 詳細な書式設定のリファレンスについては、references/formatting-reference.md を参照してください

コメント