PDFをPython(PyPDF2)で操作する - PDF・暗号化PDFファイルの読み込み

PDFをPython(PyPDF2)で操作する - PDF・暗号化PDFファイルの読み込み
目次

今回はPDFファイルをPythonで操作する方法を紹介したいと思います。

事前準備

まずは事前準備を行いましょう。 なお、実行環境との依存モジュールのバージョンは以下として話を進めます。

  • python 3.6
  • PyPDF2 1.26.0

PyPDF2のインストール

PythonでPDFファイルの操作をする時には PyPDF2 を使います。

PyPDF2

  • PDFファイルからの情報の抽出
  • 既存のPDFを操作し、新しいファイルを生成する

を得意としています。

それでは、 pip コマンドでモジュールをインストールしましょう。

1pip install PyPDF2

PDFファイルの準備

読み込めそうなPDFファイルを入手しましょう。

今回は アメリカ大統領からの大統領令のページ から任意の大統領令のPDFファイルをダウンロードします。

見た目はこんな感じです。全部で3ページほどありました。

executive_order

PDFファイルを読み込む

ファイル読み込みを早速やってみましょう。 以下のサンプルコードでは、PDFファイル内のページ数を標準出力に表示します。

1import PyPDF2
2
3FILE_PATH = './files/executive_order.pdf'
4
5with open(FILE_PATH, mode='rb') as f:
6    reader = PyPDF2.PdfFileReader(f)
7    print(f"Number of pages: {reader.getNumPages()}")

まずは、インストールした PyPDF2 モジュールをインポートします。 その後、PDFファイルを バイナリの読み込みモード で開きます。 開いたファイルオブジェクトから、PDFを取り扱うための PdfFileReader オブジェクトを作成します。

問題なく、以下のように表示されました。

Number of pages: 3

パスワード付きPDFファイル(暗号化PDFファイル)を読み込む

次にパスワード付きのPDFファイルを読み込みます。 PDF保存時の 暗号化オプション にて、任意のパスワードを設定したファイルのことを指します。

失敗パターン

先程の大統領令のPDFファイルにパスワード(hoge1234)を付けて保存したものを executive_order_encrypted.pdf とし、 パスワード なし の時に読み込んだコードで実行してみます。

1# PDFの読み込みに失敗するコード
2import PyPDF2
3
4FILE_PATH = './files/executive_order_encrypted.pdf'
5
6with open(FILE_PATH, mode='rb') as f:
7    reader = PyPDF2.PdfFileReader(f)
8    print(f"Number of pages: {reader.getNumPages()}")

復号化が必要性が以下のようなエラーメッセージから判断できます。

PdfReadError: File has not been decrypted

暗号化PDFファイルを復号化する

ここで言う「復号化」は パスワードを解除すること を意味しています。 decrypt 関数で復号化を行います。引数にはパスワード文字列を渡します。

なお、 decrypt 関数を呼び出す前に isEncrypted を呼び出し、 ファイルが暗号化されているか否かをチェックした方が親切でしょう。

1import PyPDF2
2
3ENCRYPTED_FILE_PATH = './files/executive_order_encrypted.pdf'
4
5with open(ENCRYPTED_FILE_PATH, mode='rb') as f:        
6    reader = PyPDF2.PdfFileReader(f)
7    if reader.isEncrypted:
8        reader.decrypt('hoge1234')
9        print(f"Number of page: {reader.getNumPages()}")
Number of pages: 3

decrypt 時に NotImplementedError が表示される場合の対処法

暗号化PDFを扱う際、以下のエラーに遭遇するかもしれません。

NotImplementedError: only algorithm code 1 and 2 are supported

これは、PyPDF2 が復号化するためのロジックを実装していないことが原因で発生するエラーです。こうなっては PyPDF2 単体で解決するのは難しくなります。

Pythonコードから qpdf をキックして復号化する

いくつか調べてわかったのですが、てっとり早いのは qpdf を組み合わせて使う方法でした。 qpdf はPDFの操作をCLI上から行うためのツールです。

Windows版であれば SourceForge からインストーラを使い、Macであれば brew install qpdf で使えるようになります。

話を戻すと、NotImplementedError の原因は、復号化の処理が PyPDF2 に実装されていないことだったので、復号化の処理のみを qpdf で行えばよいのです。

 1import PyPDF2
 2import os
 3
 4ENCRYPTED_FILE_PATH = './files/executive_order_encrypted.pdf'
 5FILE_OUT_PATH = './files/executive_order_out.pdf'
 6
 7PASSWORD='hoge1234'
 8
 9with open(ENCRYPTED_FILE_PATH, mode='rb') as f:        
10    reader = PyPDF2.PdfFileReader(f)
11    if reader.isEncrypted:
12        try:
13            reader.decrypt(PASSWORD)
14        except NotImplementedError:
15            command=f"qpdf --password='{PASSWORD}' --decrypt {ENCRYPTED_FILE_PATH} {FILE_OUT_PATH};"
16            os.system(command)            
17            with open(FILE_OUT_PATH, mode='rb') as fp:
18                reader = PyPDF2.PdfFileReader(fp)
19                print(f"Number of page: {reader.getNumPages()}")

Pythonコードから qpdf コマンドをOSのコマンドとして実行し、 複合化したPDFファイルをパスワードなしの別ファイルとして保存 します。 その後、もう一度 PdfFileReader にてファイルを読み込ませる、という算段です。

なお、このサンプルコードですと、 with 句によってファイルが自動クローズされてしまうため、実際にはもう少しコードのスコープを整理してあげる方が汎用性の面で良いと思います。

まとめ

今回は PyPDF2 を使ってPDFファイルを開く方法をまとめました。

  • ファイル読み込みには PdfFileReader を使う
  • 暗号化PDFには decrypt 関数で復号化する
  • 復号化時に NotImplementedError が出る場合には、復号化の処理は qpdf で行う