@gstypecode/pdf-field-extractor
v1.0.0
Published
PDF field extraction using configurable patterns and coordinates
Maintainers
Readme
PDF Region Extractor
PDFから特定領域(region/field)を抽出するNode.jsパッケージ。設定ファイルベースで座標とパターンを指定し、効率的な情報抽出を実現します。
🚀 特徴
- 設定ファイルベース: JSONファイルで座標・パターン・ページを指定
- 正規表現対応: 複雑なパターンマッチングをサポート
- 座標指定抽出: PDF内の特定領域からテキストを抽出
- Web UI: 設定エディタは別リポジトリ pdf-config-editor で提供
- 高性能: 大量のPDFファイルを効率的に処理
- TypeScript対応: 型安全な開発体験
📦 インストール
npm install pdf-region-extractor🔧 クイックスタート
1. 基本的な使用方法
const PdfFieldExtractor = require('pdf-region-extractor');
const fs = require('fs').promises;
const config = {
fields: {
invoiceNumber: {
description: "請求書番号",
pattern: "請求書番号[\\s::]*([A-Z0-9-]+)",
pages: "all",
region: { xMin: 100, xMax: 300, yMin: 50, yMax: 100 },
method: "regex"
}
}
};
// Buffer から抽出(ライブラリ利用が主)
const pdfBuffer = await fs.readFile('./invoice.pdf');
const result = await PdfFieldExtractor.extract(pdfBuffer, config);
// または、ファイルパスから直接抽出(CLI利用など)
// const result = await PdfFieldExtractor.extractFromFile('./invoice.pdf', config);
console.log(result);
// {
// success: true,
// data: { invoiceNumber: "INV-2024-001" },
// metadata: { processingTime: 120, extractionType: "field_extraction", ... }
// }2. 設定ファイルを使用
const PdfFieldExtractor = require('pdf-region-extractor');
const fs = require('fs').promises;
// 設定ファイルを読み込み
const configData = await fs.readFile('./config/invoice_format.json', 'utf8');
const config = JSON.parse(configData);
// PDFから情報を抽出(Buffer系)
const pdfBuffer = await fs.readFile('./invoice.pdf');
const result = await PdfFieldExtractor.extract(pdfBuffer, config);
// または、ファイルパスから直接抽出(File系)
// const result = await PdfFieldExtractor.extractFromFile('./invoice.pdf', config);3. Web UIを使用
設定エディタは別リポジトリ pdf-config-editor に移動しました。 詳細はそちらのREADMEを参照してください。
📋 設定ファイル形式
{
"fields": {
"invoiceNumber": {
"description": "請求書番号",
"pattern": "請求書番号[\\s::]*([A-Z0-9-]+)",
"pages": "all",
"region": {
"xMin": 100,
"xMax": 300,
"yMin": 50,
"yMax": 100
},
"method": "regex"
},
"totalAmount": {
"description": "合計金額",
"pattern": "合計[\\s::]*([\\d,]+)円",
"pages": [1, 2],
"region": {
"xMin": 400,
"xMax": 600,
"yMin": 700,
"yMax": 750
},
"method": "regex"
}
}
}🏗️ API設計
Buffer系とFile系の使い分け
このライブラリは2種類のAPIを提供します:
Buffer系メソッド(主な用途:npmパッケージとして利用)
extract(pdfBuffer, config, options)extractByPages(pdfBuffer, config, options)
File系メソッド(主な用途:CLI利用)
extractFromFile(pdfPath, config, options)extractByPagesFromFile(pdfPath, config, options)
使用例:
const PdfFieldExtractor = require('pdf-region-extractor');
const fs = require('fs').promises;
// ライブラリとして利用(Buffer系)
const pdfBuffer = await fs.readFile('./invoice.pdf');
const result = await PdfFieldExtractor.extract(pdfBuffer, config);
// CLIツールとして利用(File系)
const result = await PdfFieldExtractor.extractFromFile('./invoice.pdf', config);推奨:ほとんどのケースではnpmパッケージとして利用するため、Buffer系メソッドを使用してください。
🎯 API仕様
extract(pdfBuffer, config, options)
PDFバッファから指定されたフィールドを抽出します(ライブラリ利用推奨)。
パラメータ:
pdfBuffer(Buffer): PDFファイルのバッファconfig(object): 設定オブジェクトoptions(object, optional): 抽出オプションdebug(boolean): デバッグモード有効化pageNumber(number): 特定ページのみ抽出timeout(number): タイムアウト時間(ミリ秒)
戻り値:
Promise<object>: 抽出結果オブジェクトsuccess(boolean): 処理成功可否data(object): 抽出されたデータ(形式は設定により異なる)metadata(object): 処理情報(処理時間、ページ数、マッチ数など)error(string): エラーメッセージ(失敗時のみ)
詳細な出力形式については 📤 出力スキーマ を参照してください。
例:
const pdfBuffer = await fs.readFile('./invoice.pdf');
const result = await PdfFieldExtractor.extract(pdfBuffer, config, {
debug: true,
timeout: 30000
});
// 結果例
// {
// success: true,
// data: { invoiceNumber: "INV-2024-001", totalAmount: "50,000" },
// metadata: { processingTime: 150, pageCount: "auto", ... }
// }extractFromFile(pdfPath, config, options)
PDFファイルパスから指定されたフィールドを抽出します(CLI利用など)。
パラメータ:
pdfPath(string): PDFファイルパスconfig(object): 設定オブジェクトoptions(object, optional): 抽出オプション(extractと同じ)
戻り値:
Promise<object>:extractと同じ形式の抽出結果オブジェクト
例:
const result = await PdfFieldExtractor.extractFromFile('./invoice.pdf', config, {
debug: true
});loadConfig(configPath)
設定ファイルを読み込みます。
パラメータ:
configPath(string): 設定ファイルパス
戻り値:
object: 設定オブジェクト
validateConfig(config)
設定オブジェクトの妥当性を検証します。
パラメータ:
config(object): 設定オブジェクト
戻り値:
boolean: 妥当性チェック結果
extract と extractByPages の使い分け
| メソッド | 用途 | 返り値 | ページ処理 |
|---------|------|--------|----------|
| extract | 全ページからマッチしたデータを取得 | マッチしたデータのみ | 一括処理 |
| extractByPages | 各ページの成功・失敗を個別に取得 | 全ページ分の結果配列pageResults.length === totalPages | ページごと処理 |
extract を使うべき場合:
- マッチしたデータだけが必要
- ページ情報は不要
- シンプルな抽出
extractByPages を使うべき場合:
- 各ページの一致・不一致を確認したい
- マッチしなかったページを特定したい
- ページごとの処理結果が必要
重要な違い:
// extract: マッチしたデータのみ
const result = await PdfFieldExtractor.extract(pdfBuffer, config);
// result.data.fieldName: マッチした値の配列(例:253個)
// extractByPages: 全ページ分の結果
const result = await PdfFieldExtractor.extractByPages(pdfBuffer, config);
// result.pageResults.length: 260(全ページ数)
// result.metadata.successfulPages: 253(マッチしたページ数)
// result.metadata.failedPages: 7(マッチしなかったページ数)extractByPages(pdfBuffer, config, options)
PDFファイルの各ページごとに情報を抽出します。全ページの処理結果(成功・失敗含む)を取得できます。
重要: extract メソッドと異なり、このメソッドは pageResults.length === totalPages となり、マッチしなかったページも含めて全ページ分の結果が返されます。
パラメータ:
pdfBuffer(Buffer): PDFファイルのバッファconfig(object): 抽出設定オブジェクトoptions(object, optional): 抽出オプションdebug(boolean): デバッグモード有効化
戻り値:
Promise<object>: ページ別抽出結果オブジェクトsuccess(boolean): 処理成功可否extractionType(string): "page-by-page"totalPages(number): 総ページ数pageResults(array): 各ページの抽出結果配列(pageResults.length === totalPages)page(number): ページ番号result(object): extract メソッドと同じ形式の結果
consolidatedData(object): 全ページのデータを統合したオブジェクトmetadata(object): 処理情報successfulPages(number): 抽出成功ページ数failedPages(number): 抽出失敗ページ数
例:
const fs = require('fs').promises;
const pdfBuffer = await fs.readFile('./invoice.pdf');
const result = await PdfFieldExtractor.extractByPages(pdfBuffer, config, {
debug: true
});
console.log(`総ページ数: ${result.totalPages}`);
console.log(`pageResults配列の長さ: ${result.pageResults.length}`); // totalPagesと同じ
console.log(`成功: ${result.metadata.successfulPages}ページ`);
console.log(`失敗: ${result.metadata.failedPages}ページ`);
// 各ページの結果を確認
result.pageResults.forEach(({ page, result }) => {
if (result.success && result.data) {
// 抽出されたすべてのフィールドを表示
const extractedFields = Object.keys(result.data).filter(key => result.data[key] !== null);
console.log(`ページ${page}: ${extractedFields.join(', ')}`);
} else {
console.log(`ページ${page}: マッチなし`);
}
});使用例:全ページの一致・不一致を確認
const result = await PdfFieldExtractor.extractByPages(pdfBuffer, config);
// マッチしなかったページを特定
const unmatchedPages = result.pageResults
.filter(({ result }) => !result.success || Object.values(result.data || {}).every(v => v === null))
.map(({ page }) => page);
console.log('マッチしなかったページ:', unmatchedPages);
console.log(`全${result.totalPages}ページ中、${unmatchedPages.length}ページがマッチしませんでした`);📤 出力スキーマ(Output Schema)
基本構造
すべての抽出結果は以下の統一された形式で返されます:
{
success: true, // 処理成功可否
data: { ... }, // 抽出データ(形式はモードにより異なる)
metadata: {
processingTime: 150, // 処理時間(ミリ秒)
pageCount: "auto", // 処理ページ数
matchCount: 3, // マッチ数
formatName: "設定名", // 設定ファイル名
extractionType: "field_extraction" // 抽出タイプ
}
}1. フィールド抽出モード(Field Extraction)
設定タイプ: field_extraction
出力ルール
- マッチなし:
null - 1要素のみマッチ: 文字列
- 複数要素マッチ: 配列
例1: 単一マッチ
// 設定
{
"fields": {
"invoiceNumber": {
"pattern": "INV-\\d+",
"region": { "xMin": 100, "xMax": 300, "yMin": 50, "yMax": 100 }
}
}
}
// 出力
{
"success": true,
"data": {
"invoiceNumber": "INV-2024-001" // 文字列
},
"metadata": { ... }
}例2: 複数テキストボックスマッチ
PDFの指定座標領域内に複数のテキストボックスが存在し、それぞれが正規表現にマッチした場合:
// 設定
{
"fields": {
"studentId": {
"pattern": "\\d+.*",
"region": { "xMin": 40, "xMax": 200, "yMin": 100, "yMax": 150 }
}
}
}
// PDF内のテキストボックス構造:
// - テキストボックス1: "31030 赤瀬 龍臣"
// - テキストボックス2: "]"
// 出力
{
"success": true,
"data": {
"studentId": ["31030 赤瀬 龍臣", "]"] // 配列
},
"metadata": { ... }
}重要: 各テキストボックスは個別に正規表現チェックされ、マッチした要素が配列として返されます。
例3: マッチなし
{
"success": true,
"data": {
"invoiceNumber": null // マッチなし
},
"metadata": { ... }
}2. 手動テーブル抽出モード(Manual Table)
設定タイプ: manual_table
出力構造
{
"success": true,
"data": {
"テーブル名": [
{
"column1": "値1",
"column2": "値2",
"column3": "値3"
},
{
"column1": "値4",
"column2": "値5",
"column3": "値6"
}
]
},
"metadata": {
"processingTime": 200,
"extractionType": "manual_table",
...
}
}例: 請求書明細テーブル
// 設定
{
"tableDefinition": {
"tableName": "invoiceItems",
"columns": [
{ "name": "itemName", "xRange": { "min": 40, "max": 200 }, "pattern": ".+" },
{ "name": "quantity", "xRange": { "min": 210, "max": 260 }, "pattern": "\\d+" },
{ "name": "amount", "xRange": { "min": 270, "max": 340 }, "pattern": "[\\d,]+" }
]
}
}
// 出力
{
"success": true,
"data": {
"invoiceItems": [
{
"itemName": "商品A",
"quantity": "5",
"amount": "10,000"
},
{
"itemName": "商品B",
"quantity": "3",
"amount": "6,000"
}
]
},
"metadata": { ... }
}3. 自動テーブル検出モード(Auto Table Detection)
設定タイプ: auto_table
出力構造(table_matrix形式)
{
"success": true,
"data": {
"テーブル名": {
"matrix": [
[
{
"row": 0,
"column": 0,
"combinedText": "セル内容",
"textBoxes": [...], // includeCoordinates: true の場合
"avgX": 50.5,
"avgY": 200.3
},
// ...
],
// ...
],
"metadata": {
"detectedRows": 10,
"detectedColumns": 5,
"totalCells": 50,
"clusteringInfo": {
"verticalClusters": 10,
"horizontalClusters": 5,
"algorithm": "dbscan"
}
}
}
},
"metadata": { ... }
}出力構造(flat_list形式)
{
"success": true,
"data": {
"テーブル名": {
"cells": [
{ "row": 0, "column": 0, "combinedText": "A1" },
{ "row": 0, "column": 1, "combinedText": "B1" },
{ "row": 1, "column": 0, "combinedText": "A2" },
// ...
],
"metadata": { ... }
}
},
"metadata": { ... }
}例: 自動テーブル検出
// 設定
{
"autoTableDetection": {
"tableName": "detectedTable",
"extractionRegion": { "xMin": 50, "xMax": 550, "yMin": 200, "yMax": 600 },
"clustering": {
"algorithm": "dbscan",
"verticalClustering": { "epsilon": 5.0, "minPoints": 2 },
"horizontalClustering": { "epsilon": 10.0, "minPoints": 1 }
},
"output": {
"format": "table_matrix", // "table_matrix" または "flat_list"
"includeCoordinates": true, // 座標情報を含める
"includeMetadata": true // メタデータを含める
}
}
}
// 出力
{
"success": true,
"data": {
"detectedTable": {
"matrix": [
[
{ "row": 0, "column": 0, "combinedText": "ヘッダー1", "avgX": 60, "avgY": 210 },
{ "row": 0, "column": 1, "combinedText": "ヘッダー2", "avgX": 150, "avgY": 210 }
],
[
{ "row": 1, "column": 0, "combinedText": "データ1", "avgX": 60, "avgY": 230 },
{ "row": 1, "column": 1, "combinedText": "データ2", "avgX": 150, "avgY": 230 }
]
],
"metadata": {
"detectedRows": 2,
"detectedColumns": 2,
"totalCells": 4,
"clusteringInfo": {
"verticalClusters": 2,
"horizontalClusters": 2,
"algorithm": "dbscan"
}
}
}
},
"metadata": {
"processingTime": 180,
"extractionType": "auto_table",
...
}
}エラー時の出力
処理が失敗した場合は以下の形式で返されます:
{
"success": false,
"error": "エラーメッセージ",
"metadata": {
"processingTime": 50
}
}出力設定オプション
フィールドモード
- 複数マッチ時は自動的に配列化
- キャプチャグループ使用時は追加フィールド生成可能
テーブルモード(手動)
- カラム定義に基づくオブジェクト配列
- 正規表現マッチングによる値抽出
テーブルモード(自動)
| オプション | 説明 | デフォルト |
|----------|------|---------|
| format | 出力形式(table_matrix / flat_list) | table_matrix |
| includeCoordinates | 座標情報を含める | true |
| includeMetadata | メタデータを含める | true |
🔧 Web UI(設定エディタ)
設定エディタは別リポジトリ pdf-config-editor に移動しました。
🚨 重要な注意事項
座標系について
PDFの座標系は 左下原点 です。Web UIでは自動的に変換されますが、設定ファイルを手動作成する場合は注意してください。
// PDF座標系(左下原点)
{ xMin: 100, xMax: 300, yMin: 700, yMax: 750 }
// 画面座標系(左上原点)との変換
const pdfHeight = 842; // A4サイズの場合
const webY = pdfHeight - pdfY;正規表現パターン
- キャプチャグループ:
()で囲んだ部分が抽出されます - エスケープ: JSONファイルでは
\\でエスケープが必要 - マルチライン: 複数行にまたがるパターンは
[\s\S]*?を使用
// 良い例
"pattern": "請求書番号[\\s::]*([A-Z0-9-]+)"
// 悪い例(エスケープ不足)
"pattern": "請求書番号[\s::]*([A-Z0-9-]+)"ページ指定
// 全ページ
"pages": "all"
// 特定ページ
"pages": [1, 2, 3]
// 単一ページ
"pages": 1🐛 トラブルシューティング
よくある問題
1. PDFが読み込めない
症状: PDFファイル選択後にエラーが発生
原因と解決策:
- 破損したPDF: 別のPDFファイルで試す
- 暗号化PDF: パスワード保護を解除
- 大きなファイル: ファイルサイズを確認(推奨: 10MB以下)
2. 座標指定で正しく抽出できない
症状: 設定した座標からテキストが抽出されない
原因と解決策:
- 座標系の間違い: PDFは左下原点、Web UIは左上原点
- 拡大率の影響: 100%表示で座標を確認
- フォントの問題: 画像化されたテキストは抽出不可
3. 正規表現がマッチしない
症状: パターンが正しいのにマッチしない
原因と解決策:
- エスケープ不足: JSONファイルでは
\\が必要 - 文字エンコーディング: 全角・半角の違いを確認
- 改行・空白:
[\s\S]*?で対応
4. Web UIがフリーズする
症状: 複数操作後にブラウザが応答しない
原因と解決策:
- 無限ループ: 新しいタブで開き直す
- メモリ不足: ページをリロード
- 大きなPDF: PDFファイルサイズを確認
デバッグモード
const pdfBuffer = await fs.readFile('./invoice.pdf');
const result = await PdfFieldExtractor.extract(pdfBuffer, config, {
debug: true,
logLevel: 'verbose'
});ログ出力
// 詳細ログを有効化
process.env.DEBUG = 'pdf-region-extractor:*';🔬 技術仕様
依存関係
- Node.js: 14.0.0以上
- pdf-parse: PDF解析エンジン
- PDF.js: フロントエンド用PDFレンダリング
- Vue.js 3: Web UI フレームワーク
- Express: Webサーバー
アーキテクチャ
pdf-region-extractor/
├── src/
│ ├── index.js # メインAPI
│ ├── utils/
│ │ ├── pdf-parser.js # PDF解析
│ │ ├── validator.js # 設定検証
│ │ └── pdf-viewer-manager.js # UI統合管理
│ └── extractor/
│ └── universal-detector.js # 抽出エンジン
├── config/ # 設定ファイル
└── tests/ # テストスイートパフォーマンス
| 指標 | 値 | |------|-----| | 平均処理時間 | < 100ms/page | | メモリ使用量 | < 50MB/PDF | | 対応ファイルサイズ | 最大 50MB | | 同時処理数 | 最大 10ファイル |
対応PDF形式
- PDF バージョン: 1.4 - 2.0
- テキストPDF: ✅ 対応
- 画像PDF: ❌ 非対応(OCR必要)
- 暗号化PDF: ❌ 非対応
- フォーム付きPDF: ⚠️ 部分対応
🧪 開発者向け情報
開発環境セットアップ
# リポジトリクローン
git clone https://github.com/your-org/pdf-region-extractor.git
cd pdf-region-extractor
# 依存関係インストール
npm install
# 開発サーバー起動
npm run dev
# テスト実行
npm test
# カバレッジレポート
npm run test:coverageTDD開発ルール
このプロジェクトは t-wada スタイルのTDD を採用しています。
RED-GREEN-REFACTOR サイクル
- RED: テストを書く(失敗することを確認)
- GREEN: テストを通す最小限の実装
- REFACTOR: テストを保持しながら改善
品質基準
- テストカバレッジ: 最小80%、目標90%+
- 関数分離: 1関数1責務
- JSDoc: 日本語コメント必須
コントリビューション
- Issue作成: バグ報告・機能要求
- Fork: リポジトリをフォーク
- ブランチ作成:
feature/xxxまたはfix/xxx - テスト追加: 新機能にはテストを追加
- Pull Request: 詳細な説明を記載
コミット規約
feat: 新機能追加
fix: バグ修正
test: テスト追加・修正
refactor: リファクタリング
docs: ドキュメント更新📊 テストスイート
単体テスト
# 全テスト実行
npm test
# 特定テスト実行
npm test -- --grep "PDF解析"
# ウォッチモード
npm run test:watch統合テスト
# E2Eテスト実行
npm run test:e2e
# UI テスト実行
npm run test:uiカバレッジ
# カバレッジレポート生成
npm run test:coverage
# HTMLレポート表示
open coverage/index.html🔄 バージョン管理
リリース履歴
- v1.0.0: 初期リリース
- v1.1.0: Web UI追加
- v1.2.0: 統合PDF管理システム
更新方法
# マイナーバージョンアップ
npm version minor
# パッチバージョンアップ
npm version patch
# リリース
npm publish🆘 サポート
問題報告
- GitHub Issues: https://github.com/your-org/pdf-region-extractor/issues
- メールサポート: [email protected]
- ドキュメント: https://pdf-region-extractor.readthedocs.io/
FAQ
Q: 画像化されたPDFは対応していますか? A: いいえ。テキストレイヤーがあるPDFのみ対応です。OCR処理が必要な場合は別途ツールをご利用ください。
Q: 暗号化されたPDFは処理できますか? A: 現在は非対応です。パスワード保護を解除してからご利用ください。
Q: 大量のPDFファイルを効率的に処理したい A: バッチ処理機能を準備中です。v1.3.0でリリース予定です。
📄 ライセンス
MIT License - 詳細は LICENSE をご確認ください。
🙏 謝辞
- PDF.js: Mozilla Foundation
- pdf-parse: modesty氏
- Vue.js: Evan You氏
- テスト手法: t-wada氏のTDD思想
PDF Region Extractor は効率的な情報抽出を実現するための包括的なソリューションです。
