pdf-processing について
PDF の生成、抽出、編集を行う Python 向けスキルです。OCR、表の抽出、フォーム入力、結合、透かしなどの処理に適したライブラリを選び、手順を検証できます。
主な機能
- 01ReportLab で PDF を生成
- 02pdfplumber でテキストや表を抽出
- 03pypdf で結合、分割、メタデータ編集
- 04スキャン文書を OCR 処理
- 05フォーム入力と透かし追加
- 06出力を検証し PDF の例外に対応
活用シーン
- 01書式付き PDF レポートを作成
- 02PDF からテキストや表を抽出
- 03OCR でスキャン文書を処理
- 04ファイルの結合や PDF フォーム入力
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ pixel-process-ug/ superkit-agents --skill pdf-processingインストール後、Claude Code で/pdf-processingを入力して読み込まれたか確認します。
詳細
PDF 処理
概要
PDF ドキュメントを生成、操作し、データを抽出します。このスキルでは、Python の PDF エコシステムを扱います。結合・分割・メタデータには pypdf、テキストや表の抽出には pdfplumber、生成には reportlab、OCR には pytesseract を使用し、フォームへの入力、透かしの追加、複雑なドキュメントの組み立てにも対応します。
コードを使って PDF を作成、解析、変換、または結合する必要がある場合は、このスキルを適用してください。
複数フェーズのプロセス
フェーズ 1:要件
- 操作の種類(生成、抽出、操作)を判断します
- 入力 PDF の特性(スキャン、デジタル、フォーム)を特定します
- 出力要件(形式、品質、サイズ)を定義します
- データパイプライン(ソースデータから PDF、または PDF からデータ)を計画します
- データ量とパフォーマンス要件を評価します
停止 — 操作の種類と入力の特性が明確になるまで、ライブラリを選択しないでください。
フェーズ 2:実装
- タスクに適したライブラリを選択します(判断表を参照)
- 中核となる処理ロジックを実装します
- エッジケース(破損ファイル、暗号化された PDF、混在コンテンツ)に対応します
- エラー処理と検証を追加します
- ファイルサイズと処理速度を最適化します
停止 — 暗号化、回転、スキャンされた PDF のエッジケース処理を省略しないでください。
フェーズ 3:検証
- 複数の PDF ビューアーで出力が正しく表示されることを確認します
- 該当する場合、テキストが画像化されず選択可能であることを確認します
- 抽出データの正確性を検証します
- エッジケースの PDF(大容量、暗号化、スキャン)でテストします
- アクセシビリティを確認します(必要に応じてタグ付き PDF にします)
ライブラリ選択の判断表
| タスク | ライブラリ | 理由 | 代替案 |
|---|---|---|---|
| テキスト抽出 | pdfplumber | 精度が高く、レイアウトに対応 | pypdf(よりシンプルだが、精度は低め) |
| 表の抽出 | pdfplumber | 構造化された表の解析 | camelot(表に特化したツール) |
| PDF 生成 | reportlab | 完全な制御とプロ品質 | weasyprint(HTML から PDF への変換) |
| 結合/分割 | pypdf | シンプルで、信頼性が高く、高速 | — |
| フォーム入力 | pypdf | AcroForms の読み取りと入力 | pdfrw(代替 API) |
| メタデータの読み書き | pypdf | PDF プロパティの読み書き | — |
| OCR(スキャン文書) | pytesseract + pdf2image | スキャン文書からのテキスト抽出 | EasyOCR(ディープラーニング) |
| 透かしの追加 | pypdf + reportlab | ページを重ね合わせる | — |
| HTML から PDF への変換 | weasyprint | CSS ベースのレイアウトで、サーバーに適しています | playwright(ブラウザーでのレンダリング) |
ReportLab による PDF 生成
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm, mm
from reportlab.lib.colors import HexColor
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table,
TableStyle, Image, PageBreak
)
from reportlab.lib import colors
def generate_report(output_path, data):
doc = SimpleDocTemplate(
output_path,
pagesize=A4,
topMargin=2.5*cm,
bottomMargin=2.5*cm,
leftMargin=2.5*cm,
rightMargin=2.5*cm,
)
styles = getSampleStyleSheet()
styles.add(ParagraphStyle(
name='CustomTitle',
parent=styles['Title'],
fontSize=24,
textColor=HexColor('#2F5496'),
spaceAfter=20,
))
story = []
# Title
story.append(Paragraph(data['title'], styles['CustomTitle']))
story.append(Spacer(1, 12))
# Body text
story.append(Paragraph(data['body'], styles['Normal']))
story.append(Spacer(1, 20))
# Table
table_data = [['Name', 'Value', 'Status']]
for row in data['rows']:
table_data.append([row['name'], row['value'], row['status']])
table = Table(table_data, colWidths=[6*cm, 4*cm, 4*cm])
table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), HexColor('#2F5496')),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 11),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, HexColor('#F0F4FA')]),
('TOPPADDING', (0, 0), (-1, -1), 8),
('BOTTOMPADDING', (0, 0), (-1, -1), 8),
]))
story.append(table)
doc.build(story)
カスタムページテンプレート(ヘッダー/フッター)
from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate
from datetime import datetime
def add_header_footer(canvas, doc):
canvas.saveState()
# Header
canvas.setFont('Helvetica', 9)
canvas.setFillColor(HexColor('#888888'))
canvas.drawString(2.5*cm, A4[1] - 1.5*cm, 'Company Name — Confidential')
canvas.drawRightString(A4[0] - 2.5*cm, A4[1] - 1.5*cm, f'Page {doc.page}')
# Footer
canvas.drawCentredString(A4[0]/2, 1.5*cm, f'Generated on {datetime.now():%Y-%m-%d}')
canvas.restoreState()
doc = BaseDocTemplate(output_path, pagesize=A4)
frame = Frame(2.5*cm, 2.5*cm, A4[0]-5*cm, A4[1]-5*cm)
doc.addPageTemplates([PageTemplate(id='main', frames=[frame], onPage=add_header_footer)])
テキストと表の抽出
pdfplumber
import pdfplumber
with pdfplumber.open('document.pdf') as pdf:
# Extract text from all pages
full_text = ''
for page in pdf.pages:
full_text += page.extract_text() + '\n'
# Extract tables
for page in pdf.pages:
tables = page.extract_tables()
for table in tables:
for row in table:
print(row)
# Extract text from specific area
page = pdf.pages[0]
bbox = (50, 100, 400, 300) # (x0, top, x1, bottom)
cropped = page.within_bbox(bbox)
text = cropped.extract_text()
表の抽出設定
table_settings = {
"vertical_strategy": "lines", # or "text", "explicit"
"horizontal_strategy": "lines",
"snap_tolerance": 3,
"join_tolerance": 3,
"edge_min_length": 3,
"min_words_vertical": 3,
"min_words_horizontal": 1,
}
tables = page.extract_tables(table_settings)
フォーム入力
from pypdf import PdfReader, PdfWriter
reader = PdfReader('form.pdf')
writer = PdfWriter()
writer.append(reader)
# Fill form fields
writer.update_page_form_field_values(
writer.pages[0],
{
'full_name': 'Alice Johnson',
'email': '[email protected]',
'date': '2025-03-15',
'agree_terms': '/Yes', # Checkbox
},
auto_regenerate=False,
)
with open('filled_form.pdf', 'wb') as f:
writer.write(f)
OCR(スキャンされた PDF)
from pdf2image import convert_from_path
import pytesseract
def ocr_pdf(pdf_path, language='eng'):
images = convert_from_path(pdf_path, dpi=300)
full_text = ''
for i, image in enumerate(images):
text = pytesseract.image_to_string(image, lang=language)
full_text += f'\n--- Page {i+1} ---\n{text}'
return full_text
# For better accuracy with specific layouts:
def ocr_with_config(image):
custom_config = r'--oem 3 --psm 6' # LSTM engine, assume uniform block
return pytesseract.image_to_string(image, config=custom_config)
結合と分割
from pypdf import PdfReader, PdfWriter
# Merge multiple PDFs
def merge_pdfs(input_paths, output_path):
writer = PdfWriter()
for path in input_paths:
reader = PdfReader(path)
for page in reader.pages:
writer.add_page(page)
with open(output_path, 'wb') as f:
writer.write(f)
# Split PDF by page ranges
def split_pdf(input_path, ranges, output_dir):
reader = PdfReader(input_path)
for i, (start, end) in enumerate(ranges):
writer = PdfWriter()
for page_num in range(start - 1, min(end, len(reader.pages))):
writer.add_page(reader.pages[page_num])
with open(f'{output_dir}/part_{i+1}.pdf', 'wb') as f:
writer.write(f)
# Extract specific pages
def extract_pages(input_path, page_numbers, output_path):
reader = PdfReader(input_path)
writer = PdfWriter()
for num in page_numbers:
writer.add_page(reader.pages[num - 1])
with open(output_path, 'wb') as f:
writer.write(f)
透かしの追加
from pypdf import PdfReader, PdfWriter
from reportlab.pdfgen import canvas as rl_canvas
from reportlab.lib.pagesizes import A4
from io import BytesIO
def create_watermark(text, opacity=0.1):
buffer = BytesIO()
c = rl_canvas.Canvas(buffer, pagesize=A4)
c.setFillAlpha(opacity)
c.setFont('Helvetica-Bold', 60)
c.setFillColorRGB(0.5, 0.5, 0.5)
c.translate(A4[0]/2, A4[1]/2)
c.rotate(45)
c.drawCentredString(0, 0, text)
c.save()
buffer.seek(0)
return PdfReader(buffer)
def apply_watermark(input_path, output_path, watermark_text):
watermark = create_watermark(watermark_text)
reader = PdfReader(input_path)
writer = PdfWriter()
for page in reader.pages:
page.merge_page(watermark.pages[0])
writer.add_page(page)
with open(output_path, 'wb') as f:
writer.write(f)
メタデータの処理
from pypdf import PdfReader, PdfWriter
# Read metadata
reader = PdfReader('document.pdf')
info = reader.metadata
print(f'Title: {info.title}')
print(f'Author: {info.author}')
print(f'Pages: {len(reader.pages)}')
# Write metadata
writer = PdfWriter()
writer.append(reader)
writer.add_metadata({
'/Title': 'Updated Title',
'/Author': 'Author Name',
'/Subject': 'Document Subject',
'/Creator': 'My Application',
})
with open('updated.pdf', 'wb') as f:
writer.write(f)
アンチパターン/よくある間違い
| アンチパターン | 失敗する理由 | 代わりに行うこと |
|---|---|---|
| デジタル(テキストベース)PDF に OCR を適用する | テキストをすでに抽出できる場合、処理が遅く精度も低くなります | まずテキストを抽出できるか確認し、空の場合のみ OCR を実行します |
| 暗号化された PDF を処理しない | クラッシュや、気付かないまま処理に失敗する原因になります | 暗号化を検出し、パスワードを求めるか、適切にスキップします |
| 大容量 PDF 全体をメモリに読み込む | サーバーでメモリ不足になります | ページをストリーミングするか、チャンク単位で処理します |
| ページの回転メタデータを無視する | テキスト抽出結果が文字化けします | 抽出前に回転を読み取り、適用します |
| ページサイズをハードコードする | A4 以外の文書で動作しなくなります | 元の PDF からページサイズを読み取ります |
| ファイルハンドルを閉じない | 長時間実行するプロセスでリソースリークが発生します | コンテキストマネージャー(with 文)を使用します |
| 複数のビューアーでテストせずに生成する | ビューアーによって表示に差異が生じます | Adobe Reader、Preview、Chrome でテストします |
| 設定を調整せずに表を抽出する | 列の位置ずれやセルの結合が正しく処理されません | ドキュメントの種類ごとに table_settings を調整します |
不合理な正当化を防ぐための注意事項
- 直接テキストを抽出してみる前に、OCRを使用しないでください。まずPDFの種類を確認してください。
- 暗号化の検出を省略しないでください。「ほとんどのPDFは暗号化されていない」場合でも、明示的に対処してください。
- 用紙サイズをA4と決めつけないでください。元の文書から寸法を読み取ってください。
- PDFビューアーを1つだけ使ってテストしないでください。Adobe、Preview、Chromeではレンダリングが異なります。
- メモリに配慮した方法(ストリーミング、チャンク化)を使わずに、大きなPDFを処理しないでください。
連携ポイント
| スキル | 連携方法 |
|---|---|
docx-processing | DOCXからPDFへの変換パイプライン、または形式の選択 |
xlsx-processing | ExcelのデータをPDFレポートの表に反映 |
email-composer | 生成したPDFをビジネスメールに添付 |
content-research-writer | 調査結果をPDFのホワイトペーパーとして書式設定 |
file-organizer | 出力ファイルの命名規則とディレクトリ構成 |
deployment | サーバー/CI環境でのPDF生成パイプライン |
スキルの種類
FLEXIBLE — PDFの具体的なタスクに応じて、適切なライブラリと方法を選択してください。生成にはReportLab、抽出にはpdfplumber、操作にはpypdfを使用します。必要に応じて組み合わせてください。
コメント
Markdown 対応