日常生活中我们从网上找到的PDF资料中有很多都是加了水印的,随着AI的加持,我们编写代码的门槛越来越低,今天我就用python实现一个一键替换多个PDF文件中的页眉页脚
以下是软件运行起来后的界面
1、第一步选择目录,这个目录可以是根目录,程序会扫描出来所有子目录下的PDF文件
2、输入查找文本,就是需要被替换掉或者删除的文本 内容
3、输入替换为的文本,可以为空,可以替换成其他的文本
4、PDF文件列表是选择的目录以及子目录下所有的PDF文件
5、点击扫描按钮,就可以在PDF文件列表中看到所有的文件
6、点击全选或者单选某几个PDF文件即可
7、最后一步就是点击开始替换按钮,这样程序就会执行完成
我把代码附在下面,请自行复制
import os
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import fitz # PyMuPDF
import threading
import time
class PDFTextReplacer:
def __init__(self, root):
self.root = root
self.root.title("PDF 文本批量替换工具")
self.root.geometry("800x600")
# 创建主框架
main_frame = ttk.Frame(root, padding="10")
main_frame.pack(fill=tk.BOTH, expand=True)
# 目录选择部分
dir_frame = ttk.LabelFrame(main_frame, text="选择目录", padding="5")
dir_frame.pack(fill=tk.X, pady=5)
self.dir_path = tk.StringVar()
ttk.Entry(dir_frame, textvariable=self.dir_path, width=70).pack(side=tk.LEFT, padx=5)
ttk.Button(dir_frame, text="浏览...", command=self.browse_directory).pack(side=tk.LEFT, padx=5)
# 替换内容部分
replace_frame = ttk.LabelFrame(main_frame, text="替换内容", padding="5")
replace_frame.pack(fill=tk.X, pady=5)
ttk.Label(replace_frame, text="查找文本:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
self.search_text = tk.Text(replace_frame, height=3, width=70)
self.search_text.grid(row=0, column=1, padx=5, pady=5)
ttk.Label(replace_frame, text="替换为:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
self.replace_text = tk.Text(replace_frame, height=3, width=70)
self.replace_text.grid(row=1, column=1, padx=5, pady=5)
# 文件列表部分
list_frame = ttk.LabelFrame(main_frame, text="PDF文件列表", padding="5")
list_frame.pack(fill=tk.BOTH, expand=True, pady=5)
self.file_listbox = tk.Listbox(list_frame)
self.file_listbox.pack(fill=tk.BOTH, expand=True, side=tk.LEFT)
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.file_listbox.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.file_listbox.config(yscrollcommand=scrollbar.set)
# 操作按钮部分
button_frame = ttk.Frame(main_frame)
button_frame.pack(fill=tk.X, pady=10)
ttk.Button(button_frame, text="扫描文件", command=self.scan_directory).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="全选", command=self.select_all).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="取消全选", command=self.deselect_all).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="开始替换", command=self.start_replacement).pack(side=tk.RIGHT, padx=5)
# 状态栏
self.status_var = tk.StringVar()
self.status_var.set("就绪")
status_bar = ttk.Label(root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
status_bar.pack(side=tk.BOTTOM, fill=tk.X)
# 存储PDF文件路径
self.pdf_files = []
# 线程控制变量
self.processing = False
def browse_directory(self):
"""打开文件对话框选择目录"""
directory = filedialog.askdirectory()
if directory:
self.dir_path.set(directory)
self.scan_directory()
def scan_directory(self):
"""扫描选定目录中的所有PDF文件"""
directory = self.dir_path.get()
if not directory:
messagebox.showwarning("警告", "请先选择一个目录")
return
self.pdf_files = []
self.file_listbox.delete(0, tk.END)
try:
self.status_var.set("正在扫描目录...")
self.root.update() # 强制更新界面
for root, _, files in os.walk(directory):
for file in files:
if file.lower().endswith('.pdf'):
pdf_path = os.path.join(root, file)
self.pdf_files.append(pdf_path)
self.file_listbox.insert(tk.END, os.path.basename(pdf_path))
self.status_var.set(f"找到 {len(self.pdf_files)} 个PDF文件")
except Exception as e:
messagebox.showerror("错误", f"扫描目录时发生错误: {str(e)}")
self.status_var.set("扫描出错")
def select_all(self):
"""全选所有文件"""
self.file_listbox.select_set(0, tk.END)
def deselect_all(self):
"""取消全选"""
self.file_listbox.selection_clear(0, tk.END)
def start_replacement(self):
"""启动替换线程"""
if self.processing:
messagebox.showwarning("警告", "有任务正在进行中,请稍候...")
return
search_text = self.search_text.get("1.0", tk.END).strip()
replace_text = self.replace_text.get("1.0", tk.END).strip()
if not search_text:
messagebox.showwarning("警告", "请输入要查找的文本")
return
selected_indices = self.file_listbox.curselection()
if not selected_indices:
messagebox.showwarning("警告", "请至少选择一个PDF文件")
return
# 创建进度窗口
self.progress_window = tk.Toplevel(self.root)
self.progress_window.title("处理中")
self.progress_window.geometry("400x150")
self.progress_window.transient(self.root)
self.progress_window.grab_set()
self.progress_label = ttk.Label(self.progress_window, text="准备处理文件...")
self.progress_label.pack(pady=10)
self.progress_bar = ttk.Progressbar(self.progress_window, orient=tk.HORIZONTAL,
length=350, mode='determinate')
self.progress_bar.pack(pady=10)
self.file_label = ttk.Label(self.progress_window, text="")
self.file_label.pack(pady=5)
self.progress_bar['maximum'] = len(selected_indices)
self.progress_bar['value'] = 0
# 开始线程处理文件
self.processing = True
thread = threading.Thread(target=self.replace_text_in_pdfs,
args=(search_text, replace_text, selected_indices))
thread.daemon = True
thread.start()
# 启动检查线程状态的函数
self.root.after(100, self.check_thread_status, thread)
def check_thread_status(self, thread):
"""检查处理线程的状态"""
if thread.is_alive():
# 线程仍在运行,继续检查
self.root.after(100, self.check_thread_status, thread)
else:
# 线程已完成
self.processing = False
try:
self.progress_window.destroy()
except:
pass # 窗口可能已经被关闭
def replace_text_in_pdfs(self, search_text, replace_text, selected_indices):
"""在选定的PDF文件中替换文本 - 在单独线程中运行"""
success_count = 0
fail_count = 0
processed_files = []
try:
for i, index in enumerate(selected_indices):
file_path = self.pdf_files[index]
file_name = os.path.basename(file_path)
# 使用after_idle安全地更新UI
self.root.after_idle(lambda text=f"正在处理 ({i+1}/{len(selected_indices)}): {file_name}":
self.progress_label.config(text=text))
self.root.after_idle(lambda text=file_path: self.file_label.config(text=text))
self.root.after_idle(lambda val=i: self.progress_bar.config(value=val))
output_path = os.path.join(os.path.dirname(file_path),
f"替换_{os.path.basename(file_path)}")
try:
# 处理单个PDF文件
self.process_single_pdf(file_path, output_path, search_text, replace_text)
success_count += 1
processed_files.append(output_path)
except Exception as e:
fail_count += 1
print(f"处理文件 {file_path} 时出错: {str(e)}")
# 给GUI一些时间更新
time.sleep(0.1)
# 处理完成后显示结果
self.root.after_idle(lambda: messagebox.showinfo(
"完成",
f"处理完成\n成功: {success_count} 文件\n失败: {fail_count} 文件\n\n已处理的文件保存在原目录下"))
self.root.after_idle(lambda: self.status_var.set(
f"替换完成. 成功: {success_count}, 失败: {fail_count}"))
except Exception as e:
self.root.after_idle(lambda: messagebox.showerror(
"错误", f"处理过程中发生错误: {str(e)}"))
def process_single_pdf(self, input_path, output_path, search_text, replace_text):
"""处理单个PDF文件"""
try:
# 打开PDF文件
doc = fitz.open(input_path)
# 跟踪是否进行了更改
changes_made = False
# 遍历每一页
for page_num in range(len(doc)):
page = doc[page_num]
# 查找文本实例
text_instances = page.search_for(search_text)
# 如果找到匹配的文本,替换它
if text_instances:
changes_made = True
for inst in text_instances:
# PyMuPDF的文本替换操作
# 先选择文本区域
page.add_redact_annot(inst, text=replace_text)
# 应用编辑
page.apply_redactions()
# 如果做了更改,保存到新文件
if changes_made:
doc.save(output_path)
# 关闭文档
doc.close()
return changes_made
except Exception as e:
print(f"处理PDF出错: {str(e)}")
raise e
if __name__ == "__main__":
root = tk.Tk()
app = PDFTextReplacer(root)
root.mainloop()