前回(第10回)では、図面を社外に出さない「ローカル完結モード」と「外部AIモード」を設定画面で切り替え、APIキーをOSの資格情報ストアに保存できるようにしました。
最終回の第11回は、Pythonで作ったツールをPyInstallerでexe化し、Pythonを入れていない担当者のPCへ配布するための仕上げと、連載全体のまとめです。ポイントは次の3つです。
- 同梱ファイル(部屋名辞書など)の場所を、exeでもソース実行でも同じ関数で解決する
- Tesseract OCR本体はexeに含めず「インストールしてもらう」前提にし、場所を設定画面で指定できるようにする
- exeを配って終わりにせず、精度の限界と最終確認のルールをセットで渡す
このリポジトリで確認できる範囲
PyInstallerはクロスコンパイル(別のOS向けの実行ファイルを作ること)ができません。公式のREADMEにも「Windowsのアプリを作るにはWindowsで動かす」とあります。開発環境はLinuxのため、確認できたのはLinux向けにビルドした実行ファイルの起動と動作までです。
📰 出典:PyInstaller README(v6.19.0)
仕組みの説明:exeは起動のたびに一時フォルダへ展開される
PyInstallerは、スクリプトとライブラリ・Python本体を1つのフォルダ(onedir)か1つの実行ファイル(onefile)にまとめます。今回は配りやすいonefileです。onefileのexeは、起動のたびに中身を一時フォルダ(_MEIxxxxxx)へ展開してから動きます。公式ドキュメントには、そのぶん起動が少し遅いこと、強制終了すると一時フォルダが残ること、問題を調べるならonedirの方が簡単なことが書かれています。
📰 出典:PyInstaller「What PyInstaller Does and How It Does It」(v6.19.0)
そこで問題になるのが、app/config/rooms.yaml(部屋名辞書)のような同梱ファイルの場所です。exeでは展開先が sys._MEIPASS に入り、sys.frozen も設定されます。「exeなら _MEIPASS、ソース実行ならプロジェクトのフォルダ」を返す関数を1か所に用意しました。
📰 出典:PyInstaller「Run-time Information」(v6.19.0)
実装
ファイル構成(第11回時点の差分)
app/
├ __init__.py … 変更:版(__version__ = "1.0.0")。画面タイトル・exeのバージョン情報に使う
├ main.py … 変更:python app/main.py で直接起動できるよう検索パスを修正
├ core/
│ ├ resources.py … 新規:同梱ファイルの場所(_MEIPASS)とTesseractの場所
│ ├ ocr_local.py … 変更:Tesseractのフルパスを設定できるように
│ ├ settings.py … 変更:設定に tesseract_path を追加
│ ├ room_matcher.py / mapping.py … 変更:YAMLの場所を resources.py で解決
└ ui/
├ settings_dialog.py … 変更:「ローカルOCR(Tesseractの場所)」欄
└ main_window.py … 変更:起動時・設定変更時にTesseractを探して設定、タイトルに版
build/madori_ocr.spec … 新規:PyInstallerのビルド設定
tools/make_icon.py / make_release.py / capture_frozen.py … 新規:アイコン・配布物の作成、起動確認
tests/test_resources.py … 新規25件(test_settings.py にも5件追加)
requirements-dev.txt … 新規:pyinstaller==6.19.0 ほか
同梱ファイルの場所を1つの関数で決める(app/core/resources.py)
# app/core/resources.py(抜粋)
PROJECT_ROOT = Path(__file__).resolve().parents[2]
ROOMS_YAML = ("app", "config", "rooms.yaml")
def is_frozen(frozen: bool | None = None, meipass: str | None = None) -> bool:
"""PyInstallerでまとめたexeとして動いているか(`sys.frozen` と `sys._MEIPASS` の両方があるか)。"""
is_bundled = getattr(sys, "frozen", False) if frozen is None else frozen
bundle = getattr(sys, "_MEIPASS", None) if meipass is None else meipass
return bool(is_bundled) and bool(bundle)
def bundle_dir(frozen: bool | None = None, meipass: str | None = None) -> Path:
if is_frozen(frozen, meipass):
bundle = getattr(sys, "_MEIPASS", "") if meipass is None else meipass
return Path(bundle)
return PROJECT_ROOT
def resource_path(*parts: str, frozen: bool | None = None, meipass: str | None = None) -> Path:
if not parts:
raise ValueError("ファイル名を指定してください。")
for part in parts:
if not part or Path(part).is_absolute() or ".." in Path(part).parts:
raise ValueError(f"同梱ファイルの指定が不正です: {part!r}")
return bundle_dir(frozen, meipass).joinpath(*parts)
引数で差し替えられるので、exe化しなくてもpytestで「exeとして動いている場合」を確かめられます。なお一時フォルダは終了時に消えるため、利用者が編集するファイルや出力を展開先に置いてはいけません(設定は第10回から %APPDATA%\MadoriOCR に保存しています)。
Tesseractは「インストールしてもらう」前提にし、場所を指定できるようにする
Tesseract OCR本体と日本語データはPythonのパッケージではないため、PyInstallerは同梱しません。埋め込み文字のある図面はTesseractなしで読めること、同梱するとTesseractの更新のたびにexeを配り直すことになることから、利用者のPCにインストールしてもらう前提にしました。ただ、インストールしてもPATH(コマンドを探す場所の一覧)に入らないことがあるため、次の順に探して pytesseract に設定します。
# app/core/resources.py(抜粋)
def find_tesseract(configured: str = "", *, platform=None, environ=None, app_directory=None,
which=shutil.which, is_file=Path.is_file) -> TesseractLocation:
exe = tesseract_exe_name(platform) # Windowsは tesseract.exe
value = validate_tesseract_path(configured)
if value: # 1. 設定画面で指定した場所(無ければほかは探さない)
candidate = Path(value)
for path in (candidate, candidate / exe):
if is_file(path):
return TesseractLocation(path, TESSERACT_FROM_SETTINGS)
return TesseractLocation(None, None, missing_configured=value)
base = app_dir() if app_directory is None else app_directory
bundled = base / APP_DIR_TESSERACT_FOLDER / exe # 2. exeと同じフォルダの tesseract\
if is_file(bundled):
return TesseractLocation(bundled, TESSERACT_FROM_APP_DIR)
found = which("tesseract") # 3. PATH
if found:
return TesseractLocation(Path(found), TESSERACT_FROM_PATH)
for candidate in standard_tesseract_paths(platform, environ): # 4. C:\Program Files\Tesseract-OCR など
if is_file(candidate):
return TesseractLocation(candidate, TESSERACT_FROM_STANDARD)
return TesseractLocation(None, None)
# app/core/ocr_local.py(抜粋)
def configure_tesseract(path: str | Path | None) -> None:
pytesseract.pytesseract.tesseract_cmd = str(path) if path else DEFAULT_TESSERACT_CMD
設定した場所に無いときは、PATH上の別のTesseractを黙って使わず「見つかりません」と表示します(精度の違いの原因を追えなくなるため)。Tesseractが無いPCで埋め込み文字の無いページを読んだときも、「0件」だけでなく理由と設定方法を表示します。Windows用インストーラーは、Tesseractの公式ドキュメントでUB Mannheim(マンハイム大学図書館)作成のものが案内されています。
📰 出典:Tesseract ドキュメント「Installation」
ビルド設定(build/madori_ocr.spec)
specファイルはPythonとして実行される設定ファイルです。
# build/madori_ocr.spec(抜粋)
ROOT = Path(SPECPATH).resolve().parent
APP_NAME = "MadoriOCR"
# 版は app/__init__.py の __version__ だけを正とし、画面のタイトルとexeのバージョン情報をそろえる
_match = re.search(r'^__version__ = "(\d+)\.(\d+)\.(\d+)"', (ROOT / "app" / "__init__.py").read_text(encoding="utf-8"), re.M)
VERSION = tuple(int(n) for n in _match.groups()) + (0,)
datas = [
(str(ROOT / "app" / "config" / "rooms.yaml"), "app/config"),
(str(ROOT / "app" / "config" / "mapping.example.yaml"), "app/config"),
]
a = Analysis(
[str(ROOT / "app" / "main.py")],
pathex=[str(ROOT)],
datas=datas,
excludes=["pytest", "ruff", "_pytest"],
)
pyz = PYZ(a.pure)
version_info = make_version_info() if sys.platform == "win32" else None
exe = EXE(
pyz, a.scripts, a.binaries, a.datas, [],
name=APP_NAME,
upx=False, # 実行ファイルの圧縮(UPX)は使わない
console=False, # --windowed 相当:黒いコンソール画面を出さない
icon=[str(icon_file)] if icon_file.is_file() else None,
version=version_info,
)
console=False:--windowedと同じ。コンソール画面が出ない代わりにsys.stdout・sys.stderrがNoneになる、と公式ドキュメントで注意されています(Linuxでは無視されます)upx=False:UPXで圧縮するとDLLが壊れることがある、と公式ドキュメントにあるため使いません- バージョン情報:エクスプローラーのプロパティに出る版を
app/__init__.pyから作ります。作成に使う部品がWindowsでだけ入るpefileを読み込むため、LinuxではModuleNotFoundErrorで止まりました。Windowsのときだけ作るようにしています
📰 出典:PyInstaller「Using PyInstaller」(v6.19.0)
📰 出典:PyInstaller「Common Issues and Pitfalls」(v6.19.0)
keyring(第10回)の資格情報マネージャー用の部品は自動解析では見つかりませんが、PyInstaller 6.19.0同梱の設定(フック)が集めることをソースで確認しました。
Windowsでのビルド手順
REM Windows(コマンドプロンプト。プロジェクトのルートで)
py -3.13 -m venv .venv-build
.venv-build\Scripts\activate
pip install -r requirements-dev.txt
python tools\make_icon.py
pyinstaller --noconfirm --clean build\madori_ocr.spec
python tools\make_release.py
# requirements-dev.txt(抜粋)
-r requirements.txt
pyinstaller==6.19.0 # 2026-02-14公開
pyinstaller-hooks-contrib==2026.1 # 2026-02-18公開。各ライブラリ用の設定集
tools\make_release.py は、exe・利用者向けの説明・転記の設定ファイルの例・サンプル・ライブラリのライセンス文・exeのハッシュ値(SHA-256)をまとめた配布フォルダとzipを作ります。
動作確認の方法
pytest は382件すべて成功しました(第10回までの352件に30件追加)。ruff check . もエラーはありません。_MEIPASS の有無、Tesseractを探す順番、第10回の版で保存した設定ファイルがそのまま読めることなどを確かめています。Linuxでは同じspecでビルドしました(Python 3.12)。
| 確認したこと | 結果(Linux・4vCPU) |
|---|---|
| ビルド | 約39秒、1ファイル約58MB |
| 同梱ファイル | 展開先に rooms.yaml などがあり、終了後に一時フォルダが消える |
| 起動 | Xvfb(仮想ディスプレイ)上で3回起動し、ウィンドウ表示まで1.6〜1.7秒 |
| コア処理 | 同じ設定の確認用ビルドで第9回の架空サンプル8件を一括処理し、ソース実行と同じ結果 |
次は、ビルドしたLinux版の「設定…」画面です(開発環境(Linux)での確認画面です。Windows実機では見た目が異なります)。

なお、これまで案内していた python app/main.py が検索パスの都合で失敗することが分かり、修正しました(python -m app.main も使えます)。
確認できていないこと
- Windows実機でのビルド・起動時間・アイコンとバージョン情報の表示、Microsoft Defender等の反応
- Windows版Tesseractの自動検出、exeと同じフォルダに置いた場合の日本語データの読み込み
- Python 3.13系での動作(検証は3.11/3.12)
つまずきやすい点・配布時の注意
- 初回起動が遅い:展開のぶん待ち時間があり、ウイルス対策ソフトの検査でさらに延びることがあります。説明書に「十数秒かかることがある」と書いておくと二重起動を防げます
- 誤検知が起こりうる:一般に、署名の無い自作のexeはウイルス対策ソフトに疑われたり、Windowsの警告が出たりすることがあると言われます。誤検知はMicrosoftの提出窓口から報告できます。社内配布なら情シスに除外設定や配布方法を相談するのが確実です
- コード署名:署名すると「誰が作ったか」「改ざんされていないか」を確かめられるようになります。警告が必ず消えるわけではありませんが、社外へ配るなら検討の価値があります(証明書には費用と審査が必要)
- 更新版の配り方:版を
app/__init__.pyで上げると画面のタイトルに出ます。設定は%APPDATA%にあるのでexeを差し替えても残ります。版ごとのフォルダで配り、古い版も残しておくと戻せます
📰 出典:Microsoft「Submit a file for malware analysis」
ライセンス:社内で使うか、社外へ配るかで考え方が変わる
| 対象 | ライセンス | 配布時の考え方 |
|---|---|---|
| PyMuPDF | AGPL-3.0 またはArtifex社の商用ライセンス | exeを社外へ配布・販売する場合は、AGPLの条件(ソースコードの提供など)を満たすか、商用ライセンスを検討 |
| Tesseract | Apache-2.0 | 同梱する場合はライセンス文を配布物に含める |
| PyInstaller | GPL-2.0以降+例外 | 作ったアプリは自由なライセンスで配布できる |
PyMuPDFの公式ドキュメントでは、AGPLの条件を満たせない場合はArtifex社へ商用ライセンスを問い合わせるよう案内されています。社内だけで使うか、取引先へ配る・販売するかで必要な対応が変わります。個別の判断は弁護士などの専門家に確認してください。
📰 出典:Tesseract LICENSE
連載のまとめ:完成したツールでできること・できないこと
| 回 | 内容 |
|---|---|
| 第0回 | 要件整理とPython開発環境、tkinterの最初の画面 |
| 第1回 | PDFの図面ページを画面にプレビュー表示する |
| 第2回 | 図面に埋め込まれた文字を抽出し、ハイライトする |
| 第3回 | 部屋名らしき文字列を絞り込むルール |
| 第4回 | スキャン図面向けのローカルOCR |
| 第5回 | 外部AIの画像解析で精度を底上げする |
| 第6回 | 検出結果を人が確認・修正する画面 |
| 第7回 | 指定のExcelフォーマットの決まった欄へ転記する |
| 第8回 | 対応付けを設定ファイルで変えられるようにする |
| 第9回 | 複数のPDFをまとめて処理する |
| 第10回 | ローカル完結モードと外部AIモードの切り替え |
| 第11回 | exe化と配布、運用ルールの整理(この記事) |
できること:埋め込み文字・ローカルOCR・(許可時のみ)外部AIでの部屋名の読み取り、人による確認・修正、書式を残したExcelへの転記、様式ごとの対応付けの切り替え、フォルダ単位の一括処理、exeでの配布。
できないこと:手書き・縦書き・かすれの強い図面の安定した読み取り、部屋名以外(階・面積)の転記、欄が足りない場合の行追加、外部AIでの一括処理。実在の図面での精度は測っておらず、Windows実機での確認も残っています。
精度の限界と、最終確認の運用ルール
OCRも外部AIも誤読をゼロにはできません(第9回の低解像度の架空スキャンでも7部屋中1部屋を取りこぼしました)。「誤りは起きる」前提で運用を決めておきます。
- 転記前に、確認画面で図面と見比べて1件ずつ確認する
- 一括処理の「成功」は機械的な確認に通っただけ。出力したExcelを開いて確認する
- 「要確認」の件数と理由を記録し、辞書(rooms.yaml)の追加や運用の見直しに使う
- 誤転記が見つかったときの連絡先と、直し方(再実行か手修正か)を決めておく
発注者向けメモ
exeと一緒に「精度の限界」と「最終確認の運用ルール」を納品してもらう
exeだけでは「どの図面なら読めるか」「誰がどこまで確認するか」が決まりません。曖昧なままだと、誤転記のとき「不具合か運用の漏れか」で揉めがちです。次をセットで納品してもらうのがおすすめです。
- 自社の実際の図面で測った、読み取れた割合と「要確認」の割合
- 読めない図面の例と、そのときの手順
- 最終確認のルール(上の4項目のような内容)
- 配布手順(Tesseract・初回起動・更新版の差し替え)とライセンスの一覧
内製するか、外注するかの判断ポイント
| 観点 | 内製が向く | 外注が向く |
|---|---|---|
| 様式・図面の種類 | 少なく、変化もゆっくり | 多い、取引先ごとに違う |
| 配布範囲 | 社内の数人 | 社外・多拠点(署名・サポートが必要) |
| 体制 | Pythonを保守できる人が社内にいる | 担当者の異動・退職で止まるおそれがある |
最初は外注で作り、辞書・対応付けの追加は社内で行う分け方も現実的です。
保守で発生すること(契約前に範囲を決める)
- ライブラリの更新:不具合・脆弱性の修正のたびに、ビルドと動作確認が必要です
- Windowsの更新:大型更新後に、起動・ファイル選択・資格情報の保存を確認します
- AIモデルの提供終了:Anthropicの公式ドキュメントでは、非推奨になると後継モデルと提供終了日が示され、提供終了後のモデルへのリクエストは失敗すると説明されています。モデルIDは設定画面で変えられますが、後継モデルでの精度確認は必要です
📰 出典:Anthropic「Model deprecations」
- ☐ 精度の実測結果と最終確認のルールを納品物に含めたか
- ☐ 社外へ配る場合、PyMuPDFのライセンスの扱いを専門家に確認したか
- ☐ ライブラリ・Windows・AIモデルの更新への対応を、保守契約の範囲に入れたか
開発会社への質問例
- 「ウイルス対策ソフトに止められたとき、どう切り分けますか?」
- 「Tesseractは同梱ですか、インストール前提ですか?」
- 「自社の図面で精度を測った結果を、納品前に見せてもらえますか?」
まとめ
第11回では、PyInstaller 6.19.0のspecファイルを書き、同梱ファイルを sys._MEIPASS を考慮して解決し、Tesseractの場所を設定できるようにしました。Linux向けのビルドと起動は確認できましたが、Windows実機での確認は残っています。
全12回で、図面PDFの表示から読み取り・確認・転記・一括処理・配布までを作りました。AIやOCRは「確認を減らす道具」であって「確認をなくす道具」ではありません。ツールと運用ルールをセットで整えることが、長く使える自動化の近道です。連載にお付き合いいただき、ありがとうございました。
この連載の記事一覧
この記事は連載「CAD図面PDFをExcelへ自動転記するツール開発」の1回です。連載のほかの回は次のとおりです(連載の一覧ページ)。
- 【CAD図面PDFをExcelへ自動転記するツール開発 第0回】要件整理とPython開発環境、tkinterの最初の画面を作る
- 【CAD図面PDFをExcelへ自動転記するツール開発 第1回】PyMuPDFで図面PDFを画面にプレビュー表示する
- 【CAD図面PDFをExcelへ自動転記するツール開発 第2回】PyMuPDFで図面の文字を座標付きで抽出し、プレビューにハイライト表示する
- 【CAD図面PDFをExcelへ自動転記するツール開発 第3回】部屋名辞書とヒューリスティックで「部屋名らしき文字列」だけを絞り込む
- 【CAD図面PDFをExcelへ自動転記するツール開発 第4回】スキャン図面・画像PDFの部屋名をローカルOCR(Tesseract)で読み取る
- 【CAD図面PDFをExcelへ自動転記するツール開発 第5回】外部AI(Claude)の画像解析で図面の部屋名を読み取り、精度を底上げする
- 【CAD図面PDFをExcelへ自動転記するツール開発 第6回】AI・OCRの読み取り結果を人が確認・修正する画面を作る
- 【CAD図面PDFをExcelへ自動転記するツール開発 第7回】確認済みの部屋名を指定のExcelフォーマットの決まった欄へ転記する
- 【CAD図面PDFをExcelへ自動転記するツール開発 第8回】部屋名とExcelの欄の対応付けを設定ファイル(YAML)で変えられるようにする
- 【CAD図面PDFをExcelへ自動転記するツール開発 第9回】複数の図面PDFをフォルダごとまとめて処理する(進捗表示・中止・失敗しても止まらない一括処理)
- 【CAD図面PDFをExcelへ自動転記するツール開発 第10回】ローカル完結モードと外部AIモードを切り替え、APIキーをWindowsの資格情報マネージャーに保存する
- 【CAD図面PDFをExcelへ自動転記するツール開発 第11回(最終回)】PyInstallerでWindows向けexeにまとめて配布し、精度の限界と確認のルールを整理する(この記事)


コメント
コメント一覧 (1件)
[…] 次回(第11回・最終回)は「Windows向けにexe化して配布し、運用時の注意点を整理する」です。PyInstallerで単一のexeにまとめ、配布・初回起動時の注意点と、精度の限界を踏まえた最終チェック体制を整理します。 […]