MENU

Claude Codeと共に書類検索システムをExcelでつくる — 踏んだ地雷とその直し方

算数の単元設定シート。統合前は自由記述の単元名が30件近く並んでいた

幼なじみに依頼されて数年前に作った、小学校の指導案アーカイブを検索する共有Excelツール「DocSearch」。
バラバラに登録されていた「単元名」を、教科ごとの正規カリキュラム一覧に統合する作業を、2026-09-12から2日間、Claude Codeと一緒に進めました。
実は、紙ファイルをスキャンした700件くらいのファイルたち。これを、学年、教科、単元名、実施年にリネームする作業を前日にやっていました。もちろんClaude Codeが。
リネームを完了した後に、Excelツール「DocSearch」に登録していきます。もちろんClaude Codeが。
ところが、別のエージェントがOSクラッシュ(BSOD)を引き起こす。もちろんClaude Codeが。
そこから再開し、ゴールした記録。
算数・体育・社会・理科の4教科、合計456件。
Excel COM自動化とPDFテキスト抽出で、何度も足を取られながら、最終的に4教科すべて未解決件数0にたどり着きました。

この記事は、そのセッションログをもとに書き起こしたものです。何が一次情報で、どこにClaude Codeの要約・演出が入っているかは、末尾の「事実と要約を、切り分ける」に明記しました。

もくじ

「前回の続き」が、そもそも存在しなかった

作業を再開したセッションは、私のこんな一言から始まった。

前回の作業中に「デバイスの問題が起きました」と表示され、パソコンがブラックアウトし再起動しました。一太郎ファイルのpdf化を試していたところでした。続けてください。

だが、リポジトリのどこにも一太郎(.jtd)ファイルをPDF化した形跡がない。それらしい作業ログも見当たらない。数分後、勘違いに気づいて訂正を入れた。

間違えました

私は同時に複数のことができないタイプ。なのに、複数エージェントを動かすと、こうやってクラッシュさせたエージェントを間違えて、責めてしまいます。
人間なら平謝り。だけど、AIには謝りたくない。

実際に中断していたのは、算数の指導案176件分——単元名統合レビューファイル(docsearch_sansu_final_review.csv)を作った直後。私が目視チェックに入る、その手前だった。

長時間・複数セッションをまたぐ作業では、「前回何をしていたか」という記憶は、自分の中でも揺らぐ。Claude Codeは私の言葉を鵜呑みにせず、git status、直近に変更されたファイルのタイムスタンプ、途中生成物の中身から状況を再構成しました。実際の状態とすり合わせてくれました。おかげで、存在しない作業を続けようとする無駄を避けられました。

算数176件を、プルダウン一枚に仕分ける

算数の単元名統合は、文字列の完全一致、装飾記号(「」『』)の除去、引用抽出、並び替え一致、OCR再検索——Claude Codeがこの機械的な手法を組み合わせ、176件中142件をすでに自動で片付けていた(自動判定ロジックは前回セッションからの引き継ぎ)。残る34件は、単元名が「その他」のまま放置、あるいは学校の正規カリキュラム一覧に該当項目なし。人の判断が必要な行だった。

Claude Codeが採った方法は、34件を1件ずつチャットで確認、ではありませんでした。学年ごとのプルダウン付きExcelを1枚作り、私に渡しました。行番号・学年・現在の単元名・割当単元名(その学年の正規カリキュラムだけが選べるプルダウン)を並べた一覧です。ファイル名の列にはハイパーリンクも張ってありました。

算数のレビュー用Excel。割当単元名の列がプルダウンになっている

私は34件をチェックし、うち13件は学年そのものを修正して返しました。「かさ」の単元は3年ではなく2年。「垂直・平行と四角形」は5年ではなく4年。指導要領改定にともなう、カリキュラムの学年移動が理由でした。

例外が1件。「文字と式」という単元は本来6年の内容だが、ファイル名には誤って「4年」と記載されていた。

指導案の作成ミスで、721「文字と式」は6年での実施指導案でした。しかし、タイトルには4年と書いてあります。実際は6年なので、ファイル名を「6年」に変更して下さい

私がそう伝えると、Claude Codeは登録データを直すだけでなく、実ファイルの物理的なリネームまで実行しました。ハイパーリンクも張り直しました。

単元設定シートの側も様変わりした。学校側の候補一覧は、算数だけでも「算数1年」で30件、「算数6年」で31件と、自由記述のバリエーションで膨れ上がっていたが、統合後はそれぞれ21件・12件の正規リストに整理されました。

算数の単元設定シート。統合前は自由記述の単元名が30件近く並んでいた

統合後は正規カリキュラムの21件に整理された

この作業でClaude Codeが得た教訓——「その他」に分類する前に、その学年の正規カリキュラムに本当に「その他」という選択肢があるか確認すること。算数1年の候補一覧には、そもそも「その他」という項目が存在しなかった。存在しない選択肢を機械的に割り当てていたら、気づかれないまま残っていたかもしれない。

Excel COM自動化で、次々地雷を踏む

このプロジェクトには、openpyxlでの複数回保存がExcelのActiveXコントロール(検索シートのボタンなど)を過去に壊した、という前科がある。書き込みは全てwin32com経由のExcel COM自動化で——その方針をClaude Codeに引き継いでもらった。妥当な選択だったが、作業の中で何度も足を取られることになる。

同名ファイルを開くと、片方が黙って消える

学校の原本(S:\指導案検索\DocSearch_1.1.0.xlsm)と作業用コピー(D:\...\指導案検索\DocSearch_1.1.0.xlsm)。Claude Codeがこの2つを同時に開いて内容を比較しようとしました。パスは違う。なのに、ファイル名が同一というだけで、同一のExcel.Applicationインスタンス内では2つ目のWorkbooks.Openが静かにNoneを返してくる。

AttributeError: 'NoneType' object has no attribute 'Names'

単体で開き直すデバッグコード(_tmp_debug_open.py)を書いて、ようやく原因に気づいたそうです。対処はシンプルでした——原本用と作業用で、Excel.Applicationのインスタンスを2つ立てるだけです。

範囲を0件に縮めたら、ヘッダーごと巻き込む

教科×学年ごとの名前付き範囲(例:「社会1年」)を、実際に使われている単元名だけに再構成する。Claude Codeがそのスクリプトを書いていました。社会1年・2年は該当ファイルが0件で、候補一覧も当然0件になるはずだった。だが縮小後の範囲アドレスを確認すると、意図と違う。

# 縮小前(意図した状態)
社会1年: =単元設定!$O$7

# 縮小後(実際に起きたこと)
社会1年: =単元設定!$O$6:$O$7

原因はfirst_row + len(merged) - 1が0件のときfirst_row未満になり、Excelがアドレスを昇順に自動修正——その結果、1行上のヘッダーラベル(セル自体は「社会1年」という文字列)を範囲に巻き込んでいた。データは壊れていない。だが名前付き範囲の定義自体は、壊れている。

対処はnew_row_count = max(len(merged), 1)で最低1行を確保すること。加えてClaude Codeは、全ての名前付き範囲を対象に「先頭セルの値が名前自体と一致していないか」を機械的にスキャンする検証コードを書き、理科・体育・社会の統合すべてでこの検証を通しました。

列を消すと、名前だけが#REF!で残る

単元設定シートから、中身が空だった「家庭1〜4年」「生活3〜6年」の列を、Claude Codeが削除しました(家庭科は5・6年のみ、生活科は1・2年のみ実施のため、そもそも空。削除前に、この組み合わせの登録が0件であることは確認済み)。列自体はExcelが正しく詰めてくれる。だが、削除された列を参照していた名前定義は自動では消えず、壊れた参照だけが残る。

生活3年 (should be gone): =単元設定!#REF!
家庭1年 (should be gone): =単元設定!#REF!

該当する8つの名前を、明示的に.Delete()して掃除しました。

中点ひとつで、コンソールが黙って落ちる

ファイル名に含まれる中点「·」(著者名の区切りに使われていた)を、そのままprint()。cp932コンソールでエンコードできず、スクリプトが例外終了した。

UnicodeEncodeError: 'cp932' codec can't encode character '\xb7' in position 20: illegal multibyte sequence

厄介なのは、この例外が飛んだ時点で物理ファイルのリネーム(os.rename)は既に成功していたこと。ファイルは新しい名前になっているのに、Excel側のセルとハイパーリンクは古い名前のまま——中途半端な状態がその場に生まれた。

幸い、リネーム処理自体は「新ファイル名が既に存在すればスキップする」という冪等な作り。sys.stdoutをUTF-8ラッパーで包み直して再実行するだけで、安全に復旧できました。

Excel.exeだけが、裏でしぶとく生き残る

スクリプトが例外で落ちても、DispatchExで立てたExcel.exeプロセスは終了せず、バックグラウンドに残ることがある。次のスクリプトが同じファイルを開いて原因不明の失敗をする前に——tasklistで確認、必要ならtaskkillで片付ける。これをルーティンにしました。

地雷はどれも、静かに壊れる

いずれも派手にエラーになるバグではない。沈黙のNone、範囲の1行ズレ、取り残された参照、部分的に成功した副作用——どれも一見動いているように見えて、後から気づきにくい。だから構造を変える操作(列削除・範囲の再構成)の前には、毎回xlsmのタイムスタンプ付きバックアップ。直後に検証スクリプト。この2つを習慣にした。このセッションだけで、バックアップは13回。

「ファイル名だけで判断していないか?」

体育・社会は、算数と単元名の性質が違う。この2教科の学習指導要領は、具体的な単元タイトルではなく「ゲーム」「ボール運動」「器械運動」のような大分類名を内容項目に定めている。Claude Codeは自動判定と学習指導要領の知識で、体育109件・社会90件の大半を分類。それでも体育7件・社会3件が、どうしても残った。

残り10件の一覧を見て、私から指摘を入れました。

これら(121、336,337,141)は、現ファイル名を手がかりにしていませんか?

ocrの結果を読むと、割り当てられるものが複数あります。

実際その通りだった。「運動遊び」「トンパンくるん」「ナイスコンビネーションプレー」——ファイル名だけを見て「情報が足りず判断不能」。Claude Codeはそう結論づけていたが、それはファイル名という一段階圧縮された情報しか見ていなかったから。PDFの中身そのものは、読んでいなかった。

そこでClaude Codeは、該当PDFの1ページ目をフルページでOCRし直す。

=== 4年_体育_トンパンくるん_200x_※※.pdf (フルページOCR) ===
体育科学習指導案
学年・学級 4年2組
1単元
「トン!パン!くるん!」(器械運動:台上前転)
2授業づくりについて
「体ほぐしの運動」で他者との直接的な身体接触の中で...

「トンパンくるん」は意味不明な文字列ではなく、跳び箱の台上前転を擬音語で表現した単元の愛称……だった。同様に「ナイスコンビネーションプレー」はゴール型ゲーム「ラインポートボール」という球技の単元名であり、「運動遊び」という素っ気ないファイル名の裏には「やさしく、だいじに、たまごをすくえ!(多様な動きを作る運動遊び)」という具体的な単元があった。

もう一段、深い発見もあった。研究授業提案用のPDFには、1ページ目が教育論の総論から始まり、実際の単元名がその奥に埋もれているものがある。今回のOCRパイプライン(extract_text.py)は「1ページ目先頭150文字」だけを保持する設計。この種のPDFは、構造的に真の単元名を取りこぼす。

=== 全学年_体育_低学年における子どもが夢中になる体育科の教材づくりについて.pdf (先頭150文字) ===
低学年における子どもが夢中になる体育科の教材づくりについて
本来,バスケットボールやサッカーといった体育科における教材の元となる「素材」は,学校教育の場
で子どもたちに教え,学ばせるものといった目的で生み出されたものではない。歴史的・社会的に創造さ

フルページOCRで先まで読むと、正体は2年生「ならびっこフットベースボール」というボールゲーム単元の指導案だった。学年も、ファイル名も、単元名も——すべて書き換えることになった。

ここでClaude Codeが得た教訓——要約・トリミングされたテキストで「判定不能」と結論を出す前に、フルコンテンツに立ち返る経路を残しておくこと。150文字という制約は速度のためのトレードオフであって、正しさの保証ではない。判定不能なケースが少数まで絞れれば、全文OCRのコストは十分現実的だった。

指導要領を読みに行ったら、文字化けしていた

体育・社会の続きで、「サッカーやラグビーなど、球技の名前が入っているものは『ボール運動』となります」——この手がかりを私が伝えると、Claude Codeは学習指導要領そのものを読みに行くことになった。だがリポジトリ内の指導要領PDF(S:\2018\指導要領\sidouyouryou .pdf、336ページ)は、キーワード検索をかけても「ゲーム」も「ボール運動」も1件もヒットしない。抽出した本文を見て、理由がわかった。

ࠃ ޠ
ƞƞƸơ©߰
টƺ܂٨©ಔ঱ƺಔҌơૼ
ϪƍƜ༺ƏƉƞ¨

埋め込みフォントのエンコーディングが壊れている。バイト数は十分あるのに、意味のある文字列として読めない——いわゆる文字化けだった。Claude Codeは最初PaddleOCRで該当ページを画像化して読む方針に切り替え、体育・社会の該当ページ(合計32ページ)のOCRをバックグラウンドで走らせました。1ページあたり1分半ほどです。

OCRが7ページ進んだところで、私から助け船を出しました。

文字データが埋め込まれている資料が見つかりました。これを処理できるなら、こちらを使って下さい

文部科学省サイトで配布されている、同一告示の別配布PDFへのリンクとともに。実行中のOCRジョブを止めて取得したこのPDFは、埋め込みテキストが正常に読める版だった。同じ文書でも、配布元・生成経路によってテキスト層の健全性はまったく違う——ここでClaude Codeはそれを学んだ。

目次のページ番号と実際のPDFページ番号には、オフセット(+2)がある。それを突き止めてから、Claude Codeは体育・社会だけでなく、国語・算数・理科・生活・音楽・図画工作・家庭・外国語まで、全10教科分の本文をテキスト化して保存しました。いま使う予定のない教科の分まで抽出しておいたのは、次に同じ壁にぶつからないための備え。実際、この後の理科の統合では、この保存済みテキストがそのまま役に立った。

理科は、お手本からして空っぽだった

算数・体育・社会は、学校側の「正規カリキュラム一覧」がそれなりに充実していた。統合先の目標にできた。理科はまったく違う。原本を確認すると、こんな状態だった。

学年学校の原本の項目数実際に使われている単元名の種類数
3年3件19件
4年1件27件
5年0件22件
6年4件21件

お手本が空では、統合しようがない。そこでClaude Codeは学習指導要領の理科の内容(「風とゴムの力の働き」「電流の働き」「てこの規則性」など)から、学年別の正規リストをゼロから組み立て直し、統合先としました。前段で保存しておいた指導要領のテキストが、ここで生きた。

81件中19件は自動判定、59件は単元名とファイル名から目視で判断。「音の性質」は「光と音の性質」に、「ものの温度とかさ」は「金属,水,空気と温度」に、「メダカのたんじょう」は「動物の誕生」に——理科は単元名が実験・観察の対象をそのまま表していることが多く、体育・社会より機械的な対応付けがしやすかった。

1件だけ、学年の疑義が残った。5年の指導案に「てこのつり合い」という単元。だが現行の学習指導要領で「てこの規則性」は6年の内容にあたる。Claude Codeは学年を独断で書き換えず、私に確認を求めてきました。答えはこうだ。

439はファイル名はそのままで、単元割り当てを6年とする

この一言で決着。ファイル名の年度と登録学年に矛盾があっても、どちらを正とするかは、現場の先生にしか判断できない。

もう一つ、副産物があった。体育の正規リストを見直していたとき、学校の登録名「機械・器具を使っての運動遊び」が、文部科学省の正式表記「器械・器具を使っての運動遊び」と一字違い(機械/器械)であることにClaude Codeが気づいた。どちらが辞書的に正しいかより、学校側の実際の登録表記に揃えるのが検索ツールとしての正解——揃っていなかった8件を、学校側の「機械」表記へ修正しました。

体育のレビュー用Excel。1・2年は「運動遊び」の語尾を含む単元区分が並ぶ

内訳を、数字で見る

体育・社会・理科の3教科は、このセッション内で分類の全工程を観測できた。その内訳を数値でまとめておく(算数は前セッションからの引き継ぎ分を含むため対象外)。

教科件数自動判定目視判断(指導要領を読んで)ユーザーに確認・例外対応
体育109件52件50件7件
社会90件17件61件12件
理科81件19件59件3件

3教科に共通するのは、機械的な自動判定でカバーできたのは全体の2〜3割どまり、ということ。残りの大半は、指導要領という一次資料を読んで判断する必要があった。私への確認が必要だった件数は少数。だが、その少数こそが学年の誤り・データの欠落・カリキュラム改定の痕跡など、質的に重い判断を含んでいた。

作業を「知識」に変えて閉じる

作業は、理科の統合で一区切り。国語・生活・音楽・図画工作・家庭・外国語の6教科は、手つかずのまま残っている。ここで私から最後の指示を出しました。

claude.md や skill に記述した方がよい内容を精査し、記述してください

2日間で踏んだ地雷と学んだ手順を、次回の自分(あるいは別のセッション)がゼロから再発見しなくて済むように——Claude Codeに書き残してもらう作業。3つに分けた。

  • CLAUDE.md — Excel COM自動化の落とし穴、PDFテキスト抽出の注意点(文字化けの検出方法・OCRの150文字制限)、原本(S:)と作業用コピー(D:)の役割分担など、このリポジトリで今後どんな作業をするにも効いてくる技術的な知識。
  • .claude/commands/consolidate-units.md — 残り6教科にそのまま使い回せる統合手順を、実施済みの4教科での経験からスキル化したもの。「原本が空ならどうするか」「学年に疑義があるときは確認する」といった判断基準も含めた。
  • docsearch_youryou_*.txt — 全10教科分の学習指導要領本文。文字化けPDFから正常なPDFへ乗り換えた経緯ごと記録し、次は同じ壁にぶつからないようにした。

最後の点検でも、収穫があった。git addの前にgit statusを確認すると、指導案の実データが入ったフォルダが丸ごと未追跡。中身を確認すると、971MB・829件のPDFだった。このリポジトリの.gitignoreには、既に類似のデータフォルダを除外する慣習がある。それに倣ってこのフォルダも追加し、コミットしました。git addの前に一度statusを見る——その基本動作をClaude Codeが徹底してくれたおかげで、リポジトリが1GB近く膨らむ事故を防げた。

まとめ

このセッションを通じて、Claude Codeが繰り返し実践していたパターンは、次の5つです。

  1. 「わからない」と結論を出す前に、要約ではなく原文に戻れる経路を作る。 150文字OCRやファイル名だけでは、圧縮された情報からもう一度答えを絞り出そうとしているに過ぎない。判定不能の件数が少数まで絞れたら、フルページ抽出・フルOCRのコストは十分見合う。
  2. 構造を変える操作の前には必ずバックアップ、直後には必ず機械的な検証を。 「範囲を0件に縮めたらヘッダーを巻き込んだ」ような事故は、目視では気づきにくい。全件スキャンする検証コードを書くコストは、壊れたExcelファイルを配布し直すコストよりずっと低い。
  3. 同じに見えて違うものに注意する。 ファイル名が同一なだけで別インスタンスが必要になったExcel COMも、告示は同じでも配布元によってテキスト層の健全性が違った指導要領PDFも、根は同じ「同一に見えて挙動が違う」問題だった。
  4. データの矛盾は、判断できる人に判断してもらう。 学年とファイル名が食い違うケースを何度も見たが、その都度推測で押し切らず、確認を挟んだ。指導要領改定によるカリキュラムの学年移動と、単なる登録ミスの両方が混在しており、機械的な区別はつかなかった。
  5. 今日の副産物は、明日のショートカットにする。 10教科分の指導要領テキストは、今回使ったのは2〜3教科分だけ。それでも全部抽出して残したのは、次にこの種の作業をする自分(または別のセッション)への最短経路を用意しておくため。CLAUDE.mdとスキルファイルも同じ動機で書いた。

事実と要約を、切り分ける

この記事について、何が一次情報から直接確認できる事実で、どこにClaude Codeによる要約・言い換え・演出が入っているかをここに明記しておく。

事実(セッションログ・出力から直接確認できる)

  • 本文中の引用(「」で囲んだ発言)は、私が実際にセッション内で送信したメッセージからの逐語引用(誤字・句読点含め原文ママ)。要約や意訳はしていない。
  • 件数・数値(算数176件、体育109件、社会90件、理科81件=合計456件、自動判定/目視判断/確認の内訳など)は、セッション中に実行したスクリプトの出力、またはCSV・Excelの中身から得た実測値。
  • エラーメッセージ(AttributeErrorUnicodeEncodeErrorなど)は、実際にコンソールへ出力されたトレースバックをそのまま転記したもの。
  • 壊れた名前付き範囲のアドレス、文字化けした指導要領PDFの抜粋、OCR結果の引用は、実際にwin32com・PyMuPDF・PaddleOCRから得られた値・出力をそのまま転記したもの。加工はしていない。
  • 「1ページあたり1分半ほど」「OCRが7ページ進んだところで」は、生成されたファイルのタイムスタンプ・サイズの推移から確認した実測値。「971MB・829件のPDF」「13回バックアップ」は、実際にコマンドを実行して数えた件数。
  • 添付画像4枚は、実際のExcelファイルからその場でキャプチャしたもので、生成・合成した架空の画像ではない。個人名(教師の氏名)が写り込んでいた列は、撮影前に除外している。

要約・演出(Claude Codeによるもの)

  • 記事の章立て・見出し・「地雷」という比喩表現は、実際の作業の流れを後から整理して名付けたもので、セッション中にそう呼んでいたわけではない。
  • 感情・評価を含む形容は、Claude Codeの文章表現であり、ログに出てきた言葉そのものではない。
  • 各節末の「教訓」「まとめ」は、起きた出来事からClaude Codeが一般化して書き足したもの。出来事自体は事実だが、そこから導く言葉選びは要約にあたる。
  • コードブロックの一部は、実際に実行したスクリプトの該当箇所を抜粋・簡略化したもので、完全なスクリプト全文ではない。OCR全文引用の一部は、長さの都合で中略している。

公開にあたって外した推測

理科の「てこのつり合い」の学年疑義について、下書き段階では「旧課程では5年で扱われていた単元だと考えられる」という背景説明があったが、これはClaude Codeの一般知識に基づく推測であり、セッション内で当時の学習指導要領を確認して裏を取ったものではなかった。そのため本文からは外している。実際に起きたことは、学年の疑義をClaude Codeが独断で判断せず私に確認を求め、「6年に割り当てる」という回答を得て解決した、という部分だけである。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントを残す

もくじ