在使用 python-docx 時,add_paragraph() 雖然方便,
但它只能把內容加在最後面。
如果你想插隊(插在最前面、插在表格後),
或是建立超連結,就必須學會操作底層 XML。
本教學包含 5 種核心技法,由淺入深,一次搞懂。
第一步:準備測試環境
首先,我們生成一個乾淨的 demo_origin.docx 作為手術對象。
from docx import Document
import os
# 確保目錄存在
os.makedirs(r'D:\Temp', exist_ok=True)
def create_demo_doc():
doc = Document()
doc.add_heading('原始文件標題', 0)
doc.add_paragraph('這是原始文件的第一段內容。')
doc.add_paragraph('這是原始文件的第二段內容。')
doc.save(r'D:\Temp\demo_origin.docx')
print("測試檔案 'demo_origin.docx' 已生成!")
if __name__ == "__main__":
create_demo_doc()demo_origin.docx:

第二步:五大技法實戰 (完整程式碼)
這段程式碼整合了所有技巧,請直接執行。
from docx import Document
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.opc.constants import RELATIONSHIP_TYPE as RT
def run_advanced_demo():
# 讀取剛剛生成的檔案
doc = Document(r'D:\Temp\demo_origin.docx')
body = doc.element.body # 取得 XML 的 Body 元素
# =====================================================
# 方法一:高階 API (High-Level)
# =====================================================
print("--- 執行方法一:標準加入 ---")
# 限制:只能 Append 到文件「最後面」
doc.add_paragraph('方法一:我是高階 API 產生的段落 (在最後面)')
# =====================================================
# 方法二:底層 XML (Low-Level) - 絕對位置插入
# =====================================================
print("--- 執行方法二:底層 XML 組裝 ---")
# 手動組裝 XML 結構: <w:p> -> <w:r> -> <w:t>
p_elem = OxmlElement('w:p') # 1. 段落
r_elem = OxmlElement('w:r') # 2. 樣式片段
t_elem = OxmlElement('w:t') # 3. 文字
t_elem.text = '方法二:我是底層 XML 組裝的段落 (插在最前面)'
r_elem.append(t_elem) # 裝箱
p_elem.append(r_elem) # 裝箱
# 插入到 Body 的 Index 0 (最前面)
body.insert(0, p_elem)
# =====================================================
# 方法三:混合技 (Hybrid) - 先建後移 (絕對位置)
# =====================================================
print("--- 執行方法三:混合技 (Insert) ---")
# 1. 用高階 API 快速建立 (預設在最後)
new_p = doc.add_paragraph('方法三:我是混合技產生的段落 (原本在後,被移到前)')
new_p.runs[0].bold = True
# 2. 取得底層 XML 元素
p_element = new_p._p
# 3. 安全移除 (找爸爸刪除自己)
if p_element.getparent() is not None:
p_element.getparent().remove(p_element)
# 4. 插在 Index 1 (剛好在方法二的後面)
body.insert(1, p_element)
# =====================================================
# 方法四:相對定位法 (Relative) - 使用 addnext 插隊
# =====================================================
print("--- 執行方法四:相對定位 (addnext) ---")
# 情境:我想要插在「原始文件標題」的後面
# 1. 建立段落
p4 = doc.add_paragraph('方法四:我是用 addnext 插隊的段落 (在標題後面)')
p4.runs[0].font.italic = True
p4_elem = p4._p
# 2. 安全移除
if p4_elem.getparent() is not None:
p4_elem.getparent().remove(p4_elem)
# 3. 找到錨點 (Anchor)
# 目前順序:[0]方法二 -> [1]方法三 -> [2]原始標題
anchor_paragraph = body[2]
# 4. 插隊 (跟在標題後面)
anchor_paragraph.addnext(p4_elem)
# =====================================================
# 方法五:超連結混合技 (Hyperlink Hybrid) - 大魔王
# =====================================================
print("--- 執行方法五:超連結混合技 ---")
url = "https://www.google.com"
link_text = "方法五:點我前往 Google (我是超連結,我也插在最前面)"
# 1. [高階] 註冊關聯 (取得 rId)
part = doc.part
r_id = part.relate_to(url, RT.HYPERLINK, is_external=True)
# 2. [底層] 手工打造 <w:hyperlink>
hyperlink = OxmlElement('w:hyperlink')
hyperlink.set(qn('r:id'), r_id) # 設定關聯 ID
# 3. 建立內容 (Run + 樣式 + Text)
run = OxmlElement('w:r')
rPr = OxmlElement('w:rPr')
# 設定藍色
c = OxmlElement('w:color')
c.set(qn('w:val'), '0000FF')
rPr.append(c)
# 設定底線
u = OxmlElement('w:u')
u.set(qn('w:val'), 'single')
rPr.append(u)
run.append(rPr)
# 設定文字
t = OxmlElement('w:t')
t.text = link_text
run.append(t)
hyperlink.append(run) # 把 Run 裝進 Hyperlink
# 4. [混合技] 借殼上市
# 建立一個空段落容器
p_container = doc.add_paragraph()
# 把超連結塞進去
p_container._p.append(hyperlink)
# 5. [底層] 移動位置 (移到最前面 Index 0)
p_link_elem = p_container._p
# 安全移除
if p_link_elem.getparent() is not None:
p_link_elem.getparent().remove(p_link_elem)
# 插隊當老大
body.insert(0, p_link_elem)
# 存檔
output_path = r'D:\Temp\demo_result_final.docx'
doc.save(output_path)
print(f"操作完成!請開啟 '{output_path}' 查看結果。")
if __name__ == "__main__":
run_advanced_demo()輸出:

demo_result_final.docx

段落 vs 超連結段落

關鍵差異
超連結多了一個 <w:hyperlink> 標籤,它像一個「夾心層」,把 <w:r> 包在裡面。而這個標籤最重要的屬性是 r:id,它負責告訴 Word:「這個連結要連去哪裡(網址)」。
第三步:技法解析
1. 為什麼要用 getparent().remove()?
在之前的嘗試中,我們直接用 body.remove(p),這有時會報錯 ValueError: Element is not a child of this node。
這是因為 doc.add_paragraph() 產生的元素,其父節點參照可能與我們手上的 body 變數不同步。
最安全的寫法是:「找到該元素的親生父親 (getparent()),然後叫父親把它移除」。
2. insert vs addnext
- body.insert(i, elem):
- 絕對位置。
- 適合:「我要放在第 0 個」、「我要放在第 1 個」。
- 不依賴其他段落是否存在。
- anchor.addnext(elem):
- 相對位置。
- 適合:「我要跟在標題後面」、「我要跟在表格後面」。
- 必須先找到一個存在的「錨點 (Anchor)」。
3. 超連結的奧義
超連結無法純用高階 API 建立,也無法純用底層 XML (因為要註冊 .rels 關聯)。
所以必須使用 混合技:
- 用 doc.part.relate_to 處理關聯 (High-level)。
- 用 OxmlElement 建立標籤 (Low-level)。
- 用 doc.add_paragraph 建立容器 (High-level)。
- 用 insert/addnext 移動位置 (Low-level)。
這套流程是 python-docx 開發者的必備技能!
推薦hahow線上學習python: https://igrape.net/30afN
在 lxml (python-docx 的底層 XML 引擎) 中,
有 addprevious(elem) 這個方法。
addprevious vs addnext
anchor.addnext(elem):插在錨點的後面 (Next Sibling)。
anchor.addprevious(elem):插在錨點的前面 (Previous Sibling)。
為什麼我很少提 addprevious?
雖然它存在,但在實際操作 Word XML 時,我們比較少用它,原因有二:
思維習慣:我們通常習慣「由上而下」建立文件(先標題,再段落,再表格),
所以「插在後面」比較符合直覺。
替代方案:addprevious 其實就等於 insert(anchor_index, elem)。
如果你要插在某人前面,
其實就是插在那個人的位置上(把他擠到後面去)。
實戰範例
如果你想用 addprevious 來實作「插在標題前面」,程式碼會長這樣:
# 假設 anchor 是標題
anchor_paragraph = body[2]
# 建立新段落
p_new = doc.add_paragraph("我是插隊在前面的段落")
p_elem = p_new._p
# 安全移除
if p_elem.getparent() is not None:
p_elem.getparent().remove(p_elem)
# 插在標題前面
anchor_paragraph.addprevious(p_elem)
]*>.*?底下插入一個圖檔.*?</w:p>’, flags = re.DOTALL) ; new_xml, n = pattern.subn(”, xml, count=1)' title='Python正則替換:全面掌握 re.sub 與 re.subn 的差異與實戰 #substitute(替換); . 預設匹配「除\n以外的任意單一字元」; pattern = re.compile(r'<w:p[^>]*>.*?底下插入一個圖檔.*?</w:p>’, flags = re.DOTALL) ; new_xml, n = pattern.subn(”, xml, count=1)' loading='lazy' width=350 height=233 />![Python爬蟲:BeautifulSoup的 .find_all() 與 .find() 與 .select(‘標籤名[屬性名1=”屬性值1″][屬性名2=”屬性值2″]’) ; from bs4 import BeautifulSoup ; Live Server(可以預覽HTML的VS Code套件) Python爬蟲:BeautifulSoup的 .find_all() 與 .find() 與 .select(‘標籤名[屬性名1=”屬性值1″][屬性名2=”屬性值2″]’) ; from bs4 import BeautifulSoup ; Live Server(可以預覽HTML的VS Code套件)](https://i0.wp.com/savingking.com.tw/wp-content/uploads/2025/03/20250330190318_0_925655.jpg?quality=90&zoom=2&ssl=1&resize=350%2C233)








近期留言