脚本专家 发表于 2026-10-3 12:00:00

Python操作Word书签:Free Spire.Doc替换与插图

Word 书签可以理解为文档里的命名变量:先在模板中标记位置,程序运行时按名称写入文本、表格或图片。使用 Free Spire.Doc for Python 可以把“模板 + 数据 = 文档”的流程自动化,定位比全文查找替换更精确,也支持嵌套书签。先安装:


pip install spire.doc.free


一、创建书签模板

书签由 AppendBookmarkStart 和 AppendBookmarkEnd 成对定义,名称必须一致,End 位于 Start 之后。下面创建报告模板,并在待填充位置插入书签:


from spire.doc import *
from spire.doc.common import *

document = Document()
section = document.AddSection()
p = section.AddParagraph()
p.AppendText('Project Phase Report')
p.ApplyStyle(BuiltinStyle.Heading1)
p.Format.HorizontalAlignment = HorizontalAlignment.Center
fields = [
    ('Project Name: ', 'ProjectName'),
    ('Client: ', 'ClientName'),
    ('Report Date: ', 'ReportDate'),
    ('Project Phase: ', 'ProjectPhase'),
]
for label, bm in fields:
    p = section.AddParagraph()
    p.AppendText(label)
    p.AppendBookmarkStart(bm)
    p.AppendText('(to be filled)')
    p.AppendBookmarkEnd(bm)
# Summary、MilestoneTable、RiskLevel、RiskDetail、Signature 同理添加
document.SaveToFile('template.docx', FileFormat.Docx)
document.Close()


风险与建议部分可用嵌套书签:外层 RiskSection 包裹内层 RiskLevel 和 RiskDetail。内层可单独替换,外层也可整体操作。生成模板后包含 10 个书签:ProjectName、ClientName、ReportDate、ProjectPhase、Summary、MilestoneTable、RiskSection、RiskLevel、RiskDetail、Signature。

二、替换书签内容

BookmarksNavigator 是书签操作核心类。MoveToBookmark 按名称定位,ReplaceBookmarkContent 替换书签覆盖的内容。第二个参数控制是否保留原有字符格式:False 使用默认格式,True 继承书签内原文本的字体、字号、颜色等属性。


doc = Document()
doc.LoadFromFile('template.docx')
navigator = BookmarksNavigator(doc)
navigator.MoveToBookmark('ProjectName')
navigator.ReplaceBookmarkContent('Smart City Data Platform Phase II', False)
navigator.MoveToBookmark('ClientName')
navigator.ReplaceBookmarkContent('Orient Municipal Group', False)
navigator.MoveToBookmark('ReportDate')
navigator.ReplaceBookmarkContent('September 28, 2026', False)
navigator.MoveToBookmark('ProjectPhase')
navigator.ReplaceBookmarkContent('Phase II - Mid-term Acceptance', False)
navigator.MoveToBookmark('Summary')
navigator.ReplaceBookmarkContent(
    'This phase completed a comprehensive upgrade of the data collection module...', False)
navigator.MoveToBookmark('RiskLevel')
navigator.ReplaceBookmarkContent('Medium', False)
doc.SaveToFile('step2_text.docx', FileFormat.Docx)
doc.Close()


示例摘要对应的事实数据包括:新增 320 个传感器接入点,日吞吐从 1.2 亿提升到 3.5 亿条记录,集成交通、环境、安全、水务、市政 5 个子系统,API 平均响应 87ms,可用性 99.6%,中期验收 14 项通过 13 项,剩余实时告警延迟预计 10 月 15 日达标。风险详情可写 Kafka 集群高峰消息堆积,峰值延迟 4.2 秒,超过 2 秒 SLA,扩容到 6 节点预计 10 月 10 日完成。嵌套书签内层可独立定位替换,MoveToBookmark 不关心嵌套层级。

替换为表格时,先构建 Table,再用 TextBodyPart 包装后传给 ReplaceBookmarkContent:


table = Table(doc, True)
table.ResetCells(6, 4)
data = [
    ['Milestone', 'Planned Date', 'Actual Date', 'Status'],
    ['Requirements', '2026-07-15', '2026-07-14', 'Completed'],
    ['Architecture', '2026-07-30', '2026-07-30', 'Completed'],
    ['Core Development', '2026-08-31', '2026-09-02', 'Completed (2-day delay)'],
    ['Integration Test', '2026-09-20', '—', 'In Progress (85%)'],
    ['Mid-term Review', '2026-09-28', '2026-09-28', 'Passed'],
]
for i in range(6):
    for j in range(4):
      cell = table.Rows.Cells
      para = cell.AddParagraph()
      txtRange = para.AppendText(data)
      if i == 0:
            txtRange.CharacterFormat.Bold = True
navigator = BookmarksNavigator(doc)
navigator.MoveToBookmark('MilestoneTable')
part = TextBodyPart(doc)
part.BodyItems.Add(table)
navigator.ReplaceBookmarkContent(part)
doc.SaveToFile('step3_table.docx', FileFormat.Docx)
doc.Close()


Table(doc, True) 的第二个参数启用固定布局模式,列宽由 ResetCells 自动分配。TextBodyPart 用于承载表格、多段落等结构化内容。

三、在书签位置插入图片

AppendPicture 必须在段落上调用,而书签位置不一定有现成空段落。可先在临时 section 中创建带图片的段落,再用 InsertParagraph 插入书签位置,最后移除临时 section:


doc = Document()
doc.LoadFromFile('step3_table.docx')
bn = BookmarksNavigator(doc)
bn.MoveToBookmark('Signature', True, True)
section0 = doc.AddSection()
paragraph = section0.AddParagraph()
paragraph.Format.HorizontalAlignment = HorizontalAlignment.Center
picture = paragraph.AppendPicture('signature.png')
picture.Width = 150.0
picture.Height = 150.0
bn.InsertParagraph(paragraph)
doc.Sections.Remove(section0)
doc.SaveToFile('step4_image.docx', FileFormat.Docx)
doc.Close()


MoveToBookmark 的后两个 True 分别控制是否区分大小写、是否向前搜索。Width 和 Height 单位为磅,150 磅约等于 5.3 厘米,适合签章图片;横幅图片可按比例设置,如 Width=400.0、Height=100.0。

四、提取与列举书签内容

document.Bookmarks 是书签集合,支持按索引和按名称访问:


doc = Document()
doc.LoadFromFile('step4_image.docx')
for i in range(doc.Bookmarks.Count):
    bm = doc.Bookmarks
    print(f'{i+1}. {bm.Name}')
bookmark = doc.Bookmarks['ProjectName']
print(bookmark.Name)


GetBookmarkContent 返回书签内容对象,遍历 BodyItems 可取出文本。若书签包含表格,还需用 isinstance 判断 Table 类型:


navigator = BookmarksNavigator(doc)
targets = ['ProjectName', 'ClientName', 'Summary', 'RiskLevel', 'RiskDetail']
for name in targets:
    navigator.MoveToBookmark(name)
    content = navigator.GetBookmarkContent()
    text = ''
    for i in range(content.BodyItems.Count):
      item = content.BodyItems.get_Item(i)
      if isinstance(item, Paragraph):
            for j in range(item.ChildObjects.Count):
                child = item.ChildObjects.get_Item(j)
                if isinstance(child, TextRange):
                  text += child.Text
    display = text[:80] + '...' if len(text) > 80 else text
    print(f'[{name}] {display}')


五、删除书签与书签内容

只删除书签标记、保留内容,可用 Bookmarks.Remove:


doc = Document()
doc.LoadFromFile('step4_image.docx')
bookmark = doc.Bookmarks['RiskSection']
doc.Bookmarks.Remove(bookmark)
doc.SaveToFile('step6a_remove_mark.docx', FileFormat.Docx)
doc.Close()


删除后剩余 9 个书签,适合报告定稿时清理标记。若要同时删除书签及其内容,需要定位 BookmarkStart 和 BookmarkEnd 在段落子对象中的索引,再循环移除两者之间的元素:


bookmark = doc.Bookmarks['Signature']
para = bookmark.BookmarkStart.Owner
startIndex = para.ChildObjects.IndexOf(bookmark.BookmarkStart)
para2 = bookmark.BookmarkEnd.Owner
endIndex = para2.ChildObjects.IndexOf(bookmark.BookmarkEnd)
for i in range(startIndex + 1, endIndex):
    para.ChildObjects.RemoveAt(startIndex + 1)
doc.Bookmarks.Remove(bookmark)
doc.SaveToFile('step6b_remove_content.docx', FileFormat.Docx)
doc.Close()


RemoveAt 后列表会前移,因此循环中始终删除 startIndex + 1 位置,而不是递增索引。删除后同样剩余 9 个书签。

六、关键类与方法

常用类包括 Document、Section、Paragraph、TextRange、Table、TextBodyPart、BookmarksNavigator、Bookmarks。常用方法包括 AppendBookmarkStart/AppendBookmarkEnd、MoveToBookmark、ReplaceBookmarkContent、GetBookmarkContent、InsertParagraph、Bookmarks.Remove。

适用场景主要是项目报告、月报、合同、通知等模板化文档。典型流程是:创建带书签模板、替换文本书签、替换表格书签、在书签位置插入图片、保存输出;定稿阶段再按需删除书签标记。嵌套书签适合一个区域内多个字段独立填充,格式保留参数适合占位文本已预设样式的模板。需要注意:Start 与 End 必须成对且同名;MoveToBookmark 按名称查找;删除元素时注意集合索引变化;图片尺寸单位为磅。

热心网友7 发表于 2026-10-3 19:00:00

Re: Python操作Word书签:Free Spire.Doc替换与插图

感谢分享,步骤写得很清楚。模板阶段用 AppendBookmarkStart 和 AppendBookmarkEnd 成对定义书签、名称保持一致,以及外层 RiskSection 包内层 RiskLevel、RiskDetail 的嵌套做法,很适合做报告类文档。替换部分里 BookmarksNavigator 的 MoveToBookmark 加 ReplaceBookmarkContent 很直观,第二个参数控制是否保留原格式这点对模板排版很重要。表格先建 Table 再用 TextBodyPart 包装传入,也让人知道书签里不只是能填文本。 不过帖子最后 Milestone 表格好像没贴完,标题提到的插图部分也还没看到。如果方便的话,想继续看图片插入书签以及嵌套书签整体替换的完整示例。

热心网友3 发表于 2026-10-3 19:10:00

Re: Python操作Word书签:Free Spire.Doc替换与插图

书签这套思路确实比全文查找替换稳得多,模板改版之后不用重写定位逻辑,这点很关键。 有几个地方想请教一下: 一是 ReplaceBookmarkContent 执行完之后,书签本身还在不在?如果还在,那同一份 document 上依次替换其余书签应该不会有冲突;但要是书签被吃掉,后面再有引用同一位置的操作就得改顺序了。之前用别的库踩过这个坑,替换完定位就失效。 二是嵌套书签那段,内层能独立定位这点很有用。不过如果先把外层 RiskSection 整体替换了,里层的 RiskLevel 和 RiskDetail 还能再进去改吗,还是必须从内到外按顺序来?感觉这个顺序问题很容易在生产里出岔子。 三是建议模板本身也脚本生成,别让人在 Word 里手工调。手工编辑最容易动的就是书签名字,改一个字符或者误删一个,运行时直接报找不到,排查起来还绕。 另外免费版好像对段落数或者表格数有上限,你这个 10 个书签的模板肯定没问题,但要是批量生成几十上百页的报告,最好先压测一下,确认不会被截断。 最后图片插入那段正文好像断了,方便的话补一下用书签占位再塞图的做法,正好跟表格替换那部分对应起来,这块应该是很多人最想看的。

热心网友3 发表于 2026-10-3 19:20:00

Re: Python操作Word书签:Free Spire.Doc替换与插图

感谢分享,这套用书签做模板填充的思路很清晰。用 AppendBookmarkStart 和 AppendBookmarkEnd 在模板里埋点,再通过 BookmarksNavigator 的 MoveToBookmark 和 ReplaceBookmarkContent 填充,确实比全文查找替换更稳,尤其适合报告类文档反复生成。 嵌套书签 RiskSection 包 RiskLevel 和 RiskDetail 这点很实用,内层能单独替换,外层也能整体处理,灵活性挺高。替换文本时第二个参数控制是否保留原格式,这个细节讲得也明白,实际做模板时很容易忽略。表格替换先用 Table 构建、再用 TextBodyPart 包装后传给 ReplaceBookmarkContent,这个步骤也很关键。 看正文里的 MilestoneTable 数据似乎还没贴完,期待后续把完整表格示例和插图部分补上,特别是书签位置插入图片的写法。整体很实用,先收藏学习了。
页: [1]
查看完整版本: Python操作Word书签:Free Spire.Doc替换与插图