【免费】利用python实现一键替换多个PDF中的页眉页脚

推荐【免费】自动检测删除微信好友教程 微信一键清死粉

日常生活中我们从网上找到的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()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凉亭下

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值