ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python办公自动化实战:从Excel合并到PDF批量处理的完整方案

Python办公自动化实战:从Excel合并到PDF批量处理的完整方案 简介面向办公人员的 Python 自动化实战资源包适合希望通过编程减少重复劳动、提升效率的职场人士和 Python 初学者。资源以源码与讲解文档结合的方式系统覆盖 Python 环境搭建、基础语法、文件与目录操作、文本处理、Excel/CSV 表格自动化、Word/PPT 文档操作等典型办公场景并延伸到邮件发送、网络爬虫、自动化报告生成、数据分析与可视化等进阶应用。包内共有 479 个文件包括 157 个 Python 脚本、155 个 PDF 讲解文档、67 个 PPT 课件、40 个 Excel 示例表格、16 个 Word 文档等压缩包整体约 212.66MB按模块组织便于检索。目前已有 206 人学习。借助源码中的步骤注释和排错思路读者可快速落地实际任务相关章节对异常处理、代码优化、测试与部署的说明也有助于构建稳健高效的自动化应用。1. 用 Python 办公自动化接到脏活后的第一件事大多数办公自动化的需求不是从写代码开始的而是从“我看你 Excel 用得不错”这句话开始的。等你把几十张格式各异的报表、一堆 PDF 合同、按姓名命名的文件堆到一块才会意识到真正的问题不是 Python 不会写而是不知道从哪里下手拆解。我处理过的最典型场景是这样业务方发来一个压缩包里面有 46 张月度统计表每张表字段顺序不同、表头有合并单元格、几百行数据里夹着空行要求合并成一个总表并算出同比。这时第一件事不是写 pandas而是先确定三件事文件在哪、库装没装、目标表长什么样。Python 办公自动化的套路本质上就是把这套“文件定位 → 数据抽取 → 结构统一 → 结果回写”的流程固化成脚本让每一次重复劳动变成一次参数替换。这篇文章按我实际能跑通的路径展开先准备环境再解决 Excel 批处理这个最高频需求然后是 Word 和 PDF 的批量生成与抽取最后落到定时触发和异常处理。适合已经能写点 Python、但苦于不知道从哪下手做办公自动化的开发者也适合想从一堆手动操作里解放出来的业务技术岗。2. Python 办公自动化环境搭建与库选型2.1 先按这套流程装好基础环境再谈效率办公自动化脚本和 Web 项目不同它更看重“在别人电脑上能跑”而不只是“在我电脑上能跑”。所以环境安装不必追求最新版本稳定兼容更为重要。常见做法是安装 Python 3.9 到 3.11 之间的版本Windows 下记得勾选“Add Python to PATH”这一步漏掉会导致后面命令行里python命令无法识别这是 python 安装教程里最常被跳过却影响最大的一步。装完语言环境之后我强烈建议为每个项目建一个虚拟环境而不是直接往全局环境里装库。因为办公自动化往往依赖大量第三方包不同项目的依赖版本很容易冲突。用下面的命令就能完成环境初始化mkdir auto_office cd auto_office python -m venv venv source venv/bin/activate # Windows 下执行 venv\Scripts\activate pip install --upgrade pip逻辑说明venv是 Python 自带的虚拟环境模块不需要额外安装激活之后pip安装的包都会进入当前项目的venv目录不会污染系统环境。参数说明如果你需要同时维护多个项目建议装一下virtualenvwrapper-winWindows或直接使用conda管理环境但纯脚本场景venv完全够用。2.2 办公自动化常用的库和各自边界选库是办公自动化落地的核心决策。针对不同文件类型有几个库是经过大量项目验证的文件类型首选库适用场景注意边界Excel .xlsxopenpyxl读写、样式、公式、图表不支持 .xls 老格式Excel 数据分析pandas批量合并、筛选、透视写入样式能力弱Excel .xlsxlrd / xlwt老格式兼容xlrd 2.0 只读Word .docxpython-docx批量生成、替换文本不支持 .doc 老格式PDF 读取pdfplumber表格、文本抽取扫描件需 OCRPDF 处理PyPDF2 / pypdf合并、拆分、旋转加密 PDF 需要密码系统自动化pywin32调用 Office COM 接口仅限 Windows安装命令pip install pandas openpyxl python-docx pdfplumber pypdf pywin32这里尤其要提醒pandas 读取 Excel 时依赖 openpyxl处理新格式或 xlrd处理旧格式只装 pandas 不装 openpyxlread_excel会直接报 ImportError这是新手最常见的一个坑。2.3 用一段脚本验证环境是否可用为了确认环境没问题我习惯先跑一个极简的冒烟测试把一个 Excel 文件读出来打印前几行import pandas as pd df pd.read_excel(demo.xlsx, sheet_nameSheet1) print(df.head()) print(f数据维度: {df.shape})逻辑说明read_excel背后会调用 openpyxl 解析 xlsx 文件head()默认取前 5 行返回一个 DataFrame 对象。参数说明sheet_name可以传字符串表名或整数表索引从 0 开始如果文件只有一张表也可以省略需要读取全部工作表时传sheet_nameNone会返回一个以表名为键的字典。如果这段代码能跑通说明 Python 基础环境和 Excel 读写链路是通的后面所有复杂脚本都建立在这个基础之上。环境这块到此告一段落接下来进入实际需求密度最高的 Excel 批处理场景。3. Excel 批处理用 Python 办公自动化合并上百张表的实操3.1 多表合并前必须先做的三件整理工作接手的原始 Excel 几乎不可能是干净的。我在做合并之前一定会先跑一遍“摸底脚本”输出每个文件的表头、行数、列数而不是直接拿 pandas 去 concat。原因很简单如果 10 张表里有 3 张的字段名不一致直接合并会变成几十列数据全错位。摸底脚本长这样import pandas as pd import os folder 月度数据 for f in os.listdir(folder): if f.endswith(.xlsx): df pd.read_excel(os.path.join(folder, f)) print(f, | 行数:, len(df), | 列数:, df.shape[1]) print(字段:, list(df.columns)[:5])逻辑说明os.listdir返回文件夹内所有文件名endswith(.xlsx)保证只处理目标格式。参数说明如果文件分散在多个子目录可以改用glob.glob(**/*.xlsx, recursiveTrue)递归搜索recursiveTrue表示匹配所有子目录。摸底输出里要重点看字段是否统一、列数是否一致这两项是后续能否直接合并的判决依据。3.2 字段统一后再合并pandas concat 的正确用法确认字段一致后合并操作本身极其简单。常见做法是先把所有 DataFrame 放进一个列表再一次性 concat。这样比逐张 append 快很多数据量大时差异明显import pandas as pd import os folder 月度数据 frames [] for f in os.listdir(folder): if f.endswith(.xlsx): df pd.read_excel(os.path.join(folder, f)) df[来源文件] f # 标记数据来源方便回溯 frames.append(df) result pd.concat(frames, ignore_indexTrue) result.to_excel(合并结果.xlsx, indexFalse)逻辑说明pd.concat默认按列名对齐字段不一致时会生成多列所以在 concat 之前必须保证字段统一。ignore_indexTrue让合并后的 DataFrame 重新生成 0 到 N-1 的索引否则会保留每个原始 DataFrame 的索引导致出现大量重复行号。参数说明to_excel中indexFalse是关键参数不写的话会在输出文件里多出一列无意义的行号交付时会被业务方挑毛病。3.3 格式不统一时的兜底策略列名映射和类型修正现实远比理想脏。字段名类似但不完全一样的情况最普遍比如有的表叫“日期”有的叫“时间”有的叫“发生日期”。这时候不能硬 concat而是先做一次列名标准化import pandas as pd def normalize_columns(df): rename_map { 日期: date, 时间: date, 发生日期: date, 金额元: amount, 金额: amount, 销售额: amount, } return df.rename(columnsrename_map)逻辑说明rename方法按字典映射列名只有命中的列才会被替换不存在的列名不会报错。参数说明这里的映射表是静态的实际项目里如果字段名变体太多建议把映射表抽到一个 JSON 配置文件里维护业务方加新表时只改配置不碰代码。列名统一只是第一步类型也得修——金额列如果某张表里混入了“N/A”整列会被 pandas 读成 object 类型后续sum()会报错或直接跳过非数值行。处理方式是用pd.to_numeric强制转换错误时置为 NaNresult[amount] pd.to_numeric(result[amount], errorscoerce) print(result[amount].sum()) print(f存在空值的行数: {result[amount].isna().sum()})逻辑说明errorscoerce把无法转成数字的内容一律变成 NaN而不是抛异常中断脚本。isna().sum()统计缺失值数量这样你能清楚知道有多少数据需要回去找业务方确认而不是偷偷填 0。这一步做完合并后的总表才具备分析价值接下来要处理的是向 Excel 里写结果时经常会踩的坑。3.4 合并结果写回时的格式问题pandas 直接to_excel写出的表没有任何样式表头不加粗、列宽不调整。如果交付要求带样式我一般先用 pandas 把数据写出来再用 openpyxl 打开做后处理两步走from openpyxl import load_workbook from openpyxl.styles import Font wb load_workbook(合并结果.xlsx) ws wb.active for cell in ws[1]: cell.font Font(boldTrue) ws.column_dimensions[A].width 20 wb.save(合并结果.xlsx)逻辑说明load_workbook以 openpyxl 方式重新打开 pandas 生成的文件ws[1]取第一行遍历后给每个单元格设置加粗字体。column_dimensions[A]控制 A 列列宽数字是字符宽度。参数说明如果要把整个数据区域都设成文本格式可以遍历ws.iter_rows(min_row2)逐个设置number_format 但要注意这会覆盖原有的数值类型谨慎使用。到这里Excel 批处理的主流程已经完整接下来处理 Word 和 PDF。4. Word 与 PDFPython 办公自动化的批量生成和抽取方案4.1 批量生成 Word用占位符模板替代逐一打开合同、通知书、证明这类文档的特点是结构固定、只有少数信息变化。用 python-docx 逐个创建文档不是好方案维护成本太高。常见做法是准备一个带占位符的 .docx 模板然后写脚本替换占位符另存为新文件。先准备好模板内容里写{name}、{date}、{amount}这样的占位符然后执行from docx import Document import pandas as pd data pd.read_excel(人员名单.xlsx) template_path 通知模板.docx for _, row in data.iterrows(): doc Document(template_path) for para in doc.paragraphs: for key, value in row.items(): if key and isinstance(key, str): placeholder { key } if placeholder in para.text: para.text para.text.replace(placeholder, str(value)) doc.save(f通知_{row[name]}.docx)逻辑说明Document(template_path)读入模板文件iterrows()逐行遍历 Excel 数据每一行生成一个独立文档。para.text返回段落文本replace 方法把占位符替换成实际值。参数说明iterrows()返回 (索引, Series) 对这里用_忽略索引row.items()遍历每一列列名正好当占位符键使用。这段代码的前提是占位符只在段落里如果占位符出现在表格单元格里还需要额外遍历doc.tables这里暂不展开。4.2 批量替换的隐藏坑和增量保存策略占位符替换看起来简单实际有两个坑。第一python-docx 里para.text的赋值会丢失段落内的多级格式比如某段文字混合了加粗和普通字体整段替换后格式会归一到默认样式。第二如果模板里同一个占位符在一段中出现多次上面的写法只会替换一次因为每次 replace 是对para.text整体操作赋值后又变回单一文本。更稳妥的增量替换写法是逐个操作runfrom docx import Document doc Document(通知模板.docx) def replace_in_paragraph(para, mapping): for run in para.runs: for key, value in mapping.items(): if key in run.text: run.text run.text.replace(key, str(value)) mapping {{name}: 张三, {date}: 2024-06-30} for para in doc.paragraphs: replace_in_paragraph(para, mapping) doc.save(输出.docx)逻辑说明para.runs是段落里的格式片段列表每个 run 有独立格式。只替换命中占位符的 run未命中的 run 保持原样这样格式丢失的问题就解决了。参数说明如果占位符被拆分在两个相邻 run 里比如“{na”和“me}”这种按 run 替换的方式会失效处理办法是在代码里先检查全文doc.paragraphs拼接后的文本是否包含占位符再决定是否用第一段的段落级替换作为兜底。实际模板设计时建议把占位符单独放在一个独立的 run 里也就是在 Word 里单独输入占位符不要和正常文字混在一个输入批次里。4.3 PDF 内容抽取pdfplumber 处理表格型数据PDF 是办公自动化的终点站几乎所有系统都支持导出 PDF但很少支持反向解析。处理 PDF 里的表格pdfplumber 比 PyPDF2 更可靠因为 PyPDF2 只能抽文本无法还原表格结构。核心代码import pdfplumber import pandas as pd with pdfplumber.open(报表.pdf) as pdf: all_tables [] for page in pdf.pages: tables page.extract_tables() for table in tables: all_tables.extend(table) df pd.DataFrame(all_tables[1:], columnsall_tables[0]) df.to_excel(PDF转出的表格.xlsx, indexFalse)逻辑说明extract_tables()返回页面上识别出的所有表格每个表格是一个二维列表第一行是表头后续行是数据。all_tables[1:]跳过表头行columnsall_tables[0]把第一行设为列名。参数说明extract_tables有几个可选参数值得关注——table_settings里可以传vertical_strategy: text让竖线识别更智能min_words_vertical控制最少多少个单词才被认为是同一列。扫描件 PDF 需要 OCR 支持pdfplumber 本身不带 OCR 能力得配合 pytesseract但不推荐识别率和速度都很难达到生产要求。4.4 PDF 合并和拆分面向实际交付场景最后一个高频需求是合并多个 PDF 成一个完整文件或提取某几页发给客户。用 pypdfPyPDF2 的官方维护版处理from pypdf import PdfWriter, PdfReader import os writer PdfWriter() folder pdf待合并 for f in sorted(os.listdir(folder)): if f.endswith(.pdf): reader PdfReader(os.path.join(folder, f)) for page in reader.pages: writer.add_page(page) with open(合并完成.pdf, wb) as out: writer.write(out) split_reader PdfReader(合并完成.pdf) part_writer PdfWriter() for i in range(0, 3): # 提取前 3 页 part_writer.add_page(split_reader.pages[i]) with open(前3页.pdf, wb) as out: part_writer.write(out)逻辑说明PdfWriter是合并输出对象PdfReader逐文件读入add_page只添加页面对象不会在内存里保存整个文件内容。逻辑说明range(0, 3)生成 0、1、2 三个索引对应 PDF 的第 1 到第 3 页页码从 0 开始计数。参数说明pypdf 加密的 PDF 打开时要用PdfReader(path, passwordxxx)没有密码的 PDF 传空即可。到这里批处理、格式转换、合并拆分都覆盖到了下一章把这些能力串到定时触发和自动化监控里才能真正解放人力。5. 定时与监控让 Python 办公自动化从手动跑到自动跑5.1 自动处理新文件的 watchdog 监控模式很多办公自动化脚本的问题是“还是要人双击运行”。真正意义上的自动化是文件丢进文件夹的那一刻脚本自动开始处理。watchdog 是专门做目录监听的库安装后就能监控新增、修改、删除事件pip install watchdog监听脚本框架import time from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler import shutil class ExcelHandler(FileSystemEventHandler): def on_created(self, event): if event.is_directory: return if event.src_path.endswith(.xlsx): print(f检测到新文件: {event.src_path}) # 这里调用你封装好的处理函数 process_excel(event.src_path) def process_excel(path): import pandas as pd df pd.read_excel(path) df.to_csv(path.replace(.xlsx, .csv), indexFalse) shutil.move(path, 已处理文件夹/) if __name__ __main__: observer Observer() observer.schedule(ExcelHandler(), path待处理文件夹, recursiveFalse) observer.start() try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join()逻辑说明FileSystemEventHandler是事件处理器基类on_created在文件创建时触发。event.is_directory判断是不是目录事件目录新建也要过滤。observer.schedule()把事件处理器绑定到目标目录recursiveFalse表示只监控一层目录如果子目录里也会丢文件要改成True。逻辑说明time.sleep(1)让主线程保持存活KeyboardInterrupt是 CtrlC 退出时机。这里把处理逻辑process_excel单独封装方便你在里面写更复杂的业务。5.2 固定时间跑批的定时任务方案watchdog 适合“来了就处理”的场景但有些活是固定期限的例如月底最后一天出汇总表、每周五下班前发周报。这类需求可以直接用系统自带能力Windows 下用“任务计划程序”操作位python 脚本绝对路径在“起始于”里指定脚本所在目录。Linux 下用 crontabcrontab -e添加一行任务。30 9 * * 1 cd /path/to/script /usr/bin/python3 daily_report.py run.log 21参数说明这条 cron 表示每周一 9:30 执行一次daily_report.py run.log把标准输出追加到日志文件21把错误输出也重定向到同一个文件方便事后排查。注意cd到脚本目录很关键如果脚本里用了相对路径读文件不切目录会直接 FileNotFoundError。如果是 Windows 任务计划程序记得在“操作”里把“起始于”目录填上处理方式相同。5.3 批量场景下的多进程加速办公自动化处理大量文件时单线程逐个处理会很慢尤其是读 PDF 和写 Excel。几百个文件的场景下用concurrent.futures.ProcessPoolExecutor可以把处理时间缩短几倍from concurrent.futures import ProcessPoolExecutor import os def handle_one_file(filename): import pandas as pd df pd.read_excel(filename) return len(df) if __name__ __main__: files [f for f in os.listdir(数据目录) if f.endswith(.xlsx)] with ProcessPoolExecutor(max_workers4) as executor: results list(executor.map(handle_one_file, files)) print(results)逻辑说明executor.map把函数应用到文件列表的每个元素上内部会分配多个进程并行执行。max_workers4表示同时跑 4 个进程数字不一定要等于 CPU 核心数IO 密集型的 Excel 操作开 4 到 8 个都合适。逻辑说明if __name__ __main__是 Windows 下多进程的硬性要求缺了会递归创建子进程报错。handle_one_file里重新 import pandas 不是多余的多进程环境每个子进程都要重新加载模块。5.4 异常要有兜底脚本要能自己报警脚本无人值守运行最怕的是半夜跑挂了没人知道。我一般在所有处理逻辑外面包一层 catch失败时把堆栈写到日志再调用邮件发送接口通知自己。邮件这块用 smtplib 几下就能实现import smtplib from email.mime.text import MIMEText def send_alert(subject, body): msg MIMEText(body, plain, utf-8) msg[Subject] subject msg[From] senderexample.com msg[To] adminexample.com with smtplib.SMTP_SSL(smtp.example.com, 465) as server: server.login(senderexample.com, password) server.send_message(msg)逻辑说明MIMEText构造纯文本邮件SMTP_SSL走 465 端口加密连接send_message负责发送。参数说明如果公司邮箱要求走 587 端口的 STARTTLS改用smtplib.SMTP加server.starttls()密码不要硬编码在脚本里从环境变量os.getenv(MAIL_PASS)读取。到这里整个办公自动化框架已经有了完整闭环监听新文件、定时执行、批量加速、失败告警。最后聊一聊线上跑起来之后最常踩的坑以及如何验证整套链路可靠。6. Python 办公自动化排错三板斧与边缘场景处理这一章专注两个点验证自动化链路是否真的可用以及处理最典型的运行期异常。很多脚本在开发环境跑得好好的放到任务计划程序里就失灵排查方向就那么几个。第一路径问题。任务计划程序里的工作目录不一定是你脚本所在目录脚本里最好统一用绝对路径或者先os.chdir(os.path.dirname(os.path.abspath(__file__)))切到脚本目录。排查时在脚本第一步打印os.getcwd()看当前目录是不是预期目录。第二编码问题。读 CSV 或旧版 Excel 时经常遇到中文乱码pandas.read_csv(文件.csv, encodinggbk)是用 gbk 解码如果是 UTF-8 文件就报错或乱码。不确定编码时可以用charset_normalizer库检测from charset_normalizer import from_path result from_path(数据.csv).best() print(f检测结果: {result.encoding})逻辑说明from_path直接读文件内容并推测编码best返回可靠度最高的检测结果。参数说明办公场景里最常见的编码是 UTF-8 和 GBK批量处理多来源文件时先统一转成 UTF-8 再入库能省掉后续大量麻烦。第三资源占用问题。用 win32com 操作 Excel 时速度比 pandas 慢得多但有些需求必须用它——比如调用 Excel 自带的 VBA 宏、刷新数据透视表。写完一定记得:import win32com.client excel win32com.client.Dispatch(Excel.Application) wb excel.Workbooks.Open(D:\\报表.xlsx) excel.Visible False # 处理逻辑... wb.Close(False) excel.Quit()逻辑说明Dispatch(Excel.Application)启动一个不可见的 Excel 实例Open打开工作簿Quit退出。参数说明如果持续生成 Excel 进程通常是异常退出时没有调用wb.Close和excel.Quit把这两句放到finally块里可以大幅降低进程残留概率。实在遇到残留下来的 EXCEL.EXE用任务管理器结束后再跑脚本。最后一类边缘场景是文件占用冲突Excel 文件正被用户打开时脚本写入会报 PermissionError。常见做法是重试三次、每次间隔两秒import time for attempt in range(3): try: df.to_excel(输出.xlsx) break except PermissionError: print(f第 {attempt 1} 次写入失败文件可能被占用) time.sleep(2) else: raise RuntimeError(三次重试仍然失败)逻辑说明for...else在循环正常结束后执行else分支如果中途break则跳过。time.sleep(2)每次重试前等待 2 秒给用户留出关闭文件的时间。参数说明重试次数和间隔时长依据实际环境调整内网共享盘建议重试 5 次、间隔 5 秒本地文件可以缩短。验证整条链路是否健壮我给自己的标准做法是准备一个包含空文件、损坏文件、超长文件名、中文文件名和超大体积文件的混合目录跑一遍脚本确认坏文件不会中断整批任务并逐条对应日志里的告警记录。这套验证方法配合前文的监听、定时、异常告警就是一条完整的 Python 办公自动化交付链路。本文还有配套的精品资源点击获取
返回列表