🐏
PDFからMarkdownへの自動変換アプリを作った話 - 新人エンジニアの個人開発体験記
PDFからMarkdownへの自動変換アプリを作った話 - 新人エンジニアの個人開発体験記
はじめに
新人エンジニアとして日々技術習得に励む中で、以下のような問題に直面していました。
- 技術資料や論文のPDFでテキスト選択ができない
- 画像化されたPDFの内容をメモアプリに転記したい
- PDF内の文章を検索可能な形式で保存したい
これらの課題を解決するため、PDFファイルをOCR処理してMarkdown形式に変換するWebアプリケーションを開発しました。本記事では、開発過程で得られた学びと技術的な実装内容について共有いたします。
システム概要
主な機能
- PDFファイルのアップロード: ドラッグ&ドロップ対応のWebインターフェース
- 高解像度画像変換: PDFページを300dpiのPNG画像に変換
- OCR処理: Google Gemini Flash APIを使用したテキスト抽出
- Markdown生成: 抽出テキストの構造化された出力
- 並列処理: 複数ページの同時処理による高速化
処理フロー
技術スタック
分野 | 技術 | 選定理由 |
---|---|---|
バックエンド | Python 3.7+ | 豊富なライブラリエコシステム |
Webフレームワーク | Flask 2.3.3 | 軽量でシンプルな構成 |
PDF処理 | PyMuPDF (fitz) 1.23.14 | 高性能な画像変換機能 |
画像処理 | Pillow 10.0.1 | Base64エンコーディング対応 |
OCR API | OpenRouter + Gemini Flash | 高精度な日本語認識 |
並列処理 | concurrent.futures | Python標準ライブラリ |
実装のポイント
PDF→画像変換の最適化
OCRの精度向上のため、高解像度での画像変換を実装しました。
def pdf_to_images(pdf_path, dpi=300):
"""PDFを高解像度画像に変換"""
doc = fitz.open(pdf_path)
image_paths = []
for page_num in range(len(doc)):
page = doc.load_page(page_num)
# 高解像度マトリックス設定
mat = fitz.Matrix(dpi/72, dpi/72)
pix = page.get_pixmap(matrix=mat)
# PNG形式で保存(品質重視)
image_path = f"temp_page_{page_num}.png"
pix.save(image_path)
image_paths.append(image_path)
doc.close()
return image_paths
並列処理による性能改善
複数ページの同時処理により、処理時間を大幅に短縮しました。
def process_pages_parallel(image_files, max_workers=5):
"""並列でOCR処理を実行"""
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
future_to_page_index = {
executor.submit(self.extract_text, image_path): i
for i, image_path in enumerate(image_files)
}
# 結果の順序を保持
results = [None] * len(image_files)
for future in concurrent.futures.as_completed(future_to_page_index):
page_index = future_to_page_index[future]
results[page_index] = future.result()
return results
改善効果: 10ページの処理時間が3分→1分に短縮
セキュリティ対策
ファイルアップロード機能における基本的なセキュリティ対策を実装しました。
def secure_filename_generation(filename):
"""安全なファイル名の生成"""
import uuid
from werkzeug.utils import secure_filename
# UUID付きのセキュアファイル名
unique_filename = f"{uuid.uuid4().hex}_{secure_filename(filename)}"
return unique_filename
# ファイル検証
ALLOWED_EXTENSIONS = {'pdf'}
MAX_FILE_SIZE = 16 * 1024 * 1024 # 16MB
def validate_file(file):
"""ファイルバリデーション"""
if not file or file.filename == '':
return False, "ファイルが選択されていません"
if not allowed_file(file.filename):
return False, "PDFファイルのみアップロード可能です"
# ファイルサイズチェック(実装省略)
return True, "OK"
開発で学んだこと
技術的な学び
-
API統合の実装方法
- OpenRouter APIとの連携実装
- エラーハンドリングとリトライ機能の重要性
-
並列処理の活用
- ThreadPoolExecutorを使用した効率的な処理
- 順序保持とエラー処理の両立
-
パフォーマンス最適化
- 画像解像度と処理時間のトレードオフ
- メモリ使用量の最適化
開発プロセスでの学び
-
MVP(Minimum Viable Product)の重要性
- 最初から完璧を目指さず、動くものから始める
- ユーザーフィードバックを基にした改善
-
問題設定の明確化
- 「何を解決したいのか」を具体的に定義する
- 技術選定の判断基準を持つ
つまずいたポイントと解決策
問題1: OCR精度の低さ
問題: 初期実装では文字認識率が低く、実用的でない状態でした。
解決策:
- PDF→画像変換時の解像度を72dpi→300dpiに向上
- 画像形式をJPEG→PNGに変更(品質重視)
問題2: 処理時間の長さ
問題: 順次処理により、大きなPDFの処理に時間がかかりすぎていました。
解決策:
- 並列処理の導入(ThreadPoolExecutor使用)
- 適切なworker数の調整(API制限との兼ね合い)
今後の改善予定
短期的な改善
- レスポンシブデザインの実装
- 処理進捗のリアルタイム表示
- エラーメッセージの多言語対応
長期的な展望
- 表・図表の構造保持機能
- バッチ処理機能(複数ファイル一括処理)
- クラウドデプロイメント(AWS/Heroku)
まとめ
新人エンジニアとして初めての本格的な個人開発を通じて、多くの学びを得ることができました。特に以下の点が印象的でした:
- 技術選定の重要性: 適切な技術スタックの選択が開発効率に大きく影響する
- ユーザー視点の大切さ: 機能だけでなく、使いやすさも同様に重要
- 継続的改善: 完璧を目指さず、段階的な改善を積み重ねる
この開発経験は、今後のエンジニアとしての成長に大きく寄与すると確信しています。同じような課題を抱える方や、個人開発に興味のある新人エンジニアの方の参考になれば幸いです。
参考リソース
リポジトリ情報
ソースコードは以下のリポジトリで公開しています:
実際に動作するアプリケーションのコードをご覧いただけます。バグ報告や機能提案、スターをいただけると励みになります。
Discussion