简介这份Python自动化办公教程面向希望提升日常办公效率的职场人士与编程初学者系统讲解如何用Python替代重复性手工操作。内容覆盖Python基础语法、Excel读写与图表、Word与PDF文档处理、PPT制作、邮件收发、数据库增删改查、网络爬虫及文件与进程线程管理等模块并配有实操案例与章节习题帮助读者从零搭建自动化脚本能力。资源为单个docx文档压缩包约1.84MB目录按章节组织从openpyxl操作Excel到python-docx处理Word、python-pptx快速入门结构清晰便于按需查阅。目前已有1141人学习适合想用代码批量处理表格、文档与邮件的办公人群也可作为课程配套讲义参考。1. 从手工复制粘贴到脚本跑批这套 Python 自动化办公教程能省下多少时间月底做考勤汇总别人还在 Excel 里一行行拉公式、对着 Word 模板改名字、把 PPT 图表一张张换数据你已经把三个文件丢进同一个目录跑一条命令工资条、通知书、汇报 PPT 全生成好了。这不是炫技是这套 Python 自动化办公教程想让你达到的状态。它围绕 openpyxl、python-docx、python-pptx 三个库展开从 Excel 的 Workbook/Worksheet/Cell 三层对象讲起一路覆盖 Word 段落表格、PDF 拆分合并加密、PPT 图表绘制最后落到工资条、加班统计、查重、身份证提取年龄这些真实办公场景。适合每天被重复表格和文档淹没的运营、行政、财务、教务人员也适合想用脚本替代手工的 Python 初学者。下面我按自己拆这套教程的顺序把能直接抄的代码、参数含义和踩过的坑一次讲清。2. openpyxl 读写 Excel三个对象、四种写入方式和样式图表2.1 为什么先啃 Workbook、Worksheet、Cell 这三层openpyxl 把 Excel 拆成三个层次Workbook 代表整个工作簿Worksheet 代表一张表Cell 代表一个单元格。很多人一上来就ws[A1].value能跑通但遇到多表切换、动态定位就懵根子在于没建立这三层映射。教程里表 1-1 到表 1-4 把常用属性和方法列得很全我挑几个高频的wb.sheetnames拿所有表名wb.active拿当前激活表ws.max_row/ws.max_column拿有数据的最大行列ws.dimensions直接返回A1:D20这种范围字符串。理解这三层之后读、写、改样式、插图表都是在这棵树上找节点。安装就一行注意别装成只读的旧库pip install openpyxl装完先验证版本避免和老教程对不上import openpyxl print(openpyxl.__version__) # 建议 3.x2.x 部分 API 已废弃2.2 读取整行整列、切片和 iter_rows 怎么选读取是自动化的入口。教程示例 1-2 到 1-10 把读取方式铺得很细我按数据量给个选择建议小表直接ws[A1].value要遍历整表用ws.iter_rows()只要某几行用切片ws[2:4]要按列拿用ws[C]。下面这段是我常用的读取骨架带表头映射避免后面靠列号硬编码import openpyxl wb openpyxl.load_workbook(excelTest.xlsx, data_onlyTrue) ws wb.active # 第一行当表头建立 列名 - 列号 的映射 headers [cell.value for cell in ws[1]] col_map {name: idx for idx, name in enumerate(headers, start1)} rows [] for row in ws.iter_rows(min_row2, values_onlyTrue): if row[col_map[姓名] - 1] is None: # 跳过空行 continue rows.append({ 姓名: row[col_map[姓名] - 1], 加班时长: row[col_map[加班时长] - 1], }) print(rows[:3])逻辑说明data_onlyTrue让公式单元格返回计算结果而不是公式字符串这是读报表时最容易翻车的参数——不加它你拿到的是SUM(B2:B10)这种文本。values_onlyTrue直接返回值的元组省去.value。col_map把列名转成列号后面表头顺序变了也不用改代码。参数上min_row2跳过表头max_row不传就自动到有数据的最后一行。2.3 写入list、range、cell、append 四种姿势的边界写入教程给了示例 1-11 到 1-14四种方式各有适用面。append(iterable)适合逐行追加一次一行ws.cell(row, column, value)适合按坐标精确落点range配合循环适合批量填直接给ws[A1] 值适合零星修改。我一般用 append 写流水用 cell 写固定模板位置import openpyxl from openpyxl.utils import get_column_letter wb openpyxl.Workbook() ws wb.active ws.title 汇总 ws.append([姓名, 部门, 加班时长]) # 表头 for name, dept, hours in [(A同学, 运营, 12), (B同学, 财务, 8)]: ws.append([name, dept, hours]) # 逐行追加 # 按坐标写汇总行 last ws.max_row 1 ws.cell(rowlast, column1, value合计) ws.cell(rowlast, column3, valuefSUM(C2:C{last-1})) # 写公式 # 列号转字母方便动态设列宽 for col in range(1, ws.max_column 1): ws.column_dimensions[get_column_letter(col)].width 14 wb.save(workbook_test.xlsx)逻辑说明append按当前最大行往下写不会覆盖已有数据。写公式时直接赋字符串SUM(...)openpyxl 会存成公式但注意——用data_onlyTrue再打开时公式没被 Excel 计算过就返回 None所以公式文件最好先用 Excel 打开保存一次再读。get_column_letter把 1 转成 A是动态设列宽、拼单元格地址的常用工具教程示例 1-10 专门讲了列数字与字母的互转。2.4 样式、合并与图表让脚本产出的表能直接交差只写数据不设样式交上去还是会被打回。教程示例 1-15 到 1-23 覆盖了字体、行高列宽、合并拆分和饼图条形图气泡图散点图。样式核心是Font、Alignment、PatternFill三个类合并用merge_cells图表用openpyxl.chart下的PieChart、BarChart等。下面这段生成一个带表头样式和饼图的表import openpyxl from openpyxl.styles import Font, Alignment, PatternFill from openpyxl.chart import PieChart, Reference wb openpyxl.Workbook() ws wb.active ws.append([部门, 人数]) for d, n in [(运营, 12), (财务, 8), (技术, 20)]: ws.append([d, n]) # 表头加粗、居中、填充底色 for cell in ws[1]: cell.font Font(boldTrue, colorFFFFFF) cell.alignment Alignment(horizontalcenter) cell.fill PatternFill(solid, fgColor4472C4) # 饼图数据从 B2 到 B4分类标签取 A2 到 A4 pie PieChart() pie.title 部门人数占比 data Reference(ws, min_col2, min_row1, max_row4) labels Reference(ws, min_col1, min_row2, max_row4) pie.add_data(data, titles_from_dataTrue) pie.set_categories(labels) ws.add_chart(pie, D2) # 图表左上角落在 D2 wb.save(chart_test.xlsx)逻辑说明Reference用min_col/min_row/max_row框数据区域titles_from_dataTrue表示第一行当系列名。add_chart(图表对象, 锚点)的锚点是图表左上角位置不是数据区。合并单元格用ws.merge_cells(A1:C1)拆分用unmerge_cells注意合并后只有左上角单元格保留值其余变 None读取时要按左上角取。3. python-docx 与 PDF段落表格图片、转换合并加密一条链3.1 Word 文档结构Document、Paragraph、Run 的层级关系python-docx 的结构和 openpyxl 类似也是三层Document是文档Paragraph是段落Run是段落里一段连续同格式的文字。很多人设字体没生效就是因为把样式设在了 Paragraph 上而不是 Run 上——一个段落里可以有多个 Run每个 Run 能独立设字体。教程示例 2-1 到 2-9 把创建、标题、段落、列表、引用、图片、表格、字体段落样式、读取都过了一遍。安装pip install python-docx下面这段生成一份带标题、正文、表格的文档并演示 Run 级样式from docx import Document from docx.shared import Pt, RGBColor from docx.enum.text import WD_ALIGN_PARAGRAPH doc Document() doc.add_heading(月度加班统计报告, level1) p doc.add_paragraph() run p.add_run(本报告由脚本自动生成数据截至本月末。) run.font.size Pt(12) run.font.color.rgb RGBColor(0x33, 0x33, 0x33) p.alignment WD_ALIGN_PARAGRAPH.LEFT table doc.add_table(rows1, cols3) table.style Table Grid hdr table.rows[0].cells for i, t in enumerate([姓名, 部门, 加班时长]): hdr[i].text t for name, dept, hours in [(A同学, 运营, 12), (B同学, 财务, 8)]: cells table.add_row().cells cells[0].text, cells[1].text, cells[2].text name, dept, hours doc.save(report.docx)逻辑说明add_heading的level1对应一级标题会套用内置 Heading 1 样式。add_run返回 Run 对象字体、颜色、加粗都设在它上面。表格styleTable Grid才有边框不设就是无框线。add_row()每次追加一行返回该行 cells 列表。3.2 Word 转 PDF、PDF 读取合并拆分加密教程第二章把 PDF 操作放在 Word 之后示例 2-10 到 2-14 覆盖转换、读取、合并、拆分、加密。Word 转 PDF 依赖本机装了 Word 或 LibreOffice纯 Python 库做不到无损转换这点教程没展开我补一句常见做法是调docx2pdf需本机 Office或libreoffice --headless --convert-to pdf。PDF 读取合并拆分用PyPDF2from PyPDF2 import PdfReader, PdfWriter # 读取文本 reader PdfReader(input.pdf) print(len(reader.pages)) print(reader.pages[0].extract_text()[:200]) # 合并两个 PDF writer PdfWriter() for f in [a.pdf, b.pdf]: for page in PdfReader(f).pages: writer.add_page(page) writer.write(merged.pdf) # 拆分把第一页单独存 w2 PdfWriter() w2.add_page(PdfReader(input.pdf).pages[0]) w2.write(first_page.pdf) # 加密 w3 PdfWriter() for page in PdfReader(input.pdf).pages: w3.add_page(page) w3.encrypt(your_password) w3.write(encrypted.pdf)逻辑说明extract_text()对扫描件无效扫描件是图片得走 OCR这是 PDF 处理最大的认知坑。add_page逐页加合并顺序就是循环顺序。encrypt传密码字符串加密后打开需密码。拆分本质是新建 writer 只加指定页。3.3 实操案例通知书、证明、工资条的模板化生成教程示例 2-15、2-16 和 1-24 到 1-27 是整套教程最值钱的部分——把前面 API 串成真实业务。生成违章罚单通知书、复工证明、工资条套路都一样读一份 Excel 数据源遍历每行套 Word 模板替换占位符另存为独立文件。我一般用docx的paragraph.text.replace或直接操作 Run 做替换from docx import Document import openpyxl wb openpyxl.load_workbook(staff.xlsx, data_onlyTrue) ws wb.active headers [c.value for c in ws[1]] for row in ws.iter_rows(min_row2, values_onlyTrue): data dict(zip(headers, row)) doc Document(template.docx) for p in doc.paragraphs: for key, val in data.items(): if f{{{{{key}}}}} in p.text: # 模板里写 {{姓名}} p.text p.text.replace(f{{{{{key}}}}}, str(val)) doc.save(foutput/{data[姓名]}_证明.docx)逻辑说明模板里用{{姓名}}这种双花括号占位避免和正文冲突。p.text ...会清掉该段落原有 Run 格式如果模板对字体有严格要求得改在 Run 层面替换。批量生成前先os.makedirs(output, exist_okTrue)建目录否则保存报路径不存在。4. python-pptx 图表流程图、条形图、折线图、饼图的脚本化4.1 PPT 基本结构在 Python 里的映射python-pptx 的对象模型比前两个多一层Presentation是演示文稿Slides是幻灯片集合每张Slide上有Shapes形状里再分文本框、表格、图表。教程 3.1.2 专门讲了 PPT 基本结构在 Python 中的含义这是理解后续操作的前提。安装pip install python-pptx创建和加页from pptx import Presentation from pptx.util import Inches, Pt prs Presentation() # 用空白版式索引 6 通常是空白页 blank prs.slide_layouts[6] slide prs.slides.add_slide(blank) # 加标题文本框 tb slide.shapes.add_textbox(Inches(0.5), Inches(0.3), Inches(9), Inches(0.8)) tf tb.text_frame tf.text 季度汇报 tf.paragraphs[0].runs[0].font.size Pt(28) prs.save(demo.pptx)逻辑说明slide_layouts[6]是空白版式不同模板索引可能不同稳妥做法是遍历prs.slide_layouts打印 name 确认。add_textbox(left, top, width, height)四个参数都是Inches或Cm对象直接传数字会被当成 EMU1 英寸 914400 EMU这是新手最常踩的坑。4.2 图表绘制条形图、折线图、饼图的参数差异教程 3.3 把流程图、条形图、折线图、饼图分开讲示例 3-11 到 3-15 给了完整代码。PPT 里加图表和 Excel 不同用的是chart_data和add_chartfrom pptx import Presentation from pptx.chart.data import CategoryChartData from pptx.enum.chart import XL_CHART_TYPE from pptx.util import Inches prs Presentation() slide prs.slides.add_slide(prs.slide_layouts[6]) chart_data CategoryChartData() chart_data.categories [Q1, Q2, Q3, Q4] chart_data.add_series(营收, (120, 150, 130, 180)) x, y, cx, cy Inches(1), Inches(1), Inches(8), Inches(5) graphic_frame slide.shapes.add_chart( XL_CHART_TYPE.COLUMN_CLUSTERED, x, y, cx, cy, chart_data ) chart graphic_frame.chart chart.has_legend True prs.save(chart.pptx)逻辑说明CategoryChartData装分类和系列add_series(系列名, 值元组)可多次调用加多系列。XL_CHART_TYPE决定图表类型COLUMN_CLUSTERED是簇状柱形折线用LINE_MARKERS饼图用PIE。add_chart返回的是 graphic_frame真正的图表对象在.chart上。流程图没有现成图表类型教程用形状加连接线拼add_shape配合MSO_SHAPE枚举画框再用add_connector连线。4.3 把 Excel 数据灌进 PPT 图表的完整链路单独会画图不够真实场景是 Excel 出数据、PPT 出图。链路是openpyxl 读数据 → 组装成 CategoryChartData → python-pptx 画图。下面这段把 Excel 里各部门人数画成饼图放进 PPTimport openpyxl from pptx import Presentation from pptx.chart.data import CategoryChartData from pptx.enum.chart import XL_CHART_TYPE from pptx.util import Inches wb openpyxl.load_workbook(dept.xlsx, data_onlyTrue) ws wb.active cats, vals [], [] for row in ws.iter_rows(min_row2, values_onlyTrue): if row[0] is None: continue cats.append(row[0]) vals.append(row[1]) prs Presentation() slide prs.slides.add_slide(prs.slide_layouts[6]) cd CategoryChartData() cd.categories cats cd.add_series(人数, tuple(vals)) slide.shapes.add_chart(XL_CHART_TYPE.PIE, Inches(1), Inches(1), Inches(8), Inches(5), cd) prs.save(dept_chart.pptx)逻辑说明tuple(vals)必须转元组列表在某些版本会报类型错。饼图只有一个系列多系列饼图无意义。数据为空时add_series会抛异常所以循环里要跳过空行。这条链路跑通月度汇报 PPT 就能从「手动换数据两小时」压到「跑脚本十秒」。5. 避坑与排查那些让脚本白跑的细节5.1 公式读出来是字符串或 None现象用 openpyxl 读带公式的单元格拿到SUM(B2:B10)或 None不是计算结果。原因load_workbook默认data_onlyFalse返回公式本身设了data_onlyTrue但文件从没被 Excel 打开计算过缓存值为空。解决读结果就加data_onlyTrue且确保文件至少被 Excel 保存过一次要保留公式就默认读两者别混用同一个 workbook 对象。5.2 合并单元格只有左上角有值现象遍历合并区域除左上角外全是 None统计漏数据。原因openpyxl 合并后只在左上角单元格存值其余是 MergedCell 对象。解决读之前先拿ws.merged_cells.ranges拿到所有合并区域判断当前单元格是否在区域内是就取区域左上角的值。写的时候只往左上角写。5.3 字体样式设了不生效现象给段落设了字体生成文档打开还是默认宋体。原因python-docx 里样式要设在 Run 上设在 Paragraph 上会被 Run 自身样式覆盖或者模板自带的样式优先级更高。解决定位到具体 Run 再设run.font或者改doc.styles[Normal].font全局默认。PPT 同理文本框的字体要落到paragraph.runs[0].font。5.4 坐标单位搞错元素跑到画布外现象PPT 里加的文本框或图表看不见或跑到页面外。原因Inches、Cm、Pt是不同单位对象直接传数字被当 EMU1 英寸等于 914400 EMU传 1 实际是 1 EMU肉眼不可见。解决所有位置尺寸统一用Inches()或Cm()包一层别裸传数字。调试时先打印prs.slide_width看画布 EMU 尺寸。5.5 批量生成文件覆盖或路径不存在现象循环生成一百个文件最后只剩一个或直接报路径错误。原因文件名没带唯一标识后一个覆盖前一个输出目录没提前创建。解决文件名拼上数据里的唯一字段如姓名、工号生成前os.makedirs(out_dir, exist_okTrue)并在文件名里过滤掉/ \ : * ? |这些非法字符。6. 进阶技巧把零散脚本收成一个可复用工具函数单文件脚本跑一次就丢真正省时间的是把常用操作封成函数下次改个参数就能用。我习惯建一个office_utils.py把读 Excel 成字典列表、按模板批量生成 Word、Excel 数据灌 PPT 图表这三件事封进去业务脚本只调函数。下面是我常用的读取封装带表头映射和空行跳过import openpyxl def read_sheet(path, sheet_nameNone, header_row1): 读 Excel 成字典列表表头当 key。 wb openpyxl.load_workbook(path, data_onlyTrue) ws wb[sheet_name] if sheet_name else wb.active headers [c.value for c in ws[header_row]] result [] for row in ws.iter_rows(min_rowheader_row 1, values_onlyTrue): if all(v is None for v in row): # 整行空则跳过 continue result.append(dict(zip(headers, row))) wb.close() return result逻辑说明header_row参数化方便处理表头不在第一行的报表。all(v is None ...)比只判断第一列更稳避免第一列空但后面有数据被误跳。wb.close()释放文件句柄批量处理几百个文件时不加这句容易句柄耗尽。验证封装是否可靠我一般做三步拿一份真实报表跑一遍打印前三条和总条数故意在中间插一个空行看是否被正确跳过把表头顺序调换看字典 key 是否跟着变。三步过了再上批量。再进一步把「读数据 → 套模板 → 存文件」串成命令行工具用argparse接参数import argparse from office_utils import read_sheet parser argparse.ArgumentParser() parser.add_argument(--src, requiredTrue, help数据源 Excel 路径) parser.add_argument(--out, defaultoutput, help输出目录) args parser.parse_args() rows read_sheet(args.src) print(f共读取 {len(rows)} 条记录输出到 {args.out})逻辑说明requiredTrue让缺参数时直接报错而不是静默跑空。default给输出目录兜底。这样同事不会写 Python 也能用把命令存成.bat或.sh双击就跑。从那以后我每次写批量生成脚本都强制先跑一遍「三条数据 一个空行 表头乱序」的验证用例确认封装函数扛得住再上真实数据。这套教程里的示例代码是骨架真正让你少加班的是把这些骨架按自己的报表结构重新拼一遍。希望帮到你。本文还有配套的精品资源点击获取