父親から「会社のPDFをExcelに変換したいけど、ネットにアップロードするのはセキュリティ的にまずい」と相談されて、ローカルで動くツールを作りました。
なぜ作ったのか
父親の会社では、取引先から送られてくるPDFの明細書をExcelに手動で入力し直す作業があります。毎月何十ファイルもあるそうで、かなり大変そうでした。
「ネットの変換サイトを使えばいいじゃん」と最初は思ったんですが、会社の機密情報をオンラインサービスにアップロードするのは確かにリスクがあります。
そんな時、AsposePDFというライブラリがWebAssembly版を出していることを知って、「これならブラウザ内で完結するな」と思って作ってみました。
作ったもの
基本的には「PDFファイルをドラッグ&ドロップして、ボタンを押したらExcelになる」シンプルなWebアプリです。
主な機能
- 完全ローカル処理: ファイルは一切外部に送信されない
- リアルタイムプレビュー: 変換結果を事前確認
- 複数の変換方式: 表形式データ抽出 + スペース区切り対応
技術選択の理由
最初は Python で何か作ろうかと思ったんですが、父親のPCに Python 環境を入れるのは面倒だなと。ブラウザなら何も設定不要で使えるので、Web アプリにしました。
// AsposePDF の WebAssembly 版を使用
declare global {
interface Window {
AsposePdfExtractText?: (buffer: ArrayBuffer, fileName: string) => {
errorCode: number;
extractText: string;
};
AsposePdfTablesToCSV?: (buffer: ArrayBuffer, fileName: string, outputFileName: string) => {
errorCode: number;
filesNameResult: string[];
};
}
}
実装で工夫したところ
1. 複数の変換方式
実際にテストしてみると、PDFの種類によって最適な変換方法が違うことがわかりました:
// 表形式データの場合
if (extractTableData && window.AsposePdfTablesToCSV) {
const csvResult = window.AsposePdfTablesToCSV(arrayBuffer, selectedFile.name, `output_${Date.now()}.csv`, "\t");
// ...
}
// スペース区切りデータの場合(銀行明細など)
const splitTextBySpaces = (text: string): string[][] => {
const lines = text.split('\n').filter((line: string) => line.trim());
return lines.map((line: string) => {
// 2つ以上の連続するスペース/タブで区切る
return line.split(/\s{2,}/).filter(cell => cell.trim());
});
};
父親がよく扱う銀行明細は、こんな感じの形式でした:
20250125 111111 振込 -11111.00 1234567.89
20250126 222222 入金 50000.00 1284567.89
これを手動で Excel に打ち込むのは確かに大変そうです。
2. エラーハンドリング
AsposePDF は 168MB という巨大な WebAssembly ファイルを読み込むので、ローディング周りで苦労しました:
useEffect(() => {
const loadAsposePdf = async () => {
try {
if (typeof window !== 'undefined' && window.AsposePdfExtractText) {
setIsAsposePdfReady(true);
return;
}
const script = document.createElement('script');
script.src = '/AsposePDFforJS.js';
script.onload = () => {
// ライブラリの初期化を待つ
setTimeout(() => {
if (window.AsposePdfExtractText) {
setIsAsposePdfReady(true);
}
}, 1000);
};
document.head.appendChild(script);
} catch (error) {
console.error('Aspose PDF loading failed:', error);
}
};
loadAsposePdf();
}, []);
最初は初期化のタイミングがうまく取れなくて、「変換ボタンを押しても何も起きない」という状況になりました。1秒待つという力技で解決しましたが、もっと良い方法があるかもしれません。
3. プレビュー機能
父親から「変換結果が正しいかわからない」という指摘があったので、Excel ダウンロード前にプレビューできるようにしました:
{excelData && (
<div className="bg-white rounded-lg shadow-md p-6">
<div className="overflow-auto max-h-96 border rounded">
<table className="min-w-full divide-y divide-gray-200">
<tbody className="bg-white divide-y divide-gray-200">
{excelData.slice(0, 50).map((row, rowIndex) => (
<tr key={`row-${rowIndex}`}>
{row.map((cell, cellIndex) => (
<td key={`cell-${rowIndex}-${cellIndex}`}
className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{cell}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
実際の効果
父親に使ってもらった結果:
- 作業時間: 1ファイル30分 → 5分ぐらい
- 精度: 手入力ミスが激減
- 安心感: 社内のPCで完結するのでセキュリティ面でも安心
「これで残業が減る」と喜んでくれました。
ただし、完璧ではありません:
- 複雑なレイアウトのPDFだと列がずれることがある
- スキャンされたPDFには対応できない(OCR機能がない)
- 大きいファイル(50MB以上)だとブラウザが重くなる
技術スタック
使った技術は以下の通りです:
{
"dependencies": {
"react": "^19.1.0",
"react-router": "^7.7.1",
"aspose-pdf-js": "^25.6.0",
"xlsx": "^0.18.5",
"file-saver": "^2.0.5"
},
"devDependencies": {
"@tailwindcss/vite": "^4.1.4",
"typescript": "^5.8.3",
"vite": "^6.3.3"
}
}
技術選択の理由
- React 19: まだ新しいけど、TypeScript の型推論が良くなったので
- React Router 7: ファイルベースルーティングが便利
- TailwindCSS 4: CSS を書くのが面倒だったので
- Bun: npm より速いらしいので(父親のPCでも高速に動かしたかった)
AsposePDF を選んだ理由
PDF ライブラリの選択肢はいくつかありました:
- PDF.js: Mozilla 製で軽量だが、Excel 変換機能が弱い
- PDFtk: サーバーサイドツールで今回の要件に合わない
- AsposePDF: 高機能だが 168MB と重い
結局、「多少重くても高精度な変換をしたい」ということで AsposePDF にしました。
セットアップ方法
GitHub からクローンして使う場合:
# プロジェクトをクローン
git clone https://github.com/gimwachan-git/pdf-to-excel-poc.git
cd pdf-to-excel-poc
# 依存関係をインストール
bun install
# ⚠️ 重要:AsposePDF ファイルをダウンロード
mkdir -p public
curl -o public/AsposePDFforJS.js https://cdn.jsdelivr.net/npm/aspose-pdf-js@25.6.0/AsposePDFforJS.js
curl -o public/AsposePDFforJS.wasm.zip https://cdn.jsdelivr.net/npm/aspose-pdf-js@25.6.0/AsposePDFforJS.wasm.zip
cd public && unzip AsposePDFforJS.wasm.zip && rm AsposePDFforJS.wasm.zip && cd ..
# 開発サーバー起動
bun run dev
AsposePDF の WebAssembly ファイルが 168MB もあるので、GitHub には上げられませんでした。手動でダウンロードする必要があります。
使い方
- ブラウザで
http://localhost:5173
を開く - PDF ファイルを選択
- 変換オプションを設定(表形式データ抽出など)
- 「Convert to Excel」ボタンをクリック
- プレビューで確認後、Excel ファイルをダウンロード
変換オプション
// 表形式データ抽出(請求書、報告書など)
<input
type="checkbox"
checked={extractTableData}
onChange={(e) => setExtractTableData(e.target.checked)}
/>
// スペース区切り処理(銀行明細、ログファイルなど)
<input
type="checkbox"
checked={splitBySpaces}
onChange={(e) => setSplitBySpaces(e.target.checked)}
/>
今後の改善予定
今後、以下の機能を追加したいと考えています:
- バッチ処理: 複数ファイルの一括変換
- 列の手動調整: プレビューで列を調整できる機能
- テンプレート保存: よく使う設定の保存
- OCR対応: スキャンされたPDFへの対応
実は、OCR については Google の Tesseract.js を試してみたんですが、精度がイマイチでした。AsposePDF にも OCR 機能があるらしいので、調べてみる予定です。
セキュリティ面での工夫
業務で使うツールなので、セキュリティには気を使いました:
完全ローカル処理
// ファイルは絶対に外部に送信されない
const fileReader = new FileReader();
fileReader.onload = async (event) => {
const arrayBuffer = event.target?.result as ArrayBuffer;
// ブラウザ内で完結
const result = window.AsposePdfExtractText(arrayBuffer, selectedFile.name);
};
Dockerfile も用意
セキュリティ重視の環境向けに、Docker での実行も可能にしました:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]
社内の Docker 環境で動かす必要がある場合に備えて用意しました。
意外だった発見
ブラウザの処理能力
最初は「168MBの WebAssembly なんて重すぎて使い物にならないのでは」と思っていましたが、意外と普通に動きました。
父親の会社の古いPCでも、初回ロードに30秒ぐらいかかるものの、一度読み込んでしまえばサクサク動きます。
PDF の種類による変換精度の差
同じ PDF でも、作成方法によって変換精度が全然違いました:
- Word から出力した PDF: ほぼ完璧に変換される
- Excel から出力した PDF: 列の区切りが正確
- スキャンした PDF: 全然だめ(当然といえば当然)
- Web ページを PDF 化したもの: レイアウトが崩れがち
まとめ
「父親の仕事を楽にしたい」という単純な動機で始めたプロジェクトでしたが、もし同じような悩みを持っている人がいれば役に立てるかもしれません。
特に:
- 機密情報をオンラインサービスに上げたくない
- でも PDF → Excel 変換は自動化したい
- IT部門に頼むほどでもない小さな作業
こういう「ちょっとした業務改善」が必要な人もいるかもしれませんね。
同じような課題を抱えている方の参考になれば嬉しいです。
今の状況
現在も父親の会社で実用されています。月に50ファイルぐらい処理しているそうで、「残業が減って助かる」と言ってもらえました。
MIT ライセンスで公開しているので、自由に使ってください。バグや要望があれば GitHub の Issues に投げてもらえると対応します。
リンク
- GitHub: https://github.com/gimwachan-git/pdf-to-excel-poc
- デモ: README の Usage セクションを参照
- 技術詳細: README.md
実際に使ってもらった感想や改善点があれば、気軽にコメントしてください。