Python自动化办公实战:openpyxl、python-docx与python-pptx高效处理Excel、Word和PPT
简介:这份Python自动化办公教程面向希望用编程替代重复手工操作的职场人与初学者,系统讲解如何借助Python提升日常办公效率。内容覆盖Python基础语法与变量、条件循环等入门知识,并逐章展开Excel读写与样式图表处理、Word文档生成、PDF合并拆分、邮件收发解析、数据库增删改查、网络请求与爬虫、文件及进程线程管理等实用模块,每章配有习题与实操案例,便于边学边练。资源包为1个docx文档,约1.84MB,按章节组织目录,结构清晰,可直接作为学习笔记或教学讲义使用。目前已有1146人学习,适合需要系统掌握办公自动化技能、希望减少手动操作繁琐度的读者参考。
1. 从一份 64 页的教程说起:Python 自动化办公到底能替你省下多少手工活
月底最后一天,财务催工资条、行政催复工证明、业务催周报 PPT,三件事挤在一起,每件都是复制粘贴改格式。这份《Python 自动化办公教程》就是冲着这类场景来的:64 页正文加 60 多个编号示例,覆盖 openpyxl 操作 Excel、python-docx 处理 Word 与 PDF、python-pptx 生成 PPT 三大块,每个知识点后面都跟着可直接运行的示例代码。它适合两类人:一类是天天跟表格文档打交道、想把手动流程换成脚本的办公岗;另一类是想找一个完整练手项目、把库的 API 摸熟的 Python 初学者。教程不教你 Python 语法,默认你能看懂 for 循环和函数调用,重心全在“库怎么用、对象怎么取、样式怎么改”这些实操细节上。
2. openpyxl 的三层对象模型:Workbook、Worksheet、Cell 怎么对应到真实表格
openpyxl 是这份教程的第一章,也是整套自动化办公的地基。很多人第一次用会卡在一个认知上:代码里的对象和 Excel 界面上的东西不是一一对应的。教程开篇就把这层关系拆清楚了——Workbook 是工作簿,对应一个 .xlsx 文件;Worksheet 是工作表,对应底部标签页;Cell 是单元格,对应 A1、B2 这种坐标。三层对象各有各的属性和方法,取数据的路径永远是“先拿 Workbook,再拿 Worksheet,最后拿 Cell”,顺序错了就拿不到值。
2.1 安装与加载:load_workbook 的 filename 参数是唯一必传项
环境准备只有一条命令,但版本问题值得提前说一句。openpyxl 只支持 Excel 2010 之后的 .xlsx 格式,遇到 .xls 老文件得先另存或换库,这是教程里明确写了的边界。
pip install openpyxl装完之后,创建和加载走两条不同的路:新建用Workbook(),打开已有文件用load_workbook()。后者参数不少,但只有 filename 是必传的,可以传路径字符串,也可以传一个已打开的文件对象。操作完记得save(),否则所有修改只存在内存里,关掉进程就没了。
import openpyxl # 新建一个工作簿,内存中创建,尚未落盘 wb_new = openpyxl.Workbook() # 加载本地已有工作簿,filename 为必传参数 wb = openpyxl.load_workbook("excelTest.xlsx") # 所有写操作完成后必须保存,否则修改丢失 wb.save("workbook_test.xlsx")这里有个新手常忽略的点:save()的路径如果和原文件相同,等于原地覆盖,没有撤销。我一般会先存成带时间戳的副本,确认结果对了再替换原文件,这个习惯后面会反复用到。
2.2 读取的四种姿势:整表遍历、整行整列、切片、坐标直取
教程在读取部分给了十几个示例,归纳下来是四种取数方式,选哪种取决于你要处理的数据形状。
第一种是坐标直取,适合已知位置的单点读取。ws['A1']返回 Cell 对象,.value才是值;ws.cell(row=1, column=2)是另一种写法,行列都用数字,适合循环里拼坐标。
import openpyxl wb = openpyxl.load_workbook("excelTest.xlsx") ws = wb.active # 获取当前激活的工作表 # 坐标直取,拿到 Cell 对象再取 value c = ws["B1"] print(c.row, c.column, c.value) # 行号、列号、值 print(c.coordinate) # 坐标字符串,如 B1 # 行列数字写法,适合在循环中动态定位 print(ws.cell(row=1, column=2).value)第二种是整表遍历,用ws.rows按行拿,每行是一个元组,里面是 Cell 对象。这是最通用的读法,适合不知道数据边界、需要全量扫描的场景。第三种是整行整列,ws['C']拿 C 列全部,ws[6]拿第 6 行,返回的都是元组。第四种是切片,ws[2:4]拿第 2 到第 4 行,注意切片是左闭右开还是左闭右闭,openpyxl 这里是包含结束行的,跟 Python 列表切片不一样,这个差异坑过不少人。
# 整表按行遍历,逐格打印 for row in ws.rows: for cell in row: print(cell.value, end="\t") print() # 整列读取 for cell in ws["C"]: print(cell.value, end="\t") # 切片读取第 2 到第 4 行(含第 4 行) for row in ws[2:4]: for cell in row: print(cell.value, end="\t") print()如果数据量大,iter_rows()比ws.rows更可控,它支持 min_row、max_row、min_col、max_col 四个参数,可以只读某个矩形区域,避免把整张表拉进内存。教程里专门有一个示例讲这个,实际项目里处理几万行的报表时,这个参数组合是必用的。
2.3 写入与样式:append 批量写、字体行高列宽、合并单元格
写入部分教程给了三种方式:list 写入、range 写入、cell() 方法写入。最省事的是append(),传一个可迭代对象,它自动往下一行追加,适合从数据库或列表里批量灌数据。
# 逐行追加,每次调用写一行 ws.append(["姓名", "部门", "加班时长"]) ws.append(["张三", "技术部", 12]) ws.append(["李四", "市场部", 8])样式修改是这份教程比较细的地方。字体用Font,行高列宽用row_dimensions和column_dimensions,合并单元格用merge_cells(),拆分用unmerge_cells()。公式也可以直接写进单元格,值以等号开头,openpyxl 存的是公式字符串,打开 Excel 时才计算。
from openpyxl.styles import Font # 修改 A1 字体:加粗、字号 14、红色 ws["A1"].font = Font(bold=True, size=14, color="FF0000") # 设置行高和列宽 ws.row_dimensions[1].height = 30 ws.column_dimensions["A"].width = 20 # 合并 A1 到 C1,写入标题 ws.merge_cells("A1:C1") ws["A1"] = "月度加班统计表" # 在单元格写入公式 ws["D2"] = "=C2*1.5"参数说明上,Font的 color 用十六进制 RGB 字符串,不带井号;行高单位是磅,列宽单位是字符宽度,这两个单位不一样,调的时候别按同一个比例缩放。合并单元格有个隐藏坑:合并后只有左上角那个格子保留值,其余格子的值会被清掉,如果先写值再合并,数据就丢了,正确顺序是先合并再写值。
2.4 图表与三个实操案例:工资条、加班统计、身份证提取
教程第一章末尾放了三个实操案例,是整章最值得照着敲的部分。生成工资条考的是循环加样式复制,每人间隔插空行;统计加班时间考的是按部门聚合加公式计算;提取身份证年月日考的是字符串切片加日期格式化。图表部分覆盖饼图、条形图、气泡图、散点图四种,核心套路是先准备数据区域,再建图表对象,设置类型和引用范围,最后add_chart()挂到工作表上。
from openpyxl.chart import BarChart, Reference # 数据区域:假设 B1:C5 是类别和数值 chart = BarChart() chart.type = "col" chart.title = "部门加班时长" # 引用数据,min_col=2 表示取第二列数值 data = Reference(ws, min_col=2, min_row=1, max_row=5) cats = Reference(ws, min_col=1, min_row=2, max_row=5) chart.add_data(data, titles_from_data=True) chart.set_categories(cats) # 挂到当前工作表,锚定在 E2 位置 ws.add_chart(chart, "E2")Reference的 min_row 和 max_row 决定取数范围,titles_from_data=True表示第一行当图例标题。图表位置用锚点字符串控制,写 “E2” 就贴在 E2 左上角。这部分代码跑通一次,后面换数据源只改 Reference 的范围就行。
3. python-docx 与 PDF 处理:从生成文档到合并拆分加密
第二章从 Excel 转到 Word 和 PDF。python-docx 的对象模型比 openpyxl 多一层:Document 是文档,Paragraph 是段落,Run 是段落里格式一致的一段文字。理解 Run 是这章的关键,因为字体、字号、加粗这些样式都挂在 Run 上,不是挂在 Paragraph 上。很多人设置段落字体没生效,就是因为改错了层级。
3.1 文档结构:Document、Paragraph、Run 的层级关系
新建文档用Document(),打开已有文档用Document("xxx.docx")。加标题用add_heading(),加段落用add_paragraph(),加列表和引用也是add_paragraph()配合 style 参数。图片用add_picture(),表格用add_table(),这两个方法都返回对象,可以继续往里填内容。
from docx import Document from docx.shared import Pt, Inches doc = Document() # 加标题,level 控制层级 doc.add_heading("月度工作报告", level=1) # 加段落 p = doc.add_paragraph("本月完成情况如下:") # 加列表项,style 指定列表样式 doc.add_paragraph("完成 A 项目验收", style="List Bullet") doc.add_paragraph("完成 B 项目立项", style="List Bullet") # 加图片,指定宽度 doc.add_picture("chart.png", width=Inches(5)) # 加表格,2 行 3 列 table = doc.add_table(rows=2, cols=3) table.cell(0, 0).text = "项目" table.cell(0, 1).text = "进度" table.cell(0, 2).text = "负责人" doc.save("report.docx")样式设置要落到 Run 上。p.add_run("文字")返回一个 Run 对象,然后设run.font.size、run.font.bold、run.font.name。中文字体还有个额外坑:python-docx 设font.name只对西文生效,中文要额外设run._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体'),这个写法教程里没展开,但实际做中文报告绕不开。
from docx.oxml.ns import qn p = doc.add_paragraph() run = p.add_run("这段文字是加粗的宋体小四") run.font.bold = True run.font.size = Pt(12) run.font.name = "宋体" # 中文字体必须单独设置 eastAsia 属性 run._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体")3.2 PDF 四件套:转换、读取、合并、拆分、加密
PDF 部分教程给了五个示例:Word 转 PDF、读取内容、合并、拆分、加密。Word 转 PDF 依赖本机装了 Office 或 LibreOffice,python-docx 本身不做转换,常见做法是调docx2pdf或走 COM 接口,教程里用的是后者思路。读取内容用 PyPDF2 的PdfReader,逐页extract_text()。合并用PdfWriter逐个add_page(),拆分就是按页范围分别写出去,加密用encrypt()设密码。
from PyPDF2 import PdfReader, PdfWriter # 合并两个 PDF writer = PdfWriter() for f in ["a.pdf", "b.pdf"]: reader = PdfReader(f) for page in reader.pages: writer.add_page(page) with open("merged.pdf", "wb") as out: writer.write(out) # 拆分:只取第一个文件的前两页 reader = PdfReader("a.pdf") writer = PdfWriter() for page in reader.pages[:2]: writer.add_page(page) with open("split.pdf", "wb") as out: writer.write(out) # 加密 writer.encrypt("mypassword")参数上,encrypt()传用户密码,打开文件时就要输。合并时注意页面顺序按循环顺序来,想调整顺序就调整文件列表或页索引。拆分时reader.pages[:2]是切片,取前两页,这个切片是标准 Python 语义,左闭右开,跟 openpyxl 的切片行为不同,两个库混用时容易记混。
3.3 两个生成类案例:罚单通知书与复工证明
这章的两个实操案例都是“模板填充”思路:先建一个带占位符的文档,再用循环把数据逐条替换进去,每条数据生成一份新文档。罚单通知书考的是表格填充加金额格式化,复工证明考的是日期处理和落款排版。这类批量生成在行政、人事场景里复用率极高,把模板和字段列表拆开,换一套数据就是另一个业务。
from docx import Document records = [ {"name": "张三", "date": "2024-03-01", "dept": "技术部"}, {"name": "李四", "date": "2024-03-02", "dept": "市场部"}, ] for r in records: doc = Document("template.docx") # 遍历段落,替换占位符 for p in doc.paragraphs: if "{{name}}" in p.text: for run in p.runs: run.text = run.text.replace("{{name}}", r["name"]) run.text = run.text.replace("{{date}}", r["date"]) run.text = run.text.replace("{{dept}}", r["dept"]) doc.save(f"证明_{r['name']}.docx")这里有个细节:占位符如果被 Word 拆到多个 Run 里,直接替换p.text不生效,得逐个 Run 替换,或者先合并 Run。上面代码按 Run 替换是稳妥写法,但如果占位符本身被拆开(比如{{na和me}}分在两个 Run),还是会漏,实际项目里我一般要求模板制作时占位符一次性输入、不中途改格式。
4. python-pptx 生成演示文稿:页面、文本框、图表四件套
第三章讲 PPT,篇幅比前两章短,但覆盖了从建页到画图的完整链路。python-pptx 的对象模型是 Presentation → Slide → Shape 三层,Presentation 是文件,Slide 是单页,Shape 是页面上的元素(文本框、图片、表格、图表都算 Shape)。新建用Presentation(),加页用slides.add_slide(layout),layout 决定版式,空白页用第 6 个版式索引。
4.1 建页与占位符:layout 索引决定页面骨架
from pptx import Presentation from pptx.util import Inches, Pt prs = Presentation() # 用第 6 个版式(空白页)新建一页 slide = prs.slides.add_slide(prs.slide_layouts[6]) # 加文本框,指定位置和大小 txBox = slide.shapes.add_textbox(Inches(1), Inches(1), Inches(8), Inches(1)) tf = txBox.text_frame tf.text = "季度经营分析" # 段落级样式 p = tf.paragraphs[0] p.font.size = Pt(28) p.font.bold = True prs.save("demo.pptx")add_textbox的四个参数依次是 left、top、width、height,单位用Inches或Pt。文本框里的内容通过text_frame操作,tf.text设第一段,要加多段就tf.add_paragraph()。字体样式挂在 paragraph 或 run 上,跟 Word 的逻辑类似。
4.2 图片、表格与四类图表:流程图、条形图、折线图、饼图
图片用shapes.add_picture(path, left, top, width),表格用shapes.add_table(rows, cols, left, top, width, height),表格单元格通过table.cell(r, c).text填。图表部分教程给了流程图、条形图、折线图、饼图四种,其中流程图是用形状拼的,条形图和折线图用add_chart配合CategoryChartData。
from pptx.chart.data import CategoryChartData from pptx.enum.chart import XL_CHART_TYPE # 准备图表数据 chart_data = CategoryChartData() chart_data.categories = ["一季度", "二季度", "三季度", "四季度"] chart_data.add_series("销售额", (120, 150, 130, 180)) # 插入条形图 slide.shapes.add_chart( XL_CHART_TYPE.COLUMN_CLUSTERED, Inches(1), Inches(2), Inches(8), Inches(4), chart_data )XL_CHART_TYPE是枚举,条形图用COLUMN_CLUSTERED,折线图用LINE,饼图用PIE。add_series的第一个参数是系列名,第二个是数值元组,类别数量要和数值数量对齐,不对齐会报错。图表样式(颜色、图例位置)可以通过返回的 chart 对象继续调,教程里有一个示例专门讲样式设置。
5. 避坑与排查:五个真实翻车现场
5.1 保存后文件打不开或数据丢失
现象:脚本跑完没报错,打开生成的 xlsx 提示文件损坏,或者内容全是空的。原因通常是save()路径写错、在with open里重复保存,或者对同一个 Workbook 对象反复 save 到同一路径导致句柄冲突。解决:保存前打印绝对路径确认,保存后立刻用load_workbook()回读一次验证,确认能打开再往下走。
5.2 中文乱码或字体设置不生效
现象:Word 里设了字体,打开还是默认宋体;Excel 里中文显示成问号。原因:python-docx 的font.name只管西文,中文要走 eastAsia 属性;Excel 乱码多半是读写时编码没对齐,或者源文件本身是 CSV 被当 xlsx 打开。解决:Word 按 3.1 节的写法补 eastAsia;Excel 确认文件后缀和实际格式一致,CSV 用encoding="utf-8-sig"读。
5.3 合并单元格后数据消失
现象:先写值再merge_cells(),合并完只剩左上角的值,其他全没了。原因:openpyxl 合并时只保留左上角单元格,其余值被清空,这是设计行为不是 bug。解决:先合并再写值,或者合并前把要保留的值挪到左上角格子。
5.4 切片范围差一行
现象:想读第 2 到第 4 行,结果只读到 2、3 行,或者多读了一行空行。原因:openpyxl 的ws[2:4]包含结束行,PyPDF2 的pages[:2]不包含结束索引,两个库语义相反。解决:openpyxl 切片按“含头含尾”记,PyPDF2 按标准 Python 切片记,混用时在代码里加注释标清楚。
5.5 大批量写入内存爆掉或速度极慢
现象:几万行数据append()循环写,内存一路涨,最后卡死。原因:openpyxl 默认把整个工作簿放在内存里,行数一多就撑不住。解决:改用write_only=True模式创建 Workbook,用ws.append()流式写,写完save(),内存占用能降一个量级;读的时候用read_only=True配iter_rows()。
# 只写模式,适合大数据量导出 wb = openpyxl.Workbook(write_only=True) ws = wb.create_sheet() for row in big_data: ws.append(row) wb.save("big.xlsx")6. 把三个库串成一条流水线:一个可复用的月度报表脚本
单看每个库都不难,真正省时间的是把它们串起来。我现在的习惯是:Excel 做数据源和汇总,Word 做正式文书,PPT 做汇报材料,一个脚本从头跑到尾。下面这个骨架是我反复改过几版的,去掉业务字段后可以直接套。
import openpyxl from docx import Document from pptx import Presentation from pptx.util import Inches from pptx.chart.data import CategoryChartData from pptx.enum.chart import XL_CHART_TYPE # 第一步:从 Excel 读数据 wb = openpyxl.load_workbook("source.xlsx", read_only=True) ws = wb.active rows = [] for row in ws.iter_rows(min_row=2, values_only=True): if row[0] is None: continue rows.append({"name": row[0], "dept": row[1], "hours": row[2]}) wb.close() # 第二步:按部门聚合 summary = {} for r in rows: summary[r["dept"]] = summary.get(r["dept"], 0) + r["hours"] # 第三步:生成 Word 汇总 doc = Document() doc.add_heading("月度加班汇总", level=1) for dept, hours in summary.items(): doc.add_paragraph(f"{dept}:{hours} 小时", style="List Bullet") doc.save("summary.docx") # 第四步:生成 PPT 图表 prs = Presentation() slide = prs.slides.add_slide(prs.slide_layouts[6]) chart_data = CategoryChartData() chart_data.categories = list(summary.keys()) chart_data.add_series("加班时长", tuple(summary.values())) slide.shapes.add_chart( XL_CHART_TYPE.COLUMN_CLUSTERED, Inches(1), Inches(1), Inches(8), Inches(5), chart_data ) prs.save("summary.pptx")这段代码的关键在第一步的read_only=True和values_only=True:前者让大文件不占内存,后者让iter_rows直接返回值而不是 Cell 对象,省掉一层.value。聚合用字典累加,几行就够。Word 和 PPT 部分都是前面章节的最小组合,没有新 API。
验证方法我固定走三步:先看文件能不能正常打开,再抽查两三个数据点和源表对得上,最后把生成的 PPT 图表和 Word 列表交叉核对一遍。这套流程跑顺之后,原来一上午的活压缩到十分钟以内,剩下的时间用来检查数据本身对不对,而不是耗在复制粘贴上。
从那以后我每次写批量生成脚本,都强制先拿三条假数据跑一遍全流程,确认输出文件能打开、字段没错位,再换真实数据。这个习惯帮我挡掉过好几次“脚本跑完才发现模板占位符被 Word 拆成两半”的事故。希望这份教程的拆解能帮到你,把重复劳动真正交给代码。
本文还有配套的精品资源,点击获取