如果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