基于Python开发高效文件搜索与内容匹配工具
目录
- 1.概述
- 2.功能使用
- 2.1 文件夹选择与搜索设置
- 2.2 文件内容搜索
- 2.3 单文件搜索
- 2.4 结果展示与导出
- 2.5 文件树信息导入
- 3.代码结构与实现
- 3.1 多线程处理
- 3.2 文件编码支持
- 3.3 导入导出功能
- 4.效果展示
- 5.相关源码
- 6.总结
在日常的开发和办公中,查找和筛选特定文件或文件内容的需求十分常见。尤其是在处理大量文件时,如何高效地搜索到所需内容,往往是一个挑战。今天,我们将分享一个基于PyQt6的文件搜索工具,它不仅能根据文件后缀进行搜索,还支持关键字匹配和详细信息展示。通过这款工具,您可以更高效地管理和查找文件,提升工作效率。
1.概述
这款基于PyQt6开发的文件搜索工具,具备文件搜索、文件内容匹配、搜索结果显示及导出功能。程序支持通过拖拽输入框选择目标文件夹,并对其中的文件进行后缀筛选和内容搜索。匹配结果可以实时显示在界面上,用户可以查看匹配的文件名、大小以及路径,还可以导出搜索结果或者树结构信息。
该工具的核心特点包括:
- 多线程搜索:使用PyQt的QThread进行文件搜索操作,避免UI卡顿。
- 多种文件编码支持:支持UTF-8、GBK等常见编码格式,能够准确读取文件内容。
- 实时展示匹配结果:搜索结果实时更新,方便用户查看和处理。
- 导入导出功能:支持导入文件树信息及导出搜索结果。
2.功能使用
2.1 文件夹选择与搜索设置
启动应用后,用户首先需要选择一个目标文件夹,输入需要搜索的文件后缀以及关键词。文件后缀支持通过分号分隔的多项选择,例如 .txt; .log,而关键词则是搜索文件内容时所需要匹配的字符串。
界面上设计了拖拽功能,用户可以直接将目标文件夹拖拽到输入框中,免去手动输入路径的麻烦。
2.2 文件内容搜索
输入完搜索条件后,点击“开始搜索”按钮,程序会启动一个后台线程(SearchWorker),在目标文件夹中递归查找符合条件的文件。搜索的内容不仅是文件名匹配,还包括文件内部的内容是否包含指定的关键字。程序采用了多种编码方式进行文件内容的读取,以确保文件格式不同的情况下能够正确处理。
搜索过程中,UI界面并不会卡顿,用户可以继续操作其他功能。匹配到的文件会实时显示在左侧的文件树结构中,每一项显示文件名、大小和路径等信息。
2.3 单文件搜索
除了支持批量文件搜索外,程序还提供了单文件搜索的功能。在“单文件搜索”板块中,用户可以选择指定的文件,输入要查找的关键词,点击搜索按钮后,程序会展示匹配到的行及其内容,并在右侧的文本框中显示完整匹配结果。
该功能可以帮助开发人员快速定位特定代码段或文件中的问题,极大提高了工作效率。
2.4 结果展示与导出
搜索结束后,程序会统计文件总数、匹配的行数和搜索所花费的时间,展示在界面上。同时,所有匹配到的结果会以列表的形式显示在右侧,用户可以选择具体的行进行查看。
如果用户需要保存搜索结果,程序提供了导出功能。用户可以将所有匹配的文件内容导出为文本文件,方便后续查看或共享。另外,文件树的结构信息也可以导出,便于存档和分析。
2.5 文件树信息导入
程序还支持导入文件树信息。用户可以将之前导出的树结构文件导入到工具中,快速恢复之前的搜索状态和文件结构。导入后的文件树可以继续进行搜索或查看,提升了工具的灵活性和可用性。
3.代码结构与实现
3.1 多线程处理
为了保证UI界面的流畅性,程序使用了PyQt的QThread来处理搜索任务。具体来说,SearchWorker类负责遍历目标文件夹并查找符合条件的文件,而FileReader类则负责读取文件内容并检查是否包含指定的关键字。多线程的使用有效避免了UI在进行文件搜索时的卡顿问题,让用户能够在后台进行其他操作。
class SearchWorker(QThread): update_file = pyqtSignal(dict) finished = pyqtSignal() def __init__(self, folder, extensions, keyword): super().__init__() self.folder = folder self.extensions = extensions self.keyword = keyword def run(self): # 搜索文件并匹配关键字 pass
3.2 文件编码支持
文件内容的读取采用了多种常见编码格式(如UTF-8、GBK等),以确保无论文件是用哪种编码保存,程序都能正确地读取其内容。在FileReader类中,我们尝试了不同的编码方式,直到成功读取文件为止。
def file_contains_keyword(self, path, keyword):
for encoding in [‘utf-8’, ‘gbk’, ‘latin-1’]:try:with open(path, ‘r’, encoding=encoding) as f:return any(keyword in line for line in f)except (UnicodeDecodeError, Exception):continuereturn False
3.3 导入导出功能
导出功能实现了将文件匹配结果和文件树结构导出为文本文件,支持用户将数据保存在指定路径。通过QFileDialog对话框,用户可以选择保存的位置或导入的数据文件。
def export_match_list(self): # 导出匹配结果 pass def import_tree_info(self): # 导入文件树 pass
4.效果展示
5.相关源码
import sys import os import time from PyQt6.QtWidgets import ( QApplication, QMainWindow, QWidget, QvboxLayout, QHBoxLayout, QGridLayout, QLabel, QLineEdit, QPushButton, QTreeWidget, QTreeWidgetItem, QListWidget, QTextEdit, QScrollArea, QFileDialog, QMessageBox ) from PyQt6.QtCore import Qt, QThread, pyqtSignal, QMimeData, QSize from PyQt6.QtGui import QDragEnterEvent, QDropEvent class DraggableLineEdit(QLineEdit): def __init__(self, parent=None): super().__init__(parent) self.setAcceptDrops(True) def dragEnterEvent(self, event: QDragEnterEvent): if event.mimeData().hasUrls(): event.acceptProposedAction() def dropEvent(self, event: QDropEvent): urls = event.mimeData().urls() if urls: path = urls[0].toLocalFile() if os.path.isdir(path) or os.path.isfile(path): self.setText(path) class SearchWorker(QThread): update_file = pyqtSignal(dict) finished = pyqtSignal() def __init__(self, folder, extensions, keyword): super().__init__() self.folder = folder self.extensions = extensions self.keyword = keyword self.running = True def run(self): for root, _, files in os.walk(self.folder): if not self.running: break for file in files: if any(file.endswith(ext) for ext in self.extensions): path = os.path.join(root编程客栈, file) if self.file_contains_keyword(path, self.keyword): size = os.path.getsize(path) self.update_file.emit({ "name": file, "size": self.format_size(size), "path": path }) self.finished.emit() def file_contains_keyword(self, path, keyword): for encoding in ['utf-8', 'gbk', 'latin-1']: try: with open(path, 'r', encoding=encoding) as f: return any(keyword in line for line in f) except (UnicphpodeDecodeError, Exception): continue return False def format_size(self, size): if size < 1024: return f"{size} B" elif size < 1024 * 1024: return f"{size/1024:.1f} KB" else: return f"{size/(1024 * 1024):.1f} MB" class FileReader(QThread): update_line = pyqtSignal(str) finished = pyqtSignal() def __init__(self, path, keyword): super().__init__() self.path = path self.keyword = keyword self.results = [] def run(self): self.results = [] for encoding in ['utf-8', 'gbk', 'latin-1']: try: with open(self.path, 'r', encoding=encoding) as f: for i, line in enumerate(f, 1): if self.keyword in line: text = f"Line {i}: {line.strip()[:50]}" self.update_line.emit(text) self.results.append(line.strip()) break except (UnicodeDecodeError, Exception): continue self.finished.emit() class AllFilesReader(QThread): update_line = pyqtSignal(str) finished = pyqtSignal() def __init__(self, paths, keyword): super().__init__() self.paths = paths self.keyword = keyword def run(self): for path in self.paths: if not os.path.isfile(path): continue reader = FileReader(path, self.keyword) reader.update_line.connect(self.update_line.emit) reader.start() reader.wait() self.finished.emit() class MainWindow(QMainWindow): def __init__(self): super().__init__() self.current_matches = [] self.search_thread = None self.file_reader = None self.start_time = 0 self.init_ui() def init_ui(self): self.setWindowTitle("PyQt6文件搜索工具") self.setGeometry(100, 100, 1200, 700) main_widget = QWidget() self.setCentralWidget(main_widget) main_layout = QVBoxLayout(main_widget) # 顶部控制面板 top_panel = QWidget() top_layout = QGridLayout(top_panel) self.folder_input = DraggableLineEdit() self.ext_input = QLineEdit(".txt") self.keyword_input = QLineEdit() self.btn_search = QPushButton("开始搜索") self.btn_search.clicked.connect(self.start_search) top_layout.addwidget(QLabel("目标文件夹:"), 0, 0) top_layout.addWidget(self.folder_input, 0, 1) top_layout.addWidget(self.create_browse_btn(), 0, 2) top_layout.addWidget(QLabel("文件后缀:"), 1, 0) top_layout.addWidget(self.ext_input, 1, 1) top_layout.addWidget(QLabel("搜索内容:"), 2, 0) top_layout.addWidget(self.keyword_input, 2, 1) top_layout.addWidget(self.btn_search, 0, 3, 3, 1) # 统计信息 stats_panel = QWidget() stats_layout = QHBoxLayout(stats_panel) self.file_count = QLabel("文件总数: 0") self.line_count = QLabel("匹配行数: 0") self.time_label = QLabel("耗时: 0.00秒") stats_layout.addWidget(self.file_count) stats_layout.addWidget(self.line_count) stats_layout.addWidget(self.time_label) # 主内容区域 content_panel = QWidget() content_layout = QHBoxLayout(content_panel) # 左侧结果树 self.tree = QTreeWidget() self.tree.setHeaderLabels(["文件名", "大小", "路径"]) self.tree.setColumnWidth(0, 250) self.tree.setColumnWidth(1, 100) self.tree.doubleClicked.connect(self.on_tree_double_click) tree_scroll = QScrollArea() tree_scroll.setWidgetResizable(True) tree_scroll.setWidget(self.tree) # 右侧面板 right_panel = QWidget() right_layout = QVBoxLayout(right_panel) # 按钮面板 btn_panel = QWidget() btn_layout = QHBoxLayout(btn_panel) self.btn_search_all = QPushButton("搜索所有文件") self.btn_export_matches = QPushButton("导出结果") self.btn_export_tree = QPushButton("导出树信息") self.btn_import_tree = QPushButton("导入树信息") btn_layout.addWidget(self.btn_search_all) btn_layout.addWidget(self.btn_export_matches) btn_layout.addWidget(self.btn_export_tree) btn_layout.addWidget(self.btn_import_tree) # 单文件搜索 single_panel = QWidget() single_layout = QHBoxLayout(single_panel) self.single_input = DraggableLineEdit() btn_single = QPushButton("搜索") btn_single.clicked.connect(self.single_file_search) single_layout.addWidget(QLabel("单文件搜索:")) single_layout.addWidget(self.single_input) single_layout.addWidget(btn_single) # 匹配列表 self.match_list = QListWidget() self.match_list.doubleClicked.connect(self.on_list_double_click) list_scroll = QScrollArea() list_scroll.setWidgetResizable(True) list_scroll.setWidget(self.match_list) # 详情文本框 self.detail_text = QTextEdit() self.detail_text.setReadOnly(True) right_layout.addWidget(single_panel) right_layout.addWidget(btn_panel) right_layout.addWidget(list_scroll) right_layout.addWidget(self.detail_text) content_layout.addWidget(tree_scroll) content_layout.addWidget(right_panel) main_layout.addWidget(top_panel) main_layout.addWidget(stats_panel) main_layout.addWidget(content_panel) # 连接新按钮信号 self.btn_search_all.clicked.connect(self.search_all_files) self.btn_export_matches.clicked.connect(self.export_match_list) self.btn_export_tree.clicked.connect(self.export_tree_info) self.btn_import_tree.clicked.connect(self.import_tree_info) def create_browse_btn(self): btn = QPushButton("浏览") btn.clicked.connect(self.browse_folder) btn.setFixedSize(QSize(80, 30)) return btn def browse_folder(self): path = QFileDialog.getExistingDirectory(self, "选择文件夹") if path: self.folder_input.setText(path) def start_search(self): if self.search_thread and self.search_thread.isRunning(): return folder = self.folder_input.text() exts = self.ext_input.text().strip().split(";") keyword = self.keyword_input.text().strip() if not all([folder, exts, keyword]): QMessageBox.critical(self, "错误", "请填写所有搜索条件") return self.tree.clear() self.match_list.clear() self.current_matches = [] self.update_counts() self.start_time = time.time() self.search_thread = SearchWorker(folder, exts, keyword) self.search_thread.update_file.connect(self.add_file_result) self.search_thread.finished.connect(self.on_search_finished) self.search_thread.start() self.btn_search.setEnabled(False) def add_file_result(self, data): item = QTreeWidgetItem() item.setText(0, data["name"]) item.setText(1, data["size"]) item.setText(2, data["path"]) self.tree.addTopLevelItem(item) self.file_count.setText(f"文件总数: {self.tree.topLevelItemCount()}") def on_search_finished(self): self.btn_search.setEnabled(True) elapsed = time.time() - self.start_time self.time_label.setText(f"耗时: {elapsed:.2f}秒") def single_file_search(self): path = self.single_input.text() keyword = self.keyword_input.text().strip() if not os.path.isfile(path): QMessageBox.critical(self, "错误", "无效的文件路径") return self.match_list.clear() self.current_matches = [] self.file_reader = FileReader(path, keyword) self.file_reader.update_line.connect(self.match_list.addItem) self.file_reader.finished.connect(lambda: ( self.line_count.setText(f"匹配行数: {self.match_list.count()}"), self.current_matches.extend(self.file_reader.results) )) self.file_reader.start() def on_tree_double_click(self): item = self.tree.currentItem() if not item: return path = item.text(2) keyword =php self.keyword_input.text().strip() self.match_list.clear() self.current_matches = [] self.file_reader = FileReader(path, keyword) self.file_reader.update_line.connect(self.match_list.addItem) self.file_reader.finished.connect(lambda: ( self.line_count.setText(f"匹配行数: {self.match_list.count()}"), self.current_matches.extend(self.file_reader.results) )) self.file_reader.start() def on_list_double_click(self): index = self.match_list.currentRow() if 0 <= index < len(self.current_matches): self.detail_text.setPlainText(self.current_matches[index]) def search_all_files(self): paths = [] root = self.tree.invisibleRootItem() for i in range(root.childCount()): item = root.child(i) paths.append(item.text(2)) keyword = self.keyword_input.text().strip() if not keyword: QMessageBox.critical(self, "错误", "请输入搜索关键字") return self.match_list.clear() self.current_matches = [] self.all_files_reader = AllFilesReader(paths, keyword) self.all_files_reader.update_line.connect(self.match_list.addItem) self.all_files_reader.finished.connect(lambda: ( self.line_count.setText(f"匹配行数: {self.match_list.count()}"), self.current_matches.extend(self.file_reader.results) if self.file_reader else None )) self.all_filepythons_reader.start() def export_match_list(self): keyword = self.keyword_input.text().strip() or "search" timestamp = time.strftime("%Y%m%d_%H%M%S") filename = f"{keyword}_{timestamp}.txt" desktop = f"D:/桌面/" path = os.path.join(desktop, filename) with open(path, 'w', encoding='utf-8') as f: for i in range(selandroidf.match_list.count()): f.write(self.match_list.item(i).text() + "\n") QMessageBox.information(self, "导出完成", f"文件已保存到:{path}") def export_tree_info(self): items = [] root = self.tree.invisibleRootItem() for i in range(root.childCount()): item = root.child(i) items.append("\t".join([ item.text(0), item.text(1), item.text(2) ])) timestamp = time.strftime("%Y%m%d_%H%M%S") filename = f"tree_export_{timestamp}.txt" desktop = f"D:/桌面/" path = os.path.join(desktop, filename) with open(path, 'w', encoding='utf-8') as f: f.write("\n".join(items)) QMessageBox.information(self, "导出完成", f"树结构已保存到:{path}") def import_tree_info(self): path, _ = QFileDialog.getOpenFileName(self, "选择导入文件", "", "文本文件 (*.txt)") if not path: return self.tree.clear() with open(path, 'r', encoding='utf-8') as f: for line in f: parts = line.strip().split('\t') if len(parts) != 3: continue item = QTreeWidgetItem() item.setText(0, parts[0]) item.setText(1, parts[1]) item.setText(2, parts[2]) self.tree.addTopLevelItem(item) self.file_count.setText(f"文件总数: {self.tree.topLevelItemCount()}") def update_counts(self): self.file_count.setText(f"文件总数: {self.tree.topLevelItemCount()}") self.line_count.setText(f"匹配行数: {self.match_list.count()}") if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec())
6.总结
这是一个基于PyQt6开发的文件搜索与内容匹配工具。该工具不仅支持高效的文件查找,还能通过关键字匹配文件内容,并提供详细的匹配结果展示和导出功能。利用PyQt的多线程特性,程序在执行搜索任务时能够保持界面的响应性,大大提升了用户体验。
未来的改进方向包括:
增强搜索效率:可以增加更多的文件搜索选项,如模糊匹配或正则表达式支持,以满足更复杂的查找需求。
界面优化:加入进度条或其他UI元素,实时反馈搜索进度。
功能扩展:支持更多的文件操作,如批量重命名、批量删除等。
以上就是基于python开发高效文件搜索与内容匹配工具的详细内容,更多关于Python文件搜索与内容匹配的资料请关注编程客栈(www.devze.com)其它相关文章!
精彩评论