Python PDF生成与解析技术

举报
柠檬🍋 发表于 2026/10/09 12:13:52 2026/10/09
【摘要】 Python PDF生成与解析技术 引言PDF(Portable Document Format)是跨平台文档交换的事实标准,凭借其格式一致性、安全性和广泛兼容性,在合同签署、报告发布、电子发票等场景中不可替代。Python社区提供了丰富的PDF处理库,从生成PDF的reportlab、fpdf2,到解析PDF的PyPDF2、PyMuPDF、pdfplumber,再到合并拆分加水印等操作,...

Python PDF生成与解析技术

引言

PDF(Portable Document Format)是跨平台文档交换的事实标准,凭借其格式一致性、安全性和广泛兼容性,在合同签署、报告发布、电子发票等场景中不可替代。Python社区提供了丰富的PDF处理库,从生成PDF的reportlab、fpdf2,到解析PDF的PyPDF2、PyMuPDF、pdfplumber,再到合并拆分加水印等操作,覆盖了PDF处理的完整生命周期。本文将系统讲解这些库的核心用法,帮助开发者掌握PDF生成、解析、编辑的完整技术栈。

reportlab:专业级PDF生成

Canvas基础绘图

reportlab是Python中最强大的PDF生成库,支持精确的页面布局控制、矢量图形绘制和复杂排版:

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.lib.colors import HexColor, black, white

c = canvas.Canvas("basic_report.pdf", pagesize=A4)
width, height = A4

# 绘制文字
c.setFont("Helvetica", 24)
c.drawString(50, height - 50, "Quarterly Sales Report")
c.setFont("Helvetica", 12)
c.drawString(50, height - 70, "2024 Q1")

# 绘制线条
c.setStrokeColor(HexColor('#4472C4'))
c.setLineWidth(2)
c.line(50, height - 80, width - 50, height - 80)

# 绘制矩形
c.setFillColor(HexColor('#E7F0FA'))
c.rect(50, height - 150, width - 100, 60, fill=1, stroke=0)
c.setFillColor(black)
c.setFont("Helvetica", 11)
c.drawString(60, height - 110, "Total Revenue: 1,250,000")
c.drawString(60, height - 130, "YoY Growth: 15.3%")

# 绘制圆形
c.setFillColor(HexColor('#4472C4'))
c.circle(width - 100, height - 120, 20, fill=1, stroke=0)
c.setFillColor(white)
c.setFont("Helvetica", 10)
c.drawCentredString(width - 100, height - 124, "Q1")

# 页脚
c.setFont("Helvetica", 8)
c.setFillColor(HexColor('#999999'))
c.drawCentredString(width / 2, 30, f"Page 1 | Generated: 2024-04-01")

c.showPage()
c.save()
print("PDF generated")

Platypus:流式文档布局

reportlab的Platypus系统提供了更高层的文档构建能力,支持自动分页、段落、表格、图片等:

from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
    PageBreak, ListFlowable, ListItem
)

doc = SimpleDocTemplate(
    "platypus_report.pdf", pagesize=A4,
    rightMargin=20*mm, leftMargin=20*mm,
    topMargin=25*mm, bottomMargin=25*mm
)

styles = getSampleStyleSheet()
title_style = ParagraphStyle(
    'CustomTitle', parent=styles['Title'],
    fontSize=22, textColor=colors.HexColor('#2F5496'), spaceAfter=10
)
heading_style = ParagraphStyle(
    'CustomHeading', parent=styles['Heading1'],
    fontSize=16, textColor=colors.HexColor('#4472C4'),
    spaceBefore=15, spaceAfter=8
)
body_style = ParagraphStyle(
    'CustomBody', parent=styles['Normal'],
    fontSize=11, leading=18, spaceAfter=6
)

story = []
story.append(Paragraph("2024 Annual Project Report", title_style))
story.append(Paragraph("Department: PMO", body_style))
story.append(Spacer(1, 10))

story.append(Paragraph("1. Overview", heading_style))
story.append(Paragraph(
    "This report summarizes the execution of major projects in 2024. "
    "A total of 32 projects were completed, including 18 technical, "
    "9 marketing, and 5 management projects. Overall completion rate "
    "reached 95%, an 8 percentage point improvement over last year.",
    body_style
))

# 表格
story.append(Paragraph("2. Statistics", heading_style))
table_data = [
    ['Category', 'Total', 'Completed', 'Rate', 'Budget'],
    ['Technical', '18', '17', '94.4%', '540'],
    ['Marketing', '9', '9', '100%', '270'],
    ['Management', '5', '4', '80%', '150'],
    ['Total', '32', '30', '93.8%', '960'],
]
table = Table(table_data, colWidths=[80, 60, 80, 60, 80])
table.setStyle(TableStyle([
    ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#4472C4')),
    ('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
    ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
    ('FONTSIZE', (0, 0), (-1, -1), 10),
    ('ALIGN', (1, 0), (-1, -1), 'CENTER'),
    ('GRID', (0, 0), (-1, -1), 0.5, colors.HexColor('#B4C7E7')),
    ('ROWBACKGROUNDS', (0, 1), (-1, -2), [colors.white, colors.HexColor('#E7F0FA')]),
    ('BACKGROUND', (0, -1), (-1, -1), colors.HexColor('#D6E4F0')),
    ('FONTNAME', (0, -1), (-1, -1), 'Helvetica-Bold'),
    ('TOPPADDING', (0, 0), (-1, -1), 8),
    ('BOTTOMPADDING', (0, 0), (-1, -1), 8),
]))
story.append(table)
story.append(Spacer(1, 15))

# 列表
story.append(Paragraph("3. Key Projects", heading_style))
items = [
    ListItem(Paragraph("Data platform: full pipeline from ingestion to analytics", body_style)),
    ListItem(Paragraph("CRM upgrade: AI recommendations and automated marketing", body_style)),
    ListItem(Paragraph("Cloud-native migration: containerized core systems", body_style)),
]
story.append(ListFlowable(items, bulletType='bullet', start='circle'))

story.append(PageBreak())
story.append(Paragraph("4. Next Year Plan", heading_style))
story.append(Paragraph(
    "Based on this year's analysis, next year will focus on: "
    "deepening data platform capabilities with AI analytics; "
    "advancing company-wide DevOps practices; "
    "strengthening information security and compliance.",
    body_style
))

doc.build(story)
print("Document generated")

自定义页面模板

from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.lib import colors
from reportlab.platypus import BaseDocTemplate, PageTemplate, Frame, Paragraph, Spacer
from reportlab.lib.styles import getSampleStyleSheet

def header_footer(canvas, doc):
    """页眉页脚回调函数"""
    canvas.saveState()
    width, height = A4
    canvas.setFont('Helvetica', 8)
    canvas.setFillColor(colors.HexColor('#999999'))
    canvas.drawString(20*mm, height - 15*mm, "Internal Document - Project Report")
    canvas.drawRightString(width - 20*mm, height - 15*mm, "Confidential")
    canvas.setStrokeColor(colors.HexColor('#CCCCCC'))
    canvas.line(20*mm, height - 18*mm, width - 20*mm, height - 18*mm)
    canvas.drawCentredString(width / 2, 15*mm, f"Page {doc.page}")
    canvas.line(20*mm, 20*mm, width - 20*mm, 20*mm)
    canvas.restoreState()

doc = BaseDocTemplate(
    "custom_template.pdf", pagesize=A4,
    leftMargin=20*mm, rightMargin=20*mm,
    topMargin=25*mm, bottomMargin=25*mm
)
frame = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height, id='normal')
template = PageTemplate(id='main', frames=frame, onPage=header_footer)
doc.addPageTemplates([template])

styles = getSampleStyleSheet()
story = [
    Paragraph("Custom Template Document", styles['Title']),
    Spacer(1, 20),
    Paragraph("This document uses a custom page template with unified header and footer.", styles['Normal']),
]
doc.build(story)

PyPDF2:PDF解析与操作

读取PDF信息

from PyPDF2 import PdfReader

reader = PdfReader("platypus_report.pdf")
print(f"Pages: {len(reader.pages)}")
print(f"Metadata: {reader.metadata}")

# 读取指定页面文本
page = reader.pages[0]
text = page.extract_text()
print(f"\nPage 1 content:\n{text[:200]}")

# 读取所有页面
full_text = ""
for i, page in enumerate(reader.pages):
    full_text += page.extract_text() + "\n"
print(f"Total characters: {len(full_text)}")

PDF合并与拆分

from PyPDF2 import PdfReader, PdfWriter
from pathlib import Path

def merge_pdfs(input_files, output_file):
    """合并多个PDF文件"""
    writer = PdfWriter()
    for input_file in input_files:
        reader = PdfReader(input_file)
        for page in reader.pages:
            writer.add_page(page)
    with open(output_file, 'wb') as f:
        writer.write(f)
    print(f"Merged: {output_file}")

def split_pdf(input_file, output_dir, pages_per_file=1):
    """将PDF拆分为多个小文件"""
    reader = PdfReader(input_file)
    total_pages = len(reader.pages)
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)
    for start in range(0, total_pages, pages_per_file):
        writer = PdfWriter()
        end = min(start + pages_per_file, total_pages)
        for i in range(start, end):
            writer.add_page(reader.pages[i])
        output_file = output_path / f"part_{start//pages_per_file + 1}.pdf"
        with open(output_file, 'wb') as f:
            writer.write(f)
    print(f"Split: {total_pages} pages -> {output_dir}")

def extract_pages(input_file, output_file, page_indices):
    """提取指定页面到新PDF"""
    reader = PdfReader(input_file)
    writer = PdfWriter()
    for idx in page_indices:
        if 0 <= idx < len(reader.pages):
            writer.add_page(reader.pages[idx])
    with open(output_file, 'wb') as f:
        writer.write(f)
    print(f"Extracted {len(page_indices)} pages -> {output_file}")

# 使用示例
merge_pdfs(['basic_report.pdf', 'platypus_report.pdf'], 'merged.pdf')
split_pdf('merged.pdf', './split_output/', pages_per_file=2)
extract_pages('merged.pdf', 'extracted.pdf', [0, 2])

PDF加密与元数据

from PyPDF2 import PdfReader, PdfWriter

def encrypt_pdf(input_file, output_file, password):
    """加密PDF文件"""
    reader = PdfReader(input_file)
    writer = PdfWriter()
    for page in reader.pages:
        writer.add_page(page)
    writer.encrypt(password)
    with open(output_file, 'wb') as f:
        writer.write(f)
    print(f"Encrypted: {output_file}")

def decrypt_pdf(input_file, output_file, password):
    """解密PDF文件"""
    reader = PdfReader(input_file)
    if reader.is_encrypted:
        reader.decrypt(password)
    writer = PdfWriter()
    for page in reader.pages:
        writer.add_page(page)
    with open(output_file, 'wb') as f:
        writer.write(f)
    print(f"Decrypted: {output_file}")

def add_metadata(input_file, output_file, metadata):
    """添加PDF元数据"""
    reader = PdfReader(input_file)
    writer = PdfWriter()
    for page in reader.pages:
        writer.add_page(page)
    writer.add_metadata(metadata)
    with open(output_file, 'wb') as f:
        writer.write(f)

# 使用
encrypt_pdf('merged.pdf', 'encrypted.pdf', 'secret123')
decrypt_pdf('encrypted.pdf', 'decrypted.pdf', 'secret123')
add_metadata('merged.pdf', 'with_meta.pdf', {
    '/Title': 'Annual Report',
    '/Author': 'PMO',
    '/Subject': '2024 Project Summary',
    '/Creator': 'Python PDF Tool',
})

PyMuPDF:高性能PDF解析

PyMuPDF(fitz)是速度最快的PDF处理库之一,支持文本提取、图像提取、页面渲染等高级功能:

import fitz  # PyMuPDF

doc = fitz.open("platypus_report.pdf")
print(f"Pages: {doc.page_count}")
print(f"Metadata: {doc.metadata}")

# 高质量文本提取
for page_num in range(doc.page_count):
    page = doc[page_num]
    text = page.get_text("text")
    print(f"\n=== Page {page_num + 1} ===")
    print(text[:300])

# 提取结构化文本(保留布局信息)
page = doc[0]
blocks = page.get_text("dict")["blocks"]
for block in blocks:
    if "lines" in block:
        for line in block["lines"]:
            for span in line["spans"]:
                print(f"Text: {span['text']}, Font: {span['font']}, Size: {span['size']:.1f}")

# 提取图片
image_list = page.get_images(full=True)
for img_index, img in enumerate(image_list):
    xref = img[0]
    base_image = doc.extract_image(xref)
    image_bytes = base_image["image"]
    image_ext = base_image["ext"]
    with open(f"extracted_image_{img_index}.{image_ext}", "wb") as f:
        f.write(image_bytes)
    print(f"Extracted: extracted_image_{img_index}.{image_ext}")

# 页面渲染为图片
pix = page.get_pixmap(matrix=fitz.Matrix(2, 2))  # 2x zoom
pix.save("page_render.png")
print("Page rendered as PNG")

doc.close()

PyMuPDF高级操作

import fitz

doc = fitz.open("platypus_report.pdf")

# 搜索文本
for page_num in range(doc.page_count):
    page = doc[page_num]
    results = page.search_for("project")
    for rect in results:
        print(f"Page {page_num+1}: found 'project' at {rect}")

# 添加注释
page = doc[0]
annot = page.add_text_annot(fitz.Point(100, 100), "Important note")
annot.set_info(title="Reviewer", content="Verify data accuracy")
annot.update()

# 添加高亮
rect = fitz.Rect(50, 50, 200, 70)
highlight = page.add_highlight_annot(rect)
highlight.update()

# 旋转页面
page.set_rotation(90)

# 插入新页面
new_page = doc.new_page(pno=0)
new_page.insert_text(
    fitz.Point(72, 72), "New inserted page",
    fontsize=14, fontname="helv"
)

doc.save("modified.pdf")
doc.close()

pdfplumber:精准表格提取

pdfplumber专注于PDF文本和表格的精准提取,特别适合处理结构化报表:

import pdfplumber

# 提取文本
with pdfplumber.open("platypus_report.pdf") as pdf:
    print(f"Total pages: {len(pdf.pages)}")
    for i, page in enumerate(pdf.pages):
        text = page.extract_text()
        if text:
            print(f"\n=== Page {i+1} ===")
            print(text[:300])

# 提取表格
with pdfplumber.open("platypus_report.pdf") as pdf:
    page = pdf.pages[0]
    tables = page.extract_tables()
    for table_idx, table in enumerate(tables):
        print(f"\n=== Table {table_idx + 1} ===")
        for row in table:
            print(row)

# 高级表格提取配置
with pdfplumber.open("platypus_report.pdf") as pdf:
    page = pdf.pages[0]
    table_settings = {
        "vertical_strategy": "text",
        "horizontal_strategy": "text",
        "snap_tolerance": 5,
        "join_tolerance": 5,
        "edge_min_length": 10,
    }
    tables = page.extract_tables(table_settings)
    for table in tables:
        for row in table:
            print(row)

# 提取文字位置信息
with pdfplumber.open("platypus_report.pdf") as pdf:
    page = pdf.pages[0]
    words = page.extract_words()
    for word in words[:10]:
        print(f"Word: {word['text']}, Pos: ({word['x0']:.0f}, {word['top']:.0f})")

pdfplumber表格提取实战

import pdfplumber
import csv
from pathlib import Path

def extract_tables_to_csv(pdf_path, output_dir):
    """提取PDF中所有表格并保存为CSV"""
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)
    with pdfplumber.open(pdf_path) as pdf:
        for page_num, page in enumerate(pdf.pages, 1):
            tables = page.extract_tables()
            for table_idx, table in enumerate(tables, 1):
                if not table:
                    continue
                csv_path = output_path / f"page{page_num}_table{table_idx}.csv"
                with open(csv_path, 'w', encoding='utf-8', newline='') as f:
                    writer = csv.writer(f)
                    writer.writerows(table)
                print(f"Saved: {csv_path} ({len(table)} rows)")

extract_tables_to_csv("platypus_report.pdf", "./extracted_tables/")

FPDF2:轻量级PDF生成

fpdf2是FPDF的现代化分支,API简洁,适合快速生成简单PDF:

from fpdf import FPDF

class CustomPDF(FPDF):
    def header(self):
        self.set_font('Helvetica', 'B', 15)
        self.cell(0, 10, 'Product Specification', 0, 1, 'C')
        self.ln(5)

    def footer(self):
        self.set_y(-15)
        self.set_font('Helvetica', 'I', 8)
        self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C')

pdf = CustomPDF()
pdf.add_page()

# 标题
pdf.set_font('Helvetica', 'B', 16)
pdf.cell(0, 10, 'Technical Parameters', 0, 1, 'L')
pdf.ln(5)

# 正文
pdf.set_font('Helvetica', '', 11)
pdf.multi_cell(0, 7, 'This document describes the technical specifications, performance parameters, and usage requirements of the product.')
pdf.ln(5)

# 表格
pdf.set_font('Helvetica', 'B', 10)
pdf.set_fill_color(68, 114, 196)
pdf.set_text_color(255, 255, 255)
pdf.cell(60, 8, 'Parameter', 1, 0, 'C', True)
pdf.cell(60, 8, 'Value', 1, 0, 'C', True)
pdf.cell(50, 8, 'Unit', 1, 1, 'C', True)

pdf.set_text_color(0, 0, 0)
pdf.set_font('Helvetica', '', 10)
params = [
    ('Power', '1500', 'W'),
    ('Voltage', '220', 'V'),
    ('Frequency', '50', 'Hz'),
    ('Dimensions', '300x200x150', 'mm'),
    ('Weight', '2.5', 'kg'),
]
for name, value, unit in params:
    pdf.cell(60, 8, name, 1, 0, 'L')
    pdf.cell(60, 8, value, 1, 0, 'C')
    pdf.cell(50, 8, unit, 1, 1, 'C')

pdf.ln(10)
pdf.set_font('Helvetica', 'B', 12)
pdf.set_text_color(0, 0, 255)
pdf.cell(0, 10, 'Notes', 0, 1)
pdf.set_text_color(0, 0, 0)
pdf.set_font('Helvetica', '', 10)
pdf.multi_cell(0, 6, '1. Do not use in humid environments\n2. Check power connection before use\n3. Regular cleaning and maintenance')

pdf.output('fpdf_demo.pdf')
print("FPDF document generated")

PDF水印添加

使用PyPDF2添加水印

from PyPDF2 import PdfReader, PdfWriter
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.lib.colors import HexColor
from io import BytesIO

def create_watermark(text, pagesize=A4, opacity=0.3):
    """创建水印PDF页面"""
    buffer = BytesIO()
    c = canvas.Canvas(buffer, pagesize=pagesize)
    width, height = pagesize
    c.saveState()
    c.setFont('Helvetica', 40)
    c.setFillColor(HexColor('#CCCCCC'))
    c.setFillAlpha(opacity)
    c.translate(width / 2, height / 2)
    c.rotate(45)
    c.drawCentredString(0, 0, text)
    c.restoreState()
    c.showPage()
    c.save()
    buffer.seek(0)
    return PdfReader(buffer)

def add_watermark(input_pdf, output_pdf, watermark_text):
    """给PDF添加水印"""
    reader = PdfReader(input_pdf)
    watermark_reader = create_watermark(watermark_text)
    watermark_page = watermark_reader.pages[0]
    writer = PdfWriter()
    for page in reader.pages:
        page.merge_page(watermark_page)
        writer.add_page(page)
    with open(output_pdf, 'wb') as f:
        writer.write(f)
    print(f"Watermark added: {output_pdf}")

add_watermark('platypus_report.pdf', 'watermarked.pdf', 'CONFIDENTIAL')

批量添加水印

from PyPDF2 import PdfReader, PdfWriter
from pathlib import Path

def batch_add_watermark(input_dir, output_dir, watermark_text):
    """批量给目录下所有PDF添加水印"""
    input_path = Path(input_dir)
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)
    watermark_reader = create_watermark(watermark_text)
    watermark_page = watermark_reader.pages[0]
    for pdf_file in input_path.glob('*.pdf'):
        reader = PdfReader(str(pdf_file))
        writer = PdfWriter()
        for page in reader.pages:
            page.merge_page(watermark_page)
            writer.add_page(page)
        output_file = output_path / pdf_file.name
        with open(output_file, 'wb') as f:
            writer.write(f)
        print(f"Processed: {pdf_file.name} -> {output_file.name}")

综合实战:PDF报告生成与处理系统

"""
PDF报告生成与处理系统
功能:数据采集 → 生成PDF报告 → 添加水印 → 提取摘要
"""
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
from PyPDF2 import PdfReader, PdfWriter
from io import BytesIO
from datetime import datetime
from pathlib import Path
import pdfplumber

class PDFReportSystem:
    def __init__(self, output_dir='./pdf_output'):
        self.output_dir = Path(output_dir)
        self.output_dir.mkdir(parents=True, exist_ok=True)
        self.styles = self._init_styles()

    def _init_styles(self):
        styles = getSampleStyleSheet()
        styles.add(ParagraphStyle(
            name='ReportTitle', fontSize=22,
            textColor=colors.HexColor('#2F5496'), alignment=1, spaceAfter=20
        ))
        styles.add(ParagraphStyle(
            name='SectionHead', fontSize=14,
            textColor=colors.HexColor('#4472C4'), spaceBefore=12, spaceAfter=6
        ))
        return styles

    def generate_report(self, title, sections, filename=None):
        """生成PDF报告"""
        if filename is None:
            filename = f"report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
        filepath = self.output_dir / filename
        doc = SimpleDocTemplate(
            str(filepath), pagesize=A4,
            leftMargin=20*mm, rightMargin=20*mm,
            topMargin=25*mm, bottomMargin=25*mm
        )
        story = []
        story.append(Paragraph(title, self.styles['ReportTitle']))
        story.append(Paragraph(
            f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
            self.styles['Normal']
        ))
        story.append(Spacer(1, 15))
        for section_title, content in sections.items():
            story.append(Paragraph(section_title, self.styles['SectionHead']))
            if isinstance(content, str):
                story.append(Paragraph(content, self.styles['Normal']))
            elif isinstance(content, list) and content and isinstance(content[0], list):
                table = Table(content)
                table.setStyle(TableStyle([
                    ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#4472C4')),
                    ('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
                    ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
                    ('FONTSIZE', (0, 0), (-1, -1), 10),
                    ('GRID', (0, 0), (-1, -1), 0.5, colors.HexColor('#B4C7E7')),
                    ('TOPPADDING', (0, 0), (-1, -1), 6),
                    ('BOTTOMPADDING', (0, 0), (-1, -1), 6),
                ]))
                story.append(table)
            story.append(Spacer(1, 10))
        doc.build(story)
        print(f"Report generated: {filepath}")
        return filepath

    def add_watermark(self, pdf_path, watermark_text="CONFIDENTIAL"):
        """添加水印"""
        reader = PdfReader(str(pdf_path))
        wm_buffer = BytesIO()
        from reportlab.pdfgen import canvas
        c = canvas.Canvas(wm_buffer, pagesize=A4)
        width, height = A4
        c.saveState()
        c.setFont('Helvetica', 40)
        c.setFillColor(colors.HexColor('#CCCCCC'))
        c.setFillAlpha(0.3)
        c.translate(width / 2, height / 2)
        c.rotate(45)
        c.drawCentredString(0, 0, watermark_text)
        c.restoreState()
        c.showPage()
        c.save()
        wm_buffer.seek(0)
        wm_page = PdfReader(wm_buffer).pages[0]
        writer = PdfWriter()
        for page in reader.pages:
            page.merge_page(wm_page)
            writer.add_page(page)
        watermarked_path = pdf_path.parent / f"wm_{pdf_path.name}"
        with open(watermarked_path, 'wb') as f:
            writer.write(f)
        print(f"Watermark added: {watermarked_path}")
        return watermarked_path

    def extract_summary(self, pdf_path):
        """提取PDF摘要信息"""
        summary = {'pages': 0, 'text_length': 0, 'tables': 0, 'preview': ''}
        with pdfplumber.open(str(pdf_path)) as pdf:
            summary['pages'] = len(pdf.pages)
            for page in pdf.pages:
                text = page.extract_text() or ''
                summary['text_length'] += len(text)
                summary['tables'] += len(page.extract_tables())
                if not summary['preview'] and text:
                    summary['preview'] = text[:200]
        return summary

# 使用示例
system = PDFReportSystem()
report_path = system.generate_report(
    "2024 Annual Operations Report",
    {
        "1. Overview": "This report analyzes the overall operations of 2024, covering sales, costs, and profit metrics.",
        "2. Key Data": [
            ['Metric', 'Value', 'YoY Change'],
            ['Revenue', '12,500,000', '+15.3%'],
            ['Cost', '8,200,000', '+8.7%'],
            ['Profit', '4,300,000', '+28.5%'],
            ['Margin', '34.4%', '+3.8pp'],
        ],
        "3. Conclusion": "Strong annual performance with significant profit growth driven by product optimization and efficiency gains.",
    }
)
wm_path = system.add_watermark(report_path, "INTERNAL")
summary = system.extract_summary(wm_path)
print(f"\nPDF Summary: {summary}")

最佳实践

库的选择策略

PDF库选择决策指南:reportlab适用于需要精确控制布局的专业级PDF生成,支持矢量图形、复杂排版和流式文档,是生成报表和文档的首选;PyPDF2适用于PDF页面级操作,如合并、拆分、加密、加水印,API简洁但文本提取能力有限;PyMuPDF(fitz)适用于高性能PDF解析和编辑,速度最快,支持文本提取、图片提取、页面渲染、注释添加等全功能;pdfplumber适用于精准表格提取和文字定位,特别适合处理结构化报表;fpdf2适用于快速生成简单PDF,API简洁但功能不如reportlab丰富。

性能优化

# 1. 大PDF解析使用PyMuPDF(最快)
import fitz
doc = fitz.open("large.pdf")
for page in doc:
    text = page.get_text("text")
doc.close()

# 2. 表格提取优先用pdfplumber
import pdfplumber
with pdfplumber.open("report.pdf") as pdf:
    for page in pdf.pages:
        tables = page.extract_tables()

# 3. 批量操作注意内存管理
from PyPDF2 import PdfReader, PdfWriter
from pathlib import Path
def batch_merge(input_dir, output_file):
    writer = PdfWriter()
    for pdf_file in sorted(Path(input_dir).glob('*.pdf')):
        reader = PdfReader(str(pdf_file))
        for page in reader.pages:
            writer.add_page(page)
    with open(output_file, 'wb') as f:
        writer.write(f)

# 4. 中文字体处理
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
pdfmetrics.registerFont(TTFont('SimSun', '/System/Library/Fonts/STSong.ttf'))

中文字体处理

from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.styles import ParagraphStyle
from reportlab.platypus import SimpleDocTemplate, Paragraph
from reportlab.lib.pagesizes import A4

# 注册中文字体
font_paths = {
    'SimSun': '/System/Library/Fonts/STSong.ttf',
    'SimHei': '/System/Library/Fonts/STHeiti Medium.ttc',
}
for name, path in font_paths.items():
    try:
        pdfmetrics.registerFont(TTFont(name, path))
    except Exception as e:
        print(f"Font {name} registration failed: {e}")

# 使用中文字体
chinese_style = ParagraphStyle('Chinese', fontName='SimSun', fontSize=12, leading=20)
doc = SimpleDocTemplate("chinese.pdf", pagesize=A4)
story = [Paragraph("Chinese text example with SimSun font.", chinese_style)]
doc.build(story)

总结

Python PDF处理技术覆盖了从生成到解析再到编辑的完整生命周期。本文系统讲解了reportlab的Canvas底层绘图和Platypus流式文档布局两种生成方式、PyPDF2的合并拆分加密解密操作、PyMuPDF的高性能文本图片提取与页面编辑、pdfplumber的精准表格提取、fpdf2的轻量级快速生成,以及水印添加和批量处理等实战场景。

关键要点回顾:第一,reportlab是专业PDF生成的首选,Canvas适合精确控制,Platypus适合流式排版。第二,PyPDF2擅长页面级操作(合并、拆分、加密、水印),但文本提取能力有限。第三,PyMuPDF是性能最强的全能库,解析速度快且功能全面。第四,pdfplumber在表格提取方面表现最佳,适合处理结构化报表。第五,fpdf2适合快速生成简单PDF,API最简洁。第六,中文字体处理需注册TTF字体文件,否则中文无法正常显示。第七,批量处理时注意内存管理,及时关闭文件对象。

在实际项目中,应根据具体需求选择合适的库组合:生成报表用reportlab,解析提取用PyMuPDF或pdfplumber,页面操作用PyPDF2,快速生成用fpdf2。合理运用这些技术,能够构建出完整的PDF自动化处理流水线,满足从文档生成到数据分析的各种业务需求。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。