[color=]目录/文件递归检索工具功能简介
[color=]本软件是一个功能强大的目录和文件检索工具,具有直观的图形界面,能够帮助用户快速分析文件系统结构。主要功能如下:
[color=]核心功能
[color=]1. 目录结构检索
[color=]递归扫描
[color=]:深度遍历指定目录及其所有子目录
[color=]多种检索模式
[color=]:
[color=]仅文件夹模式:只显示目录结构
[color=]仅文件模式:只显示文件列表
[color=]文件+文件夹模式:完整显示目录树结构(默认模式)
[color=]2. 智能过滤系统
[color=]文件后缀过滤
[color=]:支持多后缀过滤(如:.txt; .py; .jpg)
[color=]屏蔽词管理
[color=]:
[color=]支持通配符(如
[color=]
[color=].tmp; backup_
[color=])
[color=]可创建和管理多个屏蔽配置
[color=]支持导入/导出配置

image.png (233.61 KB, 下载次数: 0)
下载附件
2025-6-3 08:46 上传
[color=]3. 结果输出
[color=]树形结构展示
[color=]:直观显示目录层级关系
[color=]可视化标识
[color=]:
[color=]统计信息
[color=]:自动生成项目数量统计
[color=]结果导出
[color=]:一键导出为文本文件

image.png (420.6 KB, 下载次数: 0)
下载附件
2025-6-3 08:46 上传
[color=]特色功能
[color=]4. 用户友好界面
[color=]直观操作
[color=]:清晰的按钮布局和分组
[color=]深色主题
[color=]:减轻视觉疲劳
[color=]实时状态提示
[color=]:显示当前操作状态
[color=]智能路径建议
[color=]:自动生成默认输出路径
[color=]5. 配置管理
[color=]配置文件存储
[color=]:配置文件保存在程序同目录
[color=]多配置支持
[color=]:可创建和管理多个屏蔽配置
[color=]配置导出
[color=]:支持将配置导出为JSON文件
[color=]6. 高效性能
[color=]快速扫描
[color=]:优化递归算法提高效率
[color=]错误处理
[color=]:自动跳过无权限访问的目录
[color=]排序功能
[color=]:文件和文件夹按名称排序
[color=]使用场景
[color=]项目结构分析
[color=]:快速查看项目目录结构
[color=]文件系统清理
[color=]:识别特定类型的文件(如临时文件)
[color=]文档编制
[color=]:生成项目目录树文档
[color=]资产盘点
[color=]:统计特定类型的文件数量
[color=]系统维护
[color=]:查找分散的配置文件或日志文件
[color=]本工具特别适合开发人员、系统管理员和数据分析师使用,能够显著提高文件系统分析的效率。
[color=]源代码
import os
import sys
import re
import json
import fnmatch
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
QPushButton, QRadioButton, QButtonGroup, QGroupBox, QFileDialog, QTextEdit,
QComboBox, QMessageBox, QCheckBox, QListWidget, QListWidgetItem, QInputDialog
)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont, QPalette, QColor
# 获取脚本所在目录
if getattr(sys, 'frozen', False):
# 如果是打包后的可执行文件
APP_DIR = os.path.dirname(sys.executable)
else:
# 如果是脚本文件
APP_DIR = os.path.dirname(os.path.abspath(__file__))
# 修改配置文件路径为脚本所在目录
CONFIG_FILE = os.path.join(APP_DIR, "config.json")
class FileSearchApp(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("目录/文件递归检索工具")
self.setGeometry(300, 300, 800, 650)
# 初始化变量
self.ignore_configs = []
self.current_ignore_config = {"name": "默认配置", "patterns": []}
self.load_config()
# 创建UI
self.init_ui()
class FileSearchApp(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("目录/文件递归检索工具 V1.0 by:Sunf10wer")
self.setGeometry(300, 300, 800, 650)
# 初始化变量
self.ignore_configs = []
self.current_ignore_config = {"name": "默认配置", "patterns": []}
self.load_config()
# 创建UI
self.init_ui()
def init_ui(self):
# 主布局
main_widget = QWidget()
main_layout = QVBoxLayout()
main_widget.setLayout(main_layout)
self.setCentralWidget(main_widget)
# 设置标题样式
title_label = QLabel("目录/文件递归检索工具")
title_font = QFont("Arial", 16, QFont.Bold)
title_label.setFont(title_font)
title_label.setAlignment(Qt.AlignCenter)
title_label.setStyleSheet("color: #FFFFFF; padding: 10px;")
main_layout.addWidget(title_label)
# 目录选择
dir_layout = QHBoxLayout()
dir_label = QLabel("目标目录:")
self.dir_entry = QLineEdit()
self.dir_entry.setPlaceholderText("请选择或输入要检索的目录...")
browse_button = QPushButton("浏览...")
browse_button.clicked.connect(self.browse_directory)
dir_layout.addWidget(dir_label)
dir_layout.addWidget(self.dir_entry, 4)
dir_layout.addWidget(browse_button, 1)
main_layout.addLayout(dir_layout)
# 输出文件选择
output_layout = QHBoxLayout()
output_label = QLabel("输出文件:")
self.output_entry = QLineEdit()
self.output_entry.setPlaceholderText("输出文件名...")
output_browse_button = QPushButton("浏览...")
output_browse_button.clicked.connect(self.browse_output_file)
output_layout.addWidget(output_label)
output_layout.addWidget(self.output_entry, 4)
output_layout.addWidget(output_browse_button, 1)
main_layout.addLayout(output_layout)
# 检索类型选择
search_type_group = QGroupBox("检索类型")
search_layout = QHBoxLayout()
self.folder_radio = QRadioButton("仅文件夹")
self.file_radio = QRadioButton("仅文件")
self.both_radio = QRadioButton("文件和文件夹")
self.both_radio.setChecked(True)
self.search_type_group = QButtonGroup()
self.search_type_group.addButton(self.folder_radio)
self.search_type_group.addButton(self.file_radio)
self.search_type_group.addButton(self.both_radio)
# 文件后缀过滤
suffix_layout = QHBoxLayout()
suffix_label = QLabel("文件后缀(用分号分隔):")
self.suffix_entry = QLineEdit()
self.suffix_entry.setPlaceholderText("例如: .txt; .py; .jpg")
suffix_layout.addWidget(suffix_label)
suffix_layout.addWidget(self.suffix_entry)
search_layout.addWidget(self.folder_radio)
search_layout.addWidget(self.file_radio)
search_layout.addWidget(self.both_radio)
search_layout.addStretch()
search_type_group.setLayout(search_layout)
main_layout.addWidget(search_type_group)
main_layout.addLayout(suffix_layout)
# 屏蔽词管理
ignore_group = QGroupBox("屏蔽词管理")
ignore_layout = QVBoxLayout()
# 屏蔽词配置选择
config_layout = QHBoxLayout()
config_label = QLabel("当前配置:")
self.config_combo = QComboBox()
self.config_combo.setMinimumWidth(150)
self.config_combo.currentIndexChanged.connect(self.config_selected)
new_config_btn = QPushButton("新建配置")
new_config_btn.clicked.connect(self.create_new_config)
config_layout.addWidget(config_label)
config_layout.addWidget(self.config_combo, 1)
config_layout.addWidget(new_config_btn)
# 屏蔽词列表
ignore_list_layout = QVBoxLayout()
list_label = QLabel("屏蔽词列表(支持通配符,如 *.tmp; backup_*)")
self.ignore_list = QListWidget()
self.ignore_list.setAlternatingRowColors(True)
add_btn = QPushButton("添加屏蔽词")
add_btn.clicked.connect(self.add_ignore_pattern)
remove_btn = QPushButton("移除选中")
remove_btn.clicked.connect(self.remove_selected_pattern)
list_btn_layout = QHBoxLayout()
list_btn_layout.addWidget(add_btn)
list_btn_layout.addWidget(remove_btn)
ignore_list_layout.addWidget(list_label)
ignore_list_layout.addWidget(self.ignore_list)
ignore_list_layout.addLayout(list_btn_layout)
ignore_layout.addLayout(config_layout)
ignore_layout.addLayout(ignore_list_layout)
ignore_group.setLayout(ignore_layout)
main_layout.addWidget(ignore_group)
# 操作按钮
button_layout = QHBoxLayout()
self.search_btn = QPushButton("开始检索")
self.search_btn.setStyleSheet(
"background-color: #3498db; color: white; font-weight: bold; padding: 8px;"
)
self.search_btn.clicked.connect(self.start_search)
export_btn = QPushButton("导出配置")
export_btn.clicked.connect(self.export_config)
button_layout.addStretch()
button_layout.addWidget(self.search_btn)
button_layout.addWidget(export_btn)
button_layout.addStretch()
main_layout.addLayout(button_layout)
# 状态栏
self.status_bar = self.statusBar()
self.status_label = QLabel("就绪")
self.status_bar.addWidget(self.status_label)
# 更新UI
self.update_config_combo()
self.update_ignore_list()
# 连接信号
self.dir_entry.textChanged.connect(self.update_output_filename)
def load_config(self):
"""从配置文件加载屏蔽词配置"""
if os.path.exists(CONFIG_FILE):
try:
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
self.ignore_configs = json.load(f)
# 确保至少有一个默认配置
if not any(cfg['name'] == '默认配置' for cfg in self.ignore_configs):
self.ignore_configs.insert(0, {"name": "默认配置", "patterns": []})
except:
self.ignore_configs = [{"name": "默认配置", "patterns": []}]
else:
self.ignore_configs = [{"name": "默认配置", "patterns": []}]
self.current_ignore_config = self.ignore_configs[0]
def save_config(self):
"""保存屏蔽词配置到文件"""
try:
with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
json.dump(self.ignore_configs, f, ensure_ascii=False, indent=2)
return True
except Exception as e:
QMessageBox.critical(self, "保存错误", f"保存配置时出错: {str(e)}")
return False
def update_config_combo(self):
"""更新配置下拉框"""
self.config_combo.clear()
for config in self.ignore_configs:
self.config_combo.addItem(config['name'])
# 选择当前配置
current_index = next(
(i for i, config in enumerate(self.ignore_configs)
if config['name'] == self.current_ignore_config['name']),
0
)
self.config_combo.setCurrentIndex(current_index)
def update_ignore_list(self):
"""更新屏蔽词列表"""
self.ignore_list.clear()
for pattern in self.current_ignore_config['patterns']:
self.ignore_list.addItem(pattern)
def config_selected(self, index):
"""配置选择改变事件"""
if 0 0 else ""
# 添加文件夹/文件标识
if item['type'] == 'folder':
line = f"{indent}{prefix} {item['name']}/"
total_folders += 1
else:
line = f"{indent}{prefix} {item['name']}"
total_files += 1
f.write(line + "\n")
total_items += 1
# 添加统计信息
f.write("\n" + "=" * 70 + "\n\n")
f.write(f"统计信息:\n")
f.write(f"总项目数: {total_items}\n")
f.write(f"文件夹数: {total_folders}\n")
f.write(f"文件数: {total_files}\n")
self.status_label.setText(f"检索完成!找到 {total_items} 个项目,结果已保存到: {output_file}")
QMessageBox.information(self, "完成",
f"检索完成!\n"
f"总项目数: {total_items}\n"
f"文件夹数: {total_folders}\n"
f"文件数: {total_files}\n"
f"结果已保存到:\n{output_file}"
)
except Exception as e:
self.status_label.setText("检索出错")
QMessageBox.critical(self, "错误", f"检索过程中出错: {str(e)}")
def recursive_traverse(self, root_dir, current_dir, results, depth,
search_folders, search_files, search_both,
file_extensions, ignore_patterns):
"""递归遍历目录,保持实际目录结构"""
try:
# 获取当前目录下的条目
entries = os.listdir(current_dir)
except Exception as e:
# 跳过无权访问的目录
return
# 排序条目
entries.sort(key=lambda s: s.lower())
# 获取当前目录相对于根目录的相对路径
rel_dir = os.path.relpath(current_dir, root_dir)
# 如果是根目录,添加根目录项
if current_dir == root_dir:
results.append({
'name': os.path.basename(root_dir) or os.path.splitdrive(root_dir)[0],
'path': root_dir,
'depth': depth,
'type': 'folder'
})
# 处理文件夹
folders = [e for e in entries if os.path.isdir(os.path.join(current_dir, e))]
for folder in folders:
folder_path = os.path.join(current_dir, folder)
rel_path = os.path.relpath(folder_path, root_dir)
# 检查是否在屏蔽列表中
if self.is_ignored(rel_path, ignore_patterns):
continue
# 添加到结果(如果需要检索文件夹)
if search_folders or search_both:
results.append({
'name': folder,
'path': folder_path,
'depth': depth + 1,
'type': 'folder'
})
# 递归处理子目录
self.recursive_traverse(
root_dir,
folder_path,
results,
depth + 1,
search_folders,
search_files,
search_both,
file_extensions,
ignore_patterns
)
# 处理文件
files = [e for e in entries if os.path.isfile(os.path.join(current_dir, e))]
for file in files:
file_path = os.path.join(current_dir, file)
rel_path = os.path.relpath(file_path, root_dir)
# 检查是否在屏蔽列表中
if self.is_ignored(rel_path, ignore_patterns):
continue
# 检查文件后缀
if (search_files or search_both) and file_extensions:
ext = os.path.splitext(file)[1].lower()
if ext not in file_extensions:
continue
# 添加到结果
if search_files or search_both:
results.append({
'name': file,
'path': file_path,
'depth': depth + 1,
'type': 'file'
})
def is_ignored(self, path, patterns):
"""检查路径是否与任何屏蔽模式匹配"""
for pattern in patterns:
if fnmatch.fnmatch(path, pattern):
return True
if pattern in path:
return True
return False
if __name__ == "__main__":
app = QApplication([])
app.setStyle("Fusion")
# 设置应用样式
palette = QPalette()
palette.setColor(QPalette.Window, QColor(53, 53, 53))
palette.setColor(QPalette.WindowText, Qt.white)
palette.setColor(QPalette.Base, QColor(35, 35, 35))
palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
palette.setColor(QPalette.ToolTipBase, Qt.white)
palette.setColor(QPalette.ToolTipText, Qt.white)
palette.setColor(QPalette.Text, Qt.white)
palette.setColor(QPalette.Button, QColor(53, 53, 53))
palette.setColor(QPalette.ButtonText, Qt.white)
palette.setColor(QPalette.BrightText, Qt.red)
palette.setColor(QPalette.Highlight, QColor(142, 45, 197).lighter())
palette.setColor(QPalette.HighlightedText, Qt.black)
app.setPalette(palette)
window = FileSearchApp()
window.show()
app.exec_()
# 文件递归检索工具依赖库
PyQt5==5.15.9
pip install PyQt5
pyinstaller --onefile -w so.py
已打包版本,运行不了请自行打包
https://wwky.lanzoue.com/in2AB2xk9v2d