基于Python開發(fā)高效文件搜索與內容匹配工具
在日常的開發(fā)和辦公中,查找和篩選特定文件或文件內容的需求十分常見。尤其是在處理大量文件時,如何高效地搜索到所需內容,往往是一個挑戰(zhàn)。今天,我們將分享一個基于PyQt6的文件搜索工具,它不僅能根據(jù)文件后綴進行搜索,還支持關鍵字匹配和詳細信息展示。通過這款工具,您可以更高效地管理和查找文件,提升工作效率。
1.概述
這款基于PyQt6開發(fā)的文件搜索工具,具備文件搜索、文件內容匹配、搜索結果顯示及導出功能。程序支持通過拖拽輸入框選擇目標文件夾,并對其中的文件進行后綴篩選和內容搜索。匹配結果可以實時顯示在界面上,用戶可以查看匹配的文件名、大小以及路徑,還可以導出搜索結果或者樹結構信息。
該工具的核心特點包括:
- 多線程搜索:使用PyQt的QThread進行文件搜索操作,避免UI卡頓。
- 多種文件編碼支持:支持UTF-8、GBK等常見編碼格式,能夠準確讀取文件內容。
- 實時展示匹配結果:搜索結果實時更新,方便用戶查看和處理。
- 導入導出功能:支持導入文件樹信息及導出搜索結果。
2.功能使用
2.1 文件夾選擇與搜索設置
啟動應用后,用戶首先需要選擇一個目標文件夾,輸入需要搜索的文件后綴以及關鍵詞。文件后綴支持通過分號分隔的多項選擇,例如 .txt; .log,而關鍵詞則是搜索文件內容時所需要匹配的字符串。
界面上設計了拖拽功能,用戶可以直接將目標文件夾拖拽到輸入框中,免去手動輸入路徑的麻煩。
2.2 文件內容搜索
輸入完搜索條件后,點擊“開始搜索”按鈕,程序會啟動一個后臺線程(SearchWorker),在目標文件夾中遞歸查找符合條件的文件。搜索的內容不僅是文件名匹配,還包括文件內部的內容是否包含指定的關鍵字。程序采用了多種編碼方式進行文件內容的讀取,以確保文件格式不同的情況下能夠正確處理。
搜索過程中,UI界面并不會卡頓,用戶可以繼續(xù)操作其他功能。匹配到的文件會實時顯示在左側的文件樹結構中,每一項顯示文件名、大小和路徑等信息。
2.3 單文件搜索
除了支持批量文件搜索外,程序還提供了單文件搜索的功能。在“單文件搜索”板塊中,用戶可以選擇指定的文件,輸入要查找的關鍵詞,點擊搜索按鈕后,程序會展示匹配到的行及其內容,并在右側的文本框中顯示完整匹配結果。
該功能可以幫助開發(fā)人員快速定位特定代碼段或文件中的問題,極大提高了工作效率。
2.4 結果展示與導出
搜索結束后,程序會統(tǒng)計文件總數(shù)、匹配的行數(shù)和搜索所花費的時間,展示在界面上。同時,所有匹配到的結果會以列表的形式顯示在右側,用戶可以選擇具體的行進行查看。
如果用戶需要保存搜索結果,程序提供了導出功能。用戶可以將所有匹配的文件內容導出為文本文件,方便后續(xù)查看或共享。另外,文件樹的結構信息也可以導出,便于存檔和分析。
2.5 文件樹信息導入
程序還支持導入文件樹信息。用戶可以將之前導出的樹結構文件導入到工具中,快速恢復之前的搜索狀態(tài)和文件結構。導入后的文件樹可以繼續(xù)進行搜索或查看,提升了工具的靈活性和可用性。
3.代碼結構與實現(xiàn)
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):
continue
return False
3.3 導入導出功能
導出功能實現(xiàn)了將文件匹配結果和文件樹結構導出為文本文件,支持用戶將數(shù)據(jù)保存在指定路徑。通過QFileDialog對話框,用戶可以選擇保存的位置或導入的數(shù)據(jù)文件。
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 (UnicodeDecodeError, 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) # 統(tǒng)計信息 stats_panel = QWidget() stats_layout = QHBoxLayout(stats_panel) self.file_count = QLabel("文件總數(shù): 0") self.line_count = QLabel("匹配行數(shù): 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) # 主內容區(qū)域 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, "錯誤", "請?zhí)顚懰兴阉鳁l件") 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"文件總數(shù): {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"匹配行數(shù): {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 = 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"匹配行數(shù): {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"匹配行數(shù): {self.match_list.count()}"), self.current_matches.extend(self.file_reader.results) if self.file_reader else None )) self.all_files_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(self.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"文件總數(shù): {self.tree.topLevelItemCount()}") def update_counts(self): self.file_count.setText(f"文件總數(shù): {self.tree.topLevelItemCount()}") self.line_count.setText(f"匹配行數(shù): {self.match_list.count()}") if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec())
6.總結
這是一個基于PyQt6開發(fā)的文件搜索與內容匹配工具。該工具不僅支持高效的文件查找,還能通過關鍵字匹配文件內容,并提供詳細的匹配結果展示和導出功能。利用PyQt的多線程特性,程序在執(zhí)行搜索任務時能夠保持界面的響應性,大大提升了用戶體驗。
未來的改進方向包括:
增強搜索效率:可以增加更多的文件搜索選項,如模糊匹配或正則表達式支持,以滿足更復雜的查找需求。
界面優(yōu)化:加入進度條或其他UI元素,實時反饋搜索進度。
功能擴展:支持更多的文件操作,如批量重命名、批量刪除等。
以上就是基于Python開發(fā)高效文件搜索與內容匹配工具的詳細內容,更多關于Python文件搜索與內容匹配的資料請關注腳本之家其它相關文章!
相關文章
python3+telnetlib實現(xiàn)簡單自動測試示例詳解
telnetlib 模塊提供一個實現(xiàn)Telnet協(xié)議的類 Telnet,本文重點給大家介紹python3+telnetlib實現(xiàn)簡單自動測試示例詳解,需要的朋友可以參考下2021-08-08