好友
阅读权限10
听众
最后登录1970-1-1
|
薄了微凉
发表于 2025-4-1 12:29
本帖最后由 薄了微凉 于 2025-4-1 12:32 编辑
因个人资料过多,弄了个小工具,方便搜索,另存新文件,保留原始文件,不喜勿喷,只是分享,按需自取,
PS:如有违规,请联系删帖,请管理大大们不要直接给个违规哈
## 功能特点
- 快速搜索文件名
- 实时显示搜索结果
- 支持文件大小和修改时间显示
- 双击直接打开文件
- 支持选择搜索目录
- 支持关键词联合搜索(中英文 逗号,分号,空格等分隔符)
- 支持搜索结果类型选择切换
- 支持搜索过程中终止搜索
- 支持导出搜索结果列表
- 支持批量复制搜索结果
- 支持重置清空搜索条件、搜索结果
- 过滤缓存文件和特定文件类型:'cache', 'temp', '.tmp', '.wps', '.lnk', '.et'
- 支持的分隔符:',', '、', ',', ';', ';', '/', '\\', '|', '|'
运行页面
联合搜索
批量复制
Python
[Python] 纯文本查看 复制代码 import os
import glob
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from openpyxl import Workbook
from pathlib import Path
import threading
import queue
class FileSearchApp:
def __init__(self, root):
self.root = root
self.root.title('吾爱破解论坛本地文件搜索')
self.root.geometry('1250x600')
# 创建搜索框和按钮
self.search_frame = ttk.Frame(root)
self.search_frame.pack(fill='x', padx=5, pady=5)
self.path_label = ttk.Label(self.search_frame, text='搜索路径:')
self.path_label.pack(side='left')
self.path_var = tk.StringVar(value=str(Path.home()))
self.path_entry = ttk.Entry(self.search_frame, textvariable=self.path_var, width=20)
self.path_entry.pack(side='left', padx=5)
self.browse_btn = ttk.Button(self.search_frame, text='浏览', command=self.browse_directory)
self.browse_btn.pack(side='left')
self.search_label = ttk.Label(self.search_frame, text='搜索关键词:')
self.search_label.pack(side='left', padx=(10, 0))
self.keyword_var = tk.StringVar()
self.keyword_entry = ttk.Entry(self.search_frame, textvariable=self.keyword_var, width=25)
self.keyword_entry.insert(0, '支持空格、逗号、分号等分隔符')
self.keyword_entry.config(foreground='gray')
self.keyword_entry.bind('<FocusIn>', self.on_keyword_entry_focus_in)
self.keyword_entry.bind('<FocusOut>', self.on_keyword_entry_focus_out)
self.keyword_entry.pack(side='left', padx=5)
self.type_label = ttk.Label(self.search_frame, text='文件类型:')
self.type_label.pack(side='left', padx=(10, 0))
self.file_types = {
'全部': '*',
'文档': ['.txt', '.doc', '.docx', '.pdf', '.xls', '.xlsx', '.ppt', '.pptx'],
'图片': ['.jpg', '.jpeg', '.png', '.gif', '.bmp'],
'视频': ['.mp4', '.avi', '.mkv', '.mov', '.wmv'],
'音频': ['.mp3', '.wav', '.flac', '.m4a', '.wma'],
'压缩包': ['.zip', '.rar', '.7z', '.tar', '.gz'],
'代码': ['.py', '.js', '.java', '.cpp', '.c', '.h', '.cs', '.html', '.css', '.php', '.go', '.rs', '.rb', '.swift']
}
self.type_var = tk.StringVar(value='全部')
self.type_combobox = ttk.Combobox(self.search_frame, textvariable=self.type_var, values=list(self.file_types.keys()), state='readonly', width=10)
self.type_combobox.pack(side='left', padx=5)
self.type_combobox.bind('<<ComboboxSelected>>', self.on_type_changed)
self.search_btn = ttk.Button(self.search_frame, text='搜索', command=self.start_search)
self.search_btn.pack(side='left')
self.stop_btn = ttk.Button(self.search_frame, text='停止', command=self.stop_search, state='disabled')
self.stop_btn.pack(side='left', padx=5)
self.export_btn = ttk.Button(self.search_frame, text='导出列表', command=self.export_results)
self.export_btn.pack(side='left', padx=5)
self.reset_btn = ttk.Button(self.search_frame, text='重置', command=self.reset_search)
self.reset_btn.pack(side='left', padx=5)
# 创建结果显示区域
self.result_frame = ttk.Frame(root)
self.result_frame.pack(fill='both', expand=True, padx=5, pady=5)
# 创建Treeview来显示搜索结果
self.tree = ttk.Treeview(self.result_frame, columns=('name', 'path', 'size', 'modified'), show='headings', selectmode='extended')
# 添加批量复制按钮
self.batch_copy_btn = ttk.Button(self.search_frame, text='批量复制', command=self.batch_copy_files)
self.batch_copy_btn.pack(side='left', padx=5)
# 添加排序功能
self.sort_order = {'name': True, 'path': True, 'size': True, 'modified': True} # True表示升序
def sort_column(col):
items = [(self.tree.set(item, col), item) for item in self.tree.get_children('')]
# 特殊处理文件大小排序
if col == 'size':
def size_to_bytes(size_str):
units = {'B': 1, 'KB': 1024, 'MB': 1024**2, 'GB': 1024**3, 'TB': 1024**4}
size, unit = size_str.split()
return float(size) * units[unit.strip()]
items = [(size_to_bytes(self.tree.set(item, col)), item) for item in self.tree.get_children('')]
# 特殊处理修改时间排序
elif col == 'modified':
from datetime import datetime
items = [(datetime.strptime(self.tree.set(item, col), '%Y-%m-%d %H:%M'), item)
for item in self.tree.get_children('')]
# 根据当前排序顺序决定是升序还是降序
items.sort(reverse=not self.sort_order[col])
self.sort_order[col] = not self.sort_order[col]
# 重新插入排序后的项
for index, (val, item) in enumerate(items):
self.tree.move(item, '', index)
# 更新表头箭头
for c in ('name', 'path', 'size', 'modified'):
# 获取当前表头文本(不包含箭头)
current_text = self.tree.heading(c)['text'].split(' ')[0]
# 只在当前排序列显示箭头
if c == col:
self.tree.heading(c, text=current_text + (' ↑' if self.sort_order[c] else ' ↓'))
else:
self.tree.heading(c, text=current_text)
self.tree.heading('name', text='文件名', command=lambda: sort_column('name'))
self.tree.heading('path', text='路径', command=lambda: sort_column('path'))
self.tree.heading('size', text='大小', command=lambda: sort_column('size'))
self.tree.heading('modified', text='修改时间', command=lambda: sort_column('modified'))
self.tree.column('name', width=200)
self.tree.column('path', width=300)
self.tree.column('size', width=100)
self.tree.column('modified', width=150)
# 添加垂直滚动条
self.vsb = ttk.Scrollbar(self.result_frame, orient='vertical', command=self.tree.yview)
# 添加水平滚动条
self.hsb = ttk.Scrollbar(self.result_frame, orient='horizontal', command=self.tree.xview)
self.tree.configure(yscrollcommand=self.vsb.set, xscrollcommand=self.hsb.set)
# 使用grid布局管理器
self.tree.grid(row=0, column=0, sticky='nsew')
self.vsb.grid(row=0, column=1, sticky='ns')
self.hsb.grid(row=1, column=0, sticky='ew')
# 配置grid权重
self.result_frame.grid_rowconfigure(0, weight=1)
self.result_frame.grid_columnconfigure(0, weight=1)
# 创建右键菜单
self.context_menu = tk.Menu(self.root, tearoff=0)
self.context_menu.add_command(label='打开文件', command=self.open_selected_file)
self.context_menu.add_command(label='打开文件位置', command=self.open_file_location)
self.context_menu.add_command(label='复制文件', command=self.copy_file)
# 绑定双击事件和右键菜单事件
self.tree.bind('<Double-1>', self.open_file)
self.tree.bind('<Button-3>', self.show_context_menu)
# 用于存储搜索结果的队列和列表
self.result_queue = queue.Queue()
self.all_results = []
self.searching = False
self.stop_search_flag = False
def browse_directory(self):
directory = tk.filedialog.askdirectory()
if directory:
self.path_var.set(directory)
def start_search(self):
if self.searching:
return
self.stop_search_flag = False
search_path = self.path_var.get()
keyword = self.keyword_var.get()
if not os.path.exists(search_path):
messagebox.showerror('错误', '搜索路径不存在!')
return
if not keyword:
messagebox.showerror('错误', '请输入搜索关键词!')
return
# 清空现有结果和存储
for item in self.tree.get_children():
self.tree.delete(item)
self.all_results.clear()
self.searching = True
self.search_btn.configure(text='搜索中...', state='disabled')
self.stop_btn.configure(state='normal')
# 启动搜索线程
search_thread = threading.Thread(target=self.search_files, args=(search_path, keyword))
search_thread.daemon = True
search_thread.start()
# 启动更新UI的定时器
self.root.after(100, self.update_results)
def search_files(self, path, keyword):
try:
selected_type = self.type_var.get()
file_extensions = self.file_types[selected_type]
# 将关键词按空格、中英文标点符号分隔成列表
# 支持空格、中英文逗号、分号、顿号、斜杠、竖线等常用分隔符
separators = [',', '、', ',', ';', ';', '/', '\\', '|', '|']
for sep in separators:
keyword = keyword.replace(sep, ' ')
keywords = [k.strip().lower() for k in keyword.split() if k.strip()]
for root, dirs, files in os.walk(path):
if self.stop_search_flag:
break
for file in files:
if self.stop_search_flag:
break
# 过滤缓存文件和特定文件类型
file_lower = file.lower()
file_ext = os.path.splitext(file_lower)[1]
if any(pattern in file_lower for pattern in ['cache', 'temp', '.tmp']) or \
file_ext in ['.wps', '.lnk', '.et']:
continue
# 检查文件名是否包含任意一个关键词
if any(k in file_lower for k in keywords):
# 检查文件类型
if selected_type != '全部':
file_ext = os.path.splitext(file)[1].lower()
if file_ext not in file_extensions:
continue
file_path = os.path.join(root, file)
try:
size = os.path.getsize(file_path)
modified = os.path.getmtime(file_path)
result = {
'name': file,
'path': root,
'size': self.format_size(size),
'modified': self.format_time(modified)
}
self.result_queue.put(result)
self.all_results.append(result)
except OSError:
continue
finally:
self.searching = False
self.root.after(0, self.reset_search_ui)
def update_results(self):
while not self.result_queue.empty():
result = self.result_queue.get()
self.tree.insert('', 'end', values=(
result['name'],
result['path'],
result['size'],
result['modified']
))
if self.searching:
self.root.after(100, self.update_results)
else:
self.reset_search_ui()
def reset_search_ui(self):
self.search_btn.configure(text='搜索', state='normal')
self.stop_btn.configure(state='disabled')
def on_type_changed(self, event):
selected_type = self.type_var.get()
file_extensions = self.file_types[selected_type]
# 清空当前显示的结果
for item in self.tree.get_children(""):
self.tree.delete(item)
# 如果没有搜索结果,直接返回
if not self.all_results:
return
# 重新显示符合条件的结果
for result in self.all_results:
file_name = result['name']
file_ext = os.path.splitext(file_name)[1].lower()
# 判断是否显示该文件
if selected_type == "全部" or (isinstance(file_extensions, list) and file_ext in file_extensions):
self.tree.insert('', 'end', values=(
result['name'],
result['path'],
result['size'],
result['modified']
))
def stop_search(self):
self.stop_search_flag = True
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)
def open_selected_file(self):
self.open_file(None)
def open_file_location(self):
selected_item = self.tree.selection()
if not selected_item:
return
values = self.tree.item(selected_item[0])['values']
file_path = os.path.join(values[1], values[0])
try:
os.system(f'explorer /select,"{file_path}"')
except OSError:
messagebox.showerror('错误', '无法打开文件位置!')
def format_size(self, size):
for unit in ['B', 'KB', 'MB', 'GB']:
if size < 1024:
return f'{size:.1f} {unit}'
size /= 1024
return f'{size:.1f} TB'
def format_time(self, timestamp):
from datetime import datetime
return datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M')
def open_file(self, event):
selected_item = self.tree.selection()
if not selected_item:
return
values = self.tree.item(selected_item[0])['values']
file_path = os.path.join(values[1], values[0])
try:
os.startfile(file_path)
except OSError:
messagebox.showerror('错误', '无法打开文件!')
def on_keyword_entry_focus_in(self, event):
if self.keyword_entry.get() == '支持空格、逗号、分号等分隔符':
self.keyword_entry.delete(0, tk.END)
self.keyword_entry.config(foreground='black')
def on_keyword_entry_focus_out(self, event):
if not self.keyword_entry.get():
self.keyword_entry.insert(0, '支持空格、逗号、分号等分隔符')
self.keyword_entry.config(foreground='gray')
def reset_search(self):
# 清空搜索结果
for item in self.tree.get_children():
self.tree.delete(item)
self.all_results.clear()
# 重置搜索关键词
self.keyword_entry.delete(0, tk.END)
self.keyword_entry.insert(0, '支持空格、逗号、分号等分隔符')
self.keyword_entry.config(foreground='gray')
# 重置文件类型为全部
self.type_var.set('全部')
def copy_file(self):
selected_item = self.tree.selection()
if not selected_item:
return
values = self.tree.item(selected_item[0])['values']
source_path = os.path.join(values[1], values[0])
# 选择目标目录
target_dir = filedialog.askdirectory(title='选择复制目标文件夹')
if not target_dir:
return
try:
import shutil
target_path = os.path.join(target_dir, values[0])
shutil.copy2(source_path, target_path)
messagebox.showinfo('成功', '文件复制成功!')
except Exception as e:
messagebox.showerror('错误', f'复制失败:{str(e)}')
def export_results(self):
if not self.tree.get_children():
messagebox.showwarning('警告', '没有可导出的搜索结果!')
return
file_path = filedialog.asksaveasfilename(
defaultextension='.xlsx',
filetypes=[('Excel 文件', '*.xlsx')],
title='导出搜索结果'
)
if not file_path:
return
try:
wb = Workbook()
ws = wb.active
# 写入表头
headers = ['文件名', '路径', '大小', '修改时间']
for col, header in enumerate(headers, 1):
ws.cell(row=1, column=col, value=header)
# 写入数据
for row, item in enumerate(self.tree.get_children(), 2):
values = self.tree.item(item)['values']
for col, value in enumerate(values, 1):
ws.cell(row=row, column=col, value=value)
wb.save(file_path)
messagebox.showinfo('成功', '搜索结果已成功导出!')
except Exception as e:
messagebox.showerror('错误', f'导出失败:{str(e)}')
def batch_copy_files(self):
# 获取所有选中的项
selected_items = self.tree.selection()
if not selected_items:
messagebox.showwarning('警告', '请选择要复制的文件!')
return
# 选择目标目录
target_dir = filedialog.askdirectory(title='选择复制目标文件夹')
if not target_dir:
return
try:
import shutil
success_count = 0
failed_files = []
for item in selected_items:
values = self.tree.item(item)['values']
source_path = os.path.join(values[1], values[0])
target_path = os.path.join(target_dir, values[0])
try:
shutil.copy2(source_path, target_path)
success_count += 1
except Exception as e:
failed_files.append(f'{values[0]}: {str(e)}')
# 显示复制结果
if failed_files:
error_msg = '\n'.join(failed_files)
messagebox.showwarning('复制完成',
f'成功复制 {success_count} 个文件\n'
f'失败 {len(failed_files)} 个文件:\n{error_msg}')
else:
messagebox.showinfo('成功', f'已成功复制 {success_count} 个文件!')
except Exception as e:
messagebox.showerror('错误', f'复制失败:{str(e)}')
if __name__ == '__main__':
root = tk.Tk()
app = FileSearchApp(root)
root.mainloop()
依赖
PyInstaller==6.12.0
openpyxl==3.1.2
打包
[Python] 纯文本查看 复制代码 import PyInstaller.__main__
import os
import subprocess
import sys
def check_requirements():
# 获取当前脚本所在目录
current_dir = os.path.dirname(os.path.abspath(__file__))
requirements_path = os.path.join(current_dir, 'requirements.txt')
# 检查requirements.txt文件是否存在
if not os.path.exists(requirements_path):
print('Error: requirements.txt file not found')
return False
# 安装依赖
try:
subprocess.check_call([sys.executable, '-m', 'pip', 'install', '-r', requirements_path])
return True
except subprocess.CalledProcessError as e:
print(f'Error installing dependencies: {e}')
return False
def build_exe():
# 获取当前目录
current_dir = os.path.dirname(os.path.abspath(__file__))
# 设置图标文件路径(如果有的话)
icon_path = os.path.join(current_dir, 'search_icon.ico')
# PyInstaller参数
# 设置主程序文件的绝对路径
main_script = os.path.join(current_dir, 'file_search.py')
params = [
main_script, # 主程序文件
'--name=吾爱破解论坛本地全局搜索', # 生成的exe名称
'--noconsole', # 不显示控制台窗口
'--onefile', # 打包成单个exe文件
f'--icon={icon_path}', # 设置图标
'--clean', # 清理临时文件
'--noconfirm', # 不确认覆盖
'--distpath', os.path.join(current_dir, 'dist'), # 输出目录
'--workpath', os.path.join(current_dir, 'build'), # 工作目录
'--specpath', current_dir, # spec文件目录
]
# 执行打包
PyInstaller.__main__.run(params)
if __name__ == '__main__':
if check_requirements():
build_exe()
else:
print('安装依赖时出现了错误. 检查 requirements.txt 然后重试.')
icon图标自己找喜欢的
已打包成品,按需自取,不喜勿喷,如有违规,请管理联系,自行删除,感谢。
https://wwwn.lanzoum.com/iQw2v2sbaqbe
密码:52pj |
免费评分
-
查看全部评分
|