# PDF页眉清理的自动化实践:从PyPDF2到高级策略的深度解析
每次打开一份从学术数据库下载的PDF文献,或是收到同事发来的内部报告,那些顽固的页眉总像牛皮癣一样贴在页面顶端。它们可能是机构名称、文档编号,或是毫无意义的装饰线条。手动处理一两个文件尚可忍受,但面对几十上百份文档时,那种重复点击、选中、删除的机械劳动简直是对创造力的无情消耗。更让人头疼的是,有些页眉看似删除了,保存后重新打开却又“幽灵般”地重现,或者只清除了文字却留下了底部的横线。
对于技术爱好者、数据分析师或是经常处理文档的开发者来说,这种低效的手动操作显然不是长久之计。市面上的现成工具虽然方便,但往往遇到加密文档、特殊格式或需要保留特定页眉时就束手无策。这时候,掌握用Python脚本自动化处理PDF的能力,就不仅仅是提升效率的问题,而是获得了对文档处理的完全控制权。本文将带你深入Python处理PDF页眉的多种方法,从基础的PyPDF2操作到应对复杂场景的进阶技巧,让你彻底告别手动清理的烦恼。
## 1. 环境准备与基础库选择
在开始编写任何PDF处理脚本之前,选择合适的工具库是第一步。Python生态中有多个处理PDF的库,每个都有其特点和适用场景。
**PyPDF2** 是目前最常用的基础库之一,它纯Python实现,安装简单,API相对直观。但需要注意的是,PyPDF2已经停止维护,社区推荐使用其分支 **PyPDF4** 或 **pypdf**(原PyPDF2的另一个活跃分支)。对于大多数页眉页脚删除任务,这些库都能提供足够的功能。
```bash
# 推荐安装pypdf(原PyPDF2的活跃维护版本)
pip install pypdf
# 如果需要处理更复杂的PDF结构,可以同时安装pdfplumber
pip install pdfplumber
# 对于需要OCR识别的情况,可以安装pdf2image和pytesseract
pip install pdf2image pytesseract
```
除了PyPDF2系列,还有几个值得了解的替代方案:
- **pdfplumber**:专注于PDF内容提取,提供更精细的页面元素访问,适合需要精确识别页眉位置的情况
- **PyMuPDF(fitz)**:性能极高,功能强大,但API相对复杂
- **ReportLab**:主要用于生成PDF,对修改现有PDF支持有限
> 提示:如果只是简单的页眉删除,pypdf完全够用。但如果需要处理扫描版PDF或需要精确控制页面元素,pdfplumber或PyMuPDF可能是更好的选择。
在实际项目中,我通常会根据具体需求混合使用这些库。比如用pypdf进行基础的页面操作,用pdfplumber分析页面结构,用PyMuPDF处理需要高性能的场景。
## 2. 基础方法:使用PyPDF2/pypdf批量清除页眉
让我们从最直接的方法开始——使用pypdf(原PyPDF2)进行批量处理。这种方法的核心思路是创建一个新的PDF,将原PDF的每一页内容(不包括页眉区域)复制到新页面中。
### 2.1 单文件处理的基本实现
首先看一个最简单的实现,它删除每页顶部100个点(约3.5厘米)的区域,这通常能覆盖大部分页眉:
```python
from pypdf import PdfReader, PdfWriter
def remove_header_simple(input_path, output_path, header_height=100):
"""
简单删除PDF页眉
:param input_path: 输入PDF路径
:param output_path: 输出PDF路径
:param header_height: 要删除的顶部高度(单位:点)
"""
reader = PdfReader(input_path)
writer = PdfWriter()
for page_num in range(len(reader.pages)):
page = reader.pages[page_num]
# 获取原始页面尺寸
media_box = page.mediabox
original_width = float(media_box.width)
original_height = float(media_box.height)
# 创建新的页面框,排除顶部header_height区域
new_media_box = [
0, # 左下角x
header_height, # 左下角y(从header_height开始)
original_width, # 右上角x
original_height # 右上角y
]
# 应用新的页面框
page.mediabox.lower_left = (new_media_box[0], new_media_box[1])
page.mediabox.upper_right = (new_media_box[2], new_media_box[3])
writer.add_page(page)
# 保存处理后的PDF
with open(output_path, 'wb') as output_file:
writer.write(output_file)
# 使用示例
remove_header_simple('input.pdf', 'output.pdf', header_height=80)
```
这个方法虽然简单,但有几个明显的局限性:
1. 固定高度删除可能误删正文内容
2. 无法处理页眉高度不固定的情况
3. 如果页眉有背景色或边框,可能留下残留痕迹
### 2.2 批量处理与文件夹遍历
实际工作中,我们很少只处理单个文件。下面是一个完整的批量处理脚本,包含错误处理和进度显示:
```python
import os
from pathlib import Path
from pypdf import PdfReader, PdfWriter
import time
class PDFHeaderRemover:
def __init__(self, header_height=100, backup_original=True):
self.header_height = header_height
self.backup_original = backup_original
self.processed_count = 0
self.error_files = []
def process_directory(self, input_dir, output_dir=None):
"""
处理指定目录下的所有PDF文件
"""
input_path = Path(input_dir)
# 如果没有指定输出目录,在原目录创建processed子文件夹
if output_dir is None:
output_path = input_path / 'processed'
else:
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
# 查找所有PDF文件(包括子目录)
pdf_files = list(input_path.rglob('*.pdf'))
print(f"找到 {len(pdf_files)} 个PDF文件需要处理")
for i, pdf_file in enumerate(pdf_files, 1):
try:
# 保持原始目录结构
relative_path = pdf_file.relative_to(input_path)
output_file = output_path / relative_path
output_file.parent.mkdir(parents=True, exist_ok=True)
# 处理前备份原始文件(可选)
if self.backup_original:
backup_dir = output_path / 'backup'
backup_dir.mkdir(exist_ok=True)
backup_file = backup_dir / relative_path
backup_file.parent.mkdir(parents=True, exist_ok=True)
import shutil
shutil.copy2(pdf_file, backup_file)
# 处理PDF
self._process_single_file(str(pdf_file), str(output_file))
self.processed_count += 1
# 进度显示
if i % 10 == 0 or i == len(pdf_files):
print(f"进度: {i}/{len(pdf_files)} - 已处理: {self.processed_count}")
except Exception as e:
error_msg = f"处理文件 {pdf_file} 时出错: {str(e)}"
print(f"错误: {error_msg}")
self.error_files.append((str(pdf_file), str(e)))
# 输出统计信息
print(f"\n处理完成!")
print(f"成功处理: {self.processed_count} 个文件")
print(f"失败: {len(self.error_files)} 个文件")
if self.error_files:
print("\n失败文件列表:")
for file_path, error in self.error_files:
print(f" - {file_path}: {error}")
def _process_single_file(self, input_path, output_path):
"""处理单个PDF文件"""
reader = PdfReader(input_path)
writer = PdfWriter()
for page in reader.pages:
# 调整页面框以排除页眉区域
media_box = page.mediabox
page.mediabox.lower_left = (0, self.header_height)
page.mediabox.upper_right = (float(media_box.width), float(media_box.height))
writer.add_page(page)
with open(output_path, 'wb') as f:
writer.write(f)
# 使用示例
if __name__ == "__main__":
remover = PDFHeaderRemover(header_height=80, backup_original=True)
remover.process_directory("./documents", "./processed_docs")
```
这个批量处理器包含了几个实用功能:
- 递归遍历子目录
- 保持原始目录结构
- 可选的文件备份
- 详细的错误日志
- 进度显示
> 注意:直接裁剪页面框的方法虽然简单,但可能会影响PDF中的链接、书签等交互元素。如果这些元素对你很重要,需要考虑更精细的处理方式。
## 3. 进阶技巧:智能识别与选择性删除
固定高度删除页眉的方法在很多情况下并不理想。真正的自动化应该能够智能识别页眉位置,或者至少提供更精细的控制。下面介绍几种进阶方法。
### 3.1 基于文本位置的页眉识别
使用pdfplumber可以更精确地分析页面内容,识别页眉区域:
```python
import pdfplumber
from pypdf import PdfReader, PdfWriter
def detect_header_region(pdf_path, sample_pages=3):
"""
分析PDF文件,自动检测页眉区域
返回建议的页眉高度(单位:点)
"""
header_candidates = []
with pdfplumber.open(pdf_path) as pdf:
# 抽样分析前几页
for i in range(min(sample_pages, len(pdf.pages))):
page = pdf.pages[i]
text_elements = page.extract_words()
if not text_elements:
continue
# 找出页面顶部的文本元素
top_texts = [word for word in text_elements if word['top'] < 100]
if top_texts:
# 取最底部文本的y坐标作为页眉下边界
bottom_of_header = max(word['bottom'] for word in top_texts)
header_candidates.append(bottom_of_header)
if header_candidates:
# 取最大值加上一些边距
suggested_height = max(header_candidates) + 20
return min(suggested_height, 150) # 不超过150点
else:
# 没有检测到页眉文本,返回默认值
return 50
def smart_header_removal(input_path, output_path):
"""
智能页眉删除:先检测页眉区域,再进行处理
"""
# 检测页眉高度
header_height = detect_header_region(input_path)
print(f"检测到页眉高度: {header_height:.1f} 点")
# 使用检测到的高度进行处理
reader = PdfReader(input_path)
writer = PdfWriter()
for page in reader.pages:
media_box = page.mediabox
original_height = float(media_box.height)
# 只删除检测到的页眉区域
if header_height < original_height * 0.3: # 确保不会删除太多
page.mediabox.lower_left = (0, header_height)
page.mediabox.upper_right = (float(media_box.width), original_height)
writer.add_page(page)
with open(output_path, 'wb') as f:
writer.write(f)
# 使用示例
smart_header_removal("document.pdf", "cleaned_document.pdf")
```
这种方法比固定高度更智能,但仍有局限:
- 依赖文本提取的准确性
- 对于纯图像页眉或复杂布局可能失效
- 处理速度相对较慢
### 3.2 保留特定页眉的策略
有时候我们不是要删除所有页眉,而是想保留某些特定格式的页眉(比如公司logo),只删除文本内容。这需要更精细的操作:
```python
from pypdf import PdfReader, PdfWriter
from pypdf.generic import RectangleObject
def remove_header_selective(input_path, output_path,
keep_logo_region=None,
remove_text_only=True):
"""
选择性删除页眉
:param keep_logo_region: 要保留的区域 (x0, y0, x1, y1),单位:点
:param remove_text_only: 如果为True,只删除文本;如果为False,删除整个区域
"""
reader = PdfReader(input_path)
writer = PdfWriter()
for page in reader.pages:
# 如果需要保留特定区域,先复制页面
if keep_logo_region:
# 这里可以使用更高级的方法,比如添加白色矩形覆盖不需要的部分
# 但pypdf本身不支持直接修改页面内容,需要结合其他库
pass
# 对于简单的文本删除,可以调整页面框
if not remove_text_only:
# 删除整个顶部区域
media_box = page.mediabox
page.mediabox.lower_left = (0, 100) # 删除顶部100点
page.mediabox.upper_right = (float(media_box.width), float(media_box.height))
writer.add_page(page)
with open(output_path, 'wb') as f:
writer.write(f)
```
对于真正需要保留特定元素的情况,可能需要使用PyMuPDF这样的高级库:
```python
import fitz # PyMuPDF
def selective_header_removal_pymupdf(input_path, output_path):
"""
使用PyMuPDF进行选择性页眉删除
可以更精确地控制页面元素
"""
doc = fitz.open(input_path)
for page_num in range(len(doc)):
page = doc[page_num]
# 查找页眉区域的文本
text_instances = page.search_for("") # 搜索所有文本
for inst in text_instances:
# 如果文本在页面顶部(页眉区域)
if inst.y0 < 100: # 假设页眉在顶部100点内
# 添加白色矩形覆盖文本
redact_rect = fitz.Rect(inst.x0, inst.y0, inst.x1, inst.y1)
page.add_redact_annot(redact_rect, fill=(1, 1, 1)) # 白色填充
# 应用所有覆盖
page.apply_redactions()
# 保存处理后的文档
doc.save(output_path)
doc.close()
```
## 4. 处理加密PDF与特殊格式
现实中的PDF文件往往比我们想象的更复杂。加密PDF、扫描件、多层PDF等都需要特殊处理。
### 4.1 处理加密PDF
许多学术文献或商业文档都有密码保护。pypdf提供了基本的密码处理功能:
```python
from pypdf import PdfReader, PdfWriter
def process_encrypted_pdf(input_path, output_path, password=None):
"""
处理加密的PDF文件
"""
try:
# 尝试用密码打开
reader = PdfReader(input_path)
if reader.is_encrypted:
if password:
success = reader.decrypt(password)
if not success:
print(f"密码错误: {input_path}")
return False
else:
print(f"需要密码: {input_path}")
return False
# 正常处理页面
writer = PdfWriter()
for page in reader.pages:
# 这里可以添加页眉处理逻辑
media_box = page.mediabox
page.mediabox.lower_left = (0, 80)
page.mediabox.upper_right = (float(media_box.width), float(media_box.height))
writer.add_page(page)
# 保存时可以重新加密(可选)
# writer.encrypt("new_password")
with open(output_path, 'wb') as f:
writer.write(f)
return True
except Exception as e:
print(f"处理加密PDF时出错 {input_path}: {str(e)}")
return False
# 批量处理带密码的PDF
def batch_process_encrypted(folder_path, password_dict):
"""
批量处理加密PDF,支持不同文件使用不同密码
password_dict: {文件名: 密码} 的字典
"""
import os
for filename in os.listdir(folder_path):
if filename.lower().endswith('.pdf'):
input_path = os.path.join(folder_path, filename)
output_path = os.path.join(folder_path, 'cleaned', filename)
# 查找对应的密码
password = password_dict.get(filename)
success = process_encrypted_pdf(input_path, output_path, password)
if success:
print(f"成功处理: {filename}")
else:
print(f"处理失败: {filename}")
```
### 4.2 处理扫描件与图像PDF
对于扫描版的PDF(本质是图像),传统的文本处理方法无效。这时需要OCR技术:
```python
import pdf2image
import pytesseract
from PIL import Image
import tempfile
import os
def ocr_based_header_removal(input_path, output_path, header_keywords=None):
"""
使用OCR处理扫描版PDF的页眉
这种方法较慢,适合少量文件或关键文档
"""
if header_keywords is None:
header_keywords = ['页眉', 'header', 'copyright', 'confidential']
# 将PDF转换为图像
images = pdf2image.convert_from_path(input_path)
processed_images = []
for i, image in enumerate(images):
# 获取图像尺寸
width, height = image.size
# 分析顶部区域(假设页眉在顶部15%)
header_region = (0, 0, width, int(height * 0.15))
header_image = image.crop(header_region)
# 使用OCR识别文本
header_text = pytesseract.image_to_string(header_image, lang='chi_sim+eng')
# 检查是否包含页眉关键词
is_header = any(keyword.lower() in header_text.lower()
for keyword in header_keywords)
if is_header:
# 裁剪掉页眉区域
cropped_image = image.crop((0, int(height * 0.15), width, height))
processed_images.append(cropped_image)
else:
processed_images.append(image)
# 将处理后的图像保存为PDF
if processed_images:
processed_images[0].save(
output_path,
save_all=True,
append_images=processed_images[1:],
resolution=100.0
)
return len(processed_images)
# 使用示例
# 注意:这需要安装Tesseract OCR和中文语言包
# processed_count = ocr_based_header_removal("scanned.pdf", "cleaned_scanned.pdf")
```
这种方法虽然强大,但有明显缺点:
- 处理速度非常慢
- OCR准确率影响结果
- 图像质量可能下降
- 无法保留文本选择功能
### 4.3 性能优化与批量处理策略
当处理大量PDF时,性能成为关键考虑因素。以下是一些优化建议:
**并行处理实现:**
```python
import concurrent.futures
from pathlib import Path
def parallel_process_pdfs(input_dir, output_dir, max_workers=4):
"""
使用多线程/多进程并行处理PDF
"""
input_path = Path(input_dir)
output_path = Path(output_dir)
output_path.mkdir(exist_ok=True)
# 收集所有PDF文件
pdf_files = list(input_path.rglob('*.pdf'))
def process_file(pdf_file):
try:
relative_path = pdf_file.relative_to(input_path)
output_file = output_path / relative_path
output_file.parent.mkdir(parents=True, exist_ok=True)
# 调用处理函数
remove_header_simple(str(pdf_file), str(output_file))
return (str(pdf_file), True, None)
except Exception as e:
return (str(pdf_file), False, str(e))
# 使用线程池并行处理
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {executor.submit(process_file, pdf_file): pdf_file
for pdf_file in pdf_files}
results = []
for future in concurrent.futures.as_completed(futures):
results.append(future.result())
# 统计结果
success_count = sum(1 for _, success, _ in results if success)
print(f"并行处理完成: {success_count}/{len(pdf_files)} 成功")
return results
```
**内存优化技巧:**
```python
def memory_efficient_processing(input_path, output_path, chunk_size=10):
"""
分块处理大型PDF,减少内存占用
"""
reader = PdfReader(input_path)
writer = PdfWriter()
total_pages = len(reader.pages)
# 分块处理页面
for start in range(0, total_pages, chunk_size):
end = min(start + chunk_size, total_pages)
print(f"处理页面 {start+1} 到 {end}")
for page_num in range(start, end):
page = reader.pages[page_num]
# 处理页眉
media_box = page.mediabox
page.mediabox.lower_left = (0, 80)
page.mediabox.upper_right = (float(media_box.width), float(media_box.height))
writer.add_page(page)
# 定期写入,释放内存
if end % (chunk_size * 5) == 0 or end == total_pages:
temp_output = f"{output_path}.temp"
with open(temp_output, 'wb') as f:
writer.write(f)
# 重新打开继续添加
writer = PdfReader(temp_output)
# 最终保存
with open(output_path, 'wb') as f:
writer.write(f)
```
## 5. 实战案例与避坑指南
在实际项目中应用这些技术时,会遇到各种预料之外的问题。这里分享几个真实案例和对应的解决方案。
### 5.1 案例一:学术文献批量整理
**场景**:研究生需要整理200篇PDF文献,这些文献来自不同数据库,页眉格式各异,有些还有水印。
**挑战**:
1. 页眉高度不统一(从50点到120点不等)
2. 部分文献有背景色或边框线
3. 需要保留文献的元数据和书签
**解决方案**:
```python
def academic_paper_cleaner(input_dir, output_dir):
"""
专门针对学术文献的清理工具
"""
from pypdf import PdfReader, PdfWriter, PageObject
import pdfplumber
input_path = Path(input_dir)
output_path = Path(output_dir)
output_path.mkdir(exist_ok=True)
for pdf_file in input_path.rglob('*.pdf'):
try:
# 使用pdfplumber分析页面结构
with pdfplumber.open(pdf_file) as pdf:
first_page = pdf.pages[0]
# 检测页眉区域
words = first_page.extract_words()
top_words = [w for w in words if w['top'] < 150]
if top_words:
# 计算页眉高度(取最大底部坐标+边距)
header_bottom = max(w['bottom'] for w in top_words)
header_height = header_bottom + 15
else:
# 没有检测到文本,使用默认值
header_height = 80
# 检查是否有水平线(页眉分隔线)
lines = first_page.lines
header_lines = [l for l in lines
if l['top'] < 150 and abs(l['height']) < 2]
if header_lines:
# 如果有分隔线,确保删除线以下区域
line_bottom = max(l['bottom'] for l in header_lines)
header_height = max(header_height, line_bottom + 5)
# 使用pypdf处理,保留元数据
reader = PdfReader(pdf_file)
writer = PdfWriter()
# 复制元数据
if reader.metadata:
writer.add_metadata(reader.metadata)
# 处理每一页
for page in reader.pages:
media_box = page.mediabox
original_height = float(media_box.height)
# 确保不会删除太多内容
safe_header_height = min(header_height, original_height * 0.2)
page.mediabox.lower_left = (0, safe_header_height)
page.mediabox.upper_right = (float(media_box.width), original_height)
writer.add_page(page)
# 保存
output_file = output_path / pdf_file.relative_to(input_path)
output_file.parent.mkdir(parents=True, exist_ok=True)
with open(output_file, 'wb') as f:
writer.write(f)
print(f"处理完成: {pdf_file.name}")
except Exception as e:
print(f"处理失败 {pdf_file.name}: {str(e)}")
```
**关键改进**:
1. 结合pdfplumber进行智能高度检测
2. 特别处理页眉分隔线
3. 保留原始PDF的元数据
4. 添加安全限制,防止过度删除
### 5.2 案例二:企业文档标准化
**场景**:公司需要将各部门提交的季度报告统一格式,删除页眉页脚,但保留公司logo。
**挑战**:
1. 需要保留特定元素(logo)
2. 文档可能包含表单字段
3. 需要处理批注和注释
**解决方案**:
```python
def corporate_document_standardizer(input_path, output_path, logo_region=None):
"""
企业文档标准化处理
"""
import fitz # PyMuPDF
doc = fitz.open(input_path)
for page_num in range(len(doc)):
page = doc[page_num]
# 定义标准页眉区域(顶部80点)
header_rect = fitz.Rect(0, 0, page.rect.width, 80)
# 如果指定了logo区域,从删除区域中排除
if logo_region:
logo_rect = fitz.Rect(*logo_region)
# 计算需要删除的区域(header_rect减去logo_rect)
# 这里简化处理:只删除logo区域之外的文本
# 查找header_rect内的所有文本,但不在logo_rect内
text_instances = page.get_text("dict")["blocks"]
for block in text_instances:
if "lines" in block:
for line in block["lines"]:
for span in line["spans"]:
text_rect = fitz.Rect(span["bbox"])
# 如果文本在页眉区域但不在logo区域
if text_rect.intersects(header_rect) and not text_rect.intersects(logo_rect):
# 添加红色action覆盖(实际使用时改为白色)
page.add_redact_annot(text_rect, fill=(1, 1, 1))
else:
# 没有logo区域,直接删除整个页眉区域的文本
words = page.get_text("words")
for word in words:
word_rect = fitz.Rect(word[:4])
if word_rect.intersects(header_rect):
page.add_redact_annot(word_rect, fill=(1, 1, 1))
# 应用所有覆盖
page.apply_redactions()
# 保存处理后的文档
doc.save(output_path)
doc.close()
```
### 5.3 常见问题与解决方案
**问题1:处理后文件大小显著增加**
**原因**:PyPDF2在复制页面时可能不会优化资源,导致重复嵌入字体和图像。
**解决方案**:
```python
def optimize_pdf_size(input_path, output_path):
"""
优化PDF文件大小
"""
from pypdf import PdfReader, PdfWriter
reader = PdfReader(input_path)
writer = PdfWriter()
# 启用压缩
for page in reader.pages:
writer.add_page(page)
# 设置压缩选项
with open(output_path, 'wb') as f:
writer.write(f)
# 或者使用外部工具如ghostscript
# import subprocess
# subprocess.run(['gs', '-sDEVICE=pdfwrite', '-dCompatibilityLevel=1.4',
# '-dPDFSETTINGS=/ebook', '-dNOPAUSE', '-dQUIET', '-dBATCH',
# f'-sOutputFile={output_path}', input_path])
```
**问题2:处理后链接和书签丢失**
**原因**:直接修改页面框不会自动调整链接位置。
**解决方案**:
```python
def preserve_links_and_bookmarks(input_path, output_path, header_height=80):
"""
处理页眉时保留链接和书签
"""
from pypdf import PdfReader, PdfWriter
reader = PdfReader(input_path)
writer = PdfWriter()
# 复制书签
if reader.outline:
writer.clone_document_from_reader(reader)
for page_num, page in enumerate(reader.pages):
# 处理页面内容
media_box = page.mediabox
page.mediabox.lower_left = (0, header_height)
page.mediabox.upper_right = (float(media_box.width), float(media_box.height))
# 调整链接位置
if '/Annots' in page:
annotations = page['/Annots']
for annot in annotations:
# 这里需要解析和调整链接坐标
# 实际实现较复杂,需要处理PDF内部坐标系统
pass
writer.add_page(page)
with open(output_path, 'wb') as f:
writer.write(f)
```
**问题3:处理速度太慢**
**优化策略**:
1. 使用PyMuPDF代替PyPDF2处理大型文件
2. 实现并行处理
3. 避免不必要的页面分析
4. 使用缓存机制
```python
def fast_header_removal(input_path, output_path):
"""
使用PyMuPDF进行快速处理
"""
import fitz
doc = fitz.open(input_path)
for page in doc:
# 定义页眉区域
header_rect = fitz.Rect(0, 0, page.rect.width, 80)
# 直接删除该区域的文本(最快的方法)
page.add_redact_annot(header_rect, fill=(1, 1, 1))
page.apply_redactions()
doc.save(output_path)
doc.close()
```
在实际使用这些脚本的过程中,我发现最重要的不是追求最完美的算法,而是找到适合具体场景的平衡点。对于日常的文档整理,简单的固定高度删除加上一些安全限制就足够了。对于重要的归档文档,才需要投入时间实现智能识别和选择性保留。最关键的是始终保持原始文件的备份,并在批量处理前先用少量样本测试,这样可以避免很多不必要的麻烦。