200字
Python + PyQt5 打造一款 Excel 某列正则替换神器
2026-03-20
2026-03-20

告别繁琐的手工删改!使用 Python + PyQt5 打造一款 Excel 正则替换神器

image-fsYx.png

在日常的数据处理和办公中,我们经常会遇到这样的抓狂场景:系统导出的 Excel 数据里,某一列夹杂了大量的冗余信息,比如带有特定括号的备注、被特定符号包裹的无用代码等。如果只是简单的替换,Excel 自带的 Ctrl+H 还能应付;但如果是**“删除以 A 开头、以 B 结尾”**这种带有规则的动态内容,Excel 自身的功能就显得捉襟见肘了。

为了彻底解决这个痛点,释放我们的双手,我用 Python 配合 PyQt5 开发了一款轻量级的桌面小工具——Excel 内容正则替换工具。今天就把这款工具的设计思路和源码分享给大家!

完整代码1:Excel某列字符批量替换 - GitCode
完整代码2: 放在文章末尾


🌟 工具亮点

这款小工具虽然体积不大,但可以说是“麻雀虽小,五脏俱全”:

  1. 傻瓜式操作:支持直接拖拽 Excel 文件到窗口,或者点击按钮选择。
  2. 智能表头识别:自动读取 Excel 的第一行作为表头,并生成下拉菜单供你精准选择要处理的列。
  3. 强大的正则内核:底层基于 Python 的 re 模块,你只需要输入“开头”和“结尾”的字符,它就能自动帮你生成正则表达式进行非贪婪匹配替换。
  4. 细节拉满的选项:提供了一个贴心的复选框——是否包含开头和结尾的字符。你可以自由决定是把这层“壳”一起删掉,还是只掏空里面的内容。
  5. 安全的数据预览:为了防止“一失足成千古恨”,工具内置了数据预览表格,点击预览即可查看前 20 行的处理结果。
  6. 一键无损导出:处理满意后,点击导出,瞬间生成一份干净清爽的全新 Excel 文件。
  7. 高颜值 UI:告别简陋的默认界面,注入了精心调配的 QSS 样式表,扁平化设计,赏心悦目。

📸 界面展示

(在这里插入一张你运行程序后的界面截图,展示其美观的 UI 和功能分区)

工具分为四个主要区块:

  • 文件选择:支持按钮和拖拽。
  • 列选择:自动加载所有列名。
  • 规则设置:设定你的删除规则。
  • 数据预览 (前20行):所见即所得。

🛠️ 核心技术原理解析

1. 为什么选 PyQt5 和 pandas?

  • PyQt5 是 Python 中非常成熟且强大的 GUI 库,配合 QGridLayout 和自定义的 QSS 样式,可以轻松画出现代化的界面。
  • pandas 则是数据处理的王者。在这个工具中,无论是读取 Excel(pd.read_excel)、提取列名,还是导出 Excel(to_excel),pandas 都提供了极简的 API。

2. 核心正则逻辑

整个工具最灵魂的代码在于数据的替换逻辑。我们来看核心处理方法:

import re
import pandas as pd

# 假设 df_copy 是读取的 DataFrame,column_name 是选中的列,start_str 和 end_str 是用户输入的头尾
if include:
    # 包含首尾:使用 .*? 进行非贪婪匹配
    pattern = f'{re.escape(start_str)}.*?{re.escape(end_str)}'
else:
    # 不包含首尾:使用正则表达式的“零宽断言”(Lookbehind 和 Lookahead)
    pattern = f'(?<={re.escape(start_str)}).*?(?={re.escape(end_str)})'

# 应用正则替换
df_copy[column_name] = df_copy[column_name].astype(str).str.replace(pattern, '', regex=True)

知识点解析

  • re.escape():这是一个非常重要的安全措施。如果用户输入的开头或结尾包含正则表达式的特殊字符(如 *, ?, (, )),escape 会自动对其进行转义,将其视为普通文本处理。
  • .*?:非贪婪匹配,确保它只会匹配到最近的一个结束字符,而不会把两个括号之间所有的数据全部吞噬。
  • (?<=...)(?=...):也就是大名鼎鼎的断言。当用户选择“不包含首尾”时,我们需要用到它。它表示匹配的内容必须紧跟在 start_str 之后,且紧挨在 end_str 之前,但匹配结果中并不包含这两个边界本身。

🚀 如何在本地跑起来?

如果你想直接使用或者进行二次开发,非常简单。

  1. 环境准备:确保你的电脑安装了 Python 3。

  2. 安装依赖
    新建一个 requirements.txt 文件,写入:

    PyQt5>=5.15.0
    pandas>=1.3.0
    openpyxl>=3.0.0
    

    然后执行 pip install -r requirements.txt(注:openpyxl 是 pandas 处理 Excel 必不可少的底层引擎)

  3. 运行代码:将完整的 Python 代码保存为 main.py,然后在终端执行 python main.py 即可召唤出这个神器!


💡 总结

通过不到 200 行代码,我们将 Python 的 GUI 编程与数据处理能力完美结合,解决了一个高频的办公痛点。这个小项目非常适合初学者作为练手项目:它既包含了 PyQt5 布局、事件绑定、拖拽事件的进阶用法,又涵盖了 pandas 的基本操作和正则表达式的高级应用。

如果你觉得这个小工具对你有帮助,或者代码中的某些技巧给了你启发,欢迎点赞、收藏、转发!如果你有任何优化的想法,也欢迎在评论区交流讨论。

Life is short, you need Python. 😉

import sys
import re
import pandas as pd
from PyQt5.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel,
    QLineEdit, QComboBox, QCheckBox, QTableWidget, QFileDialog, QTableWidgetItem, 
    QGridLayout, QGroupBox, QMessageBox, QHeaderView
)

class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.df = None
        self.initUI()

    def initUI(self):
        self.setWindowTitle('Excel 内容替换工具')
        self.setGeometry(100, 100, 900, 700)
        self.setAcceptDrops(True)

        # 界面美化:添加 QSS 样式表
        self.setStyleSheet("""
            QWidget {
                font-family: 'Microsoft YaHei', 'Segoe UI', sans-serif;
                font-size: 10pt;
                color: #2f3640;
                background-color: #f5f6fa;
            }
            QGroupBox {
                font-weight: bold;
                border: 1px solid #dcdde1;
                border-radius: 8px;
                margin-top: 16px;
                padding-top: 16px;
                background-color: #ffffff;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                subcontrol-position: top left;
                left: 15px;
                padding: 0 5px;
                color: #00a8ff;
            }
            QPushButton {
                background-color: #00a8ff;
                color: white;
                border: none;
                border-radius: 6px;
                padding: 8px 16px;
                font-weight: bold;
                min-height: 20px;
            }
            QPushButton:hover {
                background-color: #0097e6;
            }
            QPushButton:pressed {
                background-color: #0088cc;
            }
            QLineEdit, QComboBox {
                border: 1px solid #dcdde1;
                border-radius: 5px;
                padding: 6px 12px;
                background-color: #fdfdfd;
                selection-background-color: #00a8ff;
            }
            QLineEdit:focus, QComboBox:focus {
                border: 1px solid #00a8ff;
                background-color: #ffffff;
            }
            QTableWidget {
                border: 1px solid #dcdde1;
                border-radius: 6px;
                background-color: white;
                gridline-color: #f1f2f6;
                alternate-background-color: #fafbfc;
                selection-background-color: #00a8ff;
                selection-color: white;
            }
            QHeaderView::section {
                background-color: #f5f6fa;
                padding: 8px;
                border: none;
                border-right: 1px solid #dcdde1;
                border-bottom: 1px solid #dcdde1;
                font-weight: bold;
                color: #2f3640;
            }
        """)

        main_layout = QVBoxLayout()
        main_layout.setContentsMargins(20, 20, 20, 20)
        main_layout.setSpacing(15)
        
        grid_layout = QGridLayout()
        grid_layout.setSpacing(15)

        # File selection group
        file_group = QGroupBox('文件选择')
        file_layout = QVBoxLayout()
        self.label = QLabel('📁 请选择或拖拽 Excel 文件 (支持 .xlsx, .xls)')
        self.label.setStyleSheet("color: #7f8fa6; font-style: italic;")
        file_layout.addWidget(self.label)
        
        file_btn_layout = QHBoxLayout()
        self.btn_select_file = QPushButton('📂 浏览文件')
        self.btn_select_file.clicked.connect(self.open_file_dialog)
        file_btn_layout.addWidget(self.btn_select_file)
        file_btn_layout.addStretch()
        file_layout.addLayout(file_btn_layout)
        
        file_group.setLayout(file_layout)
        grid_layout.addWidget(file_group, 0, 0)

        # Column selection group
        column_group = QGroupBox('列选择')
        column_layout = QVBoxLayout()
        self.label_column = QLabel('请选择要处理的列')
        column_layout.addWidget(self.label_column)
        self.combo_columns = QComboBox()
        column_layout.addWidget(self.combo_columns)
        column_group.setLayout(column_layout)
        grid_layout.addWidget(column_group, 0, 1)

        # Regex inputs group
        regex_group = QGroupBox('规则设置')
        regex_layout = QGridLayout()
        regex_layout.addWidget(QLabel('以xx开头(请填写)'), 0, 0)
        self.edit_start = QLineEdit()
        regex_layout.addWidget(self.edit_start, 0, 1)
        regex_layout.addWidget(QLabel('以xx结尾(请填写)'), 1, 0)
        self.edit_end = QLineEdit()
        regex_layout.addWidget(self.edit_end, 1, 1)
        self.check_include = QCheckBox('包含开头和结尾的字符(打钩将去除)')
        regex_layout.addWidget(self.check_include, 2, 0, 1, 2)
        regex_group.setLayout(regex_layout)
        grid_layout.addWidget(regex_group, 1, 0, 1, 2)

        # Action buttons group
        action_group = QGroupBox('操作')
        action_layout = QHBoxLayout()
        self.btn_preview = QPushButton('✨ 预览数据')
        self.btn_preview.setMinimumHeight(35)
        self.btn_preview.clicked.connect(self.preview_data)
        action_layout.addWidget(self.btn_preview)
        
        self.btn_export = QPushButton('💾 导出为 Excel')
        self.btn_export.setMinimumHeight(35)
        self.btn_export.clicked.connect(self.export_data)
        action_layout.addWidget(self.btn_export)
        
        action_group.setLayout(action_layout)
        grid_layout.addWidget(action_group, 2, 0, 1, 2)

        main_layout.addLayout(grid_layout)

        # Preview table
        preview_group = QGroupBox('数据预览 (前20行)')
        preview_layout = QVBoxLayout()
        self.table_preview = QTableWidget()
        self.table_preview.setAlternatingRowColors(True)
        self.table_preview.horizontalHeader().setStretchLastSection(True)
        self.table_preview.setEditTriggers(QTableWidget.NoEditTriggers) # 预览表格设为只读
        preview_layout.addWidget(self.table_preview)
        preview_group.setLayout(preview_layout)
        main_layout.addWidget(preview_group)

        self.setLayout(main_layout)

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.acceptProposedAction()

    def dropEvent(self, event):
        for url in event.mimeData().urls():
            file_path = url.toLocalFile()
            if file_path.endswith(('.xlsx', '.xls')):
                self.label.setText(f'已选择文件: {file_path}')
                self.load_excel_data(file_path)

    def open_file_dialog(self):
        options = QFileDialog.Options()
        file_path, _ = QFileDialog.getOpenFileName(self, "选择 Excel 文件", "", "Excel Files (*.xlsx *.xls);;All Files (*)", options=options)
        if file_path:
            self.label.setText(f'已选择文件: {file_path}')
            self.load_excel_data(file_path)

    def load_excel_data(self, file_path):
        try:
            self.df = pd.read_excel(file_path)
            self.combo_columns.clear()
            self.combo_columns.addItems(self.df.columns)
        except Exception as e:
            self.label.setText(f'加载文件失败: {e}')
            self.df = None

    def preview_data(self):
        processed_df = self.process_data()
        self.update_preview(processed_df)

    def process_data(self):
        if self.df is None:
            return None

        column_name = self.combo_columns.currentText()
        start_str = self.edit_start.text()
        end_str = self.edit_end.text()
        include = self.check_include.isChecked()

        df_copy = self.df.copy()

        if column_name in df_copy.columns and start_str and end_str:
            df_copy[column_name] = df_copy[column_name].astype(str)
            
            if include:
                pattern = f'{re.escape(start_str)}.*?{re.escape(end_str)}'
            else:
                pattern = f'(?<={re.escape(start_str)}).*?(?={re.escape(end_str)})'
            
            df_copy[column_name] = df_copy[column_name].str.replace(pattern, '', regex=True)
        
        return df_copy

    def export_data(self):
        processed_df = self.process_data()
        if processed_df is None:
            return

        options = QFileDialog.Options()
        save_path, _ = QFileDialog.getSaveFileName(self, "保存文件", "", "Excel Files (*.xlsx);;All Files (*)", options=options)
        if save_path:
            try:
                processed_df.to_excel(save_path, index=False)
                QMessageBox.information(self, '成功', f'文件已保存到: {save_path}')
            except Exception as e:
                self.label.setText(f'保存文件失败: {e}')

    def update_preview(self, df):
        if df is None:
            self.table_preview.clear()
            self.table_preview.setRowCount(0)
            self.table_preview.setColumnCount(0)
            return

        preview_df = df.head(20)
        self.table_preview.setRowCount(preview_df.shape[0])
        self.table_preview.setColumnCount(preview_df.shape[1])
        self.table_preview.setHorizontalHeaderLabels(preview_df.columns)

        for i in range(preview_df.shape[0]):
            for j in range(preview_df.shape[1]):
                item = str(preview_df.iloc[i, j])
                self.table_preview.setItem(i, j, QTableWidgetItem(item))

if __name__ == '__main__':
    app = QApplication(sys.argv)
    mainWin = MainWindow()
    mainWin.show()
    sys.exit(app.exec_())

Python + PyQt5 打造一款 Excel 某列正则替换神器
作者
一晌小贪欢
发表于
2026-03-20
License
CC BY-NC-SA 4.0

评论