夜雨聆风学习资料网

ARTICLE · 1182891

python-docx:Word文档的页眉页脚、页码、分节符、目录怎么做

python-docx:Word文档的页眉页脚、页码、分节符、目录怎么做

python-docx 写 Word 自动化脚本时,都会遇到同一个问题:正文写得挺顺,到页眉页脚、页码、分节符、目录就卡住了。

在合同、报告、论文这类文档,往往要求:

  • 封面不显示页眉页码;
  • 目录页用罗马数字;
  • 正文从第 1 页开始重新编号;
  • 不同章节显示不同页眉;
  • 最后还要自动生成目录。

这篇我们就来学习和探讨下这四个问题。这里顺便提下,默认安装的时候,如果安装第三方库速度很慢,可以改成使用国内的节点,比如:

pip install python-docx -i https://pypi.tuna.tsinghua.edu.cn/simple

理解 Word 的“节”

很多人操作页眉页脚失败,原因不是代码写错了,而是没理解 Word 里的 节 Section。 可以这样理解:

  • 一个 Word 文档至少有一个节;
  • 页眉、页脚、页码、页面方向、页边距,都是按“节”管理的;
  • 新节默认会继承上一节的页眉页脚;
  • 想给某一节单独设置页眉页脚,必须先断开它和上一节的链接。

所以,python-docx 里几乎所有页眉页脚操作,都从“节”开始:

from docx import Documentdoc = Document()section = doc.sections[0]  # 获取第一个节header = section.headerfooter = section.footer

这里要注意:页眉和页脚里默认已经有一个空段落,通常建议用 paragraphs[0] 来写内容,而不是直接 add_paragraph(),否则页眉顶部可能会多出一个空行。

基础页眉页脚

最简单的页眉页脚可以这样写:

from docx import Documentfrom docx.shared import Ptfrom docx.enum.text import WD_PARAGRAPH_ALIGNMENTdoc = Document()section = doc.sections[0]header = section.headerfooter = section.footer# 设置页眉header_para = header.paragraphs[0]header_para.text = "公司内部文件 - 请勿外传"header_para.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER# 设置页脚footer_para = footer.paragraphs[0]footer_para.text = "第 1 页"footer_para.alignment = WD_PARAGRAPH_ALIGNMENT.CENTERdoc.save("demo.docx")

但这里有一个问题:页脚里的“第 1 页”不会自动更新。如果你希望页码随页数变化,就需要使用 Word 的域代码。自动页码:用域代码插入 PAGE。python-docx 没有提供类似 insert_page_number() 这种高级 API,但可以通过底层 XML 插入域代码。下面这个函数可以复用:

from docx.oxml import OxmlElementfrom docx.oxml.ns import qndef add_page_field(run, field_code: str):    """在 run 中插入 Word 域代码,例如 PAGE、NUMPAGES"""    fld_char_begin = OxmlElement("w:fldChar")    fld_char_begin.set(qn("w:fldCharType"), "begin")    instr_text = OxmlElement("w:instrText")    instr_text.text = f" {field_code} "    fld_char_end = OxmlElement("w:fldChar")    fld_char_end.set(qn("w:fldCharType"), "end")    run._r.append(fld_char_begin)    run._r.append(instr_text)    run._r.append(fld_char_end)

使用方式:

from docx import Documentfrom docx.enum.text import WD_PARAGRAPH_ALIGNMENTdoc = Document()section = doc.sections[0]footer = section.footerfooter_para = footer.paragraphs[0]footer_para.alignment = WD_PARAGRAPH_ALIGNMENT.CENTERrun = footer_para.add_run()add_page_field(run, "PAGE")run.add_text(" / ")add_page_field(run, "NUMPAGES")doc.save("page_number.docx")

打开文档后,页脚会显示类似:

1 / 5

其中:

  • PAGE 表示当前页;
  • NUMPAGES 表示总页数。

如果打开后页码没有立刻显示,可以在 Word 中选中目录或页码,按 F9 更新域。

封面不要页眉页码

很多报告、论文、合同都要求封面不显示页眉页码。 最简单的方式是开启“首页不同”:

section = doc.sections[0]section.different_first_page_header_footer = True# 首页页眉留空first_header = section.first_page_headerfirst_header.paragraphs[0].text = ""# 首页页脚留空first_footer = section.first_page_footerfirst_footer.paragraphs[0].text = ""

设置后:第 1 页使用 first_page_header 和 first_page_footer;从第 2 页开始使用普通页眉页脚; 这样封面就可以保持干净,正文页继续显示页眉页码。

不同章节不同页眉:分节符 + 断开链接

更复杂一点的需求是:封面不显示页眉;目录页显示“目录”;正文页显示章节名称;每一节的页眉互不影响。

这时候就要用到 分节符。

1. 创建新节

from docx.enum.section import WD_SECTION_START# 在文档末尾新增一节,并从新页面开始new_section = doc.add_section(WD_SECTION_START.NEW_PAGE)

add_section() 会在文档末尾增加一个新节;如果希望新节从下一页开始,就使用 NEW_PAGE。

2. 断开与上一节的链接

新节的页眉页脚默认会和上一节联动。如果不取消链接,改一处就会影响前面所有页。

new_section.header.is_linked_to_previous = Falsenew_section.footer.is_linked_to_previous = False

这两行非常关键。

3. 给新节设置独立页眉 

new_header = new_section.headernew_header.paragraphs[0].text = "第二章 技术方案"new_header.paragraphs[0].alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
如果还需要独立页脚,比如正文从第 1 页开始编号,也要先断开 footer 的链接,再重新插入页码域。注意:python-docx 可以插入 PAGE 域,但“页码从 1 重新开始”这类起始编号设置,通常需要 Word 手动配合,或者通过更底层的 OOXML 配置。

自动生成目录

python-docx 不能像 Word 一样一键刷新目录,但可以插入 Word 的目录域占位符。 前提是:文档中的标题必须使用 Word 原生标题样式,例如:

doc.add_heading("第一章 项目背景", level=1)doc.add_heading("1.1 项目概述", level=2)doc.add_heading("1.2 项目目标", level=2)doc.add_heading("第二章 技术方案", level=1)doc.add_heading("2.1 系统架构", level=2)doc.add_heading("2.2 接口设计", level=2)

然后插入目录:

from docx.oxml import OxmlElementfrom docx.oxml.ns import qndef add_toc(doc):    """在文档当前位置插入目录域"""    toc_para = doc.add_paragraph()    fld_char_begin = OxmlElement("w:fldChar")    fld_char_begin.set(qn("w:fldCharType"), "begin")    instr_text = OxmlElement("w:instrText")    instr_text.set(qn("xml:space"), "preserve")    instr_text.text = ' TOC \\o "1-3" \\h \\z \\u '    fld_char_end = OxmlElement("w:fldChar")    fld_char_end.set(qn("w:fldCharType"), "end")    toc_para._element.append(fld_char_begin)    toc_para._element.append(instr_text)    toc_para._element.append(fld_char_end)

调用时可以这样:

doc = Document()# 先插入目录标题doc.add_heading("目录", level=1)add_toc(doc)# 再插入分页,让正文从下一页开始doc.add_page_break()# 后面再添加正文标题和内容doc.add_heading("第一章 项目背景", level=1)doc.add_paragraph("这里是正文内容。")doc.save("report_with_toc.docx")

打开文档后,选中目录区域,按 F9,Word 就会根据标题自动生成目录。

一个完整的小例子

下面这个例子演示了:

  • 封面不显示页眉页码;
  • 正文显示页眉;
  • 页脚显示“当前页 / 总页数”;
  • 文档开头插入目录占位符。
from docx import Documentfrom docx.shared import Ptfrom docx.enum.text import WD_PARAGRAPH_ALIGNMENTfrom docx.enum.section import WD_SECTION_STARTfrom docx.oxml import OxmlElementfrom docx.oxml.ns import qndef add_page_field(run, field_code: str):    fld_char_begin = OxmlElement("w:fldChar")    fld_char_begin.set(qn("w:fldCharType"), "begin")    instr_text = OxmlElement("w:instrText")    instr_text.text = f" {field_code} "    fld_char_end = OxmlElement("w:fldChar")    fld_char_end.set(qn("w:fldCharType"), "end")    run._r.append(fld_char_begin)    run._r.append(instr_text)    run._r.append(fld_char_end)def add_toc(doc):    toc_para = doc.add_paragraph()    fld_char_begin = OxmlElement("w:fldChar")    fld_char_begin.set(qn("w:fldCharType"), "begin")    instr_text = OxmlElement("w:instrText")    instr_text.set(qn("xml:space"), "preserve")    instr_text.text = ' TOC \\o "1-3" \\h \\z \\u '    fld_char_end = OxmlElement("w:fldChar")    fld_char_end.set(qn("w:fldCharType"), "end")    toc_para._element.append(fld_char_begin)    toc_para._element.append(instr_text)    toc_para._element.append(fld_char_end)doc = Document()# 第一节:封面 + 正文section = doc.sections[0]section.different_first_page_header_footer = True# 封面页眉页脚留空section.first_page_header.paragraphs[0].text = ""section.first_page_footer.paragraphs[0].text = ""# 普通页眉header = section.headerheader_para = header.paragraphs[0]header_para.text = "Python 自动化报告"header_para.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER# 普通页脚:自动页码footer = section.footerfooter_para = footer.paragraphs[0]footer_para.alignment = WD_PARAGRAPH_ALIGNMENT.CENTERrun = footer_para.add_run()add_page_field(run, "PAGE")run.add_text(" / ")add_page_field(run, "NUMPAGES")# 封面内容doc.add_heading("Python 自动化报告", level=0)doc.add_paragraph("作者:公众号作者")doc.add_page_break()# 目录doc.add_heading("目录", level=1)add_toc(doc)doc.add_page_break()# 正文doc.add_heading("第一章 项目背景", level=1)doc.add_paragraph("这里是第一章正文。")doc.add_heading("1.1 项目概述", level=2)doc.add_paragraph("这里是 1.1 小节。")doc.add_heading("第二章 技术方案", level=1)doc.add_paragraph("这里是第二章正文。")doc.save("report.docx")

生成后打开 report.docx,记得在目录区域按 F9 更新目录。

常见踩坑点

  1. 页码不自动更新直接写 "第 1 页" 只是普通文本,必须用 PAGE、NUMPAGES 域代码。
  2. 改了页眉,前面也变了新节必须设置 is_linked_to_previous = False,否则会和上一节联动。
  3. 页眉多出一行空白页眉默认有一个段落,建议操作 paragraphs[0],不要盲目 add_paragraph()。
  4. 目录没有自动生成python-docx 插入的是目录域占位符,需要在 Word 中按 F9 更新。
  5. 标题没有出现在目录里必须使用 add_heading() 或应用 Heading 1/2/3 样式,普通加粗文本不会被目录识别。

可以怎么继续扩展

这套代码已经支持大部分报告、合同、论文场景。后续可以继续封装成工具函数,比如:

  • 批量给文件夹里的 Word 加页眉页脚;
  • 根据 Excel 名单批量生成合同;
  • 把 Markdown 或数据库内容自动排版成 Word 报告;
  • 结合模板 .dotx 文件复用企业文档格式。

python-docx 的优势不是“什么都能一键搞定”,而是它能把 Word 里那些重复、繁琐、容易出错的排版动作,变成可复用、可测试、可批量执行的 Python 脚本。好用的自动化,把人从机械排版里解放出来。更高级的自动化替代人写文档,可以尝试使用AI来辅助编写。

相关学习资料