書籍OCRパイプライン整備 - 蔵書DB構築を加速した一日
朝、手元の専門書PDFを1冊yomitokuに通したら、Markdownの出力が思った以上にきれいに出てきた。「これなら一気に複数冊いける」と手が動き始め、気づけばOCR変換、クリーンアップの並列化、ビューアのUI修正、スラッシュコマンド設計まで、book-knowledge-baseプロジェクトだけで丸一日が過ぎていた。
yomitokuで書籍PDFをMarkdown変換
yomitokuは日本語に特化したAI OCRツールで、PDFを入力するとページ単位でMarkdownを出力してくれる。今回は実務書・参考書を含む複数冊のPDFを変換し、SQLiteデータベースに格納した。
変換の流れはシンプルで、yomitokuでMarkdown生成 → Pythonスクリプトでページ分割 → SQLiteのchunksテーブルにINSERTするだけ。以前Gemini APIで組んだパイプラインと構造は同じだが、yomitokuは日本語のレイアウト認識(段組み、ルビ、図表キャプション)が安定していて、後工程のクリーンアップ量が目に見えて減った。
cleanup-bookコマンドの大幅改善
サブエージェント並列処理で高速化
これまでクリーンアップは1ページずつ逐次処理していた。200ページの書籍だと待ち時間だけで相当かかる。そこで10ページをひとまとめにしたバッチを5つ並列で走らせるようにした。
10ページ/バッチ x 5バッチ並列 = 50ページ同時処理
サブエージェントがバッチごとに独立してクリーンアップを実行し、結果をマージする構成。ページ間に依存がないからこそ成り立つ並列化で、体感の処理時間が大きく縮まった。
OCRインポート時の前処理パイプライン
yomitokuの出力をそのままDBに入れると、ジャンクテキスト、ページヘッダー、不揃いな図幅、バラバラなセクション区切りが残る。これらをインポート前に一括で片付けるPythonスクリプトを作った。
1回のスクリプト実行で以下を順番に処理する:
- ジャンク除去 - OCR由来のゴミ文字列を削除
- ヘッダー除去 - 繰り返し出現するページヘッダーを検出・削除
- 図幅統一 - Markdown内の画像タグの幅指定を統一
- セクション統合 - 連続ページにまたがるセクションを1つにまとめる
前処理を挟むことで、後段のAIクリーンアップに渡すテキストの品質が上がり、クリーンアップの精度と速度が両方改善した。
クリーンアップ履歴管理
cleanup-historyディレクトリを導入して、クリーンアップの実行ログを保存するようにした。どのページをいつクリーンアップしたか、差分はどうだったかを追跡できる。やり直しや品質確認のときに、履歴をさかのぼれるのは地味に助かる。
蔵書リストに進捗バッジ表示
蔵書一覧のUIに進捗バッジを追加した。書籍ごとに「済」「~100済」「途中」のラベルが付き、どの書籍のクリーンアップがどこまで進んでいるか一目で分かる。複数冊を並行して処理しているときに、次にどれを触るべきかの判断が速くなった。
ルール改善 - 空セクション判定の緩和
クリーンアップのルールで、空セクションの判定が厳しすぎて連続ページの統合が途中で切れる問題があった。判定条件を緩和し、セクション見出しだけのページでも次のページと統合されるように修正した。これで長い章が途中でぶつ切りにならなくなった。
ビューアUI改善
ページ遷移時のカクつき修正
ページを矢印キーで切り替えるとき、中カラムのスクロールがカクつく問題があった。原因を追ったら、scrollByのbehavior: 'smooth'がブラウザのアニメーションフレームと競合していた。smoothを外して即座にスクロールさせたところ、カクつきが消えた。
タイトル重複表示の解消
右カラムのコンテンツ表示で、書籍タイトルがヘッダーと本文の両方に表示されていた。右カラムからヘッダー表示を除去して、タイトルは左カラムの書籍名だけで把握する設計に統一した。
中カラムの表示整理
中カラムの章ラベルを削除し、item-nameの折り返し表示を有効にした。章ラベルがあると狭い中カラムを圧迫して、ページ番号の視認性が落ちていた。折り返し表示で長いセクション名も途切れずに読めるようになった。
PageContent.vueのMarkdown判定ロジック修正
OCR出力がHTMLなのかMarkdownなのかを判定するロジックに不具合があった。hasHtmlという変数名でMarkdownの有無を判定していたのを、hasMarkdownに改名して判定条件も修正した。変数名と実際の判定内容が一致していなかったのが混乱の元だった。
埋め込みスクリプトの外部ファイル化
yomitoku関連のドキュメント(yomitoku.md)内に埋め込まれていたPythonスクリプトを、独立したファイルに分離した。ドキュメント内にコードが埋まっていると、実行もメンテナンスもやりにくい。ファイルとして切り出すことで、エディタの補完やlintが効くようになった。
記事量産用スラッシュコマンドの設計
開発ログを書くためのスラッシュコマンドを2本設計した。
- design-articles - claude-code-syncのログから記事構成を設計する
- generate-articles - 設計した構成に基づいて記事本文を生成する
まだ設計段階で実装には至っていないが、日々の開発ログをもっと手軽に記事化する仕組みを整えたい。
今日の学び
scrollBy({ behavior: 'smooth' })は、短い間隔で連続呼び出しするとアニメーションが重なってカクつく。キーボード操作のような高頻度のトリガーでは、smoothを外して即時スクロールにしたほうが操作感が良い- OCRの後処理は「AI任せ」と「ルールベース前処理」を組み合わせると精度が跳ね上がる。ジャンク除去やヘッダー削除のような決定的な処理はPythonで先に片付け、文脈依存の整形だけAIに渡す、という分業が効いた
- 変数名と実際の判定内容がずれていると、後から読んだとき確実にハマる。
hasHtmlでMarkdownを判定していた件は、名前を直しただけでバグの所在が明らかになった
振り返り
丸一日をbook-knowledge-baseに集中投下した結果、OCR変換からクリーンアップまでのパイプラインが一本の線としてつながった。朝は1冊ずつ手動で変換していたのが、夕方には複数冊を並列でクリーンアップしながら進捗バッジで状況を確認できる状態まで来た。「仕組みを作る時間」に丸一日使ったが、明日以降の1冊あたりの処理時間を考えれば十分に元が取れる投資だった。