文本快速搜索工具-py代码

之前发了一个文本快速搜索工具,想不到这么久还有人私聊我,看来有文本搜索需求的人不少,
最近使用py重构了一下文本快速搜索工具,速度没慢多少,就降低了一半,我感觉速度还可以接受。

文本快速搜索工具-py代码

分享给大家希望,能协助有需要的人。
下面直接发py代码,没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() + "
")

        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("	".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("
".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('	')
                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

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 共16条

请登录后发表评论