PythonでPDF操作を完全マスター【PyPDF2・reportlab徹底ガイド】

PDF(Portable Document Format)は、ビジネスや学術分野で最も使用される文書形式の一つです。本記事では、Pythonを使ったPDF操作の方法を、読み取りから作成、編集まで徹底解説します。初心者から上級者まで、実用的なサンプルコードとともに学べる内容です。

PDFとは

PDFは、Adobe Systemsが開発した文書形式で、異なるプラットフォーム間でも同じレイアウトを保持できる特徴があります。主な用途:

  • 公式文書・契約書
  • レポート・論文
  • 電子書籍
  • プレゼンテーション資料
  • フォーム・申請書

主要なPython PDFライブラリ

1. PyPDF2 / pypdf

  • PDF読み取り・分割・結合に特化
  • 軽量で使いやすい
  • テキスト抽出機能

2. reportlab

  • PDF作成に特化
  • 高度なレイアウト制御
  • グラフ・図表作成

3. pdfplumber

  • 高精度なテキスト・表抽出
  • 座標情報も取得可能
  • データ分析に最適

4. PyMuPDF (fitz)

  • 高速処理
  • 画像抽出・変換
  • 注釈・編集機能

環境構築とインストール

# 基本ライブラリ
pip install pypdf reportlab pdfplumber

# 追加ライブラリ
pip install PyMuPDF pillow matplotlib

PDF読み取りの基本

PyPDFでテキスト抽出

import pypdf

with open('sample.pdf', 'rb') as file:
    reader = pypdf.PdfReader(file)
    text = ""
    for page in reader.pages:
        text += page.extract_text()
    print(text)

特定ページの読み取り

import pypdf

with open('sample.pdf', 'rb') as file:
    reader = pypdf.PdfReader(file)
    page = reader.pages[0]  # 1ページ目
    print(page.extract_text())

PDFの基本情報取得

import pypdf

with open('sample.pdf', 'rb') as file:
    reader = pypdf.PdfReader(file)
    info = reader.metadata
    print(f"ページ数: {len(reader.pages)}")
    print(f"タイトル: {info.title}")
    print(f"作成者: {info.author}")

pdfplumberを使った高精度抽出

テキストと表の抽出

import pdfplumber

with pdfplumber.open('sample.pdf') as pdf:
    for page in pdf.pages:
        text = page.extract_text()
        tables = page.extract_tables()
        print(f"テキスト: {text}")
        print(f"表データ: {tables}")

CSVファイルへの表データ出力

import pdfplumber
import csv

with pdfplumber.open('table.pdf') as pdf:
    table = pdf.pages[0].extract_table()
    with open('output.csv', 'w', newline='', encoding='utf-8') as csvfile:
        writer = csv.writer(csvfile)
        writer.writerows(table)

PDF作成の基本(reportlab)

最小のPDF作成

from reportlab.pdfgen import canvas

c = canvas.Canvas("hello.pdf")
c.drawString(100, 750, "Hello World!")
c.save()

日本語対応PDF作成

from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont

pdfmetrics.registerFont(UnicodeCIDFont('HeiseiKakuGo-W5'))
c = canvas.Canvas("japanese.pdf")
c.setFont('HeiseiKakuGo-W5', 12)
c.drawString(100, 750, "こんにちは、世界!")
c.save()

複数ページのPDF作成

from reportlab.pdfgen import canvas

c = canvas.Canvas("multipage.pdf")
for i in range(3):
    c.drawString(100, 750, f"Page {i+1}")
    c.showPage()  # 新しいページ
c.save()

PDF操作の応用

PDFファイルの結合

import pypdf

merger = pypdf.PdfMerger()
files = ['file1.pdf', 'file2.pdf', 'file3.pdf']

for file in files:
    merger.append(file)

merger.write('merged.pdf')
merger.close()

PDFファイルの分割

import pypdf

with open('source.pdf', 'rb') as file:
    reader = pypdf.PdfReader(file)
    for i, page in enumerate(reader.pages):
        writer = pypdf.PdfWriter()
        writer.add_page(page)
        with open(f'page_{i+1}.pdf', 'wb') as output:
            writer.write(output)

PDFページの回転

import pypdf

with open('source.pdf', 'rb') as file:
    reader = pypdf.PdfReader(file)
    writer = pypdf.PdfWriter()
    
    for page in reader.pages:
        page.rotate(90)  # 90度回転
        writer.add_page(page)
    
    with open('rotated.pdf', 'wb') as output:
        writer.write(output)

高度なPDF作成

表付きPDFの作成

from reportlab.lib.pagesizes import A4
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle
from reportlab.lib import colors

doc = SimpleDocTemplate("table.pdf", pagesize=A4)
data = [['名前', '年齢', '職業'], ['田中', '30', 'エンジニア'], ['佐藤', '25', 'デザイナー']]

table = Table(data)
table.setStyle(TableStyle([
    ('BACKGROUND', (0, 0), (-1, 0), colors.grey),
    ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
    ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
    ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
    ('FONTSIZE', (0, 0), (-1, 0), 14),
    ('BOTTOMPADDING', (0, 0), (-1, 0), 12),
    ('BACKGROUND', (0, 1), (-1, -1), colors.beige),
    ('GRID', (0, 0), (-1, -1), 1, colors.black)
]))

doc.build([table])

グラフ付きPDF作成

from reportlab.pdfgen import canvas
from reportlab.graphics.shapes import Drawing
from reportlab.graphics.charts.barcharts import VerticalBarChart
from reportlab.graphics.renderPDF import drawToFile

# グラフ作成
drawing = Drawing(400, 200)
chart = VerticalBarChart()
chart.x = 50
chart.y = 50
chart.height = 125
chart.width = 300
chart.data = [[10, 20, 30, 40]]
chart.categoryAxis.categoryNames = ['A', 'B', 'C', 'D']
drawing.add(chart)

# PDF保存
drawToFile(drawing, fmt='PDF', fnRoot='chart')

実用的な応用例

請求書生成システム

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
import datetime

def create_invoice(invoice_data):
    c = canvas.Canvas("invoice.pdf", pagesize=A4)
    width, height = A4
    
    # ヘッダー
    c.setFont("Helvetica-Bold", 16)
    c.drawString(50, height-50, "請求書")
    
    # 日付
    c.setFont("Helvetica", 12)
    c.drawString(50, height-100, f"発行日: {datetime.date.today()}")
    
    # 明細
    y = height - 150
    for item in invoice_data:
        c.drawString(50, y, f"{item['name']}: ¥{item['price']:,}")
        y -= 20
    
    c.save()

# 使用例
invoice_data = [{'name': '商品A', 'price': 1000}, {'name': '商品B', 'price': 2000}]
create_invoice(invoice_data)

Web上のPDFダウンロード・処理

import requests
import pypdf
from io import BytesIO

def download_and_extract(url):
    response = requests.get(url)
    pdf_file = BytesIO(response.content)
    
    reader = pypdf.PdfReader(pdf_file)
    text = ""
    for page in reader.pages:
        text += page.extract_text()
    
    return text

# 使用例(架空のURL)
# text = download_and_extract('https://example.com/sample.pdf')

PDF内画像抽出

import fitz  # PyMuPDF

def extract_images(pdf_path):
    doc = fitz.open(pdf_path)
    for page_num in range(doc.page_count):
        page = doc[page_num]
        image_list = page.get_images()
        
        for img_index, img in enumerate(image_list):
            xref = img[0]
            pix = fitz.Pixmap(doc, xref)
            if pix.n - pix.alpha < 4:  # GRAY or RGBカラースペース
                pix.save(f"image_page{page_num}_{img_index}.png")
            pix = None
    
    doc.close()

PDFフォーム処理

フォームフィールドの読み取り

import pypdf

with open('form.pdf', 'rb') as file:
    reader = pypdf.PdfReader(file)
    if reader.is_encrypted:
        reader.decrypt('')
    
    fields = reader.get_form_text_fields()
    for field_name, field_value in fields.items():
        print(f"{field_name}: {field_value}")

フォームフィールドの書き込み

import pypdf

reader = pypdf.PdfReader('form.pdf')
writer = pypdf.PdfWriter()

# フォームフィールドに値を設定
writer.clone_reader_document_root(reader)
writer.update_page_form_field_values(
    writer.pages[0], {'field_name': 'new_value'}
)

with open('filled_form.pdf', 'wb') as output:
    writer.write(output)

パフォーマンス最適化

メモリ効率的な大容量PDF処理

import pypdf

def process_large_pdf(pdf_path, output_path):
    with open(pdf_path, 'rb') as input_file:
        reader = pypdf.PdfReader(input_file)
        writer = pypdf.PdfWriter()
        
        # ページ単位で処理
        for page_num in range(len(reader.pages)):
            page = reader.pages[page_num]
            # 必要な処理を実行
            writer.add_page(page)
            
            # メモリ解放
            if page_num % 100 == 0:
                print(f"処理中: {page_num}/{len(reader.pages)}")
        
        with open(output_path, 'wb') as output_file:
            writer.write(output_file)

バッチ処理

import os
import pypdf
from concurrent.futures import ThreadPoolExecutor

def process_pdf(file_path):
    try:
        with open(file_path, 'rb') as file:
            reader = pypdf.PdfReader(file)
            text = ""
            for page in reader.pages:
                text += page.extract_text()
        return f"{file_path}: {len(text)} characters"
    except Exception as e:
        return f"{file_path}: Error - {e}"

def batch_process(folder_path):
    pdf_files = [f for f in os.listdir(folder_path) if f.endswith('.pdf')]
    full_paths = [os.path.join(folder_path, f) for f in pdf_files]
    
    with ThreadPoolExecutor(max_workers=4) as executor:
        results = list(executor.map(process_pdf, full_paths))
    
    return results

エラーハンドリング

暗号化PDF対応

import pypdf
import getpass

def read_encrypted_pdf(pdf_path):
    try:
        with open(pdf_path, 'rb') as file:
            reader = pypdf.PdfReader(file)
            
            if reader.is_encrypted:
                password = getpass.getpass("PDFパスワードを入力: ")
                if not reader.decrypt(password):
                    print("パスワードが間違っています")
                    return None
            
            text = ""
            for page in reader.pages:
                text += page.extract_text()
            return text
    
    except Exception as e:
        print(f"エラー: {e}")
        return None

堅牢なPDF処理

import pypdf
import logging

def safe_pdf_process(pdf_path):
    try:
        with open(pdf_path, 'rb') as file:
            reader = pypdf.PdfReader(file)
            
            # PDFの整合性チェック
            if len(reader.pages) == 0:
                raise ValueError("PDFにページがありません")
            
            results = []
            for i, page in enumerate(reader.pages):
                try:
                    text = page.extract_text()
                    results.append(text)
                except Exception as page_error:
                    logging.warning(f"ページ {i+1} の処理でエラー: {page_error}")
                    results.append("")
            
            return results
    
    except Exception as e:
        logging.error(f"PDF処理エラー: {e}")
        return []

PDFライブラリ比較表

ライブラリ 読み取り 作成 編集 速度 日本語対応
PyPDF2/pypdf
reportlab × ×
pdfplumber × ×
PyMuPDF

トラブルシューティング

よくある問題と対処法

# 文字化け対策
import pypdf

def fix_encoding_issues(pdf_path):
    with open(pdf_path, 'rb') as file:
        reader = pypdf.PdfReader(file)
        text = ""
        for page in reader.pages:
            page_text = page.extract_text()
            # エンコーディング問題の修正
            try:
                page_text = page_text.encode('latin1').decode('utf-8')
            except:
                pass
            text += page_text
        return text

# ファイルサイズ最適化
def optimize_pdf_size(input_path, output_path):
    reader = pypdf.PdfReader(input_path)
    writer = pypdf.PdfWriter()
    
    for page in reader.pages:
        writer.add_page(page)
    
    # 圧縮設定
    writer.compress_identical_objects()
    
    with open(output_path, 'wb') as output:
        writer.write(output)

まとめ

PythonでのPDF操作は、適切なライブラリ選択により様々な処理が可能です。読み取りにはpypdfやpdfplumber、作成にはreportlab、高度な編集にはPyMuPDFを使い分けることで、効率的なPDF処理システムを構築できます。

本記事のサンプルコードを参考に、あなたのプロジェクトに最適なPDFソリューションを実装してください。エラーハンドリングとパフォーマンス最適化も忘れずに実装することで、実用的なシステムが完成します。

参考文献

  • PyPDF2/pypdf公式ドキュメント
  • reportlab公式ドキュメント
  • pdfplumber GitHub
  • PyMuPDF公式ドキュメント

らくらくPython塾 – 読むだけでマスター

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座