免费编程软件「python+pycharm」
链接:https://pan.quark.cn/s/48a86be2fdc0
一个让我社死的故事
事情是这样的。
上个月,导师让我把毕业论文改成期刊论文。内容要精简,但引用不能丢。原来正文里密密麻麻的参考文献,要全部挪到脚注里。
我看着那篇120页的文档,手动复制粘贴了10条脚注,就感觉手已经不是自己的了。
怎么办?写个Python脚本呗。
我以为这事特简单——找到文本,加个脚注,完事。结果折腾了一整天,踩了无数坑,最后在组会上被导师当众处刑:“你这脚注格式是认真的吗?”
今天我就把这个过程的来龙去脉讲清楚。从最基础的操作,到那些没人告诉你的坑,一篇讲透。
先搞清楚:脚注到底是什么鬼
在Word里,脚注就是页面底下那一小行字。正文里有个小数字标号,点一下就能跳到页面底部的说明文字。
学术论文、技术文档里特别常见。比如你写“张三在2021年提出了一种新方法”,然后右上角标个¹,页面底下写“¹ 张三,XXX期刊,2021”。
脚注和尾注的区别:脚注在页面底部,尾注在文档末尾。理解了这一点,下面的代码你就能举一反三。
好,概念讲完,开始干活。
准备工作:装哪个库?
Python操作Word,主流有两个选择:
python-docx:免费、轻量、简单。基本的读写都支持。适合提取脚注、简单操作。
Spire.Doc:功能强大,但免费版有限制(最多500段落)。适合插入、删除脚注这种需要深度操作的任务。
我们今天主要用Spire.Doc,因为插入脚注这个操作,python-docx目前还不原生支持。
安装命令:
pip install Spire.Doc装好了就开始。
读取脚注:最基础但最实用的操作
假设你现在手上有一篇别人写的论文,想快速把所有脚注提取出来,看看他都引用了什么。
这个用python-docx就能搞定:
from docx import Document def extract_footnotes(doc_path): """提取Word文档中的所有脚注""" doc = Document(doc_path) footnotes = [] for i, footnote in enumerate(doc.footnotes, 1): # 脚注可能包含多个段落,这里取第一个 text = footnote.paragraphs[0].text if footnote.paragraphs else "" footnotes.append({ 'id': i, 'text': text }) return footnotes # 使用 notes = extract_footnotes("论文.docx") for note in notes: print(f"脚注{note['id']}: {note['text']}")这段代码干的事情很简单:打开文档,遍历所有脚注,把文本拿出来打印。
但是——这里有个坑:doc.footnotes这个属性,有些版本的python-docx里可能没有。如果你运行报错,换下面这个写法:
# 备用方案:遍历段落,找脚注标记 def extract_footnotes_v2(doc_path): doc = Document(doc_path) footnotes = [] for paragraph in doc.paragraphs: # 检查段落中是否有脚注引用 for run in paragraph.runs: if hasattr(run, 'footnote') and run.footnote: footnotes.append(run.footnote.text) return footnotes插入脚注:两种常见场景
插入脚注才是重头戏。根据你的需求,有两种常见场景。
场景一:给整个段落加脚注
最简单的情况——你想在某个段落的末尾加一个脚注:
from spire.doc import * from spire.doc.common import * def add_footnote_to_paragraph(doc_path, para_index, footnote_text): """给指定段落添加脚注""" # 加载文档 doc = Document() doc.LoadFromFile(doc_path) # 获取第一个节(section) section = doc.Sections.get_Item(0) # 获取指定段落(索引从0开始) paragraph = section.Paragraphs.get_Item(para_index) # 添加脚注 footnote = paragraph.AppendFootnote(FootnoteType.Footnote) # 设置脚注内容 text = footnote.TextBody.AddParagraph().AppendText(footnote_text) # 保存 doc.SaveToFile("带脚注的文档.docx", FileFormat.Docx2016) doc.Close() # 使用:给第5个段落加脚注 add_footnote_to_paragraph("我的论文.docx", 4, "这个观点来自张三的研究")这里有个细节:para_index从0开始,所以第1段是0,第2段是1,以此类推。
场景二:给特定文字加脚注
这是我最需要的功能——给某句话后面直接加脚注,而不是整段:
def add_footnote_to_text(doc_path, target_text, footnote_text): """在特定文字后面添加脚注""" doc = Document() doc.LoadFromFile(doc_path) # 查找目标文字 selection = doc.FindString(target_text, False, True) if not selection: print(f"找不到文字:{target_text}") return # 获取文字所在位置 text_range = selection.GetAsOneRange() paragraph = text_range.OwnerParagraph index = paragraph.ChildObjects.IndexOf(text_range) # 在文字后面插入脚注 footnote = paragraph.AppendFootnote(FootnoteType.Footnote) paragraph.ChildObjects.Insert(index + 1, footnote) # 设置脚注内容 footnote.TextBody.AddParagraph().AppendText(footnote_text) # 保存 doc.SaveToFile("带脚注的文档.docx", FileFormat.Docx2016) doc.Close() # 使用:在"张三提出了一种新方法"后面加脚注 add_footnote_to_text("我的论文.docx", "张三提出了一种新方法", "张三,2023,计算机学报")这个函数的核心是找到目标文字在段落里的位置,然后把脚注塞到它后面。index + 1的意思是:插在当前文字的后面。
进阶:格式化脚注
光有文字不够,学术论文对格式要求很严。字体、大小、颜色都得对:
def add_footnote_with_style(doc_path, target_text, footnote_text): """添加带格式的脚注""" doc = Document() doc.LoadFromFile(doc_path) selection = doc.FindString(target_text, False, True) text_range = selection.GetAsOneRange() paragraph = text_range.OwnerParagraph index = paragraph.ChildObjects.IndexOf(text_range) footnote = paragraph.AppendFootnote(FootnoteType.Footnote) paragraph.ChildObjects.Insert(index + 1, footnote) # 添加脚注文本并设置格式 text = footnote.TextBody.AddParagraph().AppendText(footnote_text) text.CharacterFormat.FontName = "宋体" text.CharacterFormat.FontSize = 12 text.CharacterFormat.TextColor = Color.get_DarkBlue() # 设置脚注标号的格式(正文里那个小数字) footnote.MarkerCharacterFormat.FontName = "Calibri" footnote.MarkerCharacterFormat.FontSize = 14 footnote.MarkerCharacterFormat.Bold = True doc.SaveToFile("带格式的脚注.docx", FileFormat.Docx2016) doc.Close()看到了吗?脚注有两部分格式:一是脚注文本的格式(页面底部),二是脚注标号的格式(正文里的小数字)。两个都可以单独设置。
删除脚注:一键清理
有时候脚注加多了,想批量删掉。这个也能自动化:
def remove_all_footnotes(doc_path): """删除文档中的所有脚注""" doc = Document() doc.LoadFromFile(doc_path) section = doc.Sections[0] for para in section.Paragraphs: # 遍历段落的所有子对象,找到脚注并删除 i = 0 while i < para.ChildObjects.Count: obj = para.ChildObjects[i] if isinstance(obj, Footnote): para.ChildObjects.RemoveAt(i) # 注意:删除后不增加i,因为后面的元素会往前移 else: i += 1 doc.SaveToFile("无脚注的文档.docx", FileFormat.Docx2016) doc.Close() # 使用 remove_all_footnotes("带脚注的文档.docx")这个循环写法值得注意:找到脚注后直接删除,然后i不增加,因为后面的元素自动补上来了。如果i++写在外面,会跳过一些元素。
批量处理:真正的效率神器
真正的需求来了——你不是只有一个文档,而是几十个。
比如你要给文件夹里所有的Word文档加上同样的脚注说明:
import os from spire.doc import * def batch_add_footnote(folder_path, target_text, footnote_text): """批量给文件夹里的所有docx文件添加脚注""" for filename in os.listdir(folder_path): if not filename.endswith('.docx'): continue filepath = os.path.join(folder_path, filename) print(f"处理中:{filename}") try: doc = Document() doc.LoadFromFile(filepath) selection = doc.FindString(target_text, False, True) if selection: text_range = selection.GetAsOneRange() paragraph = text_range.OwnerParagraph index = paragraph.ChildObjects.IndexOf(text_range) footnote = paragraph.AppendFootnote(FootnoteType.Footnote) paragraph.ChildObjects.Insert(index + 1, footnote) footnote.TextBody.AddParagraph().AppendText(footnote_text) output_path = os.path.join(folder_path, f"带脚注_{filename}") doc.SaveToFile(output_path, FileFormat.Docx2016) print(f"完成:{filename}") else: print(f"跳过(未找到目标文字):{filename}") doc.Close() except Exception as e: print(f"出错:{filename} - {str(e)}") # 使用 batch_add_footnote( "D:/我的论文文件夹", "需要引用的观点", "详见张三的研究,2024" )这样一键运行,几十个文档几秒钟搞定。
我踩过的那些坑
说了这么多,下面才是真正的干货——那些文档里不会告诉你的坑。
坑1:找不到脚注属性
doc.footnotes在有些版本的python-docx里不存在。解决办法:要么更新库版本,要么用Spire.Doc替代。
坑2:脚注索引乱掉
删除脚注时,如果你是在循环里删的,索引会变。上面的代码用了while循环配合手动控制i,就是为了解决这个问题。
坑3:中文字体不生效
设置FontName = "宋体"可能不生效,因为Word里中文字体叫"SimSun"。建议用英文名:
text.CharacterFormat.FontName = "SimSun" # 宋体 text.CharacterFormat.FontName = "SimHei" # 黑体坑4:大文档处理慢
如果你的文档超过100页,Spire.Doc可能会慢。可以分节处理,每次只处理需要改的那一节,而不是整个文档。
实战:把参考文献一键转成脚注
最后,来个完整实战。这是我当初最需要的功能——把正文里[1]、[2]这种引用标记,自动转成脚注:
import re from spire.doc import * def refs_to_footnotes(doc_path, ref_dict): """ 将正文中的引用标记转换为脚注 ref_dict: {引用标记: 参考文献内容} 的字典 例如: {"[1]": "张三,计算机学报,2023"} """ doc = Document() doc.LoadFromFile(doc_path) section = doc.Sections[0] for para in section.Paragraphs: # 获取段落的完整文本 para_text = para.Text for ref_mark, ref_content in ref_dict.items(): if ref_mark in para_text: # 找到引用标记的位置 selection = doc.FindString(ref_mark, False, True) if not selection: continue text_range = selection.GetAsOneRange() # 检查这个引用是否在当前段落 if text_range.OwnerParagraph != para: continue index = para.ChildObjects.IndexOf(text_range) # 删除引用标记文字 para.ChildObjects.RemoveAt(index) # 在相同位置插入脚注 footnote = para.AppendFootnote(FootnoteType.Footnote) para.ChildObjects.Insert(index, footnote) # 设置脚注内容为参考文献 footnote.TextBody.AddParagraph().AppendText(ref_content) # 设置脚注标号格式(保持数字格式) footnote.MarkerCharacterFormat.FontSize = 10 doc.SaveToFile("转成脚注的文档.docx", FileFormat.Docx2016) doc.Close() # 使用 ref_dict = { "[1]": "张三,一种新的深度学习方法,计算机学报,2023,45(3): 123-130", "[2]": "李四,图像识别技术综述,软件学报,2022,33(5): 456-467" } refs_to_footnotes("我的论文.docx", ref_dict)这个脚本干的事情:找到每个引用标记,删掉它,然后在同样的位置插入一个脚注,脚注里放参考文献内容。
总结
用Python操作Word脚注,核心就这几件事:
读取用
python-docx的doc.footnotes插入用
Spire.Doc的AppendFootnote(),注意区分段落尾和文字后删除用
ChildObjects遍历,注意索引问题批量处理就是上面3个操作的循环
最关键的忠告:操作前备份原文件。我当初就没备份,差点把自己整篇论文搞废。
希望这篇能帮你少走弯路。如果你也遇到过脚注相关的奇葩问题,欢迎在评论区分享。