之前发了一个文本快速搜索工具,想不到这么久还有人私聊我,看来有文本搜索需求的人不少,
最近使用py重构了一下文本快速搜索工具,速度没慢多少,就降低了一半,我感觉速度还可以接受。
分享给大家希望,能帮助有需要的人。
下面直接发py代码,没python环境的划到最后,下载打包好的
[Python] 纯文本查看 复制代码
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)
# 统计信息
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 = 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_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"文件总数: {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())
蓝奏打包好的链接:https://wwfh.lanzout.com/islwx2r8v6ch