实战环境:华为云 FlexusX ECS | Ubuntu 24.04.4 | Python 3.12.3
核心库:python-docx 1.1.2 + openpyxl 3.1.5 + Pillow
涉及实验:Word 20个 + Excel 4个 = 24个实验,全部上机跑通
写在前面
还在手动写周报、做报表、批量生成合同?Python 两大 Office 库——python-docx 和 openpyxl——让你彻底告别重复劳动。
本文从零搭建环境,24 个实验逐行跑通 Word 文档生成与 Excel 电子表格处理的全流程。所有代码均可直接复制使用。
环境搭建
# 安装依赖 (Ubuntu 24.04 需 --break-system-packages 跳过 PEP 668) pip3 install python-docx openpyxl Pillow --break-system-packages \ -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
验证安装:
>>> from docx import Document >>> import openpyxl >>> openpyxl.__version__ '3.1.5'
第一篇:Word 文档 (python-docx)
1.1 核心架构速览
┌─────────────────────────────────────────────────────┐
│ Document │
│ │ │
│ ┌────────────┼────────────┐ │
│ ▼ ▼ ▼ │
│ paragraphs tables sections │
│ │ │ │
│ ▼ ▼ │
│ Run Header/Footer │
│ (最小格式单元) (页眉/页脚) │
│ │
│ docx 本质 = ZIP 包 { XML + media } │
│ word/document.xml <- 核心内容文件 │
└─────────────────────────────────────────────────────┘
1.2 基础操作:创建文档、段落、标题
实验1-3:创建文档 → 添加段落 → 标题分级 → 分页
from docx import Document
doc = Document()
# 标题 (level 1~9)
doc.add_heading("一级标题 Heading 1", level=1)
doc.add_heading("二级标题 Heading 2", level=2)
# 普通段落
p = doc.add_paragraph("这是一个普通段落。")
p.add_run(" 同一段落内的 Run 文字。") # Run = 行内文字元素
# 换行符
p2 = doc.add_paragraph("第一行。")
p2.add_run("\n第二行(用 \\n 换行)。")
# 分页
doc.add_page_break()
doc.add_paragraph("新页面内容。")
doc.save("output.docx")
踩坑提示:add_run("\n") 是软换行(Line Break),不会产生新段落。Word 中 Shift+Enter 对应 run 中的 \n,Enter 对应新 paragraph。
1.3 表格操作:创建、合并、增加行列
实验4-5:表格全生命周期
from docx import Document
doc = Document()
doc.add_heading("Python 数据类型对比表", level=1)
# 创建 5行×4列 表格
table = doc.add_table(rows=5, cols=4)
table.style = 'Light Grid Accent 1'
# 表头
headers = ['类型名称', '英文名称', '可变性', '示例']
for i, h in enumerate(headers):
table.rows[0].cells[i].text = h
# 数据行
data = [
['整数', 'int', '不可变', '42'],
['字符串', 'str', '不可变', '"hello"'],
['列表', 'list', '可变', '[1, 2, 3]'],
['字典', 'dict', '可变', '{"a": 1}'],
]
for row_idx, row_data in enumerate(data, 1):
for col_idx, val in enumerate(row_data):
table.rows[row_idx].cells[col_idx].text = val
# === 表格高级操作 ===
t2 = doc.add_table(rows=3, cols=3)
# 合并单元格
t2.cell(0, 0).merge(t2.cell(0, 2))
t2.cell(0, 0).text = "合并了 (0,0)~(0,2)"
# 新增行
row_new = t2.add_row()
for j in range(3):
row_new.cells[j].text = f"新增列{j}"
doc.save("table_demo.docx")
真实服务器输出:
行数: 4, 列数: 3
表格样式速查(内置63种)
| 类别 | 样式名示例 | 效果 |
|---|---|---|
| 浅色网格 | Light Grid Accent 1~6 |
浅色表头+边框 |
| 中等底纹 | Medium Shading 1 Accent 1~6 |
隔行变色 |
| 列表样式 | Light List Accent 1~6 |
无网格线 |
| 彩色 | Colorful Grid/List/Shading |
多彩表头 |
1.4 Run:最小格式单元
实验7-8:bold / italic / underline / strike / 字号 / 颜色
p = doc.add_paragraph()
p.add_run("正常文字。")
p.add_run("粗体文字").bold = True
p.add_run("斜体文字").italic = True
run = p.add_run("粗斜体下划线删除线")
run.bold = True
run.italic = True
run.underline = True
run.font.strike = True # 删除线
# 多级字号
sizes = [8, 10, 12, 14, 18, 24, 36, 48]
for sz in sizes:
run = p.add_run(f"{sz}pt ")
run.font.size = Pt(sz)
run.font.color.rgb = RGBColor(0, 51, 102)
Run 属性总表
| 属性 | 类型 | 示例 |
|---|---|---|
run.bold |
bool | True |
run.italic |
bool | True |
run.underline |
bool/str | True / 'single' / 'double' |
run.font.strike |
bool | True = 删除线 |
run.font.size |
Pt | Pt(12) |
run.font.color.rgb |
RGBColor | RGBColor(0xFF, 0, 0) |
run.font.name |
str | '宋体' |
1.5 样式系统:内置样式 + 修改 + 应用
实验6:36种内置段落样式
from docx.enum.style import WD_STYLE_TYPE
# 查看所有段落样式
styles = [s.name for s in doc.styles if s.type == WD_STYLE_TYPE.PARAGRAPH]
# ['Normal', 'Header', 'Footer', 'Heading 1'~'Heading 9',
# 'Title', 'Subtitle', 'Quote', 'Intense Quote',
# 'List Bullet', 'List Number', ...]
# 应用样式
doc.add_paragraph("这是引用样式", style='Quote')
doc.add_paragraph("这是列表项", style='List Bullet')
# 修改默认样式
style = doc.styles['Normal']
style.font.size = Pt(11)
style.font.name = 'Times New Roman'
1.6 文档属性与批量生成
实验9:设置元数据 + 循环生成多份文档
import datetime
# 设置核心属性 (Word 文件→右键→属性可见)
doc.core_properties.title = "Python办公自动化示例"
doc.core_properties.author = "Python Learner"
doc.core_properties.subject = "python-docx实战"
doc.core_properties.created = datetime.datetime.now()
# 批量生成:4人入职通知书
users = ["张三", "李四", "王五", "赵六"]
for user in users:
doc = Document()
doc.add_heading(f"{user} 的入职通知书", level=1)
doc.add_paragraph(f"尊敬的{user}:")
doc.add_paragraph(" 恭喜您通过面试!请于2026年8月1日报到。")
doc.save(f"入职通知_{user}.docx")
服务器实测:4份文档瞬间生成,每份 ~36KB。
1.7 计量单位:Pt / Emu / Twips
实验10-11:理解 docx 内部坐标系统
from docx.shared import Pt, Cm, Inches, Emu # 实测输出 1 pt = 12700 EMU 1 cm = 360000 EMU 1 inch = 914400 EMU
换算关系: 1 inch = 72 pt = 2.54 cm 1 pt = 12700 EMU (English Metric Unit) 1 cm = 360000 EMU = 567 twips 1 twip = 635 EMU (1/20 pt, "二十分之一点")
EMU (English Metric Unit) 是 OOXML 标准的基础长度单位,确保整数运算无精度损失。
1.8 字体颜色:RGB 真彩色
实验12:8种颜色演示
from docx.shared import RGBColor
colors = [
("红色", 0xFF, 0x00, 0x00),
("绿色", 0x00, 0x80, 0x00),
("蓝色", 0x00, 0x00, 0xFF),
("橙色", 0xFF, 0xA5, 0x00),
("紫色", 0x80, 0x00, 0x80),
]
for name, r, g, b in colors:
run = p.add_run(f"[{name}] ")
run.font.color.rgb = RGBColor(r, g, b)
1.9 中文字体:关键坑点
实验13:中文字体必须同时设置西文 + 东亚字体
from docx.oxml.ns import qn
run = p.add_run("这是宋体中文")
run.font.name = '宋体'
# ★ 关键:必须设置 eastAsia 属性,否则中文可能不生效
run._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体')
docx 本质:ZIP 包结构
用 Python 解压查看 docx 内部:
[Content_Types].xml ← 内容类型清单 docProps/core.xml ← 文档属性(作者/日期) docProps/app.xml ← 应用属性(页数/字数) word/document.xml ← ★ 核心:文档正文 XML word/styles.xml ← 样式定义 word/fontTable.xml ← 字体表 word/media/ ← 图片等媒体资源 word/theme/theme1.xml ← 主题定义
document.xml 片段预览:2319 字节的 XML,包含所有段落、表格、样式引用。
1.10 段落间距与行间距
实验14:段前/段后间距 + 行距对比
from docx.shared import Pt
# 段间距
p = doc.add_paragraph("段前6pt, 段后6pt")
p.paragraph_format.space_before = Pt(6)
p.paragraph_format.space_after = Pt(6)
# 行间距
p.paragraph_format.line_spacing = 1.5 # 1.5倍行距
p.paragraph_format.line_spacing = Pt(24) # 固定24pt
| 行距类型 | 设置方式 | 效果 |
|---|---|---|
| 单倍 | 1.0 |
默认 |
| 1.5倍 | 1.5 |
常用 |
| 双倍 | 2.0 |
论文要求 |
| 固定值 | Pt(24) |
精确控制 |
| 最小值 | 需用 XML 操作 | w:lineRule="atLeast" |
1.11 页面设置:A4 + 横竖版混排
实验15-16:分节符实现同一文档内横竖版切换
from docx.shared import Cm from docx.enum.section import WD_ORIENT # 默认页面尺寸 (Letter: 21.59cm × 27.94cm) section = doc.sections[0] # width=7772400 EMU (≈21.59cm), height=10058400 EMU (≈27.94cm) # 设为 A4 section.page_width = Cm(21) section.page_height = Cm(29.7) section.top_margin = Cm(2.54) section.bottom_margin = Cm(2.54) section.left_margin = Cm(3.18) section.right_margin = Cm(3.18) # 添加分节符 → 横版 doc.add_section() sec2 = doc.sections[1] sec2.orientation = WD_ORIENT.LANDSCAPE sec2.page_width = Cm(29.7) # 宽 sec2.page_height = Cm(21) # 高
常用纸张尺寸
| 纸张 | 宽(cm) | 高(cm) | 宽(EMU) | 高(EMU) |
|---|---|---|---|---|
| A4 | 21.0 | 29.7 | 7560310 | 10692130 |
| A3 | 29.7 | 42.0 | ~ | ~ |
| Letter | 21.59 | 27.94 | 7772400 | 10058400 |
| B5 | 17.6 | 25.0 | ~ | ~ |
1.12 页脚 + 域(Field):自动页码
实验17:PAGE / NUMPAGES 域实现"第X页/共Y页"
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
footer = doc.sections[0].footer
p = footer.paragraphs[0]
p.text = "第 "
# 插入 PAGE 域
run = p.add_run()
fldChar = OxmlElement('w:fldChar')
fldChar.set(qn('w:fldCharType'), 'begin')
run._r.append(fldChar)
run2 = p.add_run()
instrText = OxmlElement('w:instrText')
instrText.set(qn('xml:space'), 'preserve')
instrText.text = ' PAGE '
run2._r.append(instrText)
run3 = p.add_run()
fldChar2 = OxmlElement('w:fldChar')
fldChar2.set(qn('w:fldCharType'), 'end')
run3._r.append(fldChar2)
p.add_run(" 页 / 共 ")
# ... 同样方式插入 NUMPAGES 域 ...
p.add_run(" 页")
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
常用域代码
| 域代码 | 功能 | 用法 |
|---|---|---|
PAGE |
当前页码 | { PAGE } |
NUMPAGES |
总页数 | { NUMPAGES } |
DATE |
当前日期 | { DATE } |
TOC |
自动目录 | { TOC \o "1-3" } |
HYPERLINK |
超链接 | 需特殊构造 |
1.13 页眉 + 目录(TOC)
实验18:页眉 + TOC 域
# 页眉 header = doc.sections[0].header header.paragraphs[0].text = "Python-docx 实战手册" header.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER # TOC 域 (提取 Heading 1~3) # 在 Word 中打开后右键 → 更新域 生效 p = doc.add_paragraph() run = p.add_run() # 插入 TOC \o "1-3" \h \z \u 域代码
踩坑:python-docx 只能写入 TOC 域代码,实际目录内容需要 Word 打开后"更新域"才能渲染。
1.14 完整 Demo:列表 + 图片 + 综合表格
实验19-20:集成所有功能
from docx.shared import Inches
# 无序列表
for item in ["Python程序设计", "数据结构与算法"]:
doc.add_paragraph(item, style='List Bullet')
# 有序列表
for item in ["需求分析", "系统设计", "编码实现", "测试部署"]:
doc.add_paragraph(item, style='List Number')
# 插入图片
from PIL import Image
img = Image.new('RGB', (400, 200), color=(70, 130, 180))
img.save("test.png")
doc.add_picture("test.png", width=Inches(3))
# 提取文档中的图片 (docx本质是ZIP)
import zipfile
with zipfile.ZipFile("doc_with_images.docx", 'r') as z:
for name in z.namelist():
if 'media' in name:
z.extract(name, "extracted/") # 1张图片已提取
第二篇:Excel 电子表格 (openpyxl)
2.1 核心架构速览
┌─────────────────────────────────────────────────┐ │ Workbook (工作簿) │ │ │ │ │ ┌────────────────┼────────────────┐ │ │ ▼ ▼ ▼ │ │ Worksheet Worksheet Worksheet │ │ (Q1季度) (Q2季度) (Q3季度) │ │ │ │ │ ├── Cell (单元格: A1, B2, ...) │ │ ├── Row / Column 操作 │ │ ├── Styles (Font/Fill/Border/Alignment) │ │ ├── Protection (工作表/工作簿保护) │ │ └── Properties (标签颜色/显示比例) │ └─────────────────────────────────────────────────┘
2.2 基础工作流:读/写/公式
实验1:创建工作簿 → 写入数据 → 公式 → 回读验证
from openpyxl import Workbook
wb = Workbook()
ws = wb.active
ws.title = "数据表"
# 写入表头
ws['A1'] = '姓名'
ws['B1'] = '年龄'
ws['C1'] = '城市'
ws['D1'] = '薪资'
# 写入数据 (行列索引从1开始)
employees = [
['张三', 28, '深圳', 15000],
['李四', 32, '北京', 22000],
['王五', 25, '上海', 18000],
['赵六', 30, '广州', 20000],
['孙七', 27, '杭州', 17000],
]
for i, row in enumerate(employees, 2):
for j, val in enumerate(row):
ws.cell(row=i, column=j+1, value=val)
# 公式
ws['D7'] = '=AVERAGE(D2:D6)'
ws['C7'] = '平均薪资'
wb.save("basic.xlsx")
# 回读验证
wb2 = openpyxl.load_workbook("basic.xlsx")
ws2 = wb2.active
for row in ws2.iter_rows(min_row=1, max_row=7, values_only=True):
print(row)
服务器实测输出:
('姓名', '年龄', '城市', '薪资')
('张三', 28, '深圳', 15000)
('李四', 32, '北京', 22000)
('王五', 25, '上海', 18000)
('赵六', 30, '广州', 20000)
('孙七', 27, '杭州', 17000)
(None, None, '平均薪资', '=AVERAGE(D2:D6)')
2.3 工作表操作:CRUD + 移动 + 复制
实验2:多工作表全生命周期
wb = Workbook()
# 批量创建
for name in ['Q1季度', 'Q2季度', 'Q3季度', 'Q4季度']:
wb.create_sheet(name)
del wb['Sheet'] # 删除默认 Sheet
print(wb.sheetnames)
# ['Q1季度', 'Q2季度', 'Q3季度', 'Q4季度']
# 复制
wb.copy_worksheet(wb['Q1季度'])
wb['Q1季度 Copy'].title = 'Q1季度备份'
# ['Q1季度', 'Q2季度', 'Q3季度', 'Q4季度', 'Q1季度备份']
# 重命名 + 移动位置
wb['Q4季度'].title = '年报'
wb.move_sheet('年报', offset=-2) # 前移2位
# ['Q1季度', '年报', 'Q2季度', 'Q3季度', 'Q1季度备份']
工作表操作速查
| 操作 | 方法 | 说明 |
|---|---|---|
| 创建 | wb.create_sheet("name") |
默认追加到最后 |
| 创建(指定位置) | wb.create_sheet("name", 0) |
插入到第1位 |
| 复制 | wb.copy_worksheet(ws) |
副本命名规则:原名 Copy |
| 删除 | del wb['sheetname'] |
不能删除唯一工作表 |
| 移动 | wb.move_sheet(ws, offset=n) |
n>0 后移,n<0 前移 |
| 激活 | ws = wb.active |
获取当前活动工作表 |
| 按索引 | wb.worksheets[0] |
第一个工作表 |
2.4 工作表属性:标签颜色 + 显示比例
实验3:彩色标签 + 缩放 + 网格线
# 标签颜色 (6位十六进制)
ws.sheet_properties.tabColor = "FF6B35" # 橙色
colors = {
'销量统计': '2EC4B6', # 青色
'客户信息': 'E71D36', # 红色
'库存管理': 'FF9F1C', # 金黄
'财务报表': '011627', # 深蓝
}
for sname, color in colors.items():
ws = wb.create_sheet(sname)
ws.sheet_properties.tabColor = color
# 显示设置
ws.sheet_view.zoomScale = 120 # 120% 缩放
ws.sheet_view.showGridLines = True # 显示网格线
# ws.sheet_view.showGridLines = False # 隐藏网格线
# 冻结窗格
ws.freeze_panes = 'A2' # 冻结首行
# ws.freeze_panes = 'B2' # 冻结首行+首列
# 自动筛选
ws.auto_filter.ref = "A1:F6"
2.5 样式综合演示
内嵌实战:企业级员工信息表
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
# 表头样式
header_font = Font(name='微软雅黑', size=11, bold=True, color='FFFFFF')
header_fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
# 数据区样式
thin_border = Border(
left=Side(style='thin'), right=Side(style='thin'),
top=Side(style='thin'), bottom=Side(style='thin'),
)
# 隔行变色 + 状态列条件着色
for r_idx, row_data in enumerate(staff, 2):
for c_idx, val in enumerate(row_data):
cell = ws.cell(row=r_idx, column=c_idx, value=val)
cell.border = thin_border
cell.alignment = Alignment(horizontal='center', vertical='center')
if r_idx % 2 == 0: # 偶数行浅蓝背景
cell.fill = PatternFill(start_color='D9E2F3', end_color='D9E2F3', fill_type='solid')
if c_idx == 6: # 状态列
if val == '在职':
cell.font = Font(color='006100')
cell.fill = PatternFill(start_color='C6EFCE', end_color='C6EFCE', fill_type='solid')
else:
cell.font = Font(color='9C0006')
cell.fill = PatternFill(start_color='FFC7CE', end_color='FFC7CE', fill_type='solid')
openpyxl 样式组件
| 组件 | 类 | 常用参数 |
|---|---|---|
| 字体 | Font |
name, size, bold, italic, color, underline |
| 填充 | PatternFill |
start_color, end_color, fill_type='solid' |
| 边框 | Border + Side |
left, right, top, bottom (Side(style='thin')) |
| 对齐 | Alignment |
horizontal, vertical, wrap_text, text_rotation |
| 数字格式 | cell.number_format |
'#,##0.00', 'yyyy-mm-dd', '0%' |
2.6 保护工作表/工作簿
实验4:密码保护 + 锁定结构
# ===== 工作表保护 =====
ws.protection.sheet = True
ws.protection.password = '123456'
ws.protection.set_password('123456')
# 允许特定操作
ws.protection.selectLockedCells = True
ws.protection.selectUnlockedCells = True
# ws.protection.formatCells = False # 禁止格式化
# ws.protection.insertRows = False # 禁止插入行
# ===== 工作簿保护 =====
wb.security.workbookPassword = 'admin123'
wb.security.set_workbook_password('admin123')
wb.security.lockStructure = True # 锁定结构(禁止增删工作表)
2.7 性能测试
实验附加:100行×10列读写性能
写入耗时: 0.0023s 保存耗时: 0.0071s 读取耗时: 0.0102s (只读模式, 共100行)
大文件优化建议:
| 场景 | 方法 | 效果 |
|---|---|---|
| 只读 | load_workbook(read_only=True) |
内存占用↓90% |
| 只写 | Workbook(write_only=True) |
适合百万级数据导出 |
| 批量写 | 用 ws.append() 代替逐个 cell() |
速度↑3~5x |
| 数据纯读取 | iter_rows(values_only=True) |
跳过样式对象 |
踩坑记录
坑1:中文字体不生效
run.font.name = '宋体' # 仅设置西文字体,中文不生效
run._element.rPr.rFonts.set(
qn('w:eastAsia'), '宋体') # ★ 必须同时设置东亚字体
坑2:PEP 668 pip 安装限制
error: externally-managed-environment 解决: pip3 install xxx --break-system-packages
坑3:华为云香港 PyPI 超时
Timeout connecting to pypi.org 解决: -i https://mirrors.aliyun.com/pypi/simple/
坑4:TOC 目录不显示
python-docx 只能写入 TOC 域代码 必须用 Word 打开 → 右键"更新域" 才能渲染
坑5:单元格坐标从 1 开始
ws.cell(row=1, column=1) # 对应 Excel 的 A1 # 与 Python 列表索引从 0 开始的习惯不同!
坑6:del wb['Sheet']删除唯一工作表报错
openpyxl 要求工作簿至少保留 1 个工作表 先创建新工作表再删除默认的
实战文件清单
Word 产出 (19个文件)
| 文件 | 大小 | 对应实验 |
|---|---|---|
exp01_new.docx |
36KB | 实验1-2:创建+读取 |
exp03_headings.docx |
36KB | 实验3:标题/段落/分页 |
exp04_table.docx |
37KB | 实验4-5:表格/合并 |
exp06_styles.docx |
36KB | 实验6:样式系统 |
exp07_run.docx |
36KB | 实验7-8:Run格式 |
exp09_props.docx |
36KB | 实验9:文档属性 |
batch_张三~赵六.docx |
4×36KB | 实验9:批量生成 |
exp12_colors.docx |
36KB | 实验12:RGB颜色 |
exp13_fonts.docx |
36KB | 实验13:中文字体 |
exp14_spacing.docx |
36KB | 实验14:段落间距 |
exp15_page.docx |
36KB | 实验15-16:页面设置 |
exp17_footer.docx |
37KB | 实验17:页脚+域 |
exp18_toc.docx |
37KB | 实验18:页眉+TOC |
exp19_demo.docx |
38KB | 实验19-20:完整demo |
Excel 产出 (7个文件)
| 文件 | 大小 | 对应实验 |
|---|---|---|
exp01_basic.xlsx |
5.1KB | 实验1:基础读写 |
exp02_sheets.xlsx |
7.4KB | 实验2:工作表CRUD |
exp03_props.xlsx |
7.0KB | 实验3:标签颜色/缩放 |
exp04_protect_sheet.xlsx |
5.1KB | 实验4:工作表保护 |
exp04_protect_workbook.xlsx |
4.9KB | 实验4:工作簿保护 |
exp_style_demo.xlsx |
6.3KB | 综合样式演示 |
exp_big.xlsx |
8.6KB | 性能测试 |
总结
| 维度 | python-docx | openpyxl |
|---|---|---|
| 目标格式 | .docx (Word) |
.xlsx (Excel) |
| 核心概念 | Document → Paragraph → Run | Workbook → Worksheet → Cell |
| 样式方式 | 内置样式 + Run属性 | Font/Fill/Border/Alignment 对象 |
| 复杂操作 | 域(Field)、分节符(Section) | 公式、图表、数据透 视表 |
| 内部结构 | ZIP(XML) 透明可见 | ZIP(XML) 透明可见 |
| 扩展能力 | 图片/表格/页眉页脚 | 条件格式/数据验证/命名范围 |
| 性能上限 | 百页级文档 | 百万行级(只读/只写模式) |
| 学习曲线 | ★★★☆☆ (Run概念需理解) | ★★☆☆☆ (直觉型) |
再也不用手动写周报、做报表、生成合同了。Python 两行代码启动,剩下的事交给循环。













