PDF批量处理:转换、水印与加密解密

Python 办公自动化专题 · 一站式PDF批量处理解决方案

专题:Python 自动化办公系统学习

关键词:Python, 自动化办公, PDF批量处理, PDF转换, PDF水印, PDF加密, PDF压缩, OCR, Python办公

一、PDF格式转换概览

PDF(Portable Document Format)是日常办公中最常用的文档格式之一,但工作中我们经常需要在PDF与其他格式之间进行转换。无论是将Office文档转为PDF用于分发存档,还是将PDF转为可编辑的Word/Excel文档以便修改,批量转换都是提升办公效率的关键能力。Python生态中有多个成熟的库可以胜任PDF格式转换任务,选择合适的工具可以大幅简化开发工作。

1.1 常用PDF处理库对比

库名称适用场景优势局限
pdf2docxPDF转Word保留格式好,支持表格对复杂排版支持有限
Win32comOffice转PDF格式保真度最高仅限Windows,需安装Office
pdf2imagePDF转图片支持多种图片格式,DPI可控依赖poppler
PyMuPDF (fitz)通用PDF处理速度快,功能全面学习曲线较陡
PyPDF2/pypdfPDF基础操作纯Python,轻量功能有限

1.2 批量转换架构设计

设计批量PDF处理系统时,需要考虑输入输出目录管理、格式队列处理、异常重试机制以及日志记录。一个典型的批量转换管线包括:遍历输入目录 → 文件格式分类 → 选择对应的转换引擎 → 执行转换 → 输出到指定目录 → 记录处理日志。下面是一个基础的批量转换框架代码。

# 批量PDF转换基础框架 import os import logging from pathlib import Path from concurrent.futures import ThreadPoolExecutor, as_completed # 配置日志 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger('PDFConverter') class BatchPDFConverter: def __init__(self, input_dir, output_dir, max_workers=4): self.input_dir = Path(input_dir) self.output_dir = Path(output_dir) self.max_workers = max_workers self.output_dir.mkdir(parents=True, exist_ok=True) def scan_files(self, extensions): """扫描输入目录中的指定扩展名文件""" files = [] for ext in extensions: files.extend(self.input_dir.rglob(f'*{ext}')) logger.info(f'发现 {len(files)} 个待处理文件') return files def process_file(self, file_path): """处理单个文件(子类重写此方法实现具体转换逻辑)""" raise NotImplementedError def run_batch(self, extensions): files = self.scan_files(extensions) with ThreadPoolExecutor(max_workers=self.max_workers) as executor: futures = {executor.submit(self.process_file, f): f for f in files} for future in as_completed(futures): file = futures[future] try: future.result() logger.info(f'转换成功: {file.name}') except Exception as e: logger.error(f'转换失败: {file.name} - {e}')

1.3 转换质量对比分析

不同的转换方案在格式保真度、转换速度和依赖环境上各有优劣。Win32com方案通过调用Microsoft Office应用程序接口进行转换,格式保真度最高,但速度较慢且仅限Windows平台。libreoffice方案跨平台性能好,但需要安装LibreOffice。纯Python方案(如pdf2docx)轻量便捷,适合简单文档的批量处理。了解这些差异有助于在实际项目中选择最合适的方案。

# 转换质量评估:对比不同方案的输出效果 import time from collections import defaultdict class ConverterBenchmark: def __init__(self): self.results = defaultdict(dict) def measure(self, converter_name, func, *args, **kwargs): start = time.time() try: result = func(*args, **kwargs) elapsed = time.time() - start self.results[converter_name] = {'success': True, 'time': elapsed} logger.info(f'{converter_name}: {elapsed:.2f}s') return result except Exception as e: elapsed = time.time() - start self.results[converter_name] = {'success': False, 'time': elapsed, 'error': str(e)} logger.error(f'{converter_name} 失败: {e}') def report(self): print('=== 转换性能对比 ===') for name, data in self.results.items(): status = '✓' if data['success'] else '✗' print(f'{status} {name}: {data["time"]:.2f}s')

要点总结:PDF格式转换没有"万能方案",应根据具体场景选择最适合的工具。追求格式保真时选Win32com,追求跨平台时选libreoffice,追求轻量便捷时选pdf2docx/pdf2image。批量处理时务必加入异常处理和日志记录机制。

二、Office转PDF

Office文档(Word、Excel、PowerPoint)转PDF是最常见的办公需求之一。在文档分发、合同签署、资料存档等场景中,将Office文档转换为PDF可以确保文档在不同设备上的显示一致性。Python提供了多种实现方案,其中Win32com方案通过COM接口调用Office应用程序,能够实现最高质量的格式保真转换。

2.1 Word转PDF

使用Win32com库调用Microsoft Word应用程序接口进行转换。这种方法能够完整保留Word文档中的字体、段落、表格、图片等所有元素,转换质量与在Word中手动"另存为PDF"完全一致。需要注意的是,执行转换的机器上必须安装Microsoft Office软件。

# Word转PDF - 使用Win32com方案 import win32com.client import os from pathlib import Path def word_to_pdf(word_path, pdf_path=None): """将Word文档转换为PDF""" word = win32com.client.Dispatch('Word.Application') word.Visible = False word.DisplayAlerts = False try: word_path = str(Path(word_path).resolve()) if pdf_path is None: pdf_path = str(Path(word_path).with_suffix('.pdf')) else: pdf_path = str(Path(pdf_path).resolve()) doc = word.Documents.Open(word_path) doc.SaveAs(pdf_path, FileFormat=17) # 17 = wdFormatPDF doc.Close() return pdf_path finally: word.Quit() # 批量转换示例 def batch_word_to_pdf(input_dir, output_dir): input_dir = Path(input_dir) output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) for doc_file in input_dir.glob('*.docx'): pdf_path = output_dir / (doc_file.stem + '.pdf') word_to_pdf(str(doc_file), str(pdf_path)) print(f'已转换: {doc_file.name} → {pdf_path.name}')

2.2 Excel和PPT转PDF

Excel转PDF和PPT转PDF同样可以通过Win32com实现。Excel转换时需要注意工作表的选择,通常需要将整个工作簿导出为PDF,或指定特定的工作表范围。PPT转换时则需要考虑幻灯片大小和笔记页的处理。

# Excel转PDF def excel_to_pdf(excel_path, pdf_path=None): excel = win32com.client.Dispatch('Excel.Application') excel.Visible = False excel.DisplayAlerts = False try: wb = excel.Workbooks.Open(str(Path(excel_path).resolve())) if pdf_path is None: pdf_path = str(Path(excel_path).with_suffix('.pdf')) # 0 = xlPrintNoSummary, 可指定工作表范围 wb.ExportAsFixedFormat(0, str(Path(pdf_path).resolve())) wb.Close() return pdf_path finally: excel.Quit() # PPT转PDF def ppt_to_pdf(ppt_path, pdf_path=None): ppt = win32com.client.Dispatch('PowerPoint.Application') ppt.Visible = False try: prs = ppt.Presentations.Open(str(Path(ppt_path).resolve())) if pdf_path is None: pdf_path = str(Path(ppt_path).with_suffix('.pdf')) prs.SaveAs(str(Path(pdf_path).resolve()), 32) # 32 = ppSaveAsPDF prs.Close() return pdf_path finally: ppt.Quit()

2.3 批量转换整合

将上述功能整合为统一的批量转换接口,可以根据输入文件扩展名自动选择对应的转换引擎。同时建议加入文件锁定检查,防止Office文件被其他程序占用时转换失败。批量转换时还可以通过统计报告了解整体处理情况。

# 统一批量转换入口 EXTENSION_MAP = { '.docx': word_to_pdf, '.doc': word_to_pdf, '.xlsx': excel_to_pdf, '.xls': excel_to_pdf, '.pptx': ppt_to_pdf, '.ppt': ppt_to_pdf, } def auto_convert_to_pdf(input_path, output_dir=None): ext = Path(input_path).suffix.lower() converter = EXTENSION_MAP.get(ext) if converter is None: raise ValueError(f'不支持的格式: {ext}') if output_dir: pdf_name = Path(input_path).stem + '.pdf' pdf_path = str(Path(output_dir) / pdf_name) else: pdf_path = None return converter(input_path, pdf_path)

三、PDF转图片

将PDF页面转换为图片是文档展示、缩略图生成、OCR识别前处理等场景中的常见需求。Python的pdf2image库是对poppler-utils的轻量封装,可以高效地将PDF页面转换为PIL/Pillow图像对象,支持灵活的DPI控制、输出格式选择和页面范围指定。

3.1 pdf2image基础使用

pdf2image库的核心函数是convert_from_path和convert_from_bytes,两者功能类似但输入源不同。使用时可指定DPI、输出格式(JPEG/PNG/TIFF)、页面范围等参数。DPI值直接影响输出图片的质量和大小:标准屏幕显示使用150-200DPI,打印质量需要300DPI以上。转换后的PIL Image对象可以进一步处理,如裁剪、调整大小或保存为文件。

# PDF转图片基础操作 from pdf2image import convert_from_path from pathlib import Path def pdf_to_images(pdf_path, output_dir, dpi=200, fmt='jpeg'): """将PDF的每一页转换为图片""" output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) # 转换PDF为PIL图像列表 images = convert_from_path( pdf_path, dpi=dpi, fmt=fmt, output_folder=str(output_dir), thread_count=4 ) pdf_stem = Path(pdf_path).stem saved_files = [] for i, img in enumerate(images): out_path = output_dir / f'{pdf_stem}_page_{i+1:03d}.{fmt}' img.save(str(out_path), fmt.upper()) saved_files.append(str(out_path)) print(f'已保存: {out_path.name}') return saved_files # 使用示例:将PDF转为300DPI的PNG图片 # pdf_to_images('report.pdf', 'output_images', dpi=300, fmt='png')

3.2 图片格式选择与质量控制

不同图片格式适用场景不同:JPEG格式体积小,适合用于Web展示和预览,但有损压缩会导致文字边缘模糊;PNG格式无损压缩,适合用于OCR识别前处理,能完整保留文字细节;TIFF格式支持多页,常用于扫描文档存档。在实际项目中,可以根据后续处理需求灵活选择输出格式和压缩质量参数。

# 按需选择图片格式与质量控制 from PIL import Image def pdf_to_images_optimized(pdf_path, output_dir, use_for_ocr=False): """根据用途选择最佳输出配置""" if use_for_ocr: # OCR识别前处理:高DPI + PNG无损格式 fmt = 'png' dpi = 300 print('图片格式: PNG (适合OCR)') else: # 一般预览:中等DPI + JPEG压缩 fmt = 'jpeg' dpi = 150 print('图片格式: JPEG (适合预览)') images = convert_from_path(pdf_path, dpi=dpi) output_dir = Path(output_dir) output_dir.mkdir(exist_ok=True, parents=True) pdf_stem = Path(pdf_path).stem paths = [] for i, img in enumerate(images): if fmt == 'jpeg': # JPEG转RGB模式再保存 img = img.convert('RGB') out_path = output_dir / f'{pdf_stem}_{i+1:03d}.jpg' img.save(str(out_path), 'JPEG', quality=90) else: out_path = output_dir / f'{pdf_stem}_{i+1:03d}.png' img.save(str(out_path), 'PNG') paths.append(str(out_path)) return paths

3.3 批量转换与OCR前处理

在文档数字化工作流中,PDF转图片通常是OCR识别的前置步骤。将PDF页面转换为高质量图片后,送入Tesseract OCR引擎进行文字识别。批量处理时,可以利用多线程并发转换多个PDF文件,大幅提升处理效率。同时建议在转换前进行图片预处理,如灰度化、二值化、去噪等,以提高OCR识别准确率。

# 批量PDF转图片 + OCR预处理 from concurrent.futures import ProcessPoolExecutor import multiprocessing def preprocess_for_ocr(image): """图片预处理:提升OCR识别率""" img = image.convert('L') # 灰度化 # 二值化处理(阈值自适应) threshold = 128 img = img.point(lambda x: 255 if x > threshold else 0) return img def batch_pdf_to_images(pdf_dir, output_dir, max_workers=None): """多进程批量转换PDF为图片""" pdf_dir = Path(pdf_dir) output_dir = Path(output_dir) max_workers = max_workers or multiprocessing.cpu_count() pdf_files = list(pdf_dir.glob('*.pdf')) print(f'发现 {len(pdf_files)} 个PDF文件,使用 {max_workers} 个进程') with ProcessPoolExecutor(max_workers=max_workers) as executor: futures = [] for pdf in pdf_files: task_dir = output_dir / pdf.stem future = executor.submit(pdf_to_images, str(pdf), str(task_dir)) futures.append(future) for future in futures: future.result()

四、PDF转Word/Excel

将PDF文档转换为可编辑的Word或Excel格式,是日常办公中最高频的需求之一。无论是需要修改合同条款、提取表格数据,还是重新排版文档内容,可靠的PDF转换工具都能大幅提升工作效率。Python的pdf2docx库是目前最成熟的PDF转Word解决方案之一,它通过解析PDF中的文本、图片和表格元素,重建为与原始文档结构尽可能接近的Word文档。

4.1 pdf2docx基础转换

pdf2docx库将PDF中的每一页解析为文档对象模型,包括段落、图片、表格等元素的识别和重建。该库特别适合处理以文本为主的PDF文档,对于简单排版的文档可以达到较高的格式保留率。使用转换函数前需要安装pdf2docx库(pip install pdf2docx),转换过程会自动处理文字提取、字体映射、段落排列等复杂操作。

# PDF转Word基础示例 from pdf2docx import Converter from pathlib import Path def pdf_to_word(pdf_path, docx_path=None): """将PDF转换为Word文档""" pdf_path = str(Path(pdf_path).resolve()) if docx_path is None: docx_path = str(Path(pdf_path).with_suffix('.docx')) cv = Converter(pdf_path) cv.convert(docx_path, start=0, end=None) cv.close() return docx_path # 分页转换(可选指定页码范围) def pdf_to_word_pages(pdf_path, docx_path, pages): """将PDF的指定页面转换为Word""" cv = Converter(pdf_path) cv.convert(docx_path, pages=pages) # pages=[1,3,5] 只转换第1,3,5页 cv.close()

4.2 表格保留策略

PDF中的表格是转换的难点。pdf2docx内置了表格检测算法,可以识别PDF中的表格区域并重建为Word表格对象。对于复杂表格(合并单元格、嵌套表格、跨页表格),可能需要手动调整。为了提高表格识别率,可以在转换前设置不同的表格检测参数,或在转换后使用python-docx库对表格进行二次调整。

# 增强表格保留策略 from pdf2docx import Converter from pdf2docx.config import ConverterConfig def pdf_to_word_with_tables(pdf_path, docx_path): """增强表格保留的PDF转Word""" # 自定义转换配置:强化表格检测 config = ConverterConfig() config.table.max_edge_distance = 20 # 增大表格边缘检测距离 config.table.min_table_edges = 4 # 最小表格边数 cv = Converter(pdf_path) try: cv.convert(docx_path, config=config) finally: cv.close() # 转换后提取表格数据到Excel from docx import Document import pandas as pd def extract_tables_to_excel(docx_path, excel_path): """从Word文档提取所有表格到Excel""" doc = Document(docx_path) with pd.ExcelWriter(excel_path) as writer: for i, table in enumerate(doc.tables): data = [[cell.text.strip() for cell in row.cells] for row in table.rows] df = pd.DataFrame(data) df.to_excel(writer, sheet_name=f'Table_{i+1}', index=False)

4.3 批量转换PDF到Word

在实际工作中,往往需要批量处理大量PDF文档。批量转换需要处理好文件命名、输出目录组织、错误处理和进度跟踪等问题。下面展示一个完整的批量转换脚本,支持多线程并发处理、文件覆盖策略和详细的转换报告生成。

# 批量PDF转Word + 转换报告 from concurrent.futures import ThreadPoolExecutor, as_completed import json from datetime import datetime def batch_pdf_to_word(input_dir, output_dir, report_path=None): input_dir = Path(input_dir) output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) results = [] pdf_files = list(input_dir.glob('*.pdf')) with ThreadPoolExecutor(max_workers=4) as executor: futures = {} for pdf in pdf_files: docx_path = output_dir / (pdf.stem + '.docx') future = executor.submit(pdf_to_word, str(pdf), str(docx_path)) futures[future] = pdf.name for future in as_completed(futures): name = futures[future] try: future.result() results.append({'file': name, 'status': '成功'}) print(f'✓ {name}: 转换成功') except Exception as e: results.append({'file': name, 'status': f'失败: {e}'}) print(f'✗ {name}: 转换失败 - {e}') # 生成转换报告 if report_path: report = { 'time': datetime.now().isoformat(), 'total': len(results), 'success': sum(1 for r in results if r['status'] == '成功'), 'failed': sum(1 for r in results if r['status'] != '成功'), 'details': results } with open(report_path, 'w', encoding='utf-8') as f: json.dump(report, f, ensure_ascii=False, indent=2) return results

五、水印添加

为PDF文档添加水印是保护文档版权、标识文档状态(如"草稿"、"机密"、"仅供参考")的常用手段。Python提供了多种方案实现PDF水印添加,包括使用PyPDF2/pypdf库合并水印页面、使用reportlab库动态生成水印、使用PyMuPDF(fitz)直接在页面内容上绘制水印。不同方案在灵活性和性能上各有优劣,可以根据具体需求选择。

5.1 文字水印

文字水印是最常见的水印形式。通常使用reportlab库生成包含水印文字的PDF页面,然后使用PyPDF2将水印页面与原始PDF页面合并。水印的文字内容、字体大小、颜色、透明度、旋转角度和位置都可以灵活配置。合理的透明度设置(通常在0.1-0.3之间)可以让水印既清晰可见又不遮挡正文内容。

# 添加文字水印 from reportlab.lib.units import cm from reportlab.pdfgen import canvas import PyPDF2 def create_watermark(text, output_path='watermark.pdf'): """创建水印PDF页面(斜向排列的文字水印)""" c = canvas.Canvas(output_path, pagesize=(595.27, 841.89)) # A4尺寸 c.saveState() c.setFillColorRGB(0.5, 0.5, 0.5, alpha=0.2) # 透明度20% c.setFont('Helvetica', 48) c.translate(297.64, 420.94) c.rotate(45) c.drawString(-200, 0, text) c.restoreState() c.save() return output_path def add_text_watermark(input_pdf, output_pdf, text='机密'): """为PDF添加文字水印""" watermark_path = create_watermark(text) with open(input_pdf, 'rb') as f_in, open(watermark_path, 'rb') as f_wm: reader = PyPDF2.PdfReader(f_in) watermark_page = PyPDF2.PdfReader(f_wm).pages[0] writer = PyPDF2.PdfWriter() for page in reader.pages: page.merge_page(watermark_page) writer.add_page(page) with open(output_pdf, 'wb') as f_out: writer.write(f_out) return output_pdf

5.2 图片水印

图片水印通常用于添加公司Logo、签名图章或自定义图案。实现图片水印时,可以将图片嵌入到水印PDF页面中,然后与原始PDF合并。图片水印需要考虑图片的尺寸、位置和透明度,确保水印既醒目又不影响文档阅读体验。对批量处理的场景,图片水印只需要生成一次水印页面,然后对所有文档复用。

# 添加图片水印(如公司Logo) def create_image_watermark(image_path, output_path='img_watermark.pdf'): """创建包含图片水印的PDF页面""" from reportlab.lib.utils import ImageReader c = canvas.Canvas(output_path, pagesize=(595.27, 841.89)) c.saveState() c.setFillColorRGB(0.5, 0.5, 0.5, alpha=0.3) # 在页面中央绘制图片水印 img = ImageReader(image_path) img_width, img_height = 200, 100 # 设定显示尺寸 x = (595.27 - img_width) / 2 y = (841.89 - img_height) / 2 c.drawImage(img, x, y, width=img_width, height=img_height, preserveAspectRatio=True) c.restoreState() c.save() return output_path def batch_add_image_watermark(input_dir, output_dir, logo_path): """批量添加图片水印""" output_dir = Path(output_dir) output_dir.mkdir(exist_ok=True, parents=True) watermark_pdf = create_image_watermark(logo_path) with open(watermark_pdf, 'rb') as f_wm: watermark_page = PyPDF2.PdfReader(f_wm).pages[0] for pdf_file in Path(input_dir).glob('*.pdf'): with open(pdf_file, 'rb') as f_in: reader = PyPDF2.PdfReader(f_in) writer = PyPDF2.PdfWriter() for page in reader.pages: page.merge_page(watermark_page) writer.add_page(page) out_path = output_dir / pdf_file.name with open(out_path, 'wb') as f_out: writer.write(f_out) print(f'已添加水印: {pdf_file.name}')

5.3 动态水印(页号/日期/用户名)

动态水印可以根据页面信息实时生成,例如在每一页显示不同的页号、总页数、打印日期或当前用户名。这种水印需要使用reportlab库为每一页单独生成水印内容,然后逐页合并。动态水印常用于合同文档、法律文件或内部审核文档,可以在每页页脚显示"文档编号 + 打印时间 + 用户名"组合信息。

# 动态水印:每页显示页码+日期+用户名 import getpass from datetime import datetime def add_dynamic_watermark(input_pdf, output_pdf, username=None): """为PDF每页添加动态水印(页号+日期+用户)""" if username is None: username = getpass.getuser() date_str = datetime.now().strftime('%Y-%m-%d %H:%M') reader = PyPDF2.PdfReader(input_pdf) writer = PyPDF2.PdfWriter() total = len(reader.pages) for i, page in enumerate(reader.pages): # 为每页生成动态水印 watermark_text = f'{username} | {date_str} | 第{i+1}/{total}页' # 创建单页水印 c = canvas.Canvas('tmp_wm.pdf', pagesize=(595.27, 841.89)) c.saveState() c.setFillColorRGB(0.3, 0.3, 0.3, alpha=0.15) c.setFont('Helvetica', 36) c.translate(297.64, 420.94) c.rotate(45) c.drawString(-200, 0, watermark_text) c.restoreState() c.save() with open('tmp_wm.pdf', 'rb') as f_wm: wm_page = PyPDF2.PdfReader(f_wm).pages[0] page.merge_page(wm_page) writer.add_page(page) with open(output_pdf, 'wb') as f_out: writer.write(f_out) Path('tmp_wm.pdf').unlink(missing_ok=True) return output_pdf

六、加密与解密

PDF加密是保护敏感文档内容的重要安全机制。通过设置打开密码,可以限制未经授权的用户访问文档内容;通过设置权限密码,可以控制打印、复制、修改等操作。Python的pypdf库提供了完整的PDF加密解密支持,包括设置用户密码、所有者密码和权限控制参数。在企业文档管理系统中,批量加密是合规性要求的常见功能。

6.1 PDF密码添加

使用pypdf库的PdfWriter.encrypt方法可以轻松为PDF文档添加密码保护。该方法支持设置用户密码(打开文档时需要输入的密码)和所有者密码(修改权限时需要输入的密码)。当只设置用户密码时,所有者密码默认与用户密码相同。加密算法支持128位RC4和AES加密,推荐使用AES加密以获得更高的安全性。

# PDF加密示例 from pypdf import PdfReader, PdfWriter def encrypt_pdf(input_pdf, output_pdf, user_password, owner_password=None): """为PDF添加密码保护""" reader = PdfReader(input_pdf) writer = PdfWriter() # 复制所有页面 for page in reader.pages: writer.add_page(page) # 设置加密(AES-128加密) writer.encrypt( user_password=user_password, owner_password=owner_password or user_password, permissions_flag=-44 # -44 = 允许打印和复制,禁止修改 ) with open(output_pdf, 'wb') as f: writer.write(f) return output_pdf # 使用示例 # encrypt_pdf('document.pdf', 'document_encrypted.pdf', 'user123', 'admin456')

6.2 权限控制详解

PDF权限控制通过permissions_flag参数来实现精细化控制。该参数使用位掩码标志,可以组合不同的权限。常见的权限控制包括:允许打印(bit 3)、允许修改内容(bit 4)、允许复制内容(bit 5)、允许修改注释(bit 6)、允许填写表单(bit 9)、允许提取文字和图片(bit 10)等。通过组合这些标志位,可以实现精确的文档使用权限控制。

# 精细化权限控制 from pypdf.constants import Permission def encrypt_with_permissions(input_pdf, output_pdf, user_pwd, owner_pwd): """带精细权限控制的PDF加密""" reader = PdfReader(input_pdf) writer = PdfWriter() for page in reader.pages: writer.add_page(page) # 仅允许打印和填写表单,禁止复制和修改 permissions = ( Permission.PRINT | Permission.FILL_FORM | Permission.MODIFY_ANNOTATIONS ) writer.encrypt( user_password=user_pwd, owner_password=owner_pwd, permissions_flag=permissions ) with open(output_pdf, 'wb') as f: writer.write(f) return output_pdf # 批量加密:为目录下所有PDF添加相同密码 def batch_encrypt(input_dir, output_dir, password, owner_pwd=None): output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) for pdf_file in Path(input_dir).glob('*.pdf'): out_path = output_dir / pdf_file.name encrypt_pdf(str(pdf_file), str(out_path), password, owner_pwd) print(f'已加密: {pdf_file.name}')

6.3 批量解密与密码破解入门

PDF解密同样可以通过pypdf库实现。对于知道密码的文档,使用PdfReader.decrypt方法可以轻松解密。在某些合法场景下(如找回遗忘的密码),可能需要使用密码破解技术。字典攻击是常见的破解方法,通过预定义的密码列表逐个尝试。需要注意的是,密码破解仅应在合法授权的场景下使用,切勿用于非法用途。

# PDF批量解密 def decrypt_pdf(input_pdf, output_pdf, password): """解密受密码保护的PDF""" reader = PdfReader(input_pdf) if reader.is_encrypted: if reader.decrypt(password): writer = PdfWriter() for page in reader.pages: writer.add_page(page) with open(output_pdf, 'wb') as f: writer.write(f) return True return False # 密码字典攻击(仅用于合法授权场景) def pdf_password_crack(pdf_path, wordlist_path): """使用字典攻击尝试破解PDF密码(仅供合法授权使用)""" reader = PdfReader(pdf_path) if not reader.is_encrypted: print('PDF未加密') return None with open(wordlist_path, 'r', encoding='utf-8', errors='ignore') as f: for i, line in enumerate(f): password = line.strip() if password: try: if reader.decrypt(password): print(f'找到密码(第{i+1}次尝试): {password}') return password except: pass if (i + 1) % 1000 == 0: print(f'已尝试 {i+1} 个密码...') print('未找到密码') return None

七、PDF优化压缩

PDF文件体积过大不仅占用存储空间,还会影响传输和加载速度。PDF压缩的核心策略包括:降低图片分辨率、移除冗余数据、优化字体嵌入、压缩页面内容流等。Python中可以使用pypdf库进行基础压缩,也可以使用专业的Ghostscript工具获得更高的压缩率。在压缩质量和文件体积之间找到平衡点,是PDF优化的重要考量。

7.1 使用pypdf进行基础压缩

pypdf库提供了基础的PDF压缩功能,主要通过压缩页面内容流来实现。对于包含大量文本的PDF,pypdf可以有效地缩减文件体积。对于含大量图片的PDF,则需要降低图片质量或分辨率才能获得明显的压缩效果。pypdf的compress_content_streams方法可以对页面内容流进行压缩编码。

# 使用pypdf进行基础压缩 from pypdf import PdfReader, PdfWriter def compress_pdf_pypdf(input_pdf, output_pdf): """使用pypdf进行PDF基础压缩""" reader = PdfReader(input_pdf) writer = PdfWriter() for page in reader.pages: writer.add_page(page) page.compress_content_streams() # 压缩页面内容流 # 移除未使用的对象(元数据、空流等) for page in writer.pages: for key in ['/Metadata']: if key in page: del page[key] with open(output_pdf, 'wb') as f: writer.write(f) size_orig = Path(input_pdf).stat().st_size size_comp = Path(output_pdf).stat().st_size ratio = (1 - size_comp / size_orig) * 100 print(f'压缩率: {ratio:.1f}%') return output_pdf

7.2 使用Ghostscript专业压缩

Ghostscript是一个功能强大的PDF处理引擎,提供了专业的PDF压缩方案。通过设置不同的设备参数和分辨率,可以实现从低压缩高保真到高压缩低保真的多种级别。Ghostscript支持将PDF重新生成,在此过程中可以大幅缩减文件体积,尤其适用于包含大量图片的PDF文档。

# 使用Ghostscript进行专业压缩 import subprocess from pathlib import Path def compress_pdf_ghostscript(input_pdf, output_pdf, quality='ebook'): """使用Ghostscript压缩PDF quality参数: screen(最低质量), ebook(中等), printer(高), prepress(最高) """ quality_map = { 'screen': '/screen', # 72 DPI, 适合屏幕预览 'ebook': '/ebook', # 150 DPI, 适合电子书 'printer': '/printer', # 300 DPI, 适合打印 'prepress': '/prepress', # 300 DPI+, 适合印刷 } gs_device = quality_map.get(quality, '/ebook') cmd = [ 'gswin64c', '-dNOPAUSE', '-dBATCH', '-dSAFER', '-sDEVICE=pdfwrite', '-dPDFSETTINGS=' + gs_device, '-dEmbedAllFonts=true', '-dSubsetFonts=true', '-dCompressFonts=true', '-dDownsampleColorImages=true', '-dColorImageResolution=150', '-dDownsampleGrayImages=true', '-dGrayImageResolution=150', '-dDownsampleMonoImages=true', '-dMonoImageResolution=150', '-sOutputFile=' + output_pdf, input_pdf ] try: result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode == 0: orig_size = Path(input_pdf).stat().st_size comp_size = Path(output_pdf).stat().st_size ratio = (1 - comp_size / orig_size) * 100 print(f'压缩完成: {ratio:.1f}% ({orig_size//1024}KB → {comp_size//1024}KB)') return output_pdf else: print('压缩失败:', result.stderr) except FileNotFoundError: print('Ghostscript未安装,请访问 https://ghostscript.com/ 下载安装')

7.3 批量优化处理

结合以上两种方案,可以构建一套完整的批量PDF优化管线。先使用pypdf移除冗余数据,再使用Ghostscript进行深度压缩。通过统计分析可以了解整体优化效果,为后续的文档管理提供参考。批量优化时建议保留原始文件的备份,并在压缩后验证PDF文件的完整性。

# 批量PDF优化管线 def batch_optimize_pdfs(input_dir, output_dir, quality='ebook'): """批量优化PDF:去冗余 + 深度压缩""" input_dir = Path(input_dir) output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) stats = [] for pdf_file in input_dir.glob('*.pdf'): orig_size = pdf_file.stat().st_size # 第一步:pypdf预压缩(去冗余) pre_compressed = output_dir / '_temp_' + pdf_file.name compress_pdf_pypdf(str(pdf_file), str(pre_compressed)) # 第二步:Ghostscript深度压缩 final_path = output_dir / pdf_file.name compress_pdf_ghostscript(str(pre_compressed), str(final_path), quality) # 清理临时文件 pre_compressed.unlink(missing_ok=True) new_size = final_path.stat().st_size ratio = (1 - new_size / orig_size) * 100 stats.append({'file': pdf_file.name, 'original_kb': orig_size // 1024, 'compressed_kb': new_size // 1024, 'ratio': f'{ratio:.1f}%'}) print('\n=== 优化统计 ===') for s in stats: print(f'{s["file"]:30s} {s["original_kb"]:6d}KB → {s["compressed_kb"]:6d}KB ({s["ratio"]})') return stats

八、PDF/A标准化

PDF/A是ISO标准化的PDF长期存档格式,专为电子文档的长期保存而设计。与普通PDF相比,PDF/A禁止使用外部字体引用、加密、JavaScript、音频/视频嵌入等功能,要求所有字体必须嵌入且子集化,颜色必须使用设备无关的方式指定。在电子档案管理和合规性要求严格的行业中(如政府、金融、医疗),将文档转换为PDF/A格式是强制性要求。

8.1 PDF/A标准介绍

PDF/A标准分为多个级别:PDF/A-1基于PDF 1.4,分为两个一致性级别(a:可访问性,b:基本);PDF/A-2基于PDF 1.7,支持图层、透明度等高级特性;PDF/A-3允许嵌入任意格式的附件(如原始的XML数据)。选择哪个级别取决于具体使用场景:一般存档选PDF/A-1b即可,需要长期可访问性选PDF/A-2a,需要附带原始数据选PDF/A-3。

# 检查PDF是否合规PDF/A from pypdf import PdfReader def check_pdfa_compliance(pdf_path): """检查PDF文件是否符合PDF/A标准""" reader = PdfReader(pdf_path) # 检查PDF/A元数据标识 metadata = reader.metadata if metadata: print('元数据信息:') for key, value in metadata.items(): print(f' {key}: {value}') # 检查是否包含PDF/A标识 try: # 检查XMP元数据中的PDF/A标识 xmp = reader.xmp_metadata if xmp and 'pdfaid:part' in str(xmp): print('检测到PDF/A标识') return True except: pass # 检查加密状态(PDF/A禁止加密) if reader.is_encrypted: print('文件已加密,不符合PDF/A规范') return False print('未找到PDF/A标识,可能不是标准的PDF/A文件') return False

8.2 使用libreoffice转换为PDF/A

LibreOffice提供了内置的PDF/A导出功能,可以通过命令行进行批量转换。将普通PDF或Office文档转换为PDF/A格式时,LibreOffice会自动处理字体嵌入、颜色空间转换、元数据添加等合规性要求。使用--infilter参数可以指定输入格式,使用--outdir指定输出目录。

# 使用LibreOffice转换PDF/A import subprocess from pathlib import Path def convert_to_pdfa_libreoffice(input_path, output_dir=None): """使用LibreOffice将文档转换为PDF/A-1b格式""" if output_dir is None: output_dir = Path(input_path).parent else: Path(output_dir).mkdir(parents=True, exist_ok=True) cmd = [ 'soffice', '--headless', '--convert-to', 'pdf:writer_pdf_Export:{' 'SelectPdfVersion:=2' # 2 = PDF/A-1b '}', '--outdir', str(output_dir), str(Path(input_path).resolve()) ] result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode == 0: pdf_name = Path(input_path).stem + '.pdf' pdf_path = Path(output_dir) / pdf_name print(f'PDF/A转换成功: {pdf_path}') return str(pdf_path) else: print('转换失败:', result.stderr) return None

8.3 批量PDF/A标准化处理

在企业文档归档场景中,批量将历史文档转换为PDF/A格式是标准流程。处理流程通常包括:扫描目标文档 → 检查当前格式状态 → 转换为PDF/A → 验证合规性 → 输出存档。通过这些步骤可以确保存档文档符合长期保存的规范要求。

# 完整PDF/A标准化管线 def batch_pdfa_conversion(input_dir, output_dir): """批量PDF/A标准化:检查 → 转换 → 验证""" input_dir = Path(input_dir) output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) report = {'processed': [], 'failed': []} for doc_file in list(input_dir.glob('*.pdf')) + list(input_dir.glob('*.docx')): try: out_pdf = output_dir / (doc_file.stem + '.pdf') # 检查是否为PDF if doc_file.suffix.lower() == '.pdf': # 检查是否已经是PDF/A if check_pdfa_compliance(str(doc_file)): # 已经是PDF/A,直接复制 import shutil shutil.copy2(doc_file, out_pdf) report['processed'].append({doc_file.name: '已是PDF/A'}) continue # 转换为PDF/A convert_to_pdfa_libreoffice(str(doc_file), str(output_dir)) report['processed'].append({doc_file.name: '转换成功'}) except Exception as e: report['failed'].append({doc_file.name: str(e)}) print(f'处理完成: 成功 {len(report["processed"])}, 失败 {len(report["failed"])}') return report

九、实战案例

以下实战案例将综合运用前面学习的各项技术,解决真实工作中的PDF批量处理问题。通过这些完整的项目示例,可以深入理解各项技术的实际应用场景,并掌握如何将它们组合使用以构建完整的PDF自动化处理系统。

9.1 案例一:批量添加公司水印

某公司需要为所有对外分发的PDF文档添加公司Logo水印和"机密"文字水印。要求每页左上角显示浅色Logo,页面中央斜向显示"机密"二字。最终方案使用reportlab生成复合水印页面,然后使用pypdf批量合并到所有PDF中。处理完成后输出处理报告,统计成功/失败的文件列表。

# 案例一:批量添加公司水印 def company_watermark_pipeline(input_dir, output_dir, logo_path): """综合水印方案:LOGO + 机密文字双水印""" # 第一步:创建复合水印页面 c = canvas.Canvas('company_watermark.pdf', pagesize=(595.27, 841.89)) # 左上角Logo img = ImageReader(logo_path) c.saveState() c.setFillColorRGB(0, 0, 0, alpha=0.3) c.drawImage(img, 40, 750, width=80, height=40, preserveAspectRatio=True) c.restoreState() # 中央"机密"文字水印 c.saveState() c.setFillColorRGB(0.8, 0, 0, alpha=0.15) # 红色半透明 c.setFont('Helvetica-Bold', 60) c.translate(297.64, 420.94) c.rotate(45) c.drawString(-120, 0, '机 密') c.restoreState() c.save() # 第二步:批量应用水印 output_dir = Path(output_dir) output_dir.mkdir(exist_ok=True, parents=True) with open('company_watermark.pdf', 'rb') as fw: wm_page = PdfReader(fw).pages[0] for pdf_in in Path(input_dir).glob('*.pdf'): with open(pdf_in, 'rb') as fi: reader = PdfReader(fi) writer = PdfWriter() for page in reader.pages: page.merge_page(wm_page) writer.add_page(page) out_path = output_dir / pdf_in.name with open(out_path, 'wb') as fo: writer.write(fo) print(f'已处理: {pdf_in.name}') Path('company_watermark.pdf').unlink()

9.2 案例二:扫描件OCR识别转换

扫描仪生成的PDF本质上是图片集合,无法直接搜索和复制文字。本案例展示完整的"扫描PDF → 转图片 → OCR识别 → 生成可搜索PDF"流程。使用了pdf2image将PDF转换为PNG图片,通过Tesseract OCR引擎识别文字,最后使用img2pdf将结果合并到新的PDF中,并将识别文字生成为隐藏文本层,实现搜索和复制功能。

# 案例二:扫描件OCR识别转换 import pytesseract from PIL import Image import img2pdf def ocr_scanned_pdf(scanned_pdf, output_pdf, lang='chi_sim+eng'): """扫描件PDF批量OCR识别,生成可搜索PDF""" # 第一步:PDF转高分辨率图片 images = convert_from_path(scanned_pdf, dpi=300) # 第二步:OCR识别 ocr_results = [] temp_images = [] for i, img in enumerate(images): # 灰度化预处理 gray = img.convert('L') # OCR识别 text = pytesseract.image_to_string(gray, lang=lang) ocr_results.append(text) print(f'第{i+1}页识别完成,字数: {len(text)}') # 保存为临时图片用于生成PDF temp_path = f'_ocr_page_{i+1}.png' gray.save(temp_path, 'PNG') temp_images.append(temp_path) # 第三步:图片转PDF + 嵌入可搜索文本(简化版) with open(output_pdf, 'wb') as f: f.write(img2pdf.convert(temp_images)) # 清理临时文件 for p in temp_images: Path(p).unlink(missing_ok=True) print(f'OCR处理完成: {scanned_pdf} → {output_pdf}') return ocr_results

9.3 案例三:文档归档标准化处理

某企业需要将历史文档进行归档处理,要求:所有文档转换为PDF/A格式,添加公司水印,设置打印密码保护,移除元数据中的敏感信息。该案例综合运用了前八个章节的技术,通过构建完整的处理管线实现了一键自动化归档。通过脚本可以处理任意数量的文档,并自动生成归档处理报告。

# 案例三:完整文档归档管线 def document_archive_pipeline(input_dir, archive_dir, company_logo, archive_password): """完整文档归档管线:转换 → 水印 → 加密 → 报告""" timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') temp_dir = Path(archive_dir) / '_temp_' + timestamp temp_dir.mkdir(parents=True, exist_ok=True) results = [] for doc_path in Path(input_dir).iterdir(): if doc_path.suffix.lower() not in ['.pdf', '.docx', '.xlsx']: continue try: # Step 1: Office文档转为PDF pdf_path = temp_dir / (doc_path.stem + '.pdf') if doc_path.suffix.lower() in ['.docx', '.xlsx']: auto_convert_to_pdf(str(doc_path), str(temp_dir)) else: shutil.copy2(doc_path, pdf_path) # Step 2: PDF/A标准化 pdfa_path = temp_dir / (doc_path.stem + '_pdfa.pdf') convert_to_pdfa_libreoffice(str(pdf_path), str(temp_dir)) # Step 3: 添加水印 watermarked = temp_dir / (doc_path.stem + '_wm.pdf') with open(pdfa_path, 'rb') as f: reader = PdfReader(f) writer = PdfWriter() wm_page = PdfReader(open('company_watermark.pdf', 'rb')).pages[0] for page in reader.pages: page.merge_page(wm_page) writer.add_page(page) with open(watermarked, 'wb') as fw: writer.write(fw) # Step 4: 加密保护 archive_path = Path(archive_dir) / (doc_path.stem + '_archive.pdf') encrypt_pdf(str(watermarked), str(archive_path), archive_password) results.append({'file': doc_path.name, 'status': '成功', 'archive': archive_path.name}) except Exception as e: results.append({'file': doc_path.name, 'status': f'失败: {e}'}) # 清理临时目录 import shutil shutil.rmtree(temp_dir, ignore_errors=True) print('=== 归档处理报告 ===') for r in results: print(f'{r["file"]:30s} → {r["status"]}') print(f'总计: {len(results)}, 成功: {sum(1 for r in results if "成功" in r["status"])}') return results

结语:PDF批量处理是Python办公自动化的重要实践方向。从格式转换、水印加密,到优化压缩和标准化存档,掌握这些技能可以显著提升文档处理的效率。建议在实际项目中根据具体需求灵活组合各个技术模块,构建适合自己工作场景的PDF处理管线。