使用python-docx生成的Word文檔打開時(shí)彈出“無法讀取內(nèi)容“警告的解決方案
背景:使用 python-docx 基于 WPS 創(chuàng)建的模板生成 .docx 報(bào)告文件,用 Microsoft Word 打開時(shí)彈出以下警告:
“Word 在 report_xxx.docx 中發(fā)現(xiàn)無法讀取的內(nèi)容。是否恢復(fù)此文檔的內(nèi)容?如果您信任此文檔的來源,請(qǐng)單擊"是”。"
一、問題現(xiàn)象
程序運(yùn)行正常,生成的 .docx 文件內(nèi)容完整,但每次用 Microsoft Word 打開都會(huì)彈出"無法讀取內(nèi)容"警告彈窗。點(diǎn)擊"是"之后文檔可以正常顯示,但這個(gè)警告嚴(yán)重影響用戶體驗(yàn),并暗示文件存在潛在的格式問題。
二、排查過程
第一步:常規(guī) XML 結(jié)構(gòu)檢查
首先懷疑是 python-docx 生成的 XML 本身存在格式問題,逐一檢查:
word/document.xml的 body 結(jié)構(gòu)- 顏色值格式(
#000000vs000000) - 圖片關(guān)系引用
- 樣式引用完整性
- 書簽配對(duì)完整性
- 表格 XML 結(jié)構(gòu)(
tblPr/tblGrid/tblW)
結(jié)果:全部通過,未發(fā)現(xiàn)異常。
插曲:檢查過程中確實(shí)發(fā)現(xiàn)了一個(gè)
#000000顏色值格式問題——python-docx 要求顏色值不含#前綴(應(yīng)為000000),修復(fù)后警告依然存在,說明這不是根本原因。
第二步:檢查customXml目錄
將 .docx 文件作為 ZIP 包解壓,進(jìn)入 customXml/ 目錄檢查:
customXml/ ├── item1.xml ├── item2.xml ├── _rels/ │ ├── item1.xml.rels │ └── item2.xml.rels ├── itemProps1.xml ← 關(guān)鍵文件 └── itemProps2.xml
打開 customXml/itemProps1.xml,發(fā)現(xiàn)了問題所在:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ds:datastoreItem ds:itemID="{B1977F7D-...}" xmlns:ds="...">
<ds:schemaRefs>
<ds:schemaRef ds:uri="http://www.wps.cn/officeDocument/2013/wpsCustomData"/>
</ds:schemaRefs>
</ds:datastoreItem>以及 customXml/item1.xml 中的私有數(shù)據(jù):
<s:customData xmlns="http://www.wps.cn/officeDocument/2013/wpsCustomData"> <!-- WPS 私有擴(kuò)展信息:形狀、段落屬性等 --> </s:customData>
第三步:確認(rèn)完整關(guān)系鏈
word/_rels/document.xml.rels └─ rId12 → ../customXml/item1.xml (WPS 私有數(shù)據(jù)) └─ rId14 → ../customXml/item2.xml (APA 書目,正常) [Content_Types].xml └─ <Override PartName="/customXml/itemProps1.xml" ContentType="...customXmlProperties+xml"/>
三、根本原因
模板文件由 WPS(金山 Office)創(chuàng)建,WPS 會(huì)在 .docx 文件中嵌入私有的 wpsCustomData XML 數(shù)據(jù),其 schema URI 為:
http://www.wps.cn/officeDocument/2013/wpsCustomData
當(dāng) python-docx 加載該模板并保存新文檔時(shí),這份 WPS 私有 customXml 數(shù)據(jù)會(huì)被完整保留到輸出文件中。
Microsoft Word 打開文檔時(shí)會(huì)嘗試驗(yàn)證所有 customXml 的 schema 引用。由于 wpsCustomData 是 WPS 的私有 schema,Microsoft Word 無法找到或識(shí)別它,因此觸發(fā)"無法讀取內(nèi)容"警告。
四、解決方案
在 doc.save() 之后,直接對(duì)生成的 ZIP 包(即 .docx 文件)進(jìn)行處理,移除 WPS 私有數(shù)據(jù)。
實(shí)現(xiàn)代碼
def clean_wps_custom_data(docx_path):
"""
從生成的 docx 文件中移除 WPS 私有 customXml 數(shù)據(jù)。
模板由 WPS 創(chuàng)建時(shí)會(huì)嵌入 wpsCustomData 私有 XML,其 schema URI 為
http://www.wps.cn/officeDocument/2013/wpsCustomData,Microsoft Word
無法識(shí)別此私有 schema,導(dǎo)致打開時(shí)彈出"無法讀取內(nèi)容"警告。
此函數(shù)在 doc.save() 之后運(yùn)行,直接對(duì) ZIP 包進(jìn)行手術(shù),移除該私有數(shù)據(jù)。
"""
import zipfile, re, io
buffer = io.BytesIO()
try:
with zipfile.ZipFile(docx_path, 'r') as z_in:
with zipfile.ZipFile(buffer, 'w', compression=zipfile.ZIP_DEFLATED) as z_out:
file_list = z_in.namelist()
# 第一步:找出含有 WPS schema 引用的 itemProps 文件編號(hào)
wps_item_nums = set()
for fname in file_list:
if fname.startswith('customXml/itemProps') and fname.endswith('.xml'):
with z_in.open(fname) as f:
content = f.read().decode('utf-8', errors='replace')
if 'wps.cn' in content or 'wpsCustomData' in content:
m = re.search(r'itemProps(\d+)\.xml', fname)
if m:
wps_item_nums.add(m.group(1))
# 第二步:構(gòu)建需要整體刪除的文件集合
files_to_delete = set()
for num in wps_item_nums:
files_to_delete.add(f'customXml/item{num}.xml')
files_to_delete.add(f'customXml/itemProps{num}.xml')
files_to_delete.add(f'customXml/_rels/item{num}.xml.rels')
if not wps_item_nums:
print('clean_wps_custom_data: 未發(fā)現(xiàn) WPS 私有數(shù)據(jù),無需清理')
return
print(f'clean_wps_custom_data: 發(fā)現(xiàn) WPS 私有數(shù)據(jù)(item 編號(hào) {wps_item_nums}),正在清理...')
# 第三步:逐文件處理,重新打包 ZIP
for fname in file_list:
if fname in files_to_delete:
continue # 跳過 WPS 私有文件,不寫入新 zip
with z_in.open(fname) as f:
data = f.read()
if fname == '[Content_Types].xml':
# 刪除 WPS itemProps 的 Override 聲明
content = data.decode('utf-8')
for num in wps_item_nums:
content = re.sub(
r'<Override[^>]*PartName="/customXml/itemProps' + num + r'\.xml"[^>]*/>', '',
content
)
z_out.writestr(fname, content.encode('utf-8'))
elif fname == 'word/_rels/document.xml.rels':
# 刪除指向 WPS item 文件的 Relationship 條目
content = data.decode('utf-8')
for num in wps_item_nums:
content = re.sub(
r'<Relationship[^>]*Target="\.\./customXml/item' + num + r'\.xml"[^>]*/>', '',
content
)
z_out.writestr(fname, content.encode('utf-8'))
else:
z_out.writestr(fname, data)
# 將處理后的內(nèi)容寫回原文件
buffer.seek(0)
with open(docx_path, 'wb') as f:
f.write(buffer.read())
print('clean_wps_custom_data: WPS 私有數(shù)據(jù)清理完成')
except Exception as e:
print(f'clean_wps_custom_data: 清理失敗(不影響文檔內(nèi)容): {e}')調(diào)用方式
doc = Document(template_path) # ... 填充內(nèi)容 ... doc.save(output_path) clean_wps_custom_data(output_path) # ← 緊接在 save 之后調(diào)用
五、方案要點(diǎn)說明
為什么不直接修改模板文件?
可以手動(dòng)用 Microsoft Word 打開模板并重新保存,以去除 WPS 私有數(shù)據(jù)。但這種方式存在風(fēng)險(xiǎn):
- 每次模板更新后都需要手動(dòng)處理
- 在自動(dòng)化流水線中無法保證
因此選擇在代碼層面自動(dòng)清理,更加健壯。
ZIP 重打包的注意事項(xiàng)
.docx 本質(zhì)上是一個(gè) ZIP 文件。修改其內(nèi)容的正確方式是:
- 讀取原 ZIP 到內(nèi)存(
io.BytesIO) - 創(chuàng)建新 ZIP,逐文件寫入(跳過或修改目標(biāo)文件)
- 將新 ZIP 的內(nèi)容寫回原路徑
不要直接在原 ZIP 上增刪文件(Python 的 zipfile 模塊不支持原地刪除),否則會(huì)損壞文件結(jié)構(gòu)。
正則表達(dá)式中[^>]*的選擇
處理 [Content_Types].xml 和 document.xml.rels 時(shí),需要匹配完整的 XML 元素標(biāo)簽。注意:
# ? 錯(cuò)誤:[^/]* 會(huì)在 ContentType 屬性值中的 / 處提前停止 r'<Override[^>]*PartName="..."[^/]*/>' # ? 正確:[^>]* 只排除 >,允許屬性值中包含 / r'<Override[^>]*PartName="..."[^>]*/>'
ContentType="application/vnd.openxmlformats-officedocument.customXmlProperties+xml" 中包含 /,必須使用 [^>]* 才能正確匹配到 />。
只清理 WPS 私有項(xiàng),保留其他 customXml
文檔中可能存在合法的 customXml 數(shù)據(jù)(如 APA 書目數(shù)據(jù)),清理時(shí)通過檢測(cè) wps.cn 域名精確定位,只刪除 WPS 私有項(xiàng),不影響其他數(shù)據(jù)。
六、驗(yàn)證方法
用 Python 對(duì)清理前后的文件進(jìn)行驗(yàn)證:
import zipfile
def verify_clean(docx_path):
with zipfile.ZipFile(docx_path, 'r') as z:
file_list = z.namelist()
# 1. 檢查是否還有 WPS schema 引用
for fname in file_list:
if fname.startswith('customXml/itemProps') and fname.endswith('.xml'):
with z.open(fname) as f:
content = f.read().decode('utf-8', errors='replace')
if 'wps.cn' in content or 'wpsCustomData' in content:
print(f"? 仍有 WPS schema: {fname}")
return False
# 2. 檢查 document.xml.rels
with z.open('word/_rels/document.xml.rels') as f:
rels = f.read().decode('utf-8')
if 'wpsCustomData' in rels or ('customXml' in rels and 'wps.cn' in rels):
print("? document.xml.rels 中仍有 WPS 引用")
return False
print("? 清理驗(yàn)證通過,無 WPS 私有數(shù)據(jù)殘留")
return True
七、總結(jié)
| 項(xiàng)目 | 內(nèi)容 |
|---|---|
| 問題根因 | WPS 創(chuàng)建的模板內(nèi)嵌私有 wpsCustomData schema,Microsoft Word 無法識(shí)別 |
| 觸發(fā)條件 | 用 python-docx 加載 WPS 模板并保存,私有數(shù)據(jù)被保留到輸出文件 |
| 修復(fù)方式 | doc.save() 后對(duì) ZIP 包進(jìn)行后處理,刪除 WPS 私有文件并清理引用 |
| 清理范圍 | customXml/item{n}.xml、customXml/itemProps{n}.xml、customXml/_rels/item{n}.xml.rels、[Content_Types].xml 中的 Override 聲明、word/_rels/document.xml.rels 中的 Relationship 條目 |
| 副作用 | 無,不影響文檔內(nèi)容和其他合法 customXml 數(shù)據(jù) |
適用場(chǎng)景:所有使用 python-docx 基于 WPS 創(chuàng)建的模板生成 Word 文檔的場(chǎng)景,均可能遇到此問題,使用本文的清理方案可徹底解決。
以上就是使用python-docx生成的Word文檔打開時(shí)彈出“無法讀取內(nèi)容“警告的解決方案的詳細(xì)內(nèi)容,更多關(guān)于python-docx生成的Word無法讀取內(nèi)容的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python 按字典dict的鍵排序,并取出相應(yīng)的鍵值放于list中的實(shí)例
今天小編就為大家分享一篇Python 按字典dict的鍵排序,并取出相應(yīng)的鍵值放于list中的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-02-02
使用celery執(zhí)行Django串行異步任務(wù)的方法步驟
這篇文章主要介紹了使用celery執(zhí)行Django串行異步任務(wù),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Django具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06
Python導(dǎo)入其他路徑下的文件報(bào)錯(cuò)的問題及解決
文章主要討論了Python模塊導(dǎo)入的路徑問題,并提供了幾種解決方案,包括使用絕對(duì)路徑和修改sys.path等,通過這些方法,可以解決在導(dǎo)入模塊時(shí)出現(xiàn)的ModuleNotFoundError問題,特別是在使用打包工具如pyinstaller時(shí)2026-01-01
Python面向?qū)ο笾鄳B(tài)原理與用法案例分析
這篇文章主要介紹了Python面向?qū)ο笾鄳B(tài)原理與用法,結(jié)合具體案例形式分析了Python多態(tài)的具體功能、原理、使用方法與操作注意事項(xiàng),需要的朋友可以參考下2019-12-12
Python3使用tesserocr識(shí)別字母數(shù)字驗(yàn)證碼的實(shí)現(xiàn)
這篇文章主要介紹了Python3使用tesserocr識(shí)別字母數(shù)字驗(yàn)證碼的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
Appium+python+unittest搭建UI自動(dòng)化框架的實(shí)現(xiàn)
本文主要介紹了Appium+python+unittest搭建UI自動(dòng)化框架的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-03-03
Python基于opencv實(shí)現(xiàn)的人臉識(shí)別(適合初學(xué)者)
OpenCV是一個(gè)基于BSD許可開源發(fā)行的跨平臺(tái)計(jì)算機(jī)視覺庫,下面這篇文章主要給大家介紹了關(guān)于Python基于opencv實(shí)現(xiàn)的人臉識(shí)別,文中通過實(shí)例代碼介紹的非常詳細(xì),本文的教程非常適合初學(xué)者,需要的朋友可以參考下2022-03-03

