Claude Code スキルでマークダウンを繰り返し処理するときのトークン節約 6 Tips

Claude Code スキルでマークダウンを繰り返し処理するときのトークン節約 6 Tips

2026.04.16

はじめに

最近あるドキュメントのレビュースキルを作成し、繰り返し実行することがあったのですが、参照するリファレンスが多くなるとトークン数の消費量がすごいことになりませんか?

それを繰り返し実行する場合は、さらにトークンを消費してリミットに到達…というケースはよくありますよね。

私も同じような状態で、なんとか改善できないかなと既存のスキルを見直してみました。

原因を分析すると、正規表現で判定できるチェック項目や表記ルールの参照など、すべてをプロンプトで指示して Claude に処理させていました。

この記事では、上記のような繰り返しマークダウンを処理するスキルを改善した際の 6 つの学びを Tips として共有します。
改善の結果、実行時間とトークン消費を大幅に削減できました。

なお、この記事では Claude Code のスキルを例にしていますが、スキル(SKILL.md)の仕様自体は特定の AI エージェントに依存しません。Python スクリプトとの連携パターンは他の AI エージェントでも応用できます。

根本的な考え方は「Claude には Claude にしかできないことだけをやらせよう」です。

Tips 1: 既存の Lint ツールを使う

最初に検討すべきは、Claude へ任せる前に既存ツールで排除できるチェック項目がないかです。

マークダウンの形式チェックには textlint や markdownlint といった成熟したツールがあります。SKILL.md に実行コマンドを書くだけで、Claude が行っていたフォーマットチェックを丸ごと代替できます。

SKILL.md
## ステップ 1: textlint による形式チェック

以下のコマンドを実行する:

\`\`\`bash
npx textlint --format json target.md
\`\`\`

エラーがあれば修正案を提示する。

Claude に「日本語の表記ルールに従ってチェックしてください」と指示すると、ルール定義(漢字の使い分け、助詞の重複、句読点ルールなど)をプロンプトに含める必要があります。textlint ならルールはプラグインで管理されるため、結果の数行だけを渡せば済みます。

まず形式的な部分であれば textlint や markdownlint を使って検知できないかを考えましょう。

Tips 2: 決定論的チェックを Python スクリプトに切り出す

既存ツールでカバーできない独自のチェック項目でも、正規表現やキーワードマッチで判定できるものは Python に移行できます。

たとえば、ブログ記事のレビュースキルなら以下のようなチェックは Claude に任せる必要がありません。

import re

def check_markdown(text: str) -> list[dict]:
    results = []

    # 必須セクションの存在確認
    for section in ["## はじめに", "## まとめ"]:
        if section not in text:
            results.append({"status": "ERROR", "message": f"必須セクション「{section}」がない"})

    # URL 未記入のリンク
    empty_links = re.findall(r"\[.+?\]\(\s*\)", text)
    if empty_links:
        results.append({"status": "WARN", "message": f"空の URL リンクが {len(empty_links)} 件"})

    # 見出しの階層飛ばし(h2 の次に h4 など)
    headings = re.findall(r"^(#{1,6}) ", text, re.MULTILINE)
    for i in range(1, len(headings)):
        prev_level = len(headings[i - 1])
        curr_level = len(headings[i])
        if curr_level > prev_level + 1:
            results.append({"status": "WARN", "message": f"見出し階層が飛んでいる(h{prev_level} → h{curr_level})"})

    return results

「必須セクションがあるか」「見出しの階層が正しいか」といったプロジェクト固有のルールは、textlint や markdownlint ではカバーしにくい部分です。こうした独自チェックを Python に切り出すことで、Claude のプロンプトからルール記述を削除できます。

スキルの実行環境はユーザーごとに異なるため、外部パッケージへの依存は避けて Python 標準ライブラリのみで実装するのが無難です。rejsonpathlibsubprocess があれば、マークダウンの構造検証やパターンマッチはほぼカバーできます。

Tips 3: 参照データを Python 側で前処理する

文章に特定のルールやスタイルなどが決まっているケースがありますよね。
こういったケースでは、ルール定義ファイルやスタイルガイドを Claude に直接読ませるのではなく、Python でパースして結果だけを渡すのが効果的です。

たとえば、NG 表現リストが別ファイルにまとめられているケースを考えます。

ng-expressions.md(参照ファイル・100行以上)
| NG 表現 | 推奨表現 | 理由 |
| --- | --- | --- |
| 行う | 実施する | 漢字が続くと読みにくい |
| 及び | および | 常用漢字外 |
| 又は | または | 常用漢字外 |
| 但し | ただし | 常用漢字外 |
| ...(以下多数) |

Claude に「このファイルを読んで該当する表現がないか確認して」と指示すると、100 行以上がすべてトークンとして消費されます。Python でパースして該当する表現だけを渡せば数行で済みます。

def check_ng_expressions(text: str, ng_file: str) -> list[dict]:
    """NG 表現ファイルをパースして対象テキストを走査"""
    ng_table = parse_markdown_table(ng_file)  # [(ng, recommended, reason), ...]
    results = []
    for ng, recommended, reason in ng_table:
        if ng in text:
            results.append({
                "status": "WARN",
                "message": f"「{ng}」→「{recommended}」({reason})"
            })
    return results

繰り返し実行する場面では、この差が特に大きくなります。参照ファイルは毎回同じなのに、Claude は毎回全文を読み直すためです。Python なら参照データの読み取りにトークンは消費されません。

Tips 4: ファイルの必要な部分だけを Claude に渡す

マークダウンファイルの全文を Claude に渡さず、判定に必要なセクションだけを Python で抽出して渡すのも効果的です。

たとえば「まとめセクションが具体的な結論を含んでいるか」を Claude に判定させたい場合、ファイル全体を渡す必要はありません。Python で該当セクションだけを抽出して渡せば十分です。

def extract_section(text: str, heading: str) -> str | None:
    """マークダウンから指定した h2 セクションの本文を抽出"""
    lines = text.splitlines()
    target = f"## {heading}"
    capturing = False
    buffer = []
    for line in lines:
        if line.strip() == target:
            capturing = True
            continue
        elif capturing and line.startswith("## "):
            break  # 次の h2 に到達したら終了
        elif capturing:
            buffer.append(line)
    return "\n".join(buffer).strip() if buffer else None

Python スクリプトの出力を JSON にまとめると、Claude は判定に必要なセクションだけを受け取ります。

{
    "summary_section": "今回は○○を使って△△を実装しました。結果として..."
}

ファイルが数百行あっても、Claude に渡すのは「まとめ」の数行だけです。「具体的な結論を含んでいるか」のような意味理解が必要な判定は Claude に任せつつ、読み取る範囲を最小限に抑えられます。

Tips 5: URL が決まっている外部データは直接取得する

これはマークダウンの処理に限ったケースではないですが、Web 上の情報を参照して記載内容を確認することがあります。

スキルが外部サイトの情報を参照する場合、MCP サーバーや WebFetch ではなく Python の urllib.request で直接取得することでトークンを節約できます。

MCP 呼び出しでは、ツール呼び出しのリクエスト/レスポンスがトークンとして消費されます。さらに、取得したページの全文を Claude が読み取ることになりがちです。

取得先の URL パターンが決まっている場合は、Python で直接取得し、必要な部分だけを抽出しましょう。

たとえば、AWS ドキュメントの特定セクションを参照するスキルを考えます。以下のような AWS Security Hub CSPM のコントロールに関する記述の一部を参照したいケースです。

https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/ec2-controls.html

Claude に「このページの EC2.1 の説明を読んで」と頼むと、WebFetch でページ全体(8 万文字超)を取得してしまいます。URL パターンとページ構造が決まっているなら、Python で該当セクションだけを抽出できます。

import re
import urllib.request

def fetch_section(base_url: str, section_id: str) -> dict:
    """URL からページを取得し、指定セクションだけを抽出"""
    try:
        req = urllib.request.Request(base_url, headers={"User-Agent": "Mozilla/5.0"})
        with urllib.request.urlopen(req, timeout=15) as resp:
            html = resp.read().decode("utf-8")
    except Exception as e:
        return {"error": str(e)}

    # id 属性で該当セクションを特定し、次の h2 まで抽出
    pattern = re.compile(
        rf'<h2[^>]*\bid="{re.escape(section_id)}"[^>]*>(.*?)(?=<h2\s|$)',
        re.DOTALL,
    )
    m = pattern.search(html)
    if not m:
        return {"error": f"{section_id} が見つかりません"}

    # HTML タグを除去してプレーンテキスト化
    text = re.sub(r"<[^>]+>", "", m.group(1))
    # トークン節約のため長すぎる場合は切り詰め
    if len(text) > 2000:
        text = text[:2000] + "\n...(省略)"
    return {"content": text.strip()}

実際に EC2.1 を取得した結果がこちらです。

[EC2.1] Amazon EBS スナップショットはパブリックに復元できないようにすることをお勧めします
        関連する要件: PCI DSS v3.2.1/1.2.1、PCI DSS v3.2.1/1.3.1、PCI DSS v3.2.1/1.3.4、PCI DSS v3.2.1/7.2.1、NIST.800-53.r5 AC-21、NIST.800-53.r5 AC-3、NIST.800-53.r5 AC-3(7)、NIST.800-53.r5 AC-4、NIST.800-53.r5 AC-4(21)、NIST.800-53.r5 AC-6、NIST.800-53.r5 SC-7、NIST.800-53.r5 SC-7(11)、NIST.800-53.r5 SC-7(16)、NIST.800-53.r5 SC-7(20)、NIST.800-53.r5 SC-7(21)、NIST.800-53.r5 SC-7(3)、NIST.800-53.r5 SC-7(9)
        カテゴリ: 保護 &gt; セキュアなネットワーク設定
        重要度: 非常事態
        リソースタイプ : AWS::::Account
        AWS Config ルール : ebs-snapshot-public-restorable-check
        スケジュールタイプ : 定期的
        パラメータ : なし
        このコントロールは、Amazon Elastic Block Store スナップショットがパブリックではないかどうかをチェックします。Amazon EBS スナップショットを誰でも復元できる場合、コントロールは失敗します。
        EBS スナップショットは、特定の時点の EBS ボリュームのデータを Amazon S3 にバックアップするために使用されます。スナップショットを使用して、EBS ボリュームを以前の状態に復元できます。スナップショットのパブリックへの共有は滅多に認められていません。一般的に、スナップショットを公開する決定は、誤って行われたか、影響を完全に理解せずに行われています。このチェックは、そのような共有がすべて完全に計画され、意図的であったことを確認するのに役立ちます。


            修正
            パブリック EBS スナップショットをプライベートにするには、「Amazon EC2 ユーザーガイド」の「スナップショットの共有」を参照してください。[アクション、権限の変更] で、[非公開] を選択します。

8 万文字のページから約 1,200 文字だけが抽出されました。ページ全体を Claude へ渡す代わりに、必要なセクションだけを JSON に含めて渡せます。

これは AWS ドキュメントでの例ですが、取得したい URL に合わせて Claude などに分析してもらうと目的の文章を取得できるはずです。

Tips 6: 文脈判断が必要な箇所にフラグを立てる

ここまでの Tips は「Claude に任せない」方向の最適化でしたが、完全に Claude を排除できないケースもあります。「この説明は読者にとって分かりやすいか」のような、文脈を理解しないと判定できない項目です。

ただし、そのような項目でも「Claude に全文を読ませて判断させる」必要はありません。Python で検知したい文言を設定しておき、該当箇所にフラグを付けて文脈だけを Claude に読ませることでトークンを節約できます。

たとえば「曖昧な表現がないか」というチェック項目を考えます。キーワードマッチで検出はできますが、文脈によっては問題ない場合もあります。

VAGUE_PATTERNS = ["と思います", "かもしれません", "おそらく", "たぶん"]

def check_vague_expressions(text: str) -> list[dict]:
    results = []
    for pattern in VAGUE_PATTERNS:
        for i, line in enumerate(text.splitlines(), 1):
            if pattern in line:
                results.append({
                    "status": "WARN",
                    "message": f"曖昧表現「{pattern}」(行 {i})",
                    "line": i,
                    "context": line.strip(),
                    "needs_llm_review": True  # Claude に文脈確認を依頼
                })
    return results

needs_llm_review: true が付いた項目だけを Claude が確認します。SKILL.md には以下のように記述します。

`needs_llm_review` フラグ付きの項目は、`context` の内容を読んで
文脈判断し、偽陽性であれば最終レポートから除外すること。
たとえば「かもしれません」が読者への注意喚起として使われている場合は問題ない。

needs_llm_review が付いた項目を確認してください」と指示するだけで、Claude はファイル全文を読む代わりに、フラグ付きの数行だけを確認すれば済みます。

このフラグの仕組みにはもう 1 つ重要な効果があります。「Python で 100% 正確に判定しなければならない」という制約が外れるため、より多くの項目を Python へ移行できます。グレーゾーンの項目を「Python で検出 → Claude で文脈確認」という 2 段階にすれば、Python 化のハードルが大幅に下がります。

計測結果

ここまでの学びを元に、課題となっていたレビュー用のスキルを改善し、同一ドキュメントに対して複数の観点で計測してみました。

以下の条件で Claude に計算してもらっています。

  • Claude で全文チェックするスキル(Claude のみ)と、Claude+Python でチェックするスキル(Python 併用)を新規セッションで実行
  • ~/.claude/projectsの配下に出力されるセッション履歴を取得
  • jsonl に記載されたキャッシュを取り除いた純粋なトークン数や実行時間を計算

結果は以下の通りです。

指標 Claudeのみ Python併用 差分
実行時間 173.9s 65.4s 62%減
output_tokens 7,592 508 93%減
tool_result文字数 46,257 10,977 76%減
APIコスト $7.04 $2.88 59%減
API呼び出し回数 19 6 -13回
  1. Claudeのみ版が非常に重い — Grep/Read/AWS Docs検索を19回のAPI callで自力探索。特にAWSドキュメント検索(mcp__aws-documentation)を複数回呼んでいる
  2. Python併用版はほぼ全てをスクリプトで完結 — CSVパース、ガイドライン検索、メタデータ構造化をPythonで済ませ、Claudeは判定結果の出力だけ。output 508トークンで終了

タスクの複雑さ(情報収集ステップ数)が大きいほど、Pythonオフロードの効果が拡大するパターンが見えます。

出力結果はほぼ同じでしたが、実行時間とトークン数には大きな違いが出ました。
当初はトークン数の改善を目的にしていましたが、実行時間が大幅に短縮されたことも嬉しい改善でした。

まとめ

Claude Code のスキルでマークダウンファイルを繰り返し処理する際のトークン節約 Tips をまとめます。

Tips やること 効果
1. 既存 Lint ツール textlint/markdownlint を SKILL.md から実行 形式チェックを Claude から排除
2. 決定論的チェックの Python 化 正規表現・キーワードマッチをスクリプトに切り出す プロンプトからルール記述を削除
3. 参照データの前処理 NG テーブル等のリファレンスを Python でパースし結果だけ渡す 参照ファイル全文の読み取りを排除
4. 必要な部分だけ渡す Python でセクション抽出し JSON で渡す ファイル全文の読み取りを排除
5. 外部データの直接取得 urllib.request で直接取得し必要部分を抽出 MCP/WebFetch のトークンを削減
6. フラグで文脈確認を絞る Python で検知 → フラグ → 該当文脈だけ Claude に Claude の読み取り範囲を最小化

繰り返し実行するスキルでは、1 回あたりのトークン削減が回数分だけ積み上がります。正規表現で済むなら正規表現を、既存ツールで済むならそれを、URL が決まっているなら直接取得しましょう。

Claude の強みである意味理解・文脈判断が必要な部分だけを任せることで、トークン消費を抑えつつ品質を維持できます。

この記事が Claude Code のスキル開発でトークン効率を改善したい方の参考になれば幸いです。

参考リンク

この記事をシェアする

関連記事