PDFをOCRしてDocumentsに変換する

# はじめに

本記事は、Python(Google Colab環境)でのPDFのOCR処理(Google Documentsへ変換)について解説します。

Google DriveにはPDFをOCR処理してDocumentsファイルに変換してくれる機能が存在します。
Pythonコードでの処理方法について記載します。

1. PDFからテキスト抽出を行う
2. テキスト抽出にはGoogle DriveのOCR機能を用いる
3. OCR処理によってGoogle Documentsへ変換し、テキスト抽出を行う
4. Documentsに変換された際のファイル名の英字が全角になる問題への対処

特に、4のファイル名の全角問題については情報がなかったため、同じ問題に悩まされている・悩まされた方へナレッジとして共有したいなと考えました。

# 技術要素

- Google Colaboratory (以下、Colab)
- python 3.x
- [googleapis/google-api-python-client](https://github.com/googleapis/google-api-python-client) (以下、GoogleAPIクライアント)

# ソースコード

今回の最終形のソースコードです。下記の流れで処理を行っています。

1. 認証、DriveのServiceを取得
2. 処理済みのPDFファイルはファイル名で重複チェックし対象から除外する
3. 変換対象のPDFのリストを作成
4. 対象のPDFファイルを変換

詳細については後述します。

```Python
def full_to_half(val):
  """
  全角を半角に変換する
  ※OCR後のファイル名に含まれる英字が全角になってしまう問題への対応
  """
  return val.translate(str.maketrans({chr(0xFF01 + i): chr(0x21 + i) for i in range(94)}))

import os
import glob
from google.colab import auth
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload

# 認証
auth.authenticate_user()
# Driveの操作をするためのServiceを取得
drive_service = build('drive', 'v3')

# Colab上でマウントしたローカルパス
input_path = 'drive/My Drive/PDF/INPUT' # 入力(PDF)のディレクトリパス
output_path = 'drive/My Drive/PDF/OUTPUT' # 出力先のディレクトリパス

#####
# 処理済みのPDFファイルはファイル名で重複チェックし対象から除外する
####
# 再帰的にファイルを取得
files_o = glob.glob(output_path, recursive=True)
exist_filenames = ['']
for root, dirs, files_o in os.walk(output_path):
    for filename in files_o:
        # 全角を半角に変換、拡張子を除去
        exist_filename = full_to_half(filename).replace('.gdoc', '')
        # 存在するファイル名を追加
        exist_filenames.append(exist_filename)

#####
# 変換対象のPDFのリストを作成
####
# 再帰的にファイルを取得
files = glob.glob(input_path, recursive=True)
pdf_infos = []
for root, dirs, files in os.walk(input_path):
    for filename in files:
      #print(filename)
      # 存在するファイル名は対象外
      if full_to_half(filename) in exist_filenames:
        #print('存在する')
        pass
      else:
        # 拡張子がPDF
        if filename[-4:] == '.PDF' or filename[-4:] == '.pdf':
          #print('存在しない')
          filepath = os.path.join(root, filename) # Colab上のローカルファイルパス
          pdf_infos.append({
                'path': filepath,
                'name': filename
            })

#print('ファイル数: ' + str(len(pdf_infos)))

# Google DocumentsファイルのMIMEタイプ
MIME_TYPE = 'application/vnd.google-apps.document'

#####
# 対象のPDFファイルを変換
####
for pdf_info in pdf_infos:
  pdf_path = pdf_info['path']

  #print(pdf_path)

  pdf_filename = pdf_info['name']
  # OCR後のファイル名
  #print(pdf_filename)

  # 全角の英字を半角に変換
  pdf_filename = full_to_half(pdf_filename)

  body = {
      'name': pdf_filename,
      'mimeType': MIME_TYPE,
      'parents': ['出力先のDriveディレクトリID']
  }
  try:
    media_body = MediaFileUpload(pdf_path, mimetype=MIME_TYPE, resumable=True)

    drive_service.files().create(
        body=body,
        media_body=media_body,
    ).execute()
  except:
    print('エラー: Documentsファイルの作成に失敗しました。')
    print(pdf_path)
```
# 下準備

上記のコードを実行する前に下準備を行います。

## Google Driveのマウント

ColabではGoogle Driveを仮想的にローカルファイルシステムとして扱えるようにするマウントの機能を備えています。Driveを操作しても良いのですが、GoogleAPIクライアントだとWebAPI経由となり処理に時間がかかるためパフォーマンスが低下します。そのため、処理速度を上げるためにも、できるだけマウントした中で処理を行うようにします。

Colab上でDriveをマウントするには、ランタイムに接続を行い、下記のアイコンを押下します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581277/b17db1bf-7473-fdd2-ecb9-3dac202afc83.png" width="300"/>

すると以下のコードが挿入されますので、これを実行してください。

```Python
from google.colab import drive
drive.mount('/content/drive')
```

表示されたURLをブラウザで開き、その先の認証コードをコピーしてテキストボックスに貼り付けます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581277/a7b1c329-475e-4c44-491a-25ec3900c3e0.png" width="500"/>

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581277/86392c58-0f64-1a1f-b4d3-6ea45b8821a2.png" width="500"/>

これでマウントは完了です。

## GoogleAPIクライアントのインストール

Python用のGoogleAPIクライアントをインストールしておきます。

```Python
!pip install google-api-python-client
```

# 実装

先述したソースコードの実装について解説します。

## 1. 認証、DriveのServiceを取得

GoogleAPIクライアントでDriveを操作するためにServiceオブジェクトを取得します。

Colabのauthを使って認証を行い、GoogleAPIクライアントでDriveのServiceオブジェクトを取得します。 

```Python
from google.colab import auth
from googleapiclient.discovery import build

# 認証
auth.authenticate_user()
# Driveの操作をするためのServiceを取得
drive_service = build('drive', 'v3')
```

## 2. 処理済みのPDFファイルはファイル名で重複チェックし対象から除外する

今回は1箇所に変換後のファイルを格納します。また、途中で終了した場合やPDFが追加された場合の再実行を可能とするために重複チェックを行います。

仮想ローカルのルートディレクトリ内を再帰的に探索し、変数exist_filenames(配列)に存在するファイル名を順に追加します。

```Python
# 再帰的にファイルを取得
files_o = glob.glob(output_path, recursive=True)
exist_filenames = ['']
for root, dirs, files_o in os.walk(output_path):
    for filename in files_o:
        # 全角を半角に変換、拡張子を除去
        exist_filename = full_to_half(filename).replace('.gdoc', '')
        # 存在するファイル名を追加
        exist_filenames.append(exist_filename)
```

## 3. 変換対象のPDFのリストを作成

実行時に変換対象とするPDFのリストを作成します。
2の処理で取得した対象外のファイルを一致する場合はスキップします。一致しないPDFファイルの場合は新規追加分なので、処理対象のPDFとして変数pdf_infos(配列)に追加します。

```Python
# 再帰的にファイルを取得
files = glob.glob(input_path, recursive=True)
pdf_infos = []
for root, dirs, files in os.walk(input_path):
    for filename in files:
      #print(filename)
      # 存在するファイル名は対象外
      if full_to_half(filename) in exist_filenames:
        #print('存在する')
        pass
      else:
        # 拡張子がPDF
        if filename[-4:] == '.PDF' or filename[-4:] == '.pdf':
          #print('存在しない')
          filepath = os.path.join(root, filename) # Colab上のローカルファイルパス
          pdf_infos.append({
                'path': filepath,
                'name': filename
            })
```

## 4. 対象のPDFファイルを変換

3までの処理で抽出したリストを元に、PDFファイルを変換して行きます。

Drive Serviceオブジェクトのfiles().create().execute()でDriveに新しいファイルを作成します。その際、MIMEタイプにDocumentsの値を指定すると、自動的にOCR処理されたDocumentsファイルに変換してくれます。

create()のbodyパラメータに変換後のファイル名、MIMEタイプ、親ディレクトリのIDを指定します。
media_bodyパラメータにはMediaFileUpdateでGoogleへアップロードしたPDFファイルを指定します。

```Python
for pdf_info in pdf_infos:
  pdf_path = pdf_info['path']

  #print(pdf_path)

  pdf_filename = pdf_info['name']
  # OCR後のファイル名
  #print(pdf_filename)

  # 全角の英字を半角に変換
  pdf_filename = full_to_half(pdf_filename)

  body = {
      'name': pdf_filename,
      'mimeType': MIME_TYPE,
      'parents': ['出力先のDriveディレクトリID']
  }
  try:
    media_body = MediaFileUpload(pdf_path, mimetype=MIME_TYPE, resumable=True)

    drive_service.files().create(
        body=body,
        media_body=media_body,
    ).execute()
  except:
    print('エラー: Documentsファイルの作成に失敗しました。')
    print(pdf_path)
```

## 変換後のファイル名の英字が全角になる問題への対処

PDFファイルをOCR変換して出来上がったDocumentsファイルは、英字が全角になってしまいます。
これを以下のコードで調査しました。

```Python
chars = [
  'ｍ',  # Documentsファイルからコピーした文字
  'm'  # 直打ちで入力した文字
]

# 全角（変換後のファイル名）
print(hex(ord(chars[0])))
# 半角
print(hex(ord(chars[1])))

# 全角英字を半角英字へ変換
print(hex(ord(chars[0].translate(str.maketrans({chr(0xFF01 + i): chr(0x21 + i) for i in range(94)})))))
```

実行結果

```
0xff4d
0x6d
0x6d
```

上記の実行結果より、変換後のファイル名は全角であること、半角への変換が可能であることがわかりました。

変換はこちらの記事を参考にさせていただきました。
[【Python】一行で全角と半角を相互変換する（英字＋数字＋記号） - Qiita](https://qiita.com/YuukiMiyoshi/items/6ce77bf402a29a99f1bf)

# おわりに

以上で、PDFファイルのOCR変換が実装できました。
ご参考になれば幸いです。