【 Insight Masking API 徹底活用 】PowerPoint (pptx) ファイルの機密情報を python-pptx 手軽にマスキング！

近年、情報漏洩のリスクは高まるばかり。特に、プレゼンテーション資料として作成される PowerPoint ファイルには、会議の議事録や企画提案など、秘匿性の高い情報が含まれていることも少なくありません。これらの情報を安全に共有・管理するためには、適切なマスキング処理が不可欠です。

そこで今回は、**Insight Masking Web API** と Python の **python-pptx** を組み合わせることで、PowerPoint ファイルに含まれる機密情報を手軽にマスキングする方法を探ってみたいと思います。「え、PowerPointもマスキングできるの？」と思われた方もいるかもしれません。この記事では、その方法を探るとともに、具体的なコード例を交えながら説明していきます。

## なぜ Insight Masking Web API を使うのか？

「PowerPointのマスキングなんて、手動でテキストボックスを塗りつぶせば良いのでは？」と感じる方もいるかもしれません。しかし、自動化されたマスキング処理には、以下のようなメリットが期待できます。

* 効率的な処理: 複数のファイルや多くのスライドに対しても、プログラムで一括処理できるため、大幅な時間短縮につながります。
* マスキング漏れの防止: 手作業によるミスを減らし、より確実に機密情報を保護できます。
* 複雑なオブジェクトへの対応: テキストだけでなく、図形内のテキストやグラフのデータなど、複雑なオブジェクトに含まれる情報も高精度にマスキングできます。

## 準備するもの

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

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-pptx`, `requests`, `python-dotenv` です。以下の内容で `requirements.txt` ファイルをプロジェクトのルートディレクトリに作成してください。

```
python-pptx
requests
python-dotenv
```

作成後、アクティベートされた venv 仮想環境内で以下のコマンドを実行し

```bash
python3 -m venv venv
venv/bin/activate
```

必要なライブラリをインストールします。

```bash
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 で pptx ファイルをマスキングする具体的な手順

python-pptx を使うと、スライド内のオブジェクトにアクセスし、テキストを取得/更新することができます。これを利用して元のテキストを Insight Masking Web API での処理結果に更新していきます。

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

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

```python
from pptx import Presentation
import requests
import json
import os
import urllib.parse
import base64
from dotenv import load_dotenv
import argparse

load_dotenv()

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

pptx_file_path = args.pptx_file
```

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

### Insight Masking Web API との連携

次に、各セルの値を 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"
```

### pptx ファイルの読み込みとマスキング処理の実行

PowerPointファイルを読み込み、各スライドに含まれるテキスト要素や表データを処理します。

```python
try:
    # PowerPointファイルを読み込む
    prs = Presentation(pptx_file_path)

    # 各スライドを処理
    for slide in prs.slides:
        # 各スライドの図形を処理
        for shape in slide.shapes:
            # 図形がテーブルかどうかをチェック
            if shape.has_table:
                table = shape.table
                # 各行を処理
                for row_idx in range(len(table.rows)):
                    # 各セルを処理
                    for col_idx in range(len(table.columns)):
                        cell = table.cell(row_idx, col_idx)
                        if cell.text:
                            original_text = cell.text
                            masked_text = mask_text(original_text)
                            cell.text = masked_text
                            print(f"表 - 行: {row_idx}, 列: {col_idx}, 元のテキスト: {original_text}, マスク後のテキスト: {masked_text}")
            # テキストボックス内のテキストも処理
            elif shape.has_text_frame:
                for paragraph in shape.text_frame.paragraphs:
                    for run in paragraph.runs:
                        original_text = run.text
                        masked_text = mask_text(original_text)
                        run.text = masked_text
                        print(f"テキストボックス - 元のテキスト: {original_text}, マスク後のテキスト: {masked_text}")

    # マスク処理後のプレゼンテーションを保存 (元のファイル名に 'masked_' を付加)
    output_file_path = f'masked_{os.path.basename(pptx_file_path)}'
    prs.save(output_file_path)
    print(f"PowerPointファイルのマスキング処理が完了しました。結果は {output_file_path} に保存されました。")

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

## 実行方法

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

```bash
python your_script_name.py your_presentation.pptx
```

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

```bash
python3 test.py test.pptx
...
PowerPointファイルのマスキング処理が完了しました。結果は masked_test.pptx に保存されました。
```

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

入力：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/32e52917-1d7a-4a3d-89e8-261bbb198a1e.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/bda29e78-c079-4a06-a045-c0250ef3ca10.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/447c10b9-cea4-4a25-b6ac-5a7d9c696d56.png)

出力：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/1e775ef7-a112-4e38-92ed-683260e78d0e.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/b3345d37-85f1-43c3-ad0d-4d90be8b7e0f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/deba5fe7-7c2e-4416-8dc6-ae8011ba09a4.png)

簡単でしたね！

## まとめ

この記事では、`python-pptx` を活用し、コマンドライン引数で指定した PowerPoint ファイルに含まれるテキストデータ（スライド内、表内）を、Insight Masking Web API を使ってマスキングする方法をご紹介しました。API を活用することで、面倒なマスキング処理を自動化できることが確認できたのではないでしょうか？

PowerPoint ファイルのマスキング自動化にご興味をお持ちの方は、ぜひお気軽にお問い合わせください。

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

## 参考情報

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

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

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

## ソースコード全体

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

```python
from pptx import Presentation
import requests
import json
import os
import urllib.parse
import base64
from dotenv import load_dotenv
import argparse

load_dotenv()

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

pptx_file_path = args.pptx_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:
    # PowerPointファイルを読み込む
    prs = Presentation(pptx_file_path)

    # 各スライドを処理
    for slide in prs.slides:
        # 各スライドの図形を処理
        for shape in slide.shapes:
            # 図形がテーブルかどうかをチェック
            if shape.has_table:
                table = shape.table
                # 各行を処理
                for row_idx in range(len(table.rows)):
                    # 各セルを処理
                    for col_idx in range(len(table.columns)):
                        cell = table.cell(row_idx, col_idx)
                        if cell.text:
                            original_text = cell.text
                            masked_text = mask_text(original_text)
                            cell.text = masked_text
                            print(f"表 - 行: {row_idx}, 列: {col_idx}, 元のテキスト: {original_text}, マスク後のテキスト: {masked_text}")
            # テキストボックス内のテキストも処理
            elif shape.has_text_frame:
                for paragraph in shape.text_frame.paragraphs:
                    for run in paragraph.runs:
                        original_text = run.text
                        masked_text = mask_text(original_text)
                        run.text = masked_text
                        print(f"テキストボックス - 元のテキスト: {original_text}, マスク後のテキスト: {masked_text}")

    # マスク処理後のプレゼンテーションを保存 (元のファイル名に 'masked_' を付加)
    output_file_path = f'masked_{os.path.basename(pptx_file_path)}'
    prs.save(output_file_path)
    print(f"PowerPointファイルのマスキング処理が完了しました。結果は {output_file_path} に保存されました。")

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

**requirements.txt**

```
python-pptx
requests
python-dotenv
```