乐于分享
好东西不私藏

《别再手动修改PDF了!PDF修改噩梦终结者来了!Python一键批量替换多个文本,10分钟干完一天的活,效率拉满!》

《别再手动修改PDF了!PDF修改噩梦终结者来了!Python一键批量替换多个文本,10分钟干完一天的活,效率拉满!》

“带你横跨办公自动化的数据江海”

@摸鱼

闻道有先后,术业有专攻。各位大佬们大家好!~我是你们的老朋友摸鱼~,本人在十多年的日常工作中摸爬滚打攒了不少Python办公自动化的实用项目技巧,自创立”码海听潮”公众号以来,已经陆续分享70多篇原创文章啦!里面满满的办公实操干货,希望能与各位大佬共同探讨办公效率提升之道,实现不加班自由。

好叻,多了不说,少了不唠,咱直接上干货。

办公需求场景

从崩溃到优雅的进化

有一个神秘的PDF文件夹,里面有多个pdf文档,现在的需求是把所有pdf文档里的“用户手册”替换成“用户说明”, 要是这种类似的需求你的Big Boss安排你去完成,请问阁下该如何应对?

需求的PDF文件夹和PDF文档如下图:

  • 需求PDF文件夹

  • 待替换文本的PDF文档

办公痛点分析

01

 痛点1:极度的耗时与重复劳动(效率瓶颈)

    • 操作繁琐:人工操作需要经历“打开PDF -> 搜索关键词 -> 定位页面 -> 进入编辑模式 -> 修改文字 -> 保存文件 -> 关闭文件”的循环。即便使用Adobe Acrobat等专业软件,这个流程依然极度冗长。

    • 不可中断:这种重复性工作需要高度集中注意力,一旦被打断,可能忘记修改到哪一份文件,需要重新检查,进一步浪费时间。

    02

     痛点2:极高的错误率与遗漏风险(质量隐患)

      • 漏改风险:如果一个PDF文档里有多个地方出现“用户说明”,比如目录、页眉页脚、正文描述,人工操作可能会只修改了正文而忽略了页脚,导致文档内容不统一。

      • 错改风险:在连续修改多个文件后,操作者可能会手误,不小心移动了文本框的位置,或者误删了其他文字,甚至保存时覆盖了原文件,造成不可逆的文档损坏。

      03

      痛点3:格式混乱与字体丢失(技术门槛)

      • 破坏排版:直接使用PDF阅读器自带的编辑功能修改文字后,如果替换的文字长度与原文不一致(例如“用户说明”和“用户手册”字数相同,但字符宽度不同),很可能导致该行文字溢出文本框,与后面的文字重叠,或者导致该段落换行错乱。

      • 字体替换:原文档使用的字体如果没有嵌入或电脑未安装,修改时系统会自动用默认字体替换,导致修改出来的文字字体、大小、粗细与原文完全不同,一眼就能看出修改痕迹,严重影响文档的专业美观度。

      由此可见若操作成百上千个PDF文档的话整个操作流程繁琐且耗时,高频次的鼠标点击和键盘输入使操作者手指疲劳,堪称”键盘敲冒烟”式的体力劳动,加上人工疲劳操作极易导致遗漏文件夹。于是乎这时候,按以往的 “解题套路”,Python 的专属 BGM 该响起来了 ——go~ go~ go~,救苦救难的大救星这不就来了!!

      @摸鱼

      问题拆解思路

      1.遍历PDF文件夹→

      2.对pdf所有页面查找待替换的文本并替换成指定的文本

      4.保存pdf结果文本

      下面,我就用python代码让excel见识一下,什么叫”传统文化遇上赛博效率”(仅展示部分代码,非完整代码,需完整代码看文章末尾说明~)

      import sysfrom pathlib import Pathfrom typing import ListTuplefrom PyQt6.QtWidgets import (    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,     QGridLayout, QGroupBox, QLabel, QLineEdit, QPushButton,     QTableWidget, QTableWidgetItem, QProgressBar, QMessageBox,    QFileDialog, QDialog, QDialogButtonBox)from PyQt6.QtCore import Qtfrom PyQt6.QtGui import QFont, QIconimport qtawesome as qtaclass TextReplaceDialog(QDialog):    """文本替换规则编辑对话框"""    def __init__(self, rules: List[Tuple[strstr]], parent=None):        super().__init__(parent)        self.rules = rules.copy()        self.setup_ui()    def setup_ui(self):        """设置UI界面"""        self.setWindowTitle("编辑替换规则")        self.setMinimumWidth(500)        self.setModal(True)        layout = QVBoxLayout(self)        # 规则表格        self.table = QTableWidget()        self.table.setColumnCount(2)        self.table.setHorizontalHeaderLabels(["原始文本""替换文本"])        self.table.horizontalHeader().setStretchLastSection(True)        self.table.setAlternatingRowColors(True)        # 设置表头文本居中对齐        self.table.horizontalHeader().setDefaultAlignment(Qt.AlignmentFlag.AlignCenter)        # 设置表格样式        self.table.setStyleSheet("""            QTableWidget {                gridline-color: #d0d0d0;                selection-background-color: #e0e0e0;            }            QTableWidget::item {                padding: 5px;            }        """)        self.load_rules()        # 按钮区域        btn_layout = QHBoxLayout()        add_btn = QPushButton(qta.icon('fa5s.plus'), "添加")        add_btn.clicked.connect(self.add_rule)        remove_btn = QPushButton(qta.icon('fa5s.trash'), "删除")        remove_btn.clicked.connect(self.remove_rule)        btn_layout.addWidget(add_btn)        btn_layout.addWidget(remove_btn)        btn_layout.addStretch()        # 对话框按钮        dialog_btns = QDialogButtonBox(            QDialogButtonBox.StandardButton.Ok |             QDialogButtonBox.StandardButton.Cancel        )        dialog_btns.accepted.connect(self.accept)        dialog_btns.rejected.connect(self.reject)        # 组合布局        layout.addWidget(self.table)        layout.addLayout(btn_layout)        layout.addWidget(dialog_btns)    def load_rules(self):        """加载规则到表格(文本居中对齐)"""        self.table.setRowCount(len(self.rules))        for i, (old_text, new_text) in enumerate(self.rules):            # 创建原始文本项并设置居中对齐            old_item = QTableWidgetItem(old_text)            old_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)            self.table.setItem(i, 0, old_item)            # 创建替换文本项并设置居中对齐            new_item = QTableWidgetItem(new_text)            new_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)            self.table.setItem(i, 1, new_item)    def add_rule(self):        """添加新规则(文本居中对齐)"""        row = self.table.rowCount()        self.table.insertRow(row)        # 创建空项并设置居中对齐        old_item = QTableWidgetItem("")        old_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)        self.table.setItem(row, 0, old_item)        new_item = QTableWidgetItem("")        new_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)        self.table.setItem(row, 1, new_item)    def remove_rule(self):        """删除选中的规则"""        current_row = self.table.currentRow()        if current_row >= 0:            self.table.removeRow(current_row)    def get_rules(self) -> List[Tuple[strstr]]:        """获取规则列表"""        rules = []        for row in range(self.table.rowCount()):            old_text = self.table.item(row, 0).text()            new_text = self.table.item(row, 1).text()            if old_text.strip():  # 只保留有原始文本的规则                rules.append((old_text, new_text))        return rulesclass PDFProcessorUI(QMainWindow):    """PDF处理器主界面(仅UI,无实际功能)"""    def __init__(self):        super().__init__()        self.replace_rules = [            (""""),        ]        self.setup_ui()    def setup_ui(self):        """设置UI界面"""        self.setWindowTitle("PDF文本批量替换工具")        self.setMinimumSize(800600)        # 设置应用样式        self.setStyleSheet("""            QMainWindow {                background-color: #f5f5f5;            }            QGroupBox {                font-weight: bold;                border: 2px solid #cccccc;                border-radius: 5px;                margin-top: 10px;                padding-top: 10px;            }            QGroupBox::title {                subcontrol-origin: margin;                left: 10px;                padding: 0 5px 0 5px;            }            QPushButton {                padding: 8px 15px;                border-radius: 4px;                font-weight: bold;            }            QPushButton:hover {                background-color: #e0e0e0;            }            QPushButton#primaryBtn {                background-color: #2196F3;                color: white;                border: none;            }            QPushButton#primaryBtn:hover {                background-color: #1976D2;            }            QPushButton#successBtn {                background-color: #4CAF50;                color: white;                border: none;            }            QPushButton#successBtn:hover {                background-color: #45a049;            }            QPushButton#dangerBtn {                background-color: #f44336;                color: white;                border: none;            }            QPushButton#dangerBtn:hover {                background-color: #da190b;            }        """)        # 中心部件        central_widget = QWidget()        self.setCentralWidget(central_widget)        # 主布局        main_layout = QVBoxLayout(central_widget)        main_layout.setSpacing(20)        main_layout.setContentsMargins(30303030)        # 文件夹选择区域        folder_group = QGroupBox("文件夹设置")        folder_layout = QGridLayout(folder_group)        # PDF文件夹选择        folder_layout.addWidget(QLabel("PDF文件夹:"), 00)        self.pdf_folder_edit = QLineEdit()        self.pdf_folder_edit.setPlaceholderText("请选择包含PDF文件的文件夹")        folder_layout.addWidget(self.pdf_folder_edit, 01)        browse_btn = QPushButton(qta.icon('fa5s.folder-open'), "浏览")        browse_btn.clicked.connect(self.browse_pdf_folder)        folder_layout.addWidget(browse_btn, 02)        # 输出文件夹显示        folder_layout.addWidget(QLabel("输出文件夹:"), 10)        self.output_folder_edit = QLineEdit("pdf_modified")        self.output_folder_edit.setReadOnly(True)        folder_layout.addWidget(self.output_folder_edit, 11)        main_layout.addWidget(folder_group)        # 替换规则区域        rules_group = QGroupBox("替换规则")        rules_layout = QVBoxLayout(rules_group)        # 规则列表        self.rules_table = QTableWidget()        self.rules_table.setColumnCount(2)        self.rules_table.setHorizontalHeaderLabels(["原始文本""替换文本"])        self.rules_table.horizontalHeader().setStretchLastSection(True)        self.rules_table.setAlternatingRowColors(True)        # 设置表头文本居中对齐        self.rules_table.horizontalHeader().setDefaultAlignment(Qt.AlignmentFlag.AlignCenter)        self.update_rules_table()        rules_layout.addWidget(self.rules_table)        # 规则按钮        rules_btn_layout = QHBoxLayout()        edit_rules_btn = QPushButton(qta.icon('fa5s.edit'), "编辑规则")        edit_rules_btn.setObjectName("primaryBtn")        edit_rules_btn.clicked.connect(self.edit_rules)        rules_btn_layout.addWidget(edit_rules_btn)        rules_btn_layout.addStretch()        rules_layout.addLayout(rules_btn_layout)        main_layout.addWidget(rules_group)        # 进度区域        progress_group = QGroupBox("处理进度")        progress_layout = QVBoxLayout(progress_group)        self.progress_bar = QProgressBar()        self.progress_bar.setVisible(False)        progress_layout.addWidget(self.progress_bar)        self.status_label = QLabel("就绪")        self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter)        progress_layout.addWidget(self.status_label)        main_layout.addWidget(progress_group)        # 操作按钮        btn_layout = QHBoxLayout()        self.start_btn = QPushButton(qta.icon('fa5s.play'), "开始处理")        self.start_btn.setObjectName("successBtn")        self.start_btn.setMinimumHeight(40)        self.start_btn.clicked.connect(self.start_processing)        self.stop_btn = QPushButton(qta.icon('fa5s.stop'), "停止处理")        self.stop_btn.setObjectName("dangerBtn")        self.stop_btn.setMinimumHeight(40)        self.stop_btn.setEnabled(False)        self.stop_btn.clicked.connect(self.stop_processing)        open_output_btn = QPushButton(qta.icon('fa5s.folder'), "打开输出文件夹")        open_output_btn.clicked.connect(self.open_output_folder)        btn_layout.addWidget(self.start_btn)        btn_layout.addWidget(self.stop_btn)        btn_layout.addStretch()        btn_layout.addWidget(open_output_btn)        main_layout.addLayout(btn_layout)    def browse_pdf_folder(self):        """浏览PDF文件夹"""        folder = QFileDialog.getExistingDirectory(            self"选择PDF文件夹"""            QFileDialog.Option.ShowDirsOnly        )        if folder:            self.pdf_folder_edit.setText(folder)    def update_rules_table(self):        """更新规则表格显示(文本居中对齐)"""        self.rules_table.setRowCount(len(self.replace_rules))        for i, (old_text, new_text) in enumerate(self.replace_rules):            # 创建原始文本项并设置居中对齐            old_item = QTableWidgetItem(old_text)            old_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)            self.rules_table.setItem(i, 0, old_item)            # 创建替换文本项并设置居中对齐            new_item = QTableWidgetItem(new_text)            new_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)            self.rules_table.setItem(i, 1, new_item)    def edit_rules(self):        """编辑替换规则"""        dialog = TextReplaceDialog(self.replace_rules, self)        if dialog.exec() == QDialog.DialogCode.Accepted:            self.replace_rules = dialog.get_rules()            self.update_rules_table()    def start_processing(self):        """开始处理PDF(仅UI演示)"""        pdf_folder = self.pdf_folder_edit.text()        if not pdf_folder:            QMessageBox.warning(self"警告""请先选择PDF文件夹!")            return        if not os.path.exists(pdf_folder):            QMessageBox.warning(self"警告""选择的文件夹不存在!")            return        if not self.replace_rules or all(not rule[0for rule in self.replace_rules):            QMessageBox.warning(self"警告""请至少设置一条有效的替换规则!")            return        # 仅演示UI效果,不实际处理文件        self.start_btn.setEnabled(False)        self.stop_btn.setEnabled(True)        self.progress_bar.setVisible(True)        self.progress_bar.setValue(0)        self.status_label.setText("UI演示模式 - 未执行实际处理")    def stop_processing(self):        """停止处理(仅UI演示)"""        self.status_label.setText("停止处理")        self.stop_btn.setEnabled(False)        self.start_btn.setEnabled(True)    def open_output_folder(self):        """打开输出文件夹"""        output_path = os.path.abspath("pdf_modified")        if not os.path.exists(output_path):            os.makedirs(output_path)        # Windows系统        if sys.platform == 'win32':            os.startfile(output_path)        # macOS        elif sys.platform == 'darwin':            import subprocess            subprocess.run(['open', output_path])        # Linux        else:            import subprocess            subprocess.run(['xdg-open', output_path])def main():    """主函数"""    app = QApplication(sys.argv)    app.setStyle('Fusion')    # 设置应用图标    app.setWindowIcon(QIcon.fromTheme('application-pdf'))    window = PDFProcessorUI()    window.show()    sys.exit(app.exec())if __name__ == "__main__":    main()

      最终将所有的PDF文档里的“用户手册”替换成了“用户说明”,实现了之前既定的需求….

      通过上面Python自动化脚本,仅用几分钟的时间就完成原需手动操作数小时的工作任务。从最初准备手动人工机械操作的麻木到用python实现高效自动化的畅快,工作效率获得指数级提升,终于实现了不加班熬夜的自由!

      大佬们也可以举一反三,参照上面的代码思路根据自己工作中的实际情况来具体问题具体分析,实现自己定制化的需求。

      结语

      当Python遇见办公,牛马打工人终于笑出了猪叫声

      【职场人必看】每天早上一睁眼,想到又要面对:

      1.📊 堆积如山的Excel表格

      2.📑 机械重复的复制粘贴

      3.✍️ 永远改不完的各类文档

      4.诸如此类的更多……..

      是不是连Ctrl+Alt+Delete的心都有了?

      别慌!别急,摸鱼这位“职场外挂”已经带着Python代码来拯救你了!

      友情提示:考虑到没有python环境的朋友需要打包好的成品exe,摸鱼早已贴心打包好,本篇文章代码打包的exe截图如下:

      另外,《码海听潮》公众号所有文章码和exe程序已打包好上传绿联nas私有云,有需要的大佬扫一扫上面博主的个人微信二维码,需要的大佬需支付9.9元永久拥有公众号资源(写原创干货费时费力,属实不易),邀请您进入社区群获取下载链接!!,群内提供python办公自动化交流问题,解决问题,且码海听潮微信公众号文章发布会第一时间会更新到群里,非诚勿扰哈!

      码海听潮官方社区群如下:

      赶紧微信扫一扫下方二维码添加摸鱼君微信

      本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 《别再手动修改PDF了!PDF修改噩梦终结者来了!Python一键批量替换多个文本,10分钟干完一天的活,效率拉满!》

      评论 抢沙发

      4 + 3 =
      • 昵称 (必填)
      • 邮箱 (必填)
      • 网址
      ×
      订阅图标按钮