注釈付きのドキュメントを渡すときは、見た目がきれいな PDF だけを渡さないでください。

この記事では、PDF 注釈抽出におけるテキストとコメントの混同、機密データの漏洩、座標系の混同の問題に対処する、PyMuPDF に基づく階層化抽出およびセキュリティ検証ソリューションを提供します。この内容では、独立したデータ層の原理、防御的プログラミングの例、境界条件処理、および手動による承認基準について説明し、バイリンガルの比較と承認のために文書翻訳ワークフローに OmniDoc を正しく接続する方法について説明します。

PDF 注釈を引き渡す前に、多くの人は、page.get_text() を直接使用して、翻訳、アーカイブ、または次のレビュー担当者への引き渡しのために、きれいなテキストをエクスポートします。ただし、このテキストには通常、注釈は含まれません。一部の抽出方法では、テキストの途中にコメントが挿入され、セマンティクスが中断されます。サイドバー内の修正コメントやデリケートな発言は見逃される可能性があります。

この記事は、注釈付き PDF を使用する必要がある開発者およびドキュメント所有者を対象に書かれています。読むと、注釈が独立したデータ レイヤーである理由、レイヤー内のテキストと注釈を抽出する方法、どのエッジ ケースに防御的に対処する必要があるか、どの受け入れポイントを手動で確認する必要があるかが理解できるようになります。

この記事の例ではすべて合成サンプルを使用しています。コードは隔離された環境でサンプル チェックされていますが、すべての実際のファイルや製品のパフォーマンスを保証するものではありません。重要な書類を処理する前に、原本を保管し、本文の境界と許容事項に従って確認してください。

引き継ぎ前に注釈付き PDF を抽出するにはどうすればよいですか?一文の結論

テキストと注釈を 2 つの独立したデータ ストリームとして扱い、それらを個別に抽出し、注釈コンテンツに対して明示的なセキュリティ レビューを実行します。

あなたの状況推奨される実践方法理由
テキストのエクスポートには get_text() のみを使用してください完成したものとして扱わないでください通常、デフォルトの出力には注釈が含まれません。
査読者の改訂コメントは保存する必要があるpage.annots() を使用して注釈を個別に反復処理する注釈はページに添付される独立したオブジェクトです
コメントには ID 番号や内部コードが含まれる場合があります。機密ルールに基づいて照合し、redacted=True をマークしてプレースホルダーに置き換えますレビューメモには機密データが含まれる可能性があります
ページが回転しますまず page.rotation_matrix または page.derotation_matrix を使用して座標を変換します回転されていないページに基づく注釈の四角形
翻訳プロセスの準備ができましたまずリッチ テキスト タグをクリーンアップし、翻訳後に手動でチェックしますコードを含む翻訳を避ける

注釈がテキストのデータ層から独立している理由

PDF 仕様では、ページ コンテンツ ストリーム (Content Stream)と注釈辞書 (Annots Array)は構造的に分離されています。本体はテキスト表示命令で構成されます。注釈は、独自の座標、タイプ、コンテンツ属性を持つ、ページに付加された独立したオブジェクトです。 PyMuPDF の Page.annots() メソッドは、ページ テキスト ストリームの代わりにこの独立した注釈リストを走査するジェネレーターを返します。

これは、表示されているすべてのテキストが同じ方法で抽出できると想定できないことを意味します。

マーククラスのアノテーションとコンテンツクラスのアノテーションの違い

  • タグ注釈 (ハイライトや下線など)にはテキスト自体は含まれませんが、元のテキストに対応する領域 (四角形/四角形)のみが記録されます。実際のテキストは、座標を通じてテキストを検索することによってのみ取得できます。
  • コンテンツの注釈 (メモ、フリーテキストなど)には独自のコンテンツ文字列があり、直接読み取ることができます。
  • 型を区別せずに annot.info["content"] を直接読み取ると、タグ クラス アノテーションが指す元のコンテキストが失われるか、内容が空の場合は例外がスローされます。

合成例: 階層化された抽出と機密情報の傍受

正しい処理ロジックを実証するために、コード内に 3 つの典型的な注釈を含む合成シナリオを構築しました。次の入力は、実際の顧客ファイルからではなく、コード内で構築されたサンプルであり、教育的なデモンストレーションのみを目的としています。

予期される出力には、次の 3 つの構造化フィールドが含まれています。

  1. テキストフラグメント: ページの元のテキストのみが含まれ、注釈コンテンツは含まれません。
  2. 注釈リスト: 各レコードには、種類、場所、作成者、時間、内容 (存在する場合)が含まれます。
  3. セキュリティ マーク: 注釈の内容が事前に設定された機密ルール (ID 番号、内部コードなど)に一致する場合、エントリは redacted=True としてマークされ、内容はプレースホルダーに置き換えられます。

守る必要がある 3 つの境界線

  • 空のコンテンツ注釈: 一部のリーダーによって作成されたインク (インク)またはグラフィック注釈にはテキスト属性がありません。 .info["content"] に直接アクセスすると、空の文字列または None が返される可能性があります。コードは防御チェックを行う必要があり、すべての注釈に読み取り可能なテキストがあるとは想定できません。
  • 回転されたページ座標: ページが 90 度回転された後も、注釈の四角形は回転されていないページに基づいています。これらの座標を直接使用してテキストをトリミングまたは配置すると、位置が完全に崩れてしまうため、最初に座標変換を行う必要があります。
  • 孤立オブジェクト: 走査プロセス中にドキュメントが閉じられるか、ページがリロードされると、現在の注釈オブジェクトは直ちに無効になります。すべての読み取り操作は、単一ページの有効期間内に完了する必要があります。

Python 実装: 安全なアノテーションの走査と検証

この例では、PyMuPDF を使用してメモリ内にテスト PDF を構築し、階層抽出と基本的なマスキング ロジックを示します。依存ライブラリは PyMuPDF (つまり pymupdf パッケージ)です。ロックされた依存関係環境で実行してください。 annot 属性に完全にアクセスするには、バージョンが 1.23.0 以上である必要があります。このコードはネットワーク操作やファイルの書き込みを実行しません。ロジックを示すためにのみ使用されます。

import fitz  # PyMuPDF
import re

# 合成示例:仅在内存中构造,无需外部文件
def create_sample_pdf():
    doc = fitz.open()
    page = doc.new_page()
    # 插入正文
    page.insert_text((72, 72), "Confidential Project Alpha Report", fontsize=12)
    # 添加便签批注(含敏感信息模拟)
    page.add_text_annot((200, 72), "Reviewer: SSN 123-45-6789 approved.")
    # 添加高亮批注(无自身文本,关联正文)
    highlight = page.add_highlight_annot(fitz.Rect(72, 60, 250, 80))
    # 必须调用update()使info变更生效
    highlight.set_info(title="Editor", content="Check this term")
    highlight.update()
    return doc

def extract_with_safety(page, sensitive_pattern=r"\d{3}-\d{2}-\d{4}"):
    body_text = page.get_text("text")
    annotations = []

    # 显式遍历批注生成器
    for annot in page.annots():
        info = annot.info
        # 防御性获取内容,处理None或空字符串
        content = info.get("content", "") or ""

        # 边界处理:敏感词检测与脱敏
        is_sensitive = bool(re.search(sensitive_pattern, content))
        safe_content = "[REDACTED]" if is_sensitive else content

        annotations.append({
            "type": annot.type[1],
            "author": info.get("title", "Unknown"),
            "content": safe_content,
            "is_redacted": is_sensitive,
            # 注意:此为未旋转坐标系,若页面有旋转需配合rotation_matrix使用
            "rect": list(annot.rect)
        })
    return body_text, annotations

# 执行校验
doc = create_sample_pdf()
page = doc[0]
text, annots = extract_with_safety(page)

assert "SSN 123-45-6789" not in str(annots), "敏感信息未被拦截"
assert any(a["type"] == "Highlight" for a in annots), "标记类批注丢失"
assert len(text.strip()) > 0, "正文提取失败"
print(f"Extracted {len(annots)} annotations safely.")

本質的に、このコードは防御的なプログラミングです。注釈コンテンツの純度は信頼されず、注釈タイプの整合性も想定されていません。 assert ステートメントは、予想されるロジックを検証するために使用されますが、これは単なるデモンストレーションであり、すべての PDF バリアントに対するエンドツーエンドのパフォーマンス保証を表すものではありません。

注釈カテゴリの比較

注釈の種類テキストを持参するかどうかコンテンツの入手方法引き渡し時の注意点
ハイライト、下線いいえ、領域 (四角形/四角形)のみを記録します座標を使用してテキストを確認する対応する元のテキスト コンテキストを保持する必要がある
メモ、フリーテキストはい、独自のコンテンツ文字列が付属します直接読む機密性の高いコンテンツをチェックする
インク、グラフィック、シール多くの場合、テキスト属性はありません守備的なショートコールをする署名か承認印かを手動で確認する

受け入れ基準と手動レビューの境界

自動抽出ではすべてのエッジケースをカバーできるわけではありません。次の表は、コードが処理できる範囲と手動介入が必要なシナリオを区別する、ハンドオーバー中に注意する必要がある受け入れポイントを示しています。

現象を観察するコードレベルの処理人間によるレビューまたは意思決定
コメント内容は空ですが、アイコンは存在しますレコード タイプはインクまたはスタンプ、コンテンツは空白のままです署名か承認印かを確認し、画像スナップショットを保存するかどうかを決定
コメントの位置が CropBox を超えています座標はまだ有効ですが、レンダリングは表示されませんそれが履歴から残された放棄された注釈 (通常は無視できる)かどうかを判断します。
リッチテキストコメントにはHTMLタグが含まれています元の文字列を抽出し、タグを保持しますコードを含む翻訳を避けるために、翻訳前にタグをクリーンアップするかプレーンテキストに変換してください。
注釈の作成者名が文字化けするエンコードの推測を必要とせず、そのまま抽出しますソース ファイルのメタデータをチェックして、特定のエンコーディングの名前であるかどうかを確認します。
ネストされた返信スレッドPyMuPDF はフラットなリストを返しますParentID に基づいてダイアログ ツリーを再構築します。そうしないとコンテキストが失われます。

該当なし: この解決策は、パスワードを指定せずに暗号化された PDF、構造的に破損した PDF、および注釈タイプを定義するために非標準のプライベート拡張子を使用するファイルには適用されません。このような状況では、スクリプトが低レベルの C++ 例外を通知なくスキップしたりスローしたりすることがあります。これは、専門ツールを使用してソース ファイルを手動で開いて確認する必要があります。

ドキュメントの翻訳にチェックリストを使用する

まずページの順序、ソースの場所、テキストの完全性を確認し、同じ感度を下げたサンプルを翻訳プロセスに送信します。翻訳後も、タイトル、本文、イラストが正しく対応しているか確認する必要があります。文章が滑らかかどうかだけを確認することはできません。

OmniDocはマルチフォーマット文書翻訳ポータルを提供しており、公式 Web サイトでは OCR、用語集、対訳比較機能を導入しています。チームがファイルから直接処理を開始したい場合は、このパスを実際のサンプルで評価できます。アップロード許可のある感度を下げたファイルを使用し、この記事のチェック項目に従って結果を確認してください。ここでは、OmniDoc がこの記事のオープン ソース ライブラリや内部アルゴリズムを使用しているという主張はなく、また、この例が製品パフォーマンス テストに代わるものでもありません。

参考資料と次のステップ

  1. 混合した注釈タイプを含む履歴プロジェクト ファイルを選択します。
  2. 上記の検証スクリプトを実行し、手動レビューが必要なエントリの割合を数えます。
  3. 比率が予想を超えた場合は、下流のツールに依存して答えを提供するのではなく、引き継ぎプロセスに前処理ステップを追加します。

🎯 どの方法があなたに適していますか?

テキストを翻訳するだけの場合

テキストを直接抽出することもできますが、エクスポートされたコンテンツに欠落している注釈がないか確認する必要があります。

アーカイブのためにドキュメントを他の人に渡したい場合

同時に、メインテキストと注釈リストを保持し、機密コンテンツを機密扱いとしてマークします。

PDF のページが回転している場合

まず座標変換を実行し、次に注釈の位置を使用してテキストを見つけます。

ファイルが暗号化されているか、構造的に破損している場合

この解決策は適用できません。専門ツールを使用して手動でソース ファイルを開いて確認してください。

❓ よくある質問 (FAQ)

1. get_text() に注釈が含まれていないのはなぜですか?

通常、デフォルトの出力にはページ本文のみが含まれます。注釈はページ テキスト ストリームから独立したオブジェクトであり、page.annots() を介して個別にトラバースする必要があります。

2. すべてのコメントのテキストを直接読むことはできますか?

できません。ハイライトや下線などのマークタイプの注釈にはテキスト自体は含まれず、領域のみが記録されます。インクやグラフィックの注釈にもテキスト属性がない場合があり、防御的な判断が必要です。

3. ページを回転すると位置が崩れるのはなぜですか?

注釈の四角形は、回転されていないページに基づいています。テキストを配置するために直接使用すると、エラーが発生します。最初に座標変換に page.rotation_matrix または page.derotation_matrix を使用する必要があります。

4. ネストされた返信は失われますか?

PyMuPDF はフラットなリストを返します。ParentID に基づいてダイアログ ツリーが再構築されない場合、コンテキストは失われます。

5. 暗号化された PDF をこのスクリプトで処理できますか?

暗号化されており、パスワードが提供されていない PDF は、このソリューションの範囲外であるため、手動による確認が必要です。

📝 概要

注釈は PDF の独立したデータ レイヤーです。引き渡す前に、テキストと注釈を別々に抽出し、注釈に機密性の高いコンテンツがないかどうかを確認する必要があります。自動化されたスクリプトは一般的なケースをカバーできますが、エッジケースや最終的な承認は依然として手動で行う必要があります。

今すぐ試してみましょう: ドキュメントをアップロードして、元のレイアウトを保持したまま翻訳を開始します

更新日 2026-11-14