在日常工作和学习中,我们经常会遇到一些带有水印的PDF文档。这些水印可能会影响阅读体验,特别是当它们与正文内容重叠时。我从网上下载了一些论文的压缩包,里面不出意外的有某些论文辅导机构的水印,看着很是讨厌(就像下图一样),网上查了一下,对于水印来说,大多数采用PyMuPDF库,用白色色块覆盖,我也感觉不是很优雅,所以小小折腾了一下,做一个记录。
功能特点
- 支持递归搜索:自动处理指定文件夹及其所有子文件夹中的PDF文件
- 精确匹配:只删除完全匹配的文字内容,不会影响其他内容
- 原地处理:直接修改原文件,节省磁盘空间
- 安全可靠:使用临时文件机制确保文件安全
- 错误处理:完善的错误处理机制,确保批处理不会因单个文件失败而中断
- 详细日志:提供处理过程的详细信息
使用方法
- 确保已安装Python环境
- 安装必要的依赖:
pip install PyMuPDF
- 下载并配置脚本(完整代码见文末):
import fitz # PyMuPDF 库
import os
# --- 用户配置区 ---
# 1. 在这个列表中,输入您想要删除的所有文字内容
TEXTS_TO_DELETE = [
"你需要删除的水印文字"
]
# 2. 包含您所有 PDF 文件的文件夹路径
SOURCE_FOLDER = "你的文件路径"
- 运行脚本,它会自动:
- 搜索指定文件夹及其子文件夹中的所有PDF文件
- 查找并删除指定的水印文字
- 原地更新处理后的文件
- 显示处理进度和结果统计
工作原理
- 文件搜索:使用
os.walk()
递归遍历所有文件夹,找出所有PDF文件。 - 文本处理:
- 对每个PDF文件的每一页进行扫描
- 使用PyMuPDF的
search_for()
方法定位目标文本 - 通过
add_redact_annot()
和apply_redactions()
方法安全地删除文本
- 安全保存:
- 使用临时文件机制确保文件安全
- 只有在新文件成功保存后才替换原文件
- 出错时自动清理临时文件
- 错误处理:
- 支持处理损坏的PDF文件
- 跳过加密的PDF文件
- 单页处理错误不影响整体进度
- 详细的错误日志输出
完整代码
import fitz # PyMuPDF 库
import os
# --- 用户配置区 ---
# 1. 在这个列表中,输入您想要删除的所有文字内容。
# 脚本会查找并删除所有与列表中字符串完全匹配的文本。
# 您可以添加任意多个字符串。
TEXTS_TO_DELETE = [
"你需要删除的水印文字"
]
# 2. 包含您所有 PDF 文件的文件夹路径
SOURCE_FOLDER = "你的文件路径"
def process_pdf_file(filepath):
"""
处理单个PDF文件,删除指定文本并原地覆盖。
Args:
filepath: PDF文件的完整路径
Returns:
bool: 是否对文件进行了修改
"""
filename = os.path.basename(filepath)
is_modified = False
try:
# 尝试打开PDF文件
doc = fitz.open(filepath)
print(f"\n正在处理: {filename}...")
if doc.needs_pass:
print(f" -> 跳过:文件 {filename} 有密码保护")
doc.close()
return False
# 遍历PDF的每一页
for page_num in range(doc.page_count):
try:
page = doc[page_num]
except Exception as e:
print(f" -> 警告:第 {page_num + 1} 页访问出错,已跳过此页: {e}")
continue
# 对本页应用所有删节标记
for text in TEXTS_TO_DELETE:
try:
# search_for() 会返回所有匹配文本的矩形区域列表
found_instances = page.search_for(text)
# 如果找到了匹配项
if found_instances:
is_modified = True
print(f" -> 在第 {page_num + 1} 页找到并标记了文本: '{text}'")
# 为每一个找到的实例添加删节注释
for inst in found_instances:
try:
page.add_redact_annot(inst, fill=(1, 1, 1))
except Exception as e:
print(f" -> 警告:在第 {page_num + 1} 页添加删除标记时出错: {e}")
continue
except Exception as e:
print(f" -> 警告:在第 {page_num + 1} 页搜索文本时出错: {e}")
continue
# 应用本页的所有删节标记,实现真正的删除
if is_modified:
try:
page.apply_redactions()
except Exception as e:
print(f" -> 警告:在第 {page_num + 1} 页应用删除时出错: {e}")
continue
# 如果文件被修改过,则原地覆盖保存
if is_modified:
try:
# 使用临时文件进行保存
temp_filepath = filepath + ".temp"
doc.save(temp_filepath, garbage=4, deflate=True, clean=True)
doc.close()
# 删除原文件并重命名临时文件
os.remove(filepath)
os.rename(temp_filepath, filepath)
print(f" -> 已成功删除指定文本并覆盖原文件")
except Exception as e:
print(f" -> 保存文件时发生错误: {e}")
# 清理临时文件(如果存在)
if os.path.exists(temp_filepath):
os.remove(temp_filepath)
return False
else:
print(f" -> 未在该文件中找到任何需要删除的文本,已跳过。")
doc.close()
return is_modified
except Exception as e:
print(f" -> 处理文件 {filename} 时发生错误: {e}")
return False
def batch_delete_text_by_content():
"""
递归搜索并处理所有PDF文件,删除指定的文本内容。
"""
total_files = 0
modified_files = 0
# 递归遍历所有文件夹
for root, _, files in os.walk(SOURCE_FOLDER):
# 筛选出所有PDF文件
pdf_files = [f for f in files if f.lower().endswith('.pdf')]
total_files += len(pdf_files)
# 处理当前文件夹中的所有PDF文件
for pdf_file in pdf_files:
filepath = os.path.join(root, pdf_file)
if process_pdf_file(filepath):
modified_files += 1
print(f"\n🎉 处理完成!")
print(f"共处理了 {total_files} 个PDF文件")
print(f"其中 {modified_files} 个文件被修改")
# --- 运行脚本 ---
if __name__ == "__main__":
if "此处填写" in SOURCE_FOLDER:
print("错误:请先在脚本中设置 'SOURCE_FOLDER' 的正确路径!")
else:
batch_delete_text_by_content()