本地运行的检索PDF文件中出现关键字的python程序

如果PDF不是OCR的,要在一大堆PDF中检索某个关键词,比较麻烦,

下面的程序可以实现,功能有:

1)本地运行

2)指定某个文件夹,检索出结果,并且可以本地打开。

复制代码
import os
import sys
import pdfplumber
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import webbrowser
import threading
from queue import Queue


class PDFSearchApp:
    def __init__(self, root):
        self.root = root
        self.root.title("PDF关键词搜索工具")
        self.root.geometry("900x650")

        # 设置主题样式
        self.style = ttk.Style()
        if sys.platform == "win32":
            self.style.theme_use('vista')
        elif sys.platform == "darwin":
            self.style.theme_use('aqua')
        else:
            self.style.theme_use('clam')

        self.setup_ui()

        # 用于线程间通信的队列
        self.result_queue = Queue()

        # 检查是否在搜索中
        self.searching = False

    def setup_ui(self):
        """设置用户界面"""
        # 主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 文件夹选择区域
        folder_frame = ttk.LabelFrame(main_frame, text="PDF文件夹选择", padding="10")
        folder_frame.pack(fill=tk.X, pady=(0, 10))

        self.folder_var = tk.StringVar()
        folder_entry = ttk.Entry(folder_frame, textvariable=self.folder_var, width=80)
        folder_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))

        browse_btn = ttk.Button(folder_frame, text="浏览...", command=self.browse_folder)
        browse_btn.pack(side=tk.RIGHT)

        # 关键词搜索区域
        search_frame = ttk.LabelFrame(main_frame, text="搜索设置", padding="10")
        search_frame.pack(fill=tk.X, pady=(0, 10))

        ttk.Label(search_frame, text="关键词:").pack(side=tk.LEFT)

        self.keyword_var = tk.StringVar()
        keyword_entry = ttk.Entry(search_frame, textvariable=self.keyword_var, width=40)
        keyword_entry.pack(side=tk.LEFT, padx=(5, 10))
        keyword_entry.bind("<Return>", lambda event: self.start_search())

        self.search_btn = ttk.Button(search_frame, text="开始搜索", command=self.start_search)
        self.search_btn.pack(side=tk.RIGHT)

        # 结果区域
        results_frame = ttk.LabelFrame(main_frame, text="搜索结果", padding="10")
        results_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))

        # 创建带滚动条的树形视图
        tree_frame = ttk.Frame(results_frame)
        tree_frame.pack(fill=tk.BOTH, expand=True)

        # 创建垂直滚动条
        tree_scroll = ttk.Scrollbar(tree_frame)
        tree_scroll.pack(side=tk.RIGHT, fill=tk.Y)

        # 创建水平滚动条
        h_scroll = ttk.Scrollbar(tree_frame, orient="horizontal")
        h_scroll.pack(side=tk.BOTTOM, fill=tk.X)

        # 创建树形视图
        self.tree = ttk.Treeview(tree_frame,
                                 yscrollcommand=tree_scroll.set,
                                 xscrollcommand=h_scroll.set,
                                 selectmode="browse")

        # 配置滚动条
        tree_scroll.config(command=self.tree.yview)
        h_scroll.config(command=self.tree.xview)

        # 定义列
        self.tree["columns"] = ("path", "count")
        self.tree.column("#0", width=0, stretch=tk.NO)  # 隐藏默认列
        self.tree.column("path", anchor=tk.W, width=600)
        self.tree.column("count", anchor=tk.CENTER, width=100)

        # 设置列标题
        self.tree.heading("path", text="文件路径", anchor=tk.W)
        self.tree.heading("count", text="出现次数", anchor=tk.CENTER)

        # 绑定双击事件打开文件
        self.tree.bind("<Double-1>", self.open_selected_file)

        self.tree.pack(fill=tk.BOTH, expand=True)

        # 状态栏
        self.status_var = tk.StringVar()
        self.status_var.set("就绪")
        status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)

        # 添加右键菜单
        self.context_menu = tk.Menu(self.tree, tearoff=0)
        self.context_menu.add_command(label="打开文件", command=self.open_selected_file)
        self.context_menu.add_command(label="复制文件路径", command=self.copy_file_path)
        self.tree.bind("<Button-3>", self.show_context_menu)

    def browse_folder(self):
        """浏览并选择文件夹"""
        folder_path = filedialog.askdirectory(title="选择包含PDF文件的文件夹")
        if folder_path:
            self.folder_var.set(folder_path)

    def start_search(self):
        """开始搜索"""
        folder_path = self.folder_var.get().strip()
        keyword = self.keyword_var.get().strip()

        # 验证输入
        if not folder_path or not os.path.isdir(folder_path):
            messagebox.showerror("错误", "请选择有效的PDF文件夹")
            return

        if not keyword:
            messagebox.showerror("错误", "请输入搜索关键词")
            return

        # 清空之前的搜索结果
        for item in self.tree.get_children():
            self.tree.delete(item)

        # 更新状态
        self.status_var.set(f"正在搜索 '{keyword}' 在文件夹: {folder_path}...")
        self.search_btn.config(state=tk.DISABLED)
        self.searching = True

        # 在后台线程执行搜索
        threading.Thread(target=self.search_worker, args=(folder_path, keyword), daemon=True).start()

        # 开始检查结果队列
        self.root.after(100, self.check_results)

    def search_worker(self, folder_path, keyword):
        """搜索工作线程"""
        try:
            results = self.search_pdfs_for_keyword(folder_path, keyword)
            self.result_queue.put(('results', results, keyword, folder_path))
        except Exception as e:
            self.result_queue.put(('error', str(e)))

    def check_results(self):
        """检查结果队列"""
        while not self.result_queue.empty():
            msg_type, *data = self.result_queue.get()

            if msg_type == 'results':
                results, keyword, folder_path = data
                self.display_results(results, keyword, folder_path)
            elif msg_type == 'error':
                error_msg = data[0]
                messagebox.showerror("搜索错误", f"搜索过程中发生错误:\n{error_msg}")
                self.status_var.set("搜索出错")

            self.searching = False
            self.search_btn.config(state=tk.NORMAL)

        if self.searching:
            self.root.after(100, self.check_results)

    def search_pdfs_for_keyword(self, folder_path, keyword):
        """
        在指定文件夹中搜索包含关键字的PDF文件并统计出现次数

        参数:
        folder_path (str): 要搜索的文件夹路径
        keyword (str): 要搜索的关键字

        返回:
        list: 包含关键字的PDF文件信息列表,每个元素包含文件路径和出现次数
        """
        results = []
        keyword_lower = keyword.lower()
        total_files = 0
        processed_files = 0

        # 首先统计PDF文件总数(用于进度显示)
        for _, _, files in os.walk(folder_path):
            total_files += sum(1 for f in files if f.lower().endswith('.pdf'))

        # 遍历文件夹中的所有文件
        for root, _, files in os.walk(folder_path):
            for file in files:
                if file.lower().endswith('.pdf'):
                    file_path = os.path.join(root, file)
                    processed_files += 1

                    try:
                        # 更新状态
                        self.status_var.set(f"正在处理 ({processed_files}/{total_files}): {file}")

                        # 使用pdfplumber打开PDF文件
                        with pdfplumber.open(file_path) as pdf:
                            text = ""
                            # 提取所有页面的文本
                            for page in pdf.pages:
                                page_text = page.extract_text()
                                if page_text:
                                    text += page_text + "\n"

                            # 检查关键字并统计出现次数
                            text_lower = text.lower()
                            count = text_lower.count(keyword_lower)

                            if count > 0:
                                # 保留相对路径显示,更简洁
                                rel_path = os.path.relpath(file_path, folder_path)
                                results.append({
                                    'file_path': file_path,
                                    'display_path': rel_path,
                                    'count': count,
                                    'absolute_path': os.path.abspath(file_path)
                                })
                    except Exception as e:
                        print(f"处理文件 {file_path} 时出错: {e}")

        # 按出现次数排序(降序)
        results.sort(key=lambda x: x['count'], reverse=True)
        return results

    def display_results(self, results, keyword, folder_path):
        """显示搜索结果"""
        if not results:
            self.status_var.set(f"未找到包含 '{keyword}' 的PDF文件")
            messagebox.showinfo("搜索完成", f"在文件夹 '{folder_path}' 中未找到包含 '{keyword}' 的PDF文件")
            return

        # 添加结果到树形视图
        for result in results:
            self.tree.insert("", tk.END, values=(result['display_path'], result['count']),
                             tags=(result['absolute_path'],))

        self.status_var.set(f"找到 {len(results)} 个包含 '{keyword}' 的PDF文件")
        messagebox.showinfo("搜索完成", f"找到 {len(results)} 个包含 '{keyword}' 的PDF文件")

    def open_selected_file(self, event=None):
        """打开选中的文件"""
        selected_items = self.tree.selection()
        if not selected_items:
            return

        item = selected_items[0]
        file_path = self.tree.item(item, "tags")[0]

        if not file_path or not os.path.isfile(file_path):
            messagebox.showerror("错误", "无法获取有效的文件路径")
            return

        try:
            # 尝试使用系统默认程序打开
            if sys.platform == 'win32':
                os.startfile(file_path)
            elif sys.platform == 'darwin':
                subprocess.call(('open', file_path))
            else:
                subprocess.call(('xdg-open', file_path))
            self.status_var.set(f"已尝试打开: {file_path}")
        except Exception as e:
            messagebox.showerror("打开文件错误", f"无法打开文件:\n{str(e)}")

    def copy_file_path(self):
        """复制选中文件的路径到剪贴板"""
        selected_items = self.tree.selection()
        if not selected_items:
            return

        item = selected_items[0]
        file_path = self.tree.item(item, "tags")[0]

        if file_path:
            self.root.clipboard_clear()
            self.root.clipboard_append(file_path)
            self.status_var.set(f"已复制文件路径到剪贴板: {file_path}")

    def show_context_menu(self, event):
        """显示右键菜单"""
        item = self.tree.identify_row(event.y)
        if item:
            self.tree.selection_set(item)
            self.context_menu.post(event.x_root, event.y_root)


if __name__ == "__main__":
    # 检查是否安装了必要的库
    try:
        import pdfplumber
    except ImportError:
        result = messagebox.askyesno("依赖缺失",
                                     "未找到pdfplumber库。需要安装才能提取PDF文本。\n\n"
                                     "是否现在安装? (需要网络连接)\n\n"
                                     "点击'是'将运行: pip install pdfplumber\n"
                                     "点击'否'将退出程序")

        if result:
            import subprocess

            try:
                subprocess.check_call([sys.executable, "-m", "pip", "install", "pdfplumber"])
                messagebox.showinfo("安装完成", "pdfplumber库已成功安装!")
            except Exception as e:
                messagebox.showerror("安装失败", f"无法安装pdfplumber库:\n{str(e)}\n\n请手动安装后重新运行程序")
                sys.exit(1)
        else:
            sys.exit(0)

    # 创建并运行GUI
    root = tk.Tk()
    app = PDFSearchApp(root)
    root.mainloop()

功能特点

1. 用户友好界面

  • 简洁直观的Tkinter界面
  • 支持Windows、macOS和Linux
  • 响应式布局,适应不同屏幕尺寸

2. 核心功能

  • 文件夹选择:通过系统原生对话框选择PDF文件夹
  • 关键词搜索:输入关键词后按回车或点击按钮开始搜索
  • 结果展示
    • 显示文件相对路径(简洁)
    • 显示关键词出现次数
    • 按出现次数降序排列
  • 文件操作
    • 双击文件或右键菜单可直接打开PDF
    • 右键菜单提供"复制文件路径"功能

3. 高级特性

  • 后台搜索:搜索在独立线程中进行,不会冻结UI
  • 进度显示:状态栏显示当前处理的文件和进度
  • 错误处理:友好的错误提示和异常处理
  • 自动依赖检查:如果没有安装pdfplumber,会提示安装

使用说明

1. 安装依赖

首次运行时,程序会自动检查并安装pdfplumber库(需要网络连接):

  • 如果没有安装,会弹出提示询问是否安装
  • 点击"是"将自动安装
  • 如果不想自动安装,可以手动运行:pip install pdfplumber
相关推荐
SEO-狼术3 天前
自然语言提取PDF表格数据
pdf
杯莫停丶3 天前
使用Java实现PDF文件安全检测:防止恶意内容注入
java·安全·pdf
Eiceblue3 天前
Java实现PDF表格转换为CSV
java·python·pdf
阿波罗尼亚3 天前
Excel Word Pdf 格式转换
pdf·word·excel
大熊程序猿3 天前
PDF转图片工具实现
linux·运维·pdf
科杰智能制造4 天前
PDF,HTML,md格式文件在线查看工具
javascript·pdf·vue·html
何为xl4 天前
【VSCode】使用VSCode打开md文件以及转化为PDF
ide·vscode·pdf
开开心心就好4 天前
PDF转长图工具,一键多页转图片
java·服务器·前端·数据库·人工智能·pdf·推荐算法
WAZYY06194 天前
C#实现PDF合并、裁剪功能
开发语言·pdf·c#·pdf合并·pdf工具·pdf切割