スキル化する /search-estat|統計表 ID を AI に探させる

ClaudeCode
e-Stat
スキル
SKILLmd
AI

前回(Part 1)で Claude Code と e-Stat API の初期セットアップを終えました。appId を取得し、curl で軽く叩いてレスポンスが返ってくることまで確認したはずです。

ここまで来て最初にぶつかる壁が 「目的の統計表 ID(statsDataId)が見つからない」 という問題です。e-Stat には 8,000 を超える統計表が登録されており、UI で探すには分類体系が大きすぎる。API の getStatsList を毎回叩くスクリプトを書くのも面倒。

本記事では、この 「統計表 ID 探し」を Claude Code のスキル機能で自動化 する手順を解説します。一度 SKILL.md を書いてしまえば、以降は /search-estat 人口 のような自然な指示で Claude が候補を絞り込んでくれる状態になります。

筆者は元県庁職員で、現在はstats47.jp(47 都道府県の統計サイト)を 1 人で運営しています。サイトには 530 を超えるランキングがありますが、その全てを支えているのが本記事で紹介する「スキル化」の発想です。実例集の Part 2 として、Claude Code 未経験のソフトウェアエンジニア向けに、再利用可能な手順書としてのスキルを書く流れを共有します。

e-Stat の統計表 ID 探しはなぜ難しいか

e-Stat(政府統計の総合窓口)は、国勢調査・住民基本台帳・経済センサスなど、府省庁が公表する統計を一元的に集約したポータルです。データはほぼ全てが API 経由でも取得できるため、エンジニアにとっては最高クラスのオープンデータ供給源と言えます。

ただし「最高クラス」と「使いやすい」は別問題です。具体的なつらみは 3 つあります。

  1. 統計表が 8,000 件超: 同じ「人口」というキーワードでも国勢調査・住民基本台帳・推計人口・将来推計人口とソースが複数ある
  2. 公式 UI の検索が階層型: 分野 → 統計調査 → 統計表 → 表番号、と 4 段階のドリルダウンが必要。1 統計表に辿り着くまでに 10 クリックかかることもある
  3. statsDataId の命名規則が不透明: 0003448237 のような数字 ID で、人間が見ても何のデータか判別できない

API 側で用意されているのが getStatsList というエンドポイントです。キーワード検索もできますが、生のレスポンス JSON は数千行になることもあり、目視で「これだ」と決めるには疲れます。

curl "https://api.e-stat.go.jp/rest/3.0/app/json/getStatsList?appId=$ESTAT_APP_ID&searchWord=人口&limit=5"

このレスポンスをそのまま読むのではなく、「AI に要約させて候補リストにする」 のがスキル化の出発点です。

Claude Code のスキル機能とは

Claude Code には 「スキル(Skill)」 という機能があります。ざっくり言うと、プロジェクト固有の手順書を Markdown ファイルで書いておき、slash command として呼び出せる仕組み です。

特徴を 3 つにまとめます。

観点内容
ファイルベース.claude/skills/<name>/SKILL.md に書く。バイナリ依存なし、Git で管理可能
slash command/<skill-name> で呼び出せる。Claude Code セッション内で自然に使える
引数フリープロンプトは自然言語のまま渡せる。/search-estat 人口の年次推移 のような書き方が可能

スキルの実体は SKILL.md という Markdown ファイルです。冒頭に YAML frontmatter で namedescription を書き、本文に手順を書く。それだけです。

Claude Code は session 開始時に .claude/skills/ 配下を走査し、見つかった SKILL.md を「呼び出し可能なツール」として認識します。利用者が /search-estat と打つと、その SKILL.md の本文が Claude のシステムプロンプトに合流し、本文に従って動作する流れです。

note: スキル機能は Claude Code 公式の機能です(2025 年後半に正式リリース)。Anthropic のドキュメント上は "Skills" と表記されています。プロジェクト個別のスキルは .claude/skills/ に、グローバル(ユーザー全体)で使うものは ~/.claude/skills/ に置くのが基本構成です。

SKILL.md の書き方|テンプレート

最小構成の SKILL.md はこんな形になります。

---
name: search-estat
description: e-Stat API の getStatsList を呼び出し、キーワードに一致する統計表の候補を要約して返す。
---

# /search-estat

ユーザーから渡されたキーワードを `searchWord` として e-Stat API に投げ、上位 5 件の統計表を「statsDataId・統計表名・公表機関・調査年」の表形式で返却する。

## 手順

1. ユーザーのプロンプトからキーワードを抽出する(例: 「人口の年次推移」→ `人口`)
2. 環境変数 `ESTAT_APP_ID` を確認する。未設定ならエラーとして停止する
3. `getStatsList` を `searchWord=<キーワード>&limit=5` で呼び出す
4. レスポンス JSON の `LIST_INF` 配列から各表の `@id` / `TITLE` / `GOV_ORG` / `SURVEY_DATE` を抽出する
5. Markdown 表として整形し、最後に「次のアクション例: `/fetch-estat-data <id>` で取得」と添える

## エラー処理

- API がエラーステータスを返した場合は `RESULT.STATUS` と `RESULT.ERROR_MSG` をそのまま提示する
- 該当 0 件の場合は別のキーワード候補を 3 つ提案する(同義語・上位概念・関連分野)

YAML frontmatter の description は、Claude が「いつこのスキルを呼ぶべきか」を判断する材料になります。動詞を含めて、利用シーンが想像できる文章にする のが鉄則です。「e-Stat に関する何か」のような曖昧な記述だと呼び出し精度が落ちます。

本文側は普通の Markdown でよく、装飾やコードブロックも自由に書けます。手順を箇条書きで明文化しておくと、Claude が手順スキップを起こしにくくなります。

/search-estat スキルを書く 5 ステップ

実際に手を動かして /search-estat を作る流れを 5 ステップで追います。

Step 1: スキル用ディレクトリを切る

プロジェクトルートで以下を実行します。

mkdir -p .claude/skills/search-estat
touch .claude/skills/search-estat/SKILL.md

Claude Code は .claude/skills/<name>/SKILL.md という配置を期待します。<name> がそのまま slash command 名になるので、ハイフン区切りで分かりやすい名前を付けます。今回は search-estat としました。

Step 2: frontmatter を書く

エディタで SKILL.md を開き、まず frontmatter から書きます。

---
name: search-estat
description: e-Stat API の getStatsList を呼び出し、キーワードに一致する統計表 ID 候補を要約して返す。データ取得前の調査ステップで使う。
---

description は 1〜2 文で具体的に書きます。「データ取得前の調査ステップで使う」のように利用シーンを添えると、Claude が自動でスキルを選んでくれるシーンが増えます。

Step 3: 検索フローを設計する

スキル本文に書く手順を整理します。今回のフローは以下のとおりです。

文章で書くなら次の 5 アクションです。

  1. プロンプトからキーワード抽出
  2. ESTAT_APP_ID の存在確認
  3. getStatsList 呼び出し(searchWord, limit=5
  4. レスポンスから必要フィールド抽出
  5. Markdown 表に整形して返却

この各ステップを SKILL.md の ## 手順 セクションに書きます。

Step 4: 利用する API パラメータを表で明文化する

スキル内でどの API パラメータを使うかを表で書いておくと、後から読み返したときに分かりやすいです。getStatsList の主要パラメータは以下のとおり。

パラメータ必須説明
appId必須stringe-Stat の発行する API キー
searchWord任意string検索キーワード。スペース区切りで AND 検索
statsCode任意string政府統計コード(8 桁)。府省庁単位で絞り込む際に使用
surveyYears任意string調査年。YYYY または YYYYMM-YYYYMM のレンジ指定
openYears任意string公開年。新しいデータだけ欲しい時に有効
limit任意int返却件数上限。デフォルト 10 万、検索用途なら 5〜20 程度
startPosition任意intページネーション用の開始位置。1 起点

レスポンス側で抽出するフィールドも整理します。

JSON パス中身
GET_STATS_LIST.DATALIST_INF.LIST_INF[].@idstatsDataId(10 桁の数字 ID)
GET_STATS_LIST.DATALIST_INF.LIST_INF[].TITLE.$統計表名
GET_STATS_LIST.DATALIST_INF.LIST_INF[].GOV_ORG.$公表機関名(例: 総務省統計局)
GET_STATS_LIST.DATALIST_INF.LIST_INF[].SURVEY_DATE調査年(YYYY または YYYYMM)
GET_STATS_LIST.DATALIST_INF.LIST_INF[].STATISTICS_NAME統計調査名(例: 国勢調査)

Step 5: 完成版 SKILL.md を書く

ここまでの設計をまとめると、最終的な SKILL.md は次のようになります。

---
name: search-estat
description: e-Stat API の getStatsList を呼び出し、キーワードに一致する統計表 ID 候補を要約して返す。データ取得前の調査ステップで使う。
---

# /search-estat

## 目的

ユーザーが指定したキーワードに対して、e-Stat の統計表 ID(statsDataId)を上位 5 件まで提案する。データ取得スキル(/fetch-estat-data)の前段として使う。

## 前提

- 環境変数 `ESTAT_APP_ID` に有効な API キーが設定されていること
- Node.js 18 以上(fetch 標準搭載)または Python 3.9 以上

## 手順

1. ユーザーのプロンプトからキーワードを抽出する。複数語ある場合はスペース区切りで連結(例: 「県民所得 最新」→ `searchWord=県民所得 最新`)
2. `ESTAT_APP_ID` を `process.env` から取得。未設定なら以下を出力して終了:
   - `Error: ESTAT_APP_ID が未設定です。https://www.e-stat.go.jp/api/ で取得し、.env に追記してください。`
3. 以下の Node.js コードを実行する:

   ```javascript
   const url = new URL("https://api.e-stat.go.jp/rest/3.0/app/json/getStatsList");
   url.searchParams.set("appId", process.env.ESTAT_APP_ID);
   url.searchParams.set("searchWord", keyword);
   url.searchParams.set("limit", "5");
   const res = await fetch(url);
   const json = await res.json();
  1. json.GET_STATS_LIST.RESULT.STATUS を確認し、0 以外ならエラーメッセージ(ERROR_MSG)をユーザーに提示して停止

  2. json.GET_STATS_LIST.DATALIST_INF.LIST_INF を最大 5 件まで取り出し、以下の Markdown 表を作成:

    #statsDataId統計表名公表機関調査年
  3. 表の末尾に次のフォローアップを追加: 次のアクション例: /fetch-estat-data <statsDataId> で実データを取得できます。

エラー処理

  • ネットワークエラー時はリトライせず、エラー内容をそのまま出力する
  • 検索結果が 0 件の場合は、同義語・上位概念・関連分野のキーワード候補を 3 つ提案する(例: 「賃金」が 0 件なら「給与」「所得」「報酬」)
  • API のレートリミット(HTTP 429)に当たった場合は 60 秒待機を提案し、自動リトライは行わない

出力例

ユーザー: /search-estat 人口

応答:

#statsDataId統計表名公表機関調査年
10003448237人口推計 / 都道府県、年齢階級別人口総務省統計局2024
20003410379国勢調査 / 男女別人口・世帯数総務省統計局2020
...............

次のアクション例: /fetch-estat-data 0003448237 で実データを取得できます。


これで `/search-estat` の準備は完了です。Claude Code セッションを再起動するか、スキル一覧を更新すれば呼び出せるようになります。


## getStatsList のレスポンス構造

スキル本文に書いた `LIST_INF` の構造を、もう少し具体的に確認しておきます。`getStatsList?searchWord=人口&limit=2` を叩いた場合、概ね以下のような JSON が返ります。

```json
{
  "GET_STATS_LIST": {
    "RESULT": {
      "STATUS": 0,
      "ERROR_MSG": "正常に終了しました。",
      "DATE": "2026-05-19T10:00:00.000+09:00"
    },
    "PARAMETER": {
      "LANG": "J",
      "SEARCH_WORD": "人口",
      "LIMIT": 2,
      "DATA_FORMAT": "J"
    },
    "DATALIST_INF": {
      "NUMBER": 2,
      "RESULT_INF": {
        "FROM_NUMBER": 1,
        "TO_NUMBER": 2
      },
      "LIST_INF": [
        {
          "@id": "0003448237",
          "STAT_NAME": { "@code": "00200524", "$": "人口推計" },
          "GOV_ORG": { "@code": "00200", "$": "総務省" },
          "STATISTICS_NAME": "人口推計 都道府県、年齢階級別人口",
          "TITLE": { "@no": "01", "$": "都道府県、年齢階級別人口" },
          "SURVEY_DATE": "202410",
          "OPEN_DATE": "2025-04-15"
        },
        {
          "@id": "0003410379",
          "STAT_NAME": { "@code": "00200521", "$": "国勢調査" },
          "GOV_ORG": { "@code": "00200", "$": "総務省" },
          "STATISTICS_NAME": "令和2年国勢調査",
          "TITLE": { "@no": "1-1", "$": "男女別人口・世帯数及び世帯人員" },
          "SURVEY_DATE": "202010",
          "OPEN_DATE": "2021-11-30"
        }
      ]
    }
  }
}

特徴的なのは、テキスト値が { "@code": "...", "$": "..." } のような構造で返ってくる点です。これは XML 由来の名残で、$ が値本体、@xxx が属性に相当します。慣れないとアクセスパスを間違えやすいので、要約スクリプトを書くときは慎重に。

Python で同じ呼び出しをする場合は次のようになります。

import os
import requests

APP_ID = os.environ["ESTAT_APP_ID"]
url = "https://api.e-stat.go.jp/rest/3.0/app/json/getStatsList"
params = {"appId": APP_ID, "searchWord": "人口", "limit": 5}
res = requests.get(url, params=params, timeout=30)
data = res.json()

list_inf = data["GET_STATS_LIST"]["DATALIST_INF"]["LIST_INF"]
for item in list_inf:
    print(f"{item['@id']} | {item['STATISTICS_NAME']} | {item['GOV_ORG']['$']}")

このスニペットは SKILL.md の手順 3 に Python 版として併記しておくと、Python 派の開発者でも同じスキルを再利用できます。

実行例|「人口」で検索してみる

実際に Claude Code 上で /search-estat を呼び出すと、こんな対話になります。

あなた: /search-estat 人口

Claude: getStatsList を searchWord=人口, limit=5 で実行します。

| # | statsDataId | 統計表名                                       | 公表機関     | 調査年   |
| - | ----------- | ---------------------------------------------- | ------------ | -------- |
| 1 | 0003448237  | 人口推計 都道府県、年齢階級別人口              | 総務省       | 2024-10  |
| 2 | 0003410379  | 国勢調査 男女別人口・世帯数及び世帯人員        | 総務省       | 2020-10  |
| 3 | 0003448239  | 人口推計 年齢(5歳階級)、男女別人口           | 総務省       | 2024-10  |
| 4 | 0004021907  | 住民基本台帳人口・世帯数、人口動態(都道府県別) | 総務省       | 2024-01  |
| 5 | 0003337392  | 将来推計人口(都道府県・市区町村別)           | 国立社人研   | 2018     |

次のアクション例: /fetch-estat-data 0003448237 で実データを取得できます。

5 件並べてみると、それぞれが微妙に違うデータセットだと一目で分かります。直近の都道府県別が欲しいなら 0003448237、5 年に 1 度の悉皆調査なら 0003410379、将来推計が欲しいなら 0003337392「人間が選ぶ判断材料」だけを AI に整形してもらう という分業が綺麗にできています。

仮にこれを毎回手で curl していたら、JSON を読むのに 5 分、表に整形するのに 5 分かかります。スキル化すれば 10 秒で同じアウトプットが得られます。

スキル化の効果|なぜ毎回 prompt を書かないのか

「同じことを長いプロンプトで毎回頼めばいいのでは?」という疑問はもっともです。それでも筆者がスキル化に手間をかける理由は 3 つあります。

  1. プロンプトの揺れがなくなる: 自然言語で毎回頼むと、「上位 5 件で」を書き忘れて 20 件返ってきたり、Markdown 表でなく箇条書きになったりする。SKILL.md に手順を固定すると出力が安定する
  2. チームで共有できる: SKILL.md は Git にコミットすればチーム全員が同じスキルを使える。「あの分析、誰々さんのプロンプトじゃないと再現できない」問題が消える
  3. 改善が積み上がる: 「該当 0 件のときは同義語を 3 つ提案する」のような細かい工夫を SKILL.md に書き加えていける。プロンプトをコピペし続ける運用だと、こうした学びが個人の中に閉じてしまう

特に 3 つ目が大きいです。stats47.jp では現在 60 以上のスキルを運用していますが、それぞれが「過去のミスを踏まないためのチェックリスト」になっています。例えば /search-estat であれば「statsCode を指定すれば府省庁単位で絞れる」「surveyYears で古い表を除外できる」といった改善を、SKILL.md に追記していくだけで全員が恩恵を受けられます。

つまずきポイント 3 選

最後に、初めて SKILL.md を書く人が踏みやすい地雷を 3 つだけ。

1. slash command が認識されない

SKILL.md を作ったのに /search-estat がサジェストに出てこない場合、9 割は以下のどれかが原因です。

  • ディレクトリ名と frontmatter の name が不一致(search-estatsearch_estat の混在など)
  • SKILL.md が .claude/skills/search-estat/ 直下ではなくサブディレクトリに入っている
  • Claude Code セッションを再起動していない(スキル一覧は session 起動時にスキャンされる)

ディレクトリ構造を ls .claude/skills/search-estat/ で確認し、SKILL.md がトップにあることを確認してください。

2. ESTAT_APP_ID が未設定でエラー

スキル本文に「環境変数を確認する」と書いてあっても、Claude が確認をスキップして API を叩いて 401 が返るケースがあります。対策は以下 2 つ。

  • .env ファイルに ESTAT_APP_ID=xxxx を書いておき、shell 側で source .env する
  • SKILL.md 冒頭に 太字で 「実行前に必ず echo $ESTAT_APP_ID で値を確認する」と明記する

文章を太字や警告ブロックで強調すると、Claude のスキップ率が体感で半減します。

3. 表数が多すぎてレスポンスが切れる

limit を指定せず叩くと、getStatsList は最大 10 万件返してきます。Claude のコンテキスト窓を圧迫してその先の処理が破綻するので、スキル本文に必ず limit=5 を書き込む のが安全です。多めに見たい時だけ limit=20 などに上書きする運用にします。

仮に検索結果が多すぎて目的の表が下位に埋もれる場合は、statsCode(府省庁コード)か surveyYears(年)で絞り込みを掛けます。SKILL.md に「結果が広すぎる場合は statsCode を聞き返す」のような分岐を書いておくと、対話がさらに賢くなります。

次回予告|取得した ID で人口データを棒グラフ化する

ここまでで「目的の statsDataId を Claude に探させる」が完了しました。次回 Part 3 では、取得した ID を使って getStatsData を呼び、47 都道府県の人口データを D3.js で棒グラフに変換する ところまでをやります。

  • /fetch-estat-data スキルの設計
  • レスポンス JSON から都道府県別配列を作る整形ロジック
  • D3.js での横棒グラフ実装(SVG 出力)
  • 棒グラフを記事に貼るための画像化(puppeteer / sharp)

Claude Code を「データの検索」だけでなく「可視化までの一気通貫」に育てていく流れを共有します。引き続きお付き合いください。

関連ランキング・記事