攝影或3C

Python-docx 進階密技:突破限制,在 Word 任意位置插入段落的三種流派: insert_paragraph_before ; body.insert(0, p_elem) #像操作 List 一樣操作文件; target_xml_node.addnext(p_new)

在使用 python-docx 自動化生成 Word 文件時,最讓人頭痛的往往不是「怎麼寫入內容」,而是「怎麼插在對的地方」。預設的 add_paragraph 只能乖乖排在最後面,但實務上我們常需要插隊、補字,甚至在表格之間插入說明。

這篇文章將帶您實作三種不同層級的插入技巧:

  1. 高階流:使用 insert_paragraph_before(最簡單)。
  2. 底層流:使用 body.insert(像操作 List 一樣操作文件)。
  3. 駭客流:使用 addnext(實現官方沒有的 insert_after)。

第 0 步:準備環境與測試檔案

為了確保接下來的程式碼都能順利執行,我們先用 Python 產生一個標準的測試檔案 demo.docx。請在 Jupyter Notebook 中執行以下程式碼:

import os
from docx import Document

# 確保目錄存在
save_path = r'D:\Temp'
if not os.path.exists(save_path):
    os.makedirs(save_path)

# 建立一個測試用的 Word 
doc = Document()
doc.add_heading('測試文件標題', 0)
doc.add_paragraph('這是原本的第一個段落 (Index 0)。')
doc.add_paragraph('這是原本的第二個段落 (Index 1)。')
doc.add_paragraph('這是原本的第三個段落 (Index 2)。')

file_path = os.path.join(save_path, 'demo.docx')
doc.save(file_path)

print(f"測試檔案已建立於:{file_path}")

測試檔案已建立於:D:\Temp\demo.docx

方法一:高階流 (High-Level API)

適用場景: 你只想單純地在「某個段落之前」插入新內容。

這是官方封裝好的方法,最安全也最乾淨。你不需要懂 XML,只要找到目標段落物件即可。

# %%
from docx import Document

# 讀取剛剛產生的檔案
doc = Document(r'D:\Temp\demo.docx')

print("--- 方法一:使用 insert_paragraph_before ---")

# 1. 鎖定目標我們想插在 "第二個段落" 之前
# doc.paragraphs 是一個列表,[1] 代表第二段
if len(doc.paragraphs) > 1:
    target_para = doc.paragraphs[1]
    print(f"目標段落原本文字: {target_para.text}")
    
    # 2. 執行插入
    new_para = target_para.insert_paragraph_before('【方法一】我是插隊的段落 (插在第二段之前)')
    
    # (選用) 設定樣式證明它是個正常的段落物件
    new_para.runs[0].bold = True

# 儲存結果
doc.save(r'D:\Temp\demo_result_1.docx')
print("已儲存:D:\\Temp\\demo_result_1.docx")

— 方法一:使用 insert_paragraph_before —
目標段落原本文字: 這是原本的第一個段落 (Index 0)。
已儲存:D:\Temp\demo_result_1.docx

方法二:底層流 (Low-Level XML Insert)

適用場景: 你需要絕對的控制權,例如「插在整份文件的第 0 個位置」或是「插在第 5 個元素位置(不管它是表格還是段落)」。

這裡我們進入了 XML 的領域。Word 的文件內容其實包在一個 <w:body> 標籤裡,我們可以把它當作一個 Python List,用 index 來插入。

from docx import Document
from docx.oxml import OxmlElement

doc = Document(r'D:\Temp\demo.docx')
body = doc.element.body  # 取得 XML  Body 元素

print("--- 方法二:使用 XML body.insert ---")

# 1. 手動組裝 XML 結構: <w:p> -> <w:r> -> <w:t>
# 這是 Word 底層最基本的段落結構
p_elem = OxmlElement('w:p') # 段落 (Paragraph)
r_elem = OxmlElement('w:r') # 樣式區塊 (Run)
t_elem = OxmlElement('w:t') # 文字 (Text)
t_elem.text = '【方法二】我是底層 XML 強制插入最前面的段落'

# 像俄羅斯娃娃一樣裝起來
r_elem.append(t_elem)
p_elem.append(r_elem)

# 2. 插入到 Body  Index 0 (絕對的最前面連標題都會被擠下去)
# body.insert(位置, 元素)
body.insert(0, p_elem) 

doc.save(r'D:\Temp\demo_result_2.docx')
print("已儲存:D:\\Temp\\demo_result_2.docx")

— 方法二:使用 XML body.insert —
已儲存:D:\Temp\demo_result_2.docx

方法三:駭客流 (XML addnext)

適用場景: 你非常需要 insert_paragraph_after(在某段落之後插入),但官方偏偏沒有這個功能。

既然官方只給「插在前面」,我們就用底層庫 lxml 的功能,直接告訴 XML 節點:「在你的屁股後面加一個兄弟節點」。

# %%
from docx import Document
from docx.oxml import OxmlElement

doc = Document(r'D:\Temp\demo.docx')

print("--- 方法三:使用 XML addnext 實現 insert_after ---")

# 1. 同樣先組裝 XML 元素
p_new = OxmlElement('w:p')
r_new = OxmlElement('w:r')
t_new = OxmlElement('w:t')
t_new.text = '【方法三】我是用 addnext 插在第一段之後的 (實現 insert_after)'
r_new.append(t_new)
p_new.append(r_new)

# 2. 鎖定目標第一段
if len(doc.paragraphs) > 0:
    first_para = doc.paragraphs[0]
    
    # 3. 取得該段落的底層 XML 節點 (_p)
    # 每個 Paragraph 物件都有一個 ._p 屬性指向真正的 XML 記憶體位置
    target_xml_node = first_para._p
    
    # 4. 呼叫 lxml  addnext 方法 (這是 lxml 函式庫的原生功能)
    target_xml_node.addnext(p_new)

doc.save(r'D:\Temp\demo_result_3.docx')
print("已儲存:D:\\Temp\\demo_result_3.docx")

— 方法三:使用 XML addnext 實現 insert_after —
已儲存:D:\Temp\demo_result_3.docx

總結

掌握這三招,Word 文件裡的任何位置都擋不住你了!

推薦hahow線上學習python: https://igrape.net/30afN

儲蓄保險王

儲蓄險是板主最喜愛的儲蓄工具,最喜愛的投資理財工具則是ETF,最喜愛的省錢工具則是信用卡