Files
2026-07-13 21:35:39 +08:00

613 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PDF 处理高级参考
本文档包含主要技能说明中未涵盖的高级 PDF 处理功能、详细示例和附加库。
## pypdfium2 库(Apache/BSD 许可证)
### 概述
pypdfium2 是 PDFiumChromium 的 PDF 库)的 Python 绑定。它非常适合快速 PDF 渲染、图片生成,并且可以作为 PyMuPDF 的替代方案。
### 将 PDF 渲染为图片
```python
import pypdfium2 as pdfium
from PIL import Image
# 加载 PDF
pdf = pdfium.PdfDocument("document.pdf")
# 将页面渲染为图片
page = pdf[0] # 第一页
bitmap = page.render(
scale=2.0, # 更高分辨率
rotation=0 # 不旋转
)
# 转换为 PIL Image
img = bitmap.to_pil()
img.save("page_1.png", "PNG")
# 处理多个页面
for i, page in enumerate(pdf):
bitmap = page.render(scale=1.5)
img = bitmap.to_pil()
img.save(f"page_{i+1}.jpg", "JPEG", quality=90)
```
### 使用 pypdfium2 提取文本
```python
import pypdfium2 as pdfium
pdf = pdfium.PdfDocument("document.pdf")
for i, page in enumerate(pdf):
text = page.get_text()
print(f"第 {i+1} 页文本长度:{len(text)} 个字符")
```
## JavaScript 库
### pdf-libMIT 许可证)
pdf-lib 是一个功能强大的 JavaScript 库,用于在任何 JavaScript 环境中创建和修改 PDF 文档。
#### 加载和操作现有 PDF
```javascript
import { PDFDocument } from 'pdf-lib';
import fs from 'fs';
async function manipulatePDF() {
// 加载现有 PDF
const existingPdfBytes = fs.readFileSync('input.pdf');
const pdfDoc = await PDFDocument.load(existingPdfBytes);
// 获取页数
const pageCount = pdfDoc.getPageCount();
console.log(`文档共有 ${pageCount} 页`);
// 添加新页面
const newPage = pdfDoc.addPage([600, 400]);
newPage.drawText('由 pdf-lib 添加', {
x: 100,
y: 300,
size: 16
});
// 保存修改后的 PDF
const pdfBytes = await pdfDoc.save();
fs.writeFileSync('modified.pdf', pdfBytes);
}
```
#### 从零开始创建复杂 PDF
```javascript
import { PDFDocument, rgb, StandardFonts } from 'pdf-lib';
import fs from 'fs';
async function createPDF() {
const pdfDoc = await PDFDocument.create();
// 添加字体
const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica);
const helveticaBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
// 添加页面
const page = pdfDoc.addPage([595, 842]); // A4 尺寸
const { width, height } = page.getSize();
// 添加带样式的文本
page.drawText('发票 #12345', {
x: 50,
y: height - 50,
size: 18,
font: helveticaBold,
color: rgb(0.2, 0.2, 0.8)
});
// 添加矩形(页眉背景)
page.drawRectangle({
x: 40,
y: height - 100,
width: width - 80,
height: 30,
color: rgb(0.9, 0.9, 0.9)
});
// 添加表格类内容
const items = [
['项目', '数量', '单价', '合计'],
['小工具', '2', '$50', '$100'],
['小装置', '1', '$75', '$75']
];
let yPos = height - 150;
items.forEach(row => {
let xPos = 50;
row.forEach(cell => {
page.drawText(cell, {
x: xPos,
y: yPos,
size: 12,
font: helveticaFont
});
xPos += 120;
});
yPos -= 25;
});
const pdfBytes = await pdfDoc.save();
fs.writeFileSync('created.pdf', pdfBytes);
}
```
#### 高级合并与拆分操作
```javascript
import { PDFDocument } from 'pdf-lib';
import fs from 'fs';
async function mergePDFs() {
// 创建新文档
const mergedPdf = await PDFDocument.create();
// 加载源 PDF
const pdf1Bytes = fs.readFileSync('doc1.pdf');
const pdf2Bytes = fs.readFileSync('doc2.pdf');
const pdf1 = await PDFDocument.load(pdf1Bytes);
const pdf2 = await PDFDocument.load(pdf2Bytes);
// 从第一个 PDF 复制页面
const pdf1Pages = await mergedPdf.copyPages(pdf1, pdf1.getPageIndices());
pdf1Pages.forEach(page => mergedPdf.addPage(page));
// 从第二个 PDF 复制特定页面(第 0、2、4 页)
const pdf2Pages = await mergedPdf.copyPages(pdf2, [0, 2, 4]);
pdf2Pages.forEach(page => mergedPdf.addPage(page));
const mergedPdfBytes = await mergedPdf.save();
fs.writeFileSync('merged.pdf', mergedPdfBytes);
}
```
### pdfjs-distApache 许可证)
PDF.js 是 Mozilla 的 JavaScript 库,用于在浏览器中渲染 PDF。
#### 基本的 PDF 加载与渲染
```javascript
import * as pdfjsLib from 'pdfjs-dist';
// 配置 worker(对性能很重要)
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdf.worker.js';
async function renderPDF() {
// 加载 PDF
const loadingTask = pdfjsLib.getDocument('document.pdf');
const pdf = await loadingTask.promise;
console.log(`已加载 PDF,共 ${pdf.numPages} 页`);
// 获取第一页
const page = await pdf.getPage(1);
const viewport = page.getViewport({ scale: 1.5 });
// 渲染到 canvas
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
const renderContext = {
canvasContext: context,
viewport: viewport
};
await page.render(renderContext).promise;
document.body.appendChild(canvas);
}
```
#### 提取带坐标的文本
```javascript
import * as pdfjsLib from 'pdfjs-dist';
async function extractText() {
const loadingTask = pdfjsLib.getDocument('document.pdf');
const pdf = await loadingTask.promise;
let fullText = '';
// 从所有页面提取文本
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const textContent = await page.getTextContent();
const pageText = textContent.items
.map(item => item.str)
.join(' ');
fullText += `\n--- 第 ${i} 页 ---\n${pageText}`;
// 获取带坐标的文本以供高级处理
const textWithCoords = textContent.items.map(item => ({
text: item.str,
x: item.transform[4],
y: item.transform[5],
width: item.width,
height: item.height
}));
}
console.log(fullText);
return fullText;
}
```
#### 提取注释和表单
```javascript
import * as pdfjsLib from 'pdfjs-dist';
async function extractAnnotations() {
const loadingTask = pdfjsLib.getDocument('annotated.pdf');
const pdf = await loadingTask.promise;
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const annotations = await page.getAnnotations();
annotations.forEach(annotation => {
console.log(`注释类型:${annotation.subtype}`);
console.log(`内容:${annotation.contents}`);
console.log(`坐标:${JSON.stringify(annotation.rect)}`);
});
}
}
```
## 高级命令行操作
### poppler-utils 高级功能
#### 提取带边界框坐标的文本
```bash
# 提取带边界框坐标的文本(对结构化数据至关重要)
pdftotext -bbox-layout document.pdf output.xml
# XML 输出中包含每个文本元素的精确坐标
```
#### 高级图片转换
```bash
# 转换为指定分辨率的 PNG 图片
pdftoppm -png -r 300 document.pdf output_prefix
# 以高分辨率转换指定页面范围
pdftoppm -png -r 600 -f 1 -l 3 document.pdf high_res_pages
# 转换为 JPEG 并设置质量
pdftoppm -jpeg -jpegopt quality=85 -r 200 document.pdf jpeg_output
```
#### 提取嵌入图片
```bash
# 提取所有嵌入图片及元数据
pdfimages -j -p document.pdf page_images
# 列出图片信息(不提取)
pdfimages -list document.pdf
# 以原始格式提取图片
pdfimages -all document.pdf images/img
```
### qpdf 高级功能
#### 复杂页面操作
```bash
# 将 PDF 拆分为多组页面
qpdf --split-pages=3 input.pdf output_group_%02d.pdf
# 使用复杂页码范围提取特定页面
qpdf input.pdf --pages input.pdf 1,3-5,8,10-end -- extracted.pdf
# 合并来自多个 PDF 的特定页面
qpdf --empty --pages doc1.pdf 1-3 doc2.pdf 5-7 doc3.pdf 2,4 -- combined.pdf
```
#### PDF 优化与修复
```bash
# 优化 PDF 以便网络传输(线性化以实现流式传输)
qpdf --linearize input.pdf optimized.pdf
# 移除未使用对象并压缩
qpdf --optimize-level=all input.pdf compressed.pdf
# 尝试修复损坏的 PDF 结构
qpdf --check input.pdf
qpdf --fix-qdf damaged.pdf repaired.pdf
# 显示详细 PDF 结构以进行调试
qpdf --show-all-pages input.pdf > structure.txt
```
#### 高级加密
```bash
# 添加带特定权限的密码保护
qpdf --encrypt user_pass owner_pass 256 --print=none --modify=none -- input.pdf encrypted.pdf
# 检查加密状态
qpdf --show-encryption encrypted.pdf
# 移除密码保护(需要密码)
qpdf --password=secret123 --decrypt encrypted.pdf decrypted.pdf
```
## 高级 Python 技术
### pdfplumber 高级功能
#### 提取带精确坐标的文本
```python
import pdfplumber
with pdfplumber.open("document.pdf") as pdf:
page = pdf.pages[0]
# 提取所有文本及坐标
chars = page.chars
for char in chars[:10]: # 前 10 个字符
print(f"字符:'{char['text']}' 位置 x:{char['x0']:.1f} y:{char['y0']:.1f}")
# 按边界框提取文本(左、上、右、下)
bbox_text = page.within_bbox((100, 100, 400, 200)).extract_text()
```
#### 自定义设置的高级表格提取
```python
import pdfplumber
import pandas as pd
with pdfplumber.open("complex_table.pdf") as pdf:
page = pdf.pages[0]
# 使用自定义设置提取复杂布局的表格
table_settings = {
"vertical_strategy": "lines",
"horizontal_strategy": "lines",
"snap_tolerance": 3,
"intersection_tolerance": 15
}
tables = page.extract_tables(table_settings)
# 表格提取的可视化调试
img = page.to_image(resolution=150)
img.save("debug_layout.png")
```
### reportlab 高级功能
#### 创建带表格的专业报告
```python
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib import colors
# 示例数据
data = [
['产品', 'Q1', 'Q2', 'Q3', 'Q4'],
['小工具', '120', '135', '142', '158'],
['小装置', '85', '92', '98', '105']
]
# 创建带表格的 PDF
doc = SimpleDocTemplate("report.pdf")
elements = []
# 添加标题
styles = getSampleStyleSheet()
title = Paragraph("季度销售报告", styles['Title'])
elements.append(title)
# 添加带高级样式的表格
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)
]))
elements.append(table)
doc.build(elements)
```
## 复杂工作流
### 从 PDF 提取图形/图片
#### 方法一:使用 pdfimages(最快)
```bash
# 以原始质量提取所有图片
pdfimages -all document.pdf images/img
```
#### 方法二:使用 pypdfium2 + 图片处理
```python
import pypdfium2 as pdfium
from PIL import Image
import numpy as np
def extract_figures(pdf_path, output_dir):
pdf = pdfium.PdfDocument(pdf_path)
for page_num, page in enumerate(pdf):
# 渲染高分辨率页面
bitmap = page.render(scale=3.0)
img = bitmap.to_pil()
# 转换为 numpy 以进行处理
img_array = np.array(img)
# 简单的图形检测(非白色区域)
mask = np.any(img_array != [255, 255, 255], axis=2)
# 查找轮廓并提取边界框
# (此处已简化——实际实现需要更复杂的检测)
# 保存检测到的图形
# ……具体实现取决于实际需求
```
### 带错误处理的批量 PDF 处理
```python
import os
import glob
from pypdf import PdfReader, PdfWriter
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def batch_process_pdfs(input_dir, operation='merge'):
pdf_files = glob.glob(os.path.join(input_dir, "*.pdf"))
if operation == 'merge':
writer = PdfWriter()
for pdf_file in pdf_files:
try:
reader = PdfReader(pdf_file)
for page in reader.pages:
writer.add_page(page)
logger.info(f"已处理:{pdf_file}")
except Exception as e:
logger.error(f"处理 {pdf_file} 失败:{e}")
continue
with open("batch_merged.pdf", "wb") as output:
writer.write(output)
elif operation == 'extract_text':
for pdf_file in pdf_files:
try:
reader = PdfReader(pdf_file)
text = ""
for page in reader.pages:
text += page.extract_text()
output_file = pdf_file.replace('.pdf', '.txt')
with open(output_file, 'w', encoding='utf-8') as f:
f.write(text)
logger.info(f"已从以下文件提取文本:{pdf_file}")
except Exception as e:
logger.error(f"从 {pdf_file} 提取文本失败:{e}")
continue
```
### 高级 PDF 裁剪
```python
from pypdf import PdfWriter, PdfReader
reader = PdfReader("input.pdf")
writer = PdfWriter()
# 裁剪页面(左、下、右、上,单位:点)
page = reader.pages[0]
page.mediabox.left = 50
page.mediabox.bottom = 50
page.mediabox.right = 550
page.mediabox.top = 750
writer.add_page(page)
with open("cropped.pdf", "wb") as output:
writer.write(output)
```
## 性能优化技巧
### 1. 处理大型 PDF
- 使用流式方法而非将整个 PDF 加载到内存
- 使用 `qpdf --split-pages` 拆分大文件
- 使用 pypdfium2 逐页处理
### 2. 文本提取
- `pdftotext -bbox-layout` 是纯文本提取最快的方案
- 使用 pdfplumber 处理结构化数据和表格
- 对于非常大的文档,避免使用 `pypdf.extract_text()`
### 3. 图片提取
- `pdfimages` 比渲染页面快得多
- 预览使用低分辨率,最终输出使用高分辨率
### 4. 表单填写
- pdf-lib 比大多数替代方案更好地保留表单结构
- 处理前预先验证表单字段
### 5. 内存管理
```python
# 分块处理 PDF
def process_large_pdf(pdf_path, chunk_size=10):
reader = PdfReader(pdf_path)
total_pages = len(reader.pages)
for start_idx in range(0, total_pages, chunk_size):
end_idx = min(start_idx + chunk_size, total_pages)
writer = PdfWriter()
for i in range(start_idx, end_idx):
writer.add_page(reader.pages[i])
# 处理块
with open(f"chunk_{start_idx//chunk_size}.pdf", "wb") as output:
writer.write(output)
```
## 常见问题排查
### 加密的 PDF
```python
# 处理密码保护的 PDF
from pypdf import PdfReader
try:
reader = PdfReader("encrypted.pdf")
if reader.is_encrypted:
reader.decrypt("password")
except Exception as e:
print(f"解密失败:{e}")
```
### 损坏的 PDF
```bash
# 使用 qpdf 修复
qpdf --check corrupted.pdf
qpdf --replace-input corrupted.pdf
```
### 文本提取问题
```python
# 对扫描件回退到 OCR
import pytesseract
from pdf2image import convert_from_path
def extract_text_with_ocr(pdf_path):
images = convert_from_path(pdf_path)
text = ""
for i, image in enumerate(images):
text += pytesseract.image_to_string(image)
return text
```
## 许可证信息
- **pypdf**BSD 许可证
- **pdfplumber**MIT 许可证
- **pypdfium2**Apache/BSD 许可证
- **reportlab**BSD 许可证
- **poppler-utils**GPL-2 许可证
- **qpdf**Apache 许可证
- **pdf-lib**MIT 许可证
- **pdfjs-dist**Apache 许可证