【 Insight Masking API 徹底活用 】Word ファイルの機密情報も安心！Insight Masking Web API と python-docx で手軽にマスキング

ビジネス文書の作成に欠かせない Word ファイル。企画書、報告書、契約書など、重要な情報が記載されたファイルも多いのではないでしょうか。これらのファイルを安全に共有・管理するためには、適切なマスキング処理が求められます。

そこで今回は、高精度なマスキングが期待できる **Insight Masking Web API** と、Python で Word ファイル（.docx）を操作するための強力なライブラリ **python-docx** を組み合わせることで、Word ファイルに含まれる機密情報を手軽にマスキングする方法をご紹介します。「Word ファイルの中身もプログラムでマスキングできるの？」と思われた方も、ぜひこの記事を読み進めてください。

## なぜ Word ファイルのマスキングに Insight Masking Web API を活用するのか？

Word ファイルのマスキングを手作業で行うのは、時間と手間がかかり、ミスも起こりやすいものです。Insight Masking Web API と連携することで、以下のようなメリットが期待できます。

  * **高精度なマスキング:** テキストの内容を解析し、個人情報や機密情報を高精度に検出してマスキングできます。
  * **効率的な処理:** 複数のファイルや長文のドキュメントも、プログラムで自動的に処理できるため、大幅な時間短縮につながります。
  * **多様なテキスト要素に対応:** 本文だけでなく、表、ヘッダー、フッターなどに含まれるテキストデータもマスキングの対象にできます。

## 準備するもの

始める前に、以下の準備が必要です。

1.  **Python が利用可能な環境**
2.  **Insight Masking Web API の URL と API キー:** Insight Masking Web API を利用するためには、Insight Masking の Web コンソールからあらかじめプレーンテキストタイプの API を作成しておく必要があります。詳細は以下のサイトなどを参照ください。

https://qiita.com/takumats/items/10024720668022b48af7

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/920edbd1-3892-42b3-a54e-b4f1f4c20e78.png)

ではさっそくやってみましょう。

### 必要なライブラリのインストール（requirements.txt）

このプロジェクトで必要なライブラリは `python-docx`, `requests`, `python-dotenv` です。以下の内容で `requirements.txt` ファイルをプロジェクトのルートディレクトリに作成してください。

```
python-docx
requests
python-dotenv
```

作成後、アクティベートされた venv 仮想環境内で以下のコマンドを実行し、必要なライブラリをインストールします。

```bash
python3 -m venv venv
venv/bin/activate
pip3 install -r requirements.txt
```

### Insight Masking Web API の基本的な使い方

Insight Masking Web API (プレーンテキストタイプ) は、テキストデータを受け取り、その中に含まれる機密情報を検出してマスキングした結果を返してくれるサービスです。基本的なリクエストとレスポンスの形式は以下の通りです。**API キーは、HTTP リクエストのヘッダーに `X-Insight-Masking-API-KEY` として含めて送信します。**

  * **リクエスト:** マスキングしたいテキストデータを base64 エンコードしたものと、**API キーを HTTP ヘッダーに `X-Insight-Masking-API-KEY` を設定**して HTTP リクエストを送信します。
  * **レスポンス:** API は、base64 エンコードされたマスキング後のテキストデータを返します。

## Python で docx ファイルをマスキングする具体的な手順

python-docx を使うと、Word ファイル内の段落、表、ヘッダー、フッターなどの要素にアクセスし、テキストを取得/更新することができます。これを利用して元のテキストを Insight Masking Web API での処理結果に更新していきます。

### コマンドライン引数の処理

まず、`argparse` モジュールを使ってコマンドライン引数を処理します。

```python
from docx import Document
import requests
import json
import os
import urllib.parse
import base64
from dotenv import load_dotenv
import argparse

load_dotenv()

# コマンドライン引数の設定
parser = argparse.ArgumentParser(description='Wordファイルのテキスト（本文、表、ヘッダー、フッター）をInsight Masking APIでマスキングします。')
parser.add_argument('docx_file', help='マスキング対象のWordファイルパス')
args = parser.parse_args()

docx_file_path = args.docx_file
```

ここでは、必須の引数として docx ファイルのパス (`docx_file`) を定義しています。

### Insight Masking Web API との連携

次に、Word ファイルから抽出したテキストを Insight Masking Web API に送信してマスキング処理を行います。`requests` ライブラリを利用すると便利です。API のエンドポイントと API キーは環境変数から読み込みます。

```python
# Insight Masking Web APIのエンドポイント (環境変数から取得)
api_url = os.getenv('INSIGHT_MASKING_API_ENDPOINT')
if not api_url:
    print("エラー: 環境変数 INSIGHT_MASKING_API_ENDPOINT が設定されていません。")
    exit()

# APIキー (環境変数 INSIGHT_MASKING_API_KEY から取得)
api_key = os.getenv('INSIGHT_MASKING_API_KEY')
if not api_key:
    print("エラー: 環境変数 INSIGHT_MASKING_API_KEY が設定されていません。")
    exit()

headers = {
    'X-Insight-Masking-API-KEY': api_key
}

# マスキング処理を行う関数
def mask_text(text):
    plain_text = base64.b64encode((urllib.parse.unquote(urllib.parse.quote(text)) + '\n').encode())
    try:
        response = requests.post(api_url, headers=headers, data=plain_text)
        response.raise_for_status()  # エラーレスポンスの場合に例外を発生させる
        result = urllib.parse.unquote(urllib.parse.quote(base64.b64decode(response.text)))
        return result.rstrip('\n')
    except requests.exceptions.RequestException as e:
        print(f"APIリクエストエラー: {e}")
        return text
    except json.JSONDecodeError as e:
        print(f"JSONデコードエラー: {e}")
        return text
```

API の URL と、API キーは以下のように `.env` ファイルに記載しておくと便利です。

```bash
cat .env
INSIGHT_MASKING_API_ENDPOINT="https://xxxx.xx.xx/webapi/xxxxxxxxx/xxxxxxxxx/plain"
INSIGHT_MASKING_API_KEY="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"
```

### docx ファイルの読み込みとテキスト要素の処理

Word ファイルを読み込み、本文、表、ヘッダー、フッターに含まれるテキストデータを取得し、マスキング処理を行います。

```python
try:
    # Wordファイルを読み込む
    document = Document(docx_file_path)

    # 本文の段落を処理
    for paragraph in document.paragraphs:
        if paragraph.text:
            original_text = paragraph.text
            masked_text = mask_text(original_text)
            paragraph.text = masked_text
            print(f"本文 - 元のテキスト: {original_text}, マスク後のテキスト: {masked_text}")

    # 表を処理
    for table in document.tables:
        for row in table.rows:
            for cell in row.cells:
                if cell.text:
                    original_text = cell.text
                    masked_text = mask_text(original_text)
                    cell.text = masked_text
                    print(f"表 - 元のテキスト: {original_text}, マスク後のテキスト: {masked_text}")

    # ヘッダーとフッターを処理
    for section in document.sections:
        header = section.header
        for paragraph in header.paragraphs:
            if paragraph.text:
                original_text = paragraph.text
                masked_text = mask_text(original_text)
                paragraph.text = masked_text
                print(f"ヘッダー - 元のテキスト: {original_text}, マスク後のテキスト: {masked_text}")

        footer = section.footer
        for paragraph in footer.paragraphs:
            if paragraph.text:
                original_text = paragraph.text
                masked_text = mask_text(original_text)
                paragraph.text = masked_text
                print(f"フッター - 元のテキスト: {original_text}, マスク後のテキスト: {masked_text}")

    # マスク処理後の Word ファイルを保存 (元のファイル名に 'masked_' を付加)
    output_file_path = f'masked_{os.path.basename(docx_file_path)}'
    document.save(output_file_path)
    print(f"Wordファイルのマスキング処理が完了しました。結果は {output_file_path} に保存されました。")

except FileNotFoundError:
    print(f"エラー: ファイル '{docx_file_path}' が見つかりません。")
except Exception as e:
    print(f"予期せぬエラーが発生しました: {e}")
```

## 実行方法

このスクリプトは、コマンドラインから以下のように実行します。

```bash
python your_script_name.py your_document.docx
```

では実際に実行してみます。以下のように、`masked_入力ファイル名` のファイルがマスキング結果として生成されました。

```bash
python3 test.py test.docx
...
マスキング処理完了。結果は masked_test.docx に保存されました。
```

入力に使用した Word ファイルと出力結果を以下に掲載します。なおマスキングの設定等により、実行結果は異なることがあります。※記載している社名、人名、サービス名等は架空のものです

入力：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/b1cb73f5-f85b-4394-a223-5997410bfffe.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/8cc96964-cab9-43fe-91c7-6c83c983b46b.png)

出力：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/cc503986-d76a-4917-b2ba-17e5af75d3c9.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/fd637f43-dfba-45ac-9988-cfcd93e6ad04.png)

簡単でしたね！

:::note info
なお、今回作成した Python スクリプトでは書式が維持されません。簡単な書式であれば、頑張れば書式の設定も可能かもしれませんが、複雑な書式を完全に再現するのは困難を伴います。Masking 後の文章を何に使用したいかで、どこまで実装を頑張るかを検討する必要があると思います。
:::

## まとめ

この記事では、`python-docx` ライブラリと Insight Masking Web API を連携させて Word ファイルに含まれる機密情報を効率的にマスキングする方法をご紹介しました。API を活用することで、Word ファイルの様々なテキスト要素のマスキングを自動化できることが確認できたのではないでしょうか？

まだ Insight Masking をご利用いただいたことがない方で、ご興味をお持ちの方は以下から是非お問合せください。

https://www.insight-tec.com/products/masking/#f

## 参考情報

https://qiita.com/takumats/items/10024720668022b48af7

https://python-docx.readthedocs.io/en/latest/

:::note info
ソースコードの生成、テスト用 Word (docx) ファイルの用意、文章の校正などに Gemini for Google Workspace を活用しています
:::

## ソースコード全体

以下に、この記事で解説した Python スクリプトの全体像を記載します。

```python
from docx import Document
import requests
import json
import os
import urllib.parse
import base64
from dotenv import load_dotenv
import argparse

load_dotenv()

# コマンドライン引数の設定
parser = argparse.ArgumentParser(description='Wordファイルのテキスト（本文、表、ヘッダー、フッター）をInsight Masking APIでマスキングします。')
parser.add_argument('docx_file', help='マスキング対象のWordファイルパス')
args = parser.parse_args()

docx_file_path = args.docx_file

# Insight Masking Web APIのエンドポイント (環境変数から取得)
api_url = os.getenv('INSIGHT_MASKING_API_ENDPOINT')
if not api_url:
    print("エラー: 環境変数 INSIGHT_MASKING_API_ENDPOINT が設定されていません。")
    exit()

# APIキー (環境変数 INSIGHT_MASKING_API_KEY から取得)
api_key = os.getenv('INSIGHT_MASKING_API_KEY')
if not api_key:
    print("エラー: 環境変数 INSIGHT_MASKING_API_KEY が設定されていません。")
    exit()

headers = {
    'X-Insight-Masking-API-KEY': api_key
}

# マスキング処理を行う関数
def mask_text(text):
    plain_text = base64.b64encode((urllib.parse.unquote(urllib.parse.quote(text)) + '\n').encode())
    try:
        response = requests.post(api_url, headers=headers, data=plain_text)
        response.raise_for_status()  # エラーレスポンスの場合に例外を発生させる
        result = urllib.parse.unquote(urllib.parse.quote(base64.b64decode(response.text)))
        return result.rstrip('\n')
    except requests.exceptions.RequestException as e:
        print(f"APIリクエストエラー: {e}")
        return text
    except json.JSONDecodeError as e:
        print(f"JSONデコードエラー: {e}")
        return text

try:
    # Wordファイルを読み込む
    document = Document(docx_file_path)

    # 本文の段落を処理
    for paragraph in document.paragraphs:
        if paragraph.text:
            original_text = paragraph.text
            masked_text = mask_text(original_text)
            paragraph.text = masked_text
            print(f"本文 - 元のテキスト: {original_text}, マスク後のテキスト: {masked_text}")

    # 表を処理
    for table in document.tables:
        for row in table.rows:
            for cell in row.cells:
                if cell.text:
                    original_text = cell.text
                    masked_text = mask_text(original_text)
                    cell.text = masked_text
                    print(f"表 - 元のテキスト: {original_text}, マスク後のテキスト: {masked_text}")

    # ヘッダーとフッターを処理
    for section in document.sections:
        header = section.header
        for paragraph in header.paragraphs:
            if paragraph.text:
                original_text = paragraph.text
                masked_text = mask_text(original_text)
                paragraph.text = masked_text
                print(f"ヘッダー - 元のテキスト: {original_text}, マスク後のテキスト: {masked_text}")

        footer = section.footer
        for paragraph in footer.paragraphs:
            if paragraph.text:
                original_text = paragraph.text
                masked_text = mask_text(original_text)
                paragraph.text = masked_text
                print(f"フッター - 元のテキスト: {original_text}, マスク後のテキスト: {masked_text}")

    # マスク処理後の Word ファイルを保存 (元のファイル名に 'masked_' を付加)
    output_file_path = f'masked_{os.path.basename(docx_file_path)}'
    document.save(output_file_path)
    print(f"Wordファイルのマスキング処理が完了しました。結果は {output_file_path} に保存されました。")

except FileNotFoundError:
    print(f"エラー: ファイル '{docx_file_path}' が見つかりません。")
except Exception as e:
    print(f"予期せぬエラーが発生しました: {e}")
```

**requirements.txt**

```
python-docx
requests
python-dotenv
```