PythonでPDFを扱う。ページの読み込みと分割

この記事は、PythonでPDFを扱うシリーズの最初の記事です。

  • ページの読み込みと分割 (あなたはここにいます)
  • 画像と透かしを追加する
  • ページの挿入、削除、並べ替え

PDF文書形式

現在、PDF(Portable Document Format)は、最も一般的に使用されているデータフォーマットの一つである。

1990年にAdobe社によってPDF文書の構造が定義されました。

PDF形式の背景にある考え方は、通信プロセスに関与する両者(作成者、著者、送信者、受信者)にとって、送信されたデータ/文書が全く同じに見えるということです。

PDFはPostScript形式の後継であり、ISO 32000-2:2017として標準化されています。

PDF文書の処理

Linuxでは、pdftkやpdfgrepのような強力なコマンドラインツールが利用可能です。

開発者としては、Pythonをベースにして、自由に利用できるPDFライブラリを使用した独自のソフトウェアを構築することに大きな興奮を覚えることでしょう。

この記事はちょっとしたシリーズの始まりで、これらの有用なPythonライブラリを取り上げます。

パート1では、既存のPDFの操作に焦点を当てます。

コンテンツ(テキストと画像の両方)の読み込みと抽出、単一ページの回転、ドキュメントを各ページに分割する方法を学びます。

第2部では、オーバーレイに基づく電子透かしの追加について説明します。

第3部では、PDFの作成に特化し、1ページの削除と新しい文書への再合成を学びます。

ツールおよびライブラリ

Python関連のPDFツール、モジュール、ライブラリの利用可能なソリューションの範囲は少し混乱しており、何が何であるか、どのプロジェクトが継続的にメンテナンスされているかを理解するのに時間がかかります。

私たちの調査によると、これらは最新の候補です。

  • PyPDF2: ドキュメントの情報と内容を抽出し、ドキュメントをページごとに分割し、ドキュメントをマージし、ページを切り抜き、透かしを追加するための Python ライブラリです。PyPDF2 は暗号化されていない文書と暗号化された文書の両方をサポートしています。
  • PDFMiner。PDFMiner: 完全に Python で書かれており、Python 2.4 でうまく動きます。Python 3では、PDFMiner.sixというクローンパッケージを使ってください。どちらのパッケージも、PDF文書の解析、分析、変換を行うことができます。これには、PDF 1.7だけでなく、CJK言語(中国語、日本語、韓国語)、およびさまざまなフォントタイプ(Type1、TrueType、Type3、CID)のサポートが含まれています。
  • PDFQuery。PDFMiner、lxml、pyqueryのラッパーとして実装され、「高速でフレンドリーなPDFスクレイピングライブラリ」と説明されています。その設計目的は、「できるだけ少ないコードで、PDFのセットから確実にデータを抽出すること」です。
  • tabula-py。tabula-javaのシンプルなPythonラッパーで、PDFからテーブルを読み込み、Pandas DataFrameに変換することができます。また、PDFファイルをCSV/TSV/JSONファイルに変換することも可能です。
  • pdflib for Python: Popplerライブラリの拡張で、Pythonバインディングを提供します。PDF文書の解析、分析、変換が可能です。同じ名前の商用ペンダントと混同しないように。
  • PyFPDF。PythonでPDFドキュメントを生成するためのライブラリです。FPDF PHP ライブラリから移植され、多くの例やスクリプト、派生物がある、よく知られた PDFlib 拡張の代替品です。
  • PDFTables。PDF 文書として提供されるテーブルからの抽出を提供する商用サービスです。PDFTables を SAAS として利用できるように API を提供しています。
  • PyX – Pythonグラフィックスパッケージ。PyXは、PostScript、PDF、SVGファイルを作成するためのPythonパッケージです。PostScriptの描画モデルを抽象化し、TeX/LaTeXのインターフェースと組み合わせたものです。出版に耐えうる品質の2Dや3Dプロットを作成するような複雑なタスクは、これらのプリミティブから構築されます。
  • ReportLab: PDF文書の正確な作成に特化した、野心的で産業界に強いライブラリ。オープンソース版として自由に利用できるほか、ReportLab PLUSと名付けられた商用、拡張版もあります。
  • PyMuPDF (別名 “fitz”): 軽量なPDFおよびXPSビューアであるMuPDFのPythonバインディングです。このライブラリは、PDF、XPS、OpenXPS、epub、コミック、フィクションブック形式のファイルにアクセスでき、最高のパフォーマンスと高いレンダリング品質で知られています。
  • pdfrw: PDF を読み書きするための Python ベースの純粋な PDF パーサーです。ラスタライズを行わず、ベクター形式を忠実に再現します。ReportLabと連携し、ReportLabで作成した新しいPDFで、既存のPDFの一部を再利用するのに役立ちます。

| ライブラリ|使用用途
| — | — |
| PyPDF2:読み込み
| PyMuPDF | 読み取り
| pdflib | 読み取り
| PDFTables | 読書
| tabula-py | 読書
| PDFMiner.six | 読書
| PDFQuery|読書
| pdfrw|読書、執筆、作成|index.jp
| レポートラボ|執筆・制作
| PyX|ライティング/クリエイティング
| PyFPDF|執筆・作成|以下、PyPDFに焦点を当てます。

以下、PyPDF2とPyMuPDFに焦点を当て、テキストと画像を最も簡単な方法で抽出する方法を説明します。

PyPDF2の使い方を理解するためには、公式ドキュメントと他のリソースから入手可能な多くのサンプルを組み合わせることが役立ちました。

それに対して、PyMuPDFの公式ドキュメントはより分かりやすく、ライブラリの使用もかなり速くなります。

PyPDF2 でテキストを抽出する

PyPDF2は通常のソフトウェアパッケージとして、または pip3 (Python3用) を使ってインストールすることができます。

ここでのテストは、次期Debian GNU/Linuxリリース10 “Buster “用のパッケージに基づいています。

Debianのパッケージの名前は python3-pypdf2 です。

リスト1では、まず PdfFileReader クラスをインポートしています。

次に、このクラスを使って、ドキュメントを開き、 getDocumentInfo() メソッドでドキュメント情報を、 getDocumentInfo() でページ数を、そして最初のページの内容を取り出しています。

PyPDF2は0からページをカウントし始めるので、pdf.getPage(0)の呼び出しがドキュメントの最初のページを取得するのはそのためであることに注意してください。

最終的に、抽出された情報は stdout に出力されます。

リスト1: ドキュメント情報とコンテンツを抽出する。

#!/usr/bin/python


from PyPDF2 import PdfFileReader


pdf_document = "example.pdf"
with open(pdf_document, "rb") as filehandle:
    pdf = PdfFileReader(filehandle)
    info = pdf.getDocumentInfo()
    pages = pdf.getNumPages()


print (info)
    print ("number of pages: %i" % pages)


page1 = pdf.getPage(0)
    print(page1)
    print(page1.extractText())


図1:PyPDF2を使ってPDFファイルからテキストを抽出した例
上の図1に示すように、抽出されたテキストは継続的に印刷されます。

段落や文の区切りはありません。

PyPDF2のドキュメントにあるように、すべてのテキストデータはページのコンテンツストリームで提供された順番で返されるので、それに依存すると何か驚くことになるかもしれません。

これは主に、PDF文書の内部構造と、PDFライターの処理でPDF命令のストリームがどのように生成されたかに依存します。

PyMuPDFでテキストを抽出する

PyMuPDFはPyPiのウェブサイトから入手可能で、ターミナルから以下のコマンドでパッケージをインストールします。

$ pip3 install PyMuPDF


PDF文書の文書情報の表示、ページ数の表示、テキストの抽出は、PyPDF2と同様の方法で行います(リスト2参照)。

インポートするモジュールの名前は fitz で、以前の PyMuPDF の名前に戻ります。

リスト2:PyMuPDFを使用してPDF文書から内容を抽出する。

#!/usr/bin/python


import fitz


pdf_document = "example.pdf"
doc = fitz.open(pdf_document):
print ("number of pages: %i" % doc.pageCount)
print(doc.metadata)


page1 = doc.loadPage(0)
page1text = page1.getText("text")
print(page1text)


PyMuPDFの良いところは、元の文書の構造をそのまま維持することです。

改行のある段落全体が、PDF文書にそのまま保存されます(図2参照)。

図2: 抽出されたテキストデータ

PyMuPDFによるPDFからの画像抽出

PyMuPDFでは、getPageImageList()というメソッドを使って、PDF文書から画像を抽出するのが簡単です。

リスト3はPyMuPDFのwikiページにある例に基づいており、PDFからすべての画像を取り出して、ページごとにPNGファイルとして保存しています。

画像の色空間がCMYKの場合、最初にRGBに変換されます。

リスト3: 画像を抽出する

#!/usr/bin/python


import fitz


pdf_document = fitz.open("file.pdf")
for current_page in range(len(pdf_document)):
    for image in pdf_document.getPageImageList(current_page):
        xref = image[0]
        pix = fitz.Pixmap(pdf_document, xref)
        if pix.n < 5:        # this is GRAY or RGB
            pix.writePNG("page%s-%s.png" % (current_page, xref))
        else:                # CMYK: convert to RGB first
            pix1 = fitz.Pixmap(fitz.csRGB, pix)
            pix1.writePNG("page%s-%s.png" % (current_page, xref))
            pix1 = None
        pix = None


このPythonスクリプトを400ページのPDFで実行したところ、3秒以内に117枚の画像を抽出することができ、これは驚くべきことです。

個々の画像はPNGフォーマットで保存されています。

元の画像の形式とサイズを維持するために、PNGに変換する代わりに、PyMuPDF wikiにあるスクリプトの拡張バージョンを見てみてください。

図3: 抽出された画像

PyPDF2によるPDFのページ分割

この例では、まず PdfFileReaderPdfFileWriter の両クラスをインポートする必要があります。

次に、PDFファイルを開いてリーダーオブジェクトを作成し、リーダーオブジェクトの getNumPages メソッドを使用してすべてのページにわたってループします。

forループの中で、PdfFileWriterの新しいインスタンスを作成します。

このインスタンスにはまだページが含まれていません。

そして、pdfWriter.addPage()メソッドを用いて、ライターオブジェクトに現在のページを追加します。

このメソッドには、PdfFileReader.getPage()` メソッドで取得したページオブジェクトを渡します。

次のステップでは、一意なファイル名を作成します。

これは、オリジナルのファイル名に “page” という単語を加え、さらにページ番号を加えたものを使用します。

PyPDF2はページ番号を0から数えるので、現在のページ番号に1を追加します。

最後に、新しいファイル名を “write binary” モード (モード wb) で開き、pdfWriter クラスの write() メソッドを使用して、取り出したページをディスクに保存します。

リスト4: PDFを1つのページに分割する。

#!/usr/bin/python


from PyPDF2 import PdfFileReader, PdfFileWriter


pdf_document = "example.pdf"
pdf = PdfFileReader(pdf_document)


for page in range(pdf.getNumPages()):
    pdf_writer = PdfFileWriter
    current_page = pdf.getPage(page)
    pdf_writer.addPage(current_page)


outputFilename = "example-page-{}.pdf".format(page + 1)
    with open(outputFilename, "wb") as out:
        pdf_writer.write(out)


print("created", outputFilename)


図4: PDFを分割する

テキストを含むすべてのページを検索する

このユースケースは非常に実用的で、pdfgrepと似たような働きをします。

PyMuPDFを使ったこのスクリプトは、与えられた検索文字列を含むすべてのページ番号を返します。

ページが次々と読み込まれ、 searchFor() メソッドの助けを借りて、検索文字列の出現箇所をすべて検出します。

マッチした場合は、それに応じたメッセージが stdout に出力されます。

リスト5: 与えられたテキストを検索する。

#!/usr/bin/python


import fitz


filename = "example.pdf"
search_term = "invoice"
pdf_document = fitz.open(filename):


for current_page in range(len(pdf_document)):
    page = pdf_document.loadPage(current_page)
    if page.searchFor(search_term):
        print("%s found on page %i" % (search_term, current_page))


下の図5は、400ページの本の中から “Debian GNU/Linux” という単語を検索した結果を示しています。

図5: PDF 文書の検索

まとめ

ここで紹介した方法は非常に強力である。

比較的少ない行数のコードで、簡単に結果を得ることができる。

より多くのユースケースは、PDFに透かしを追加することをカバーするパート2(近日公開!)で検討されます。

タイトルとURLをコピーしました