【Python】導(dǎo)出docx格式Word文檔中的文本、圖片和附件等
零、需求
為批量批改學(xué)生在機(jī)房提交的實(shí)驗(yàn)報(bào)告,我需要對所有的實(shí)驗(yàn)文檔內(nèi)容進(jìn)行處理。需要批量提取Word文檔中的圖片和附件以便進(jìn)一步檢查。如何提???我想到了用起來比較方便的Python,經(jīng)過試驗(yàn),方案可行,故此記錄。學(xué)生的作業(yè)主要是docx或者doc文檔,學(xué)生把項(xiàng)目打成壓縮包后以文件附件的形式放到文檔中,另外附上項(xiàng)目運(yùn)行截圖以方便批閱檢查。我教的這門課有5個(gè)班,179人,總共兩千多份作業(yè)。
╮(╯▽╰)╭
壹、軟件
最終我使用Python的tkinter構(gòu)建了一個(gè)圖形界面的小工具(鏈接在末尾),能導(dǎo)出docx文檔的相關(guān)內(nèi)容,主界面如下:
界面左右分欄,左邊是待導(dǎo)出的docx文件列表,右邊是一些導(dǎo)出選項(xiàng)。配置好之后點(diǎn)擊“導(dǎo)出”按鈕開始導(dǎo)出相關(guān)內(nèi)容,導(dǎo)出界面如下:
導(dǎo)出成功后可以保存一份json格式的導(dǎo)出報(bào)告,這份報(bào)告中記錄了導(dǎo)出的文件和原始文件的關(guān)系,導(dǎo)出報(bào)告界面如下:
功能就這樣了,簡簡單單,剩下的識(shí)別作業(yè),自動(dòng)批改作業(yè)的代碼還沒整理,拿不出手,哈哈(>__<)。
貳、方案
下面介紹軟件細(xì)節(jié)相關(guān)內(nèi)容和部分探索的過程:
使用Python處理Word文檔,經(jīng)過搜索,發(fā)現(xiàn)有如下幾個(gè)主流方案:
- python-docx:python-docx是一個(gè)用于創(chuàng)建和更新Word(.docx)文件的python庫,目前只支持docx。
- pywin32:能處理doc和docx文檔,但是只能在Windows平臺(tái)上用,而且使用的時(shí)候需要電腦有安裝Office或者WPS。
- python-docxtpl:使用Word文件模板生成新的Word文檔,這個(gè)好像跟主題無關(guān),但是感覺水文檔啥的很有用,故寫一下。
經(jīng)過考慮,確認(rèn)使用python-docx,不需要另外安裝Office和WPS就能獨(dú)立處理Word文檔,支持跨平臺(tái)。至于有學(xué)生提交doc文檔?拒收?。ㄎ医o他們的是docx文檔,應(yīng)該不會(huì)故意轉(zhuǎn)成doc提交的,實(shí)在不行到時(shí)候再寫個(gè)插件用pywin32把doc轉(zhuǎn)成docx再處理吧~ QWQ)
另外,還需要用到python-oletools這個(gè)庫,配合python-docx可以用來導(dǎo)出嵌入的附件。
叁、實(shí)現(xiàn)
1. 要處理的Word文檔
要處理的Word文檔大致如下圖所示:
(o゜▽゜)o☆
在Word文檔中插入了一段文字、一張圖片和一個(gè)作為附件的壓縮包,附件是以“文件附件”的形式嵌入到文檔中的(即打包到了文檔中,換臺(tái)電腦也能正常打開),這樣可以模擬學(xué)生提交的實(shí)驗(yàn)報(bào)告文檔的大致情況。
2. Word文檔的結(jié)構(gòu)
經(jīng)過簡單的嘗試,發(fā)現(xiàn)docx是一個(gè)壓縮包,可以直接用7z打開:
(?ˉ?ˉ?)
于是將其解包,得到如下目錄:
Microsoft Windows [版本 10.0.22621.1702]
(c) Microsoft Corporation。保留所有權(quán)利。
E:\Project\pythonProject\pyHomeWorkTool\test\docx\word>tree /f
卷 卷下落花隨流水 的文件夾 PATH 列表
卷序列號(hào)為 EA73-5C63
E:.
│ [Content_Types].xml
│
├─docProps
│ app.xml
│ core.xml
│ custom.xml
│
├─word
│ │ document.xml
│ │ fontTable.xml
│ │ settings.xml
│ │ styles.xml
│ │
│ ├─embeddings
│ │ oleObject1.bin
│ │
│ ├─media
│ │ image1.png
│ │ image2.emf
│ │
│ ├─theme
│ │ theme1.xml
│ │
│ └─_rels
│ document.xml.rels
│
└─_rels
.rels
E:\Project\pythonProject\pyHomeWorkTool\test\docx\word>
簡單分析,我們發(fā)現(xiàn)/word/media/image1.png
就是剛剛插入的圖片,再編輯文檔再插入,發(fā)現(xiàn)也還是放到這個(gè)文件夾中的。基本可以確定以后Word中的所有圖片都可以到這里解壓出來。
另外我們發(fā)現(xiàn)/word/embeddings/oleObject1.bin
文件能用7z打開,也是壓縮格式,里面的[1]Ole10Native
應(yīng)該是剛剛放入的壓縮文件,至少能得到壓縮包里的文件,但是附件的原文件名就不知道在哪拿了。經(jīng)過上網(wǎng)搜索,發(fā)現(xiàn)這是一種OLE文件,doc文檔也是這種格式的文件,恰好Python有個(gè)叫python-oletools的庫可以把嵌入的文件從ole文件中轉(zhuǎn)存出來。
同時(shí)我們用WinHex查看/word/embeddings/oleObject1.bin中的[1]Ole10Native
,可以發(fā)現(xiàn)前面的這一些數(shù)據(jù)剛好是文件名。因?yàn)槲覀儾迦氲氖菈嚎s文檔,所以不太好分析原始文檔的數(shù)據(jù)范圍,不過用7z打開能直接看到原始壓縮文檔的目錄,說明并沒有對插入文檔進(jìn)行再一步的封裝,很可能只是加了點(diǎn)頭和尾啥的。我們把插入的文檔從壓縮文檔換成文本文檔應(yīng)該就更好分析了,這邊不另外截圖了,大致分析過程是一樣的。這樣我們可以發(fā)現(xiàn)[1]Ole10Native
只是在原文檔的基礎(chǔ)上在文件頭和文件尾添加了一些文件名、原始路徑和緩存路徑等,應(yīng)該是有固定格式的。我們待會(huì)兒應(yīng)該是可以把這些內(nèi)容提取出來的。其中的路徑可以作為判斷學(xué)生作業(yè)其他的依據(jù)。
( ̄︶ ̄)↗
除了上面兩個(gè)文檔,我還發(fā)現(xiàn)/word/_rels/document.xml.rels
中保存了一份文件清單,這份文件清單有我們嵌入的壓縮文檔(Id=rId5)和插入的圖片(Id=rId4),看了下rId6指向的/word/media/image2.emf
,這個(gè)是我們嵌入的附件圖標(biāo)圖片。
為啥這里強(qiáng)調(diào)它們的ID,一個(gè)原因是這里的ID是除了路徑外的另外一個(gè)文件識(shí)別標(biāo)記,另一個(gè)原因是我在另外一個(gè)文檔(/word/document.xml
)中發(fā)現(xiàn)了它們:
這個(gè)文檔看起來是一整個(gè)Word文檔的核心,Word上的所有內(nèi)容應(yīng)該都可以在這個(gè)上面找到所對應(yīng)的地方,例如我圖中的用紅色筆圈起的部分對應(yīng)著Word中的可見文字,用橙色筆圈起的部分對應(yīng)著Word中的圖片,用粉色筆圈起的部分對應(yīng)著Word中的附件(OLE)。最重要的是用紫色筆圈起的部分的ID,這部分與/word/_rels/document.xml.rels
中的ID對應(yīng),這樣我們把這兩份文檔關(guān)聯(lián)起來就能找到與文檔中插入部分對應(yīng)的文件了!
另外的一些xml中還發(fā)現(xiàn)了一些另外的有趣屬性,這些又可以作為判斷學(xué)生作業(yè)的其他依據(jù):
(~ ̄▽ ̄)~
3. 實(shí)現(xiàn)目標(biāo)
了解了docx文檔的大致結(jié)構(gòu)后,我們可以開始嘗試使用python-docx去獲取上面的一些數(shù)據(jù)了。大致需要實(shí)現(xiàn)如下功能:
- 所有文字轉(zhuǎn)存為文本文件。
- 所有圖片轉(zhuǎn)存為單獨(dú)的圖片文件,具體的格式為Word能插入的圖片格式。
- 所有插入的附件轉(zhuǎn)存為原始文檔,并嘗試恢復(fù)原文件名(圖片應(yīng)該也有原文件名的,應(yīng)該在
/word/document.xml
中,但是沒必要,因?yàn)楹芏嗤瑢W(xué)是截圖插入的,沒有保存成文件。文檔的原文件名可以幫助判斷這個(gè)附件是干啥的,比如sql文檔和txt文檔都是文本文檔,存儲(chǔ)方式一樣,只是文件名不一樣,如果有文件名我可以快速初步判斷這個(gè)是不是sql文檔)。
4. 使用python-docx打開Word文檔
導(dǎo)入python-docx:
import docx
python-docx使用起來非常簡單,用以下語句就可以打開word文檔了:
# 文件路徑
docx_file = r"E:\Project\pythonProject\pyHomeWorkTool\test\docx\word.docx"
# 打開docx文檔
docx_document = docx.Document(docx_file)
5. 提取文本
使用PyCharm的代碼調(diào)試工具,可以對docx_document
進(jìn)行簡單的分析:
可以發(fā)現(xiàn)文檔的大部分文本在paragraphs對象中,這是一個(gè)列表,我們待會(huì)兒可以循環(huán)獲取其text值,拼接起來導(dǎo)出即可。代碼表示如下:
all_text = ''
for paragraph in docx_document.paragraphs:
all_text += paragraph.text
print('所有文本:', all_text)
對測試文檔的運(yùn)行結(jié)果如下:
D:\ProgramData\Anaconda3\python.exe E:/Project/pythonProject/pyHomeWorkTool/unpack.py
打開文檔完成
所有文本: 1文字:這是一段文字。翩若驚鴻,婉若游龍。榮曜秋菊,華茂春松。髣髴兮若輕云之蔽月,飄飖兮若流風(fēng)之回雪。遠(yuǎn)而望之,皎若太陽升朝霞;迫而察之,灼若芙蕖出淥波。01234567892圖片:3附件:
進(jìn)程已結(jié)束,退出代碼為 0
目前看就還好,文字都在,但是還不知道是不是全部文檔,因?yàn)槲臋n中還有表格、文本框等其他特殊一點(diǎn)的文本。重新修改文檔,插入表格,發(fā)現(xiàn)插入的表格無法導(dǎo)出。故再次分析,發(fā)現(xiàn)表格也是在docx_document
對象下的:
于是我們再導(dǎo)出表格:
all_table_text = ''
for table in docx_document.tables:
for cell in getattr(table, '_cells'):
all_table_text += cell.text + ' ' # 單元格之間用空格隔開
print('所有表格文本:', all_table_text)
運(yùn)行結(jié)果:
D:\ProgramData\Anaconda3\python.exe E:/Project/pythonProject/pyHomeWorkTool/unpack.py
打開文檔完成
所有文本: 1文字:這是一段文字。翩若驚鴻,婉若游龍。榮曜秋菊,華茂春松。髣髴兮若輕云之蔽月,飄飖兮若流風(fēng)之回雪。遠(yuǎn)而望之,皎若太陽升朝霞;迫而察之,灼若芙蕖出淥波。01234567892圖片:3附件:
所有表格文本: 姓名 班級 科目 分?jǐn)?shù) 小明 2 Java 97 小紅 2 C 78
進(jìn)程已結(jié)束,退出代碼為 0
看起來不太理想,要想在文本文檔中重建表格的話還得寫更多判斷的代碼。不過這個(gè)滿足我的需求了,我的實(shí)驗(yàn)文檔中有個(gè)表格是填姓名的,提取出后可以根據(jù)這個(gè)判斷是誰的實(shí)驗(yàn)文檔(其實(shí)從文件名也可以判斷)。
另外的文本框還暫時(shí)不知道怎么通過docx_document
對象提取,不過既然有/word/document.xml
文件,大不了直接解析xml了。
(# ̄~ ̄#)
6. 提取圖片和OLE嵌入對象
我們前面找到了圖片的和OLE附件的文件清單(/word/_rels/document.xml.rels
)、對應(yīng)的文件和/word/document.xml
文件,以及它們之間的關(guān)系。同樣的,我們對docx_document
對象分析:
我們可以發(fā)現(xiàn),在docx_document
對象下的part
對象下的related_parts
字典中有很多鍵為“rId**”的對象,而這些對象的鍵和路徑(partname
)剛剛好與我們剛剛所看的文件清單(/word/_rels/document.xml.rels
)和/word/document.xml
文件中的一致。由此我們可以大致確定這些對象就是指向哪些文件的對象。然后發(fā)現(xiàn)對象中有個(gè)blob
字節(jié)數(shù)組,可以猜測這就是對應(yīng)的文件數(shù)據(jù),我們嘗試把這些導(dǎo)出來看看能不能打開,代碼如下:
import os
# 文件導(dǎo)出列表
export_files = []
# 遍歷所有附件
index = 0
docx_related_parts = docx_document.part.related_parts
for part in docx_related_parts:
part = docx_related_parts[part]
part_name = str(part.partname) # 附件路徑(partname)
if part_name.startswith('/word/media/') or part_name.startswith('/word/embeddings/'): # 只導(dǎo)出這兩個(gè)目錄下的
# 構(gòu)建導(dǎo)出路徑
index += 1
save_dir = os.path.dirname(os.path.abspath(__file__)) # 獲取當(dāng)前py腳本路徑
index_str = str(index).rjust(2, '0')
save_path = save_dir + '\\' + index_str + ' - ' + os.path.basename(part.partname) # 拼接路徑
print('導(dǎo)出路徑:', save_path)
# 寫入文件
with open(save_path, 'wb') as f:
f.write(part.blob)
# 記錄文件
export_files.append(save_path)
print('導(dǎo)出的所有文件:', export_files)
輸出結(jié)果:
D:\ProgramData\Anaconda3\python.exe E:/Project/pythonProject/pyHomeWorkTool/unpack.py
打開文檔完成
所有文本: 1文字:這是一段文字。翩若驚鴻,婉若游龍。榮曜秋菊,華茂春松。髣髴兮若輕云之蔽月,飄飖兮若流風(fēng)之回雪。遠(yuǎn)而望之,皎若太陽升朝霞;迫而察之,灼若芙蕖出淥波。01234567892圖片:3附件:
所有表格文本: 姓名 班級 科目 分?jǐn)?shù) 小明 2 Java 97 小紅 2 C 78
導(dǎo)出路徑: E:\Project\pythonProject\pyHomeWorkTool\01 - image2.emf
導(dǎo)出路徑: E:\Project\pythonProject\pyHomeWorkTool\02 - oleObject1.bin
導(dǎo)出路徑: E:\Project\pythonProject\pyHomeWorkTool\03 - image1.png
導(dǎo)出的所有文件: ['E:\\Project\\pythonProject\\pyHomeWorkTool\\01 - image2.emf', 'E:\\Project\\pythonProject\\pyHomeWorkTool\\02 - oleObject1.bin', 'E:\\Project\\pythonProject\\pyHomeWorkTool\\03 - image1.png']
進(jìn)程已結(jié)束,退出代碼為 0
還有三個(gè)文件:
成功!
(??????) ?
嘗試修改文件,插入更多的圖片、附件,依然能導(dǎo)出。這樣我們的圖片和附件就導(dǎo)出了,那么還有最后一個(gè)問題,就是怎么把導(dǎo)出的OLE文件還原為原來的附件?
7. 從OLE嵌入對象中提取原始附件
最后一個(gè)問題,我們需要把OLE文件中的原始文件提取出來,前面進(jìn)行過簡單的分析,如果不借助第三方工具則可以自己使用zip壓縮模塊把文件提取出來,然后把其在文件頭尾添加的原始文件名、原始路徑和緩存路徑等刪除即可。這樣的話我們得分析其的組成結(jié)構(gòu),比較麻煩。不過恰好我們在找資料的時(shí)候發(fā)現(xiàn)了一個(gè)Python庫:python-oletools,看簡介它是用來對惡意的OLE文件進(jìn)行分析的,因?yàn)橹暗腤ord文檔是doc格式的嘛,然后doc格式本質(zhì)就是一個(gè)OLE文件,文檔中可以插入一些惡意的文件,因此需要有對應(yīng)的掃描工具。這樣它自然而然的就應(yīng)該有提取OLE文件中的原始文件的能力。簡單閱讀其文檔發(fā)現(xiàn)我們可以利用它的oleobj.py腳本對OLE文件進(jìn)行提?。?br> 直接到GitHub上下載好python-oletools項(xiàng)目的源碼,同時(shí)它依賴另外一個(gè)項(xiàng)目“olefile”,也下載下來,我這邊使用的分別是0.60.1和0.46的版本。根據(jù)其項(xiàng)目文檔,我們下載好項(xiàng)目后可以直接使用命令行的方式使用它(如果提示找不到模塊,要把olefile放到oletools中),例如,我們要提取剛剛從docx中導(dǎo)出的那個(gè)OLE附件包:
Microsoft Windows [版本 10.0.22621.1702]
(c) Microsoft Corporation。保留所有權(quán)利。
C:\Users\y17mm>D:\ProgramData\Anaconda3\python.exe D:\y17mm\Downloads\oletools\oleobj.py "E:\Project\pythonProject\pyHomeWorkTool\02 - oleObject1.bin"
D:\y17mm\Downloads\oletools\oleobj.py:581: SyntaxWarning: "is" with a literal. Did you mean "=="?
if idx is -1:
oleobj 0.60.1 - http://decalage.info/oletools
THIS IS WORK IN PROGRESS - Check updates regularly!
Please report any issue at https://github.com/decalage2/oletools/issues
-------------------------------------------------------------------------------
File: 'E:\\Project\\pythonProject\\pyHomeWorkTool\\02 - oleObject1.bin'
extract file embedded in OLE object from stream '\x01Ole10Native':
Parsing OLE Package
Filename = "êú??????.rar"
Source path = "D:\y17mm\Desktop\zuoye\êú??????.rar"
Temp path = "C:\Users\y17mm\AppData\Local\Temp\êú??????.rar"
saving to file E:\Project\pythonProject\pyHomeWorkTool\02 - oleObject1.bin_________.rar
C:\Users\y17mm>
導(dǎo)出結(jié)果:
導(dǎo)出是導(dǎo)出了,但是好像不支持中文,用的應(yīng)該不是GBK的解碼方式。如果對這個(gè)不在意的話可以直接把下載下來的項(xiàng)目放到項(xiàng)目里,使用下面的代碼就可以實(shí)現(xiàn)對OLE文件的自動(dòng)提取了(如果提示找不到模塊,olefile和oletoos應(yīng)該并排放在項(xiàng)目中,或者直接使用pip安裝兩個(gè)庫也可以):
from oletools import oleobj
for file in export_files:
if file.endswith('.bin') and 'ole' in file.lower(): # .bin 作為后綴且文件名中有ole,則被認(rèn)為是OLE文件
res = oleobj.main([file])
if res == 1: # 1為成功提取
os.remove(file) # 刪除OLE文件,僅保留原始附件
else:
print(file, '提取OLE失敗')
執(zhí)行結(jié)果:
D:\ProgramData\Anaconda3\python.exe E:/Project/pythonProject/pyHomeWorkTool/unpack.py
打開文檔完成
所有文本: 1文字:這是一段文字。翩若驚鴻,婉若游龍。榮曜秋菊,華茂春松。髣髴兮若輕云之蔽月,飄飖兮若流風(fēng)之回雪。遠(yuǎn)而望之,皎若太陽升朝霞;迫而察之,灼若芙蕖出淥波。01234567892圖片:3附件:
所有表格文本: 姓名 班級 科目 分?jǐn)?shù) 小明 2 Java 97 小紅 2 C 78
導(dǎo)出路徑: E:\Project\pythonProject\pyHomeWorkTool\01 - image2.emf
導(dǎo)出路徑: E:\Project\pythonProject\pyHomeWorkTool\02 - oleObject1.bin
導(dǎo)出路徑: E:\Project\pythonProject\pyHomeWorkTool\03 - image1.png
導(dǎo)出的所有文件: ['E:\\Project\\pythonProject\\pyHomeWorkTool\\01 - image2.emf', 'E:\\Project\\pythonProject\\pyHomeWorkTool\\02 - oleObject1.bin', 'E:\\Project\\pythonProject\\pyHomeWorkTool\\03 - image1.png']
E:\Project\pythonProject\pyHomeWorkTool\oletools\oleobj.py:581: SyntaxWarning: "is" with a literal. Did you mean "=="?
if idx is -1:
oleobj 0.60.1 - http://decalage.info/oletools
THIS IS WORK IN PROGRESS - Check updates regularly!
Please report any issue at https://github.com/decalage2/oletools/issues
-------------------------------------------------------------------------------
File: 'E:\\Project\\pythonProject\\pyHomeWorkTool\\02 - oleObject1.bin'
extract file embedded in OLE object from stream '\x01Ole10Native':
Parsing OLE Package
Filename = "êú??????.rar"
Source path = "D:\y17mm\Desktop\zuoye\êú??????.rar"
Temp path = "C:\Users\y17mm\AppData\Local\Temp\êú??????.rar"
saving to file E:\Project\pythonProject\pyHomeWorkTool\02 - oleObject1.bin_________.rar
進(jìn)程已結(jié)束,退出代碼為 0
導(dǎo)出的內(nèi)容跟剛剛使用命令行導(dǎo)出的內(nèi)容一致。
這樣是能正常導(dǎo)出拿到所有原文件了,但是有兩個(gè)比較嚴(yán)重的問題:
- 文件名不支持中文
- 我們無法得知它導(dǎo)出的是哪個(gè)文件(現(xiàn)在單個(gè)一眼能看出,但是當(dāng)有幾千份就很難了)
(⊙?⊙)
同時(shí),我認(rèn)為它還有個(gè)問題,就是我們要更頻繁地讀寫磁盤,OLE文件需要前面的腳本寫入到文件后再由后的oleobj讀取,能不能直接把文件字節(jié)數(shù)組傳入進(jìn)行提取,這樣只需要寫入原始附件即可,不用寫OLE也不用刪OLE,能減少磁盤讀寫,延長磁盤壽命。
8. 從OLE嵌入對象中提取原始附件(改進(jìn)提取方式:支持中文)
我們之前是調(diào)用oleobj的main函數(shù)提取的,這樣有很多弊端,所以我們準(zhǔn)備重寫ole的提取方法,這樣我們就得看oleobj的源碼了,難度略高。經(jīng)過對原代碼的跟蹤調(diào)試,發(fā)現(xiàn)oleobj中的主要的提取流程是oleobj.process_file
和oleobj.find_ole
這兩個(gè)函數(shù),而我們剛剛調(diào)用的oleobj.main
則主要負(fù)責(zé)解析命令行參數(shù)。因此,仿照oleobj.process_file
,對其進(jìn)行簡單的修改(主要是不支持中文的問題,見re_decode
函數(shù)),得到如下處理流程:
from oletools import oleobj
def re_decode(s, encoding='gbk'):
"""
重新解碼,解決oleobj對中文亂碼的問題
:param s: 原始字符串
:param encoding: 新的解碼編碼
:return: 新的字符串
"""
i81 = s.encode('iso-8859-1')
return i81.decode(encoding)
for file_path in export_files: # 遍歷導(dǎo)出的文件
# 不符合 .bin 作為后綴且文件名中有ole,則不被認(rèn)為是OLE文件,跳過
if not (file_path.endswith('.bin') and 'ole' in file_path.lower()):
continue
# 準(zhǔn)備導(dǎo)出 OLE 文件
has_error = False
export_ole_files = []
for ole in oleobj.find_ole(file_path, None): # 找OLE文件(oleobj支持對壓縮包里的OLE處理)
if ole is None: # 沒有找到 OLE 文件,跳過
continue
for path_parts in ole.listdir(): # 遍歷OLE中的文件
# 判斷是不是[1]Ole10Native,使用列表推導(dǎo)式忽略大小寫,不是的話就不要繼續(xù)了
if '\x01ole10native'.casefold() not in [path_part.casefold() for path_part in path_parts]:
continue
stream = None
try:
# 使用 Ole File 打開 OLE 文件
stream = ole.openstream(path_parts)
opkg = oleobj.OleNativeStream(stream)
except IOError:
print('不是OLE文件:', path_parts)
if stream is not None: # 關(guān)閉文件流
stream.close()
continue
# 打印信息
if opkg.is_link:
print('是鏈接而不是文件,跳過')
continue
ole_filename = re_decode(opkg.filename)
ole_src_path = re_decode(opkg.src_path)
ole_temp_path = re_decode(opkg.temp_path)
print('文件名:{0},原路徑:{1},緩存路徑:{2}'.format(ole_filename, ole_src_path, ole_temp_path))
# 生成新的文件名(這部分與上一部分導(dǎo)出docx的文件的構(gòu)建路徑類似,可以封裝為函數(shù))
index += 1
save_dir = os.path.dirname(os.path.abspath(__file__)) # 獲取當(dāng)前py腳本路徑
index_str = str(index).rjust(2, '0')
filename = save_dir + '\\' + index_str + ' - ' + ole_filename # 拼接路徑
print('導(dǎo)出路徑:', filename)
# 轉(zhuǎn)存
try:
print('導(dǎo)出OLE中的文件:', filename)
with open(filename, 'wb') as writer:
n_dumped = 0
next_size = min(oleobj.DUMP_CHUNK_SIZE, opkg.actual_size)
while next_size:
data = stream.read(next_size)
writer.write(data)
n_dumped += len(data)
if len(data) != next_size:
print('想要讀取 {0}, 實(shí)際取得 {1}'.format(next_size, len(data)))
break
next_size = min(oleobj.DUMP_CHUNK_SIZE, opkg.actual_size - n_dumped)
export_ole_files.append(filename)
except Exception as exc:
has_error = True
print('在轉(zhuǎn)存時(shí)出現(xiàn)錯(cuò)誤:{0} {1}'.format(filename, exc))
finally:
stream.close()
if export_ole_files: # 如果有解出ole包
export_files.remove(file_path)
export_files += export_ole_files
# 刪除 bin 文件
if not has_error:
os.remove(file_path)
print('已刪除OLE打包文件,僅保留原文件',file_path)
print('最終導(dǎo)出文件:', export_files)
替換掉上一段使用oleobj.main
導(dǎo)出的代碼,運(yùn)行,得到如下結(jié)果:
D:\ProgramData\Anaconda3\python.exe E:/Project/pythonProject/pyHomeWorkTool/unpack.py
打開文檔完成
所有文本: 1文字:這是一段文字。翩若驚鴻,婉若游龍。榮曜秋菊,華茂春松。髣髴兮若輕云之蔽月,飄飖兮若流風(fēng)之回雪。遠(yuǎn)而望之,皎若太陽升朝霞;迫而察之,灼若芙蕖出淥波。01234567892圖片:3附件:
所有表格文本: 姓名 班級 科目 分?jǐn)?shù) 小明 2 Java 97 小紅 2 C 78
導(dǎo)出路徑: E:\Project\pythonProject\pyHomeWorkTool\01 - image2.emf
導(dǎo)出路徑: E:\Project\pythonProject\pyHomeWorkTool\02 - oleObject1.bin
導(dǎo)出路徑: E:\Project\pythonProject\pyHomeWorkTool\03 - image1.png
導(dǎo)出的所有文件: ['E:\\Project\\pythonProject\\pyHomeWorkTool\\01 - image2.emf', 'E:\\Project\\pythonProject\\pyHomeWorkTool\\02 - oleObject1.bin', 'E:\\Project\\pythonProject\\pyHomeWorkTool\\03 - image1.png']
文件名:授課計(jì)劃.rar,原路徑:D:\y17mm\Desktop\zuoye\授課計(jì)劃.rar,緩存路徑:C:\Users\y17mm\AppData\Local\Temp\授課計(jì)劃.rar
導(dǎo)出路徑: E:\Project\pythonProject\pyHomeWorkTool\04 - 授課計(jì)劃.rar
導(dǎo)出OLE中的文件: E:\Project\pythonProject\pyHomeWorkTool\04 - 授課計(jì)劃.rar
已刪除OLE打包文件,僅保留原文件 E:\Project\pythonProject\pyHomeWorkTool\02 - oleObject1.bin
最終導(dǎo)出文件: ['E:\\Project\\pythonProject\\pyHomeWorkTool\\01 - image2.emf', 'E:\\Project\\pythonProject\\pyHomeWorkTool\\03 - image1.png', 'E:\\Project\\pythonProject\\pyHomeWorkTool\\04 - 授課計(jì)劃.rar']
進(jìn)程已結(jié)束,退出代碼為 0
和這些文件:
中文亂碼的問題已被解決!而且我們也在export_files
中能看到所有的導(dǎo)出的文件了,至此滿足了大部分的需求。
ヾ(≧▽≦*)o
9. 從OLE嵌入對象中提取原始附件(改進(jìn)提取方式:避免多次寫入磁盤)
其實(shí)這部分在看oleobj.py
的源碼的時(shí)候就發(fā)現(xiàn)oleobj.py
的作者也提供了類似的方法,oleobj.process_file
和oleobj.find_ole
這兩個(gè)函數(shù)都支持傳入data(字節(jié)數(shù)組),我們嘗試把導(dǎo)出docx附件和導(dǎo)出ole文件內(nèi)容的代碼融合,看是否能直接把文件字節(jié)數(shù)組傳遞給后者以直接導(dǎo)出ole文件內(nèi)容,我們可以寫出如下代碼來替換上面導(dǎo)出docx和導(dǎo)出ole的部分:
import os
from oletools import oleobj
def get_new_path(i: int, file: str, out_dir: str = __file__):
"""
根據(jù)序號(hào)和文件名拿到新的路徑名,
默認(rèn)輸出目錄為當(dāng)前文件所在文件夾
:param i: 序號(hào)
:param file: 原始文件名(路徑)
:param out_dir: 輸出目錄,默認(rèn)為當(dāng)前文件所在文件夾
:return: 新文件名(格式為“序號(hào) - 原文件名”)
"""
self_dir = os.path.dirname(os.path.abspath(out_dir)) # 獲取導(dǎo)出路徑
i = str(i).rjust(2, '0') # 不夠前面添0
return self_dir + '\\' + i + ' - ' + os.path.basename(file) # 拼接路徑
def re_decode(s: str, encoding: str = 'gbk'):
"""
重新解碼,解決oleobj對中文亂碼的問題
:param s: 原始字符串
:param encoding: 新的解碼編碼,默認(rèn)為 GBK
:return: 新的字符串
"""
i81 = s.encode('iso-8859-1')
return i81.decode(encoding)
# 文件導(dǎo)出列表
export_files = []
# 遍歷所有附件
docx_related_parts = docx_document.part.related_parts
for part in docx_related_parts:
part = docx_related_parts[part]
part_name = str(part.partname) # 附件路徑(partname)
if part_name.startswith('/word/media/') or part_name.startswith('/word/embeddings/'): # 只導(dǎo)出這兩個(gè)目錄下的
# 構(gòu)建導(dǎo)出路徑
save_path = get_new_path(len(export_files) + 1, part.partname)
# ole 文件判斷
# 不符合 .bin 作為后綴且文件名中有ole,則不被認(rèn)為是OLE文件
if not (save_path.endswith('.bin') and 'ole' in save_path.lower()):
# 直接寫入文件
print('DOCX 導(dǎo)出路徑:', save_path)
with open(save_path, 'wb') as f:
f.write(part.blob)
export_files.append(save_path) # 記錄文件
else:
# 將字節(jié)數(shù)組傳遞給oleobj處理
for ole in oleobj.find_ole(save_path, part.blob):
if ole is None: # 沒有找到 OLE 文件,跳過
continue
for path_parts in ole.listdir(): # 遍歷OLE中的文件
# 判斷是不是[1]Ole10Native,使用列表推導(dǎo)式忽略大小寫,不是的話就不要繼續(xù)了
if '\x01ole10native'.casefold() not in [path_part.casefold() for path_part in path_parts]:
continue
stream = None
try:
# 使用 Ole File 打開 OLE 文件
stream = ole.openstream(path_parts)
opkg = oleobj.OleNativeStream(stream)
except IOError:
print('非OLE文件:', path_parts)
if stream is not None: # 關(guān)閉文件流
stream.close()
continue
# 打印信息
if opkg.is_link:
print('是鏈接而不是文件,跳過')
continue
ole_filename = re_decode(opkg.filename)
ole_src_path = re_decode(opkg.src_path)
ole_temp_path = re_decode(opkg.temp_path)
print('文件名:{0},原路徑:{1},緩存路徑:{2}'.format(ole_filename, ole_src_path, ole_temp_path))
# 生成新的文件名
filename = get_new_path(len(export_files) + 1, ole_filename)
print('OLE 導(dǎo)出路徑:', filename)
# 轉(zhuǎn)存
try:
print('導(dǎo)出OLE中的文件:', filename)
with open(filename, 'wb') as writer:
n_dumped = 0
next_size = min(oleobj.DUMP_CHUNK_SIZE, opkg.actual_size)
while next_size:
data = stream.read(next_size)
writer.write(data)
n_dumped += len(data)
if len(data) != next_size:
print('預(yù)計(jì)讀取 {0}, 實(shí)際取得 {1}'.format(next_size, len(data)))
break
next_size = min(oleobj.DUMP_CHUNK_SIZE, opkg.actual_size - n_dumped)
export_files.append(filename) # 記錄導(dǎo)出的文件
except Exception as exc:
print('在轉(zhuǎn)存時(shí)出現(xiàn)錯(cuò)誤:{0} {1}'.format(filename, exc))
finally:
stream.close()
print('導(dǎo)出的所有文件:', export_files)
運(yùn)行,得到如下結(jié)果:
D:\ProgramData\Anaconda3\python.exe E:/Project/pythonProject/pyHomeWorkTool/unpack.py
打開文檔完成
所有文本: 1文字:這是一段文字。翩若驚鴻,婉若游龍。榮曜秋菊,華茂春松。髣髴兮若輕云之蔽月,飄飖兮若流風(fēng)之回雪。遠(yuǎn)而望之,皎若太陽升朝霞;迫而察之,灼若芙蕖出淥波。01234567892圖片:3附件:
所有表格文本: 姓名 班級 科目 分?jǐn)?shù) 小明 2 Java 97 小紅 2 C 78
DOCX 導(dǎo)出路徑: E:\Project\pythonProject\pyHomeWorkTool\01 - image2.emf
文件名:授課計(jì)劃.rar,原路徑:D:\y17mm\Desktop\zuoye\授課計(jì)劃.rar,緩存路徑:C:\Users\y17mm\AppData\Local\Temp\授課計(jì)劃.rar
OLE 導(dǎo)出路徑: E:\Project\pythonProject\pyHomeWorkTool\02 - 授課計(jì)劃.rar
導(dǎo)出OLE中的文件: E:\Project\pythonProject\pyHomeWorkTool\02 - 授課計(jì)劃.rar
DOCX 導(dǎo)出路徑: E:\Project\pythonProject\pyHomeWorkTool\03 - image1.png
導(dǎo)出的所有文件: ['E:\\Project\\pythonProject\\pyHomeWorkTool\\01 - image2.emf', 'E:\\Project\\pythonProject\\pyHomeWorkTool\\02 - 授課計(jì)劃.rar', 'E:\\Project\\pythonProject\\pyHomeWorkTool\\03 - image1.png']
進(jìn)程已結(jié)束,退出代碼為 0
和這三個(gè)文件:
成功!至此,達(dá)成了原來講的目標(biāo),支持中文、只需要寫入一次磁盤。
上面已經(jīng)給出所有實(shí)現(xiàn)的代碼,總的在下面會(huì)給出。
( ̄▽ ̄)~■干杯□~( ̄▽ ̄)
10. 文本好像沒存盤:保存文檔中的文本
文本存盤很簡單,在拿到所有文本后,導(dǎo)出附件之前:
text_file_path = get_new_path(0, 'docx文本.txt')
with open(text_file_path, 'w', encoding='utf-8') as f:
f.write(all_text)
f.write(all_table_text)
結(jié)果:
也可以把這個(gè)文件加入到export_files
中,畢竟這個(gè)也是導(dǎo)出的文件。
φ(゜▽゜*)?
叁、總結(jié)
其實(shí)有這樣功能的代碼大多在網(wǎng)上都找不到(至少我沒找到),得自己從頭寫,網(wǎng)上能找到的大多就是可以跟你說用什么,和一些零碎的代碼,要是自己的需求比較特殊,還是得自己寫代碼。那么寫代碼的時(shí)候就不可避免的用到一些別人寫好的代碼,你要用的話就必須要懂里面的一些邏輯,就得看它的源碼、看它的文檔,同時(shí)還可以多寫一些例程去驗(yàn)證自己的想法,通過DEBUG調(diào)試的單步運(yùn)行去看程序運(yùn)行的邏輯,去翻譯源碼的注釋(英文好的直接看)……很多種方法。我在本次的實(shí)踐中最大的收獲就是學(xué)會(huì)了通過調(diào)試工具去寫代碼。一開始我用的是B站一位UP主的代碼,他是使用XPath表達(dá)式實(shí)現(xiàn)的導(dǎo)出圖片,但是我還想導(dǎo)出OLE文件內(nèi)容,仿造他的代碼寫導(dǎo)出OLE的代碼時(shí)出現(xiàn)了問題,經(jīng)過了一下午和一晚上還沒搞定,于是決定另尋出路了,通過調(diào)試發(fā)現(xiàn)了另外的導(dǎo)出方式,為此還發(fā)了個(gè)朋友圈,哈哈。這是經(jīng)驗(yàn)的總結(jié)。
再講講對于實(shí)現(xiàn)這個(gè)功能的過程中我閱讀了oleobj源碼的總結(jié)。我寫這篇文章的目的也在于此,我好不容易看懂了oleobj中的代碼,感覺不記錄一下過段時(shí)間就忘記了。我兩年前用Python寫過導(dǎo)出docx的,能用,但是現(xiàn)在那個(gè)項(xiàng)目搞不懂了,自己都看不太懂了,沒怎么寫注釋,也比較混亂,故重新寫一遍。這樣會(huì)花大量時(shí)間,還不如寫一篇總結(jié),方便自己的同時(shí)也可以給其他人以參考。
╰(°▽°)╯
1. 整體項(xiàng)目
前面的python-docx沒怎么去看源碼,大多數(shù)通過調(diào)試去檢查里面的對象的,調(diào)試器查看比較簡單就不說了,有一個(gè)對象比較迷:element,為什么要看這個(gè)對象,因?yàn)槲抑坝玫腂站那個(gè)UP主的代碼就是用這個(gè)加Xpath實(shí)現(xiàn)的提取圖片。我們解壓了docx,知道docx中的大致組成成分,docx就是由一些xml文檔和另外的附件組成的,最重要的是document.xml,其他的xml從文件名上也能看出大概是做啥的??戳讼略创a,這個(gè)element是由xml文檔解析出來的,但是沒看到它怎么創(chuàng)建段落對象等,跟了半個(gè)小時(shí)也沒看到QWQ。
oletoos文檔中你去查看oleobj時(shí)它會(huì)讓你去看rtfobj的文檔,說是rtfobj是兼容oleobj的,但是我使用命令行用rtfobj去導(dǎo)出時(shí)它告訴我這個(gè)文件里啥也沒有,只打印了個(gè)表頭,故沒有興趣再去看rtfobj源碼了。然后我用同樣的方式使用oleobj,直接把文件給導(dǎo)出了,明顯符合要求,所以后面都看oleobj去了。oletoos這邊最重要的就是oleobj,同時(shí)它依賴于olefile這個(gè)項(xiàng)目,olefile項(xiàng)目中就一個(gè)腳本文件,olefile,所以是看oleobj和olefile。
2. oleobj.py
用于解析各種MS Office文件格式(doc、xls、ppt、docx、xlsx、pptx等)中的OLE對象和文件。
main函數(shù)
oleobj.main
函數(shù)主要是負(fù)責(zé)解析命令行參數(shù)、初始化日志和調(diào)用oleobj.process_file
函數(shù)處理數(shù)據(jù)。
解析命令行參數(shù)時(shí)會(huì)調(diào)用oleobj.existing_file
函數(shù)判斷要處理的文件是否存在。在進(jìn)行處理之前會(huì)判斷要處理的文件是否是壓縮包,如果是,還會(huì)自行解壓逐個(gè)處理,這個(gè)是由xglob.iter_files
函數(shù)完成的。在xglob.iter_files
函數(shù)的循環(huán)里,如果是壓縮包,xglob.iter_files
函數(shù)會(huì)把壓縮包里的文件流傳入oleobj.process_file
函數(shù)中,如果是文件,則直接傳文件。
這樣就到了oleobj.process_file
函數(shù)的流程中了。oleobj.process_file
函數(shù)的流程結(jié)束后會(huì)返回一些狀態(tài)指示,由oleobj.main
函數(shù)匯總。
process_file函數(shù)
顧名思義,oleobj.process_file
函數(shù)就是用來處理文件的,我們寫的導(dǎo)出ole文件內(nèi)容的代碼也是參考的這個(gè)函數(shù)來進(jìn)行編寫的。oleobj.process_file
函數(shù)首先會(huì)檢測輸出目錄是否存在,不存在會(huì)自動(dòng)創(chuàng)建一個(gè)。
之后對要處理的文件進(jìn)行判斷,判斷是否為壓縮文件,壓縮文件會(huì)取得其xml_parser
,判斷是否有惡意代碼,這個(gè)應(yīng)該是觸發(fā)于我們輸入的文件為docx文檔的時(shí)候。經(jīng)過嘗試,oleobj支持直接提取docx(壓縮文檔)中的ole文件。壓縮文件取得的xml_parser
、文件字節(jié)數(shù)組和文件名會(huì)被傳入下一個(gè)環(huán)節(jié):oleobj.find_ole
函數(shù)。oleobj.find_ole
函數(shù)可以查找壓縮文檔中的ole文件,也可以直接判斷是否是ole文件,還可以處理字節(jié)數(shù)組形式的壓縮文件或者ole文件,最后返回一個(gè)olefile.OleFileIO
對象給oleobj.process_file
函數(shù)進(jìn)行下一步的處理。oleobj.process_file
函數(shù)得到olefile.OleFileIO
對象后會(huì)使用olefile.OleFileIO.listdir
獲取ole文件中的列表(我們之前可以用7z打開ole文件,所以里面是
有文件列表的),找到ole文件中的[1]Ole10Native
(不區(qū)分大小寫)文件。
找到ole文件中的[1]Ole10Native
文件后使用olefile.OleFileIO.openstream
函數(shù)打開一個(gè)這個(gè)文件流,這是原始的文件流,由olefile
提供,是一個(gè)olefile.OleStream
對象,繼承自io.BytesIO
。oleobj.process_file
函數(shù)會(huì)使用自己的oleobj.OleNativeStream
類對olefile.OleFileIO.openstream
函數(shù)返回的olefile.OleStream
對象進(jìn)行包裝,獲取一些信息。
拿到oleobj.OleNativeStream
對象后,會(huì)打印這個(gè)oleobj.OleNativeStream
對象的信息,包括文件名、原路徑和緩存路徑等,同時(shí)計(jì)算要導(dǎo)出的ole文件內(nèi)容的新的文件名(由oleobj.get_sane_embedded_filenames
函數(shù)計(jì)算)直到找到一個(gè)沒有被占用的文件名。
然后是轉(zhuǎn)存,把剛剛拿到的oleobj.OleNativeStream
對象存到由oleobj.get_sane_embedded_filenames
函數(shù)計(jì)算出的新的路徑中去,存的過程是會(huì)從olefile.OleStream
對象一塊一塊地讀取數(shù)據(jù)并寫入文件,一個(gè)文件塊最大4kb,這樣直到所有數(shù)據(jù)寫入文件完成,轉(zhuǎn)成成功!
最后返回處理結(jié)果,三個(gè)布爾值,分別是流出錯(cuò)否、轉(zhuǎn)存出錯(cuò)否和轉(zhuǎn)存完成否。
find_ole函數(shù)
在上面講到的oleobj.process_file
函數(shù)中是利用這個(gè)函數(shù)從給定的數(shù)據(jù)中拿到的olefile.OleFileIO
對象。oleobj.find_ole
函數(shù)可以從給定的文件、文件字節(jié)數(shù)組或者xml_parser
中找ole文件,即使給定的是壓縮文件。oleobj.find_ole
函數(shù)首先處理的是文件字節(jié)數(shù)組,會(huì)將文件字節(jié)數(shù)組封裝成oleobj.FakeFile
對象以可以模仿文件的讀寫操作。
隨后使用olefile.isOleFile
判斷傳入的文件(文件字節(jié)數(shù)組)是否是ole文件。
如果是ole文件,會(huì)由ppt_record_parser.is_ppt
判斷一下是否是PPT文件(就是Office的PPT),PPT文件里可能會(huì)有多個(gè)ole文件的,如果是PPT文件,則由oleobj.find_ole_in_ppt
返回多個(gè)olefile.OleFileIO
對象,否則直接返回一個(gè)olefile.OleFileIO
對象。
如果不是ole文件,會(huì)看xml_parser
對象是否不為空或者判斷是否是壓縮文件,滿足條件的話就進(jìn)入循環(huán)判斷。xml_parser
對象會(huì)鏈接多個(gè)文件,而壓縮文件會(huì)包含多個(gè)文件,oleobj.find_ole
函數(shù)通過ooxml.XmlParser.iter_non_xml
來拿到每一個(gè)文件流,拿到文件流后讀取文件頭,判斷文件頭是否是ole的文件頭(olefile.MAGIC
),如果是的話,使用olefile.OleFileIO
包裝這個(gè)文件流并返回。
最后oleobj.find_ole
函數(shù)使用的是yield
返回的文件流,在調(diào)用它的函數(shù)處理完之后會(huì)回到這里,oleobj.find_ole
函數(shù)負(fù)責(zé)把流對象關(guān)一下。
?
OleNativeStream類
包含在OLENativeStream結(jié)構(gòu)中的OLE對象,參見MS-OLEDS 2.3.6 OLENativeStream。
這個(gè)類負(fù)責(zé)解析OLE文件的文件名、原始路徑和緩存路徑等。文件名和路徑使用unicode解碼(然而中文使用的GBK編碼)。
具體的解析過程參看源碼oleobj.OleNativeStream
、oleobj.OleNativeStream.parse
、oleobj.read_zero_terminated_string
和oleobj.read_uint32
,是些比較簡單的讀取。
get_sane_embedded_filenames函數(shù)
從路徑信息中獲取一些合理的文件名,保留文件后綴。
返回幾個(gè)給調(diào)用函數(shù)驗(yàn)證,先是帶后綴,然后不帶后綴,再隨機(jī)
帶后綴,最后一次嘗試忽略最大長度限制使用noname_index
參數(shù)。
在一些惡意軟件示例中,文件名(我們專門依賴它)是空的或是空串,但原路徑和緩存路徑包含具有正確文件名的路徑。嘗試從其中任何一個(gè)文件中提取文件名(這點(diǎn)我們的代碼中沒有考慮到)。
后綴的保留尤其重要,因?yàn)樗刂浦暣安僮飨到y(tǒng)(windoze)處理文件的方式。
o(〃‘▽’〃)o
FakeFile類
這個(gè)類繼承自io.RawIOBase
類,是用來把文件字節(jié)數(shù)組封裝成文件流的(只讀)。主要有readable
(返回True,可讀)、writable
(返回False,不可寫)、seekable
(返回True,可尋址)、readinto
(讀目標(biāo)位置)、read
(讀數(shù)據(jù))、seek
(跳轉(zhuǎn)尋址)和tell
(返回讀的位置)等函數(shù)。
3. olefile.py
讀取/寫入Microsoft OLE2文件(也稱為結(jié)構(gòu)化存儲(chǔ)或Microsoft復(fù)合文檔文件格式)的模塊,如Microsoft Office 97-2003文檔、Image Composer和FlashPix文件、Outlook消息文檔等。
與Python 2.7和3.4兼容+
OleFileIO類
OLE容器對象
此類將接口封裝到OLE 2結(jié)構(gòu)化存儲(chǔ)文件。使用olefile.OleFileIO.listdir
和olefile.OleFileIO.openstream
函數(shù)訪問該文件的內(nèi)容。
-
listdir函數(shù)
返回存儲(chǔ)在ole文件中的流和存儲(chǔ)的列表。最后是利用olefile.OleFileIO._list
函數(shù)遍歷的文件樹返回的列表。 -
openstream函數(shù)
以只讀文件對象(io.BytesIO
)的形式打開流。文件名不區(qū)分大小寫。最后是利用olefile.OleFileIO._open
函數(shù)打開的FAT或MiniFAT中的流。(有資料顯示ole文件是用FAT的形式存儲(chǔ)的文件)文章來源:http://www.zghlxwxcb.cn/news/detail-732021.html
OleStream類
OLE2流,繼承自io.BytesIO
。
返回一個(gè)只讀文件對象,該對象可用于讀取OLE流(io.BytesIO
類的實(shí)例)的內(nèi)容。要打開流,請使用olefile.OleFileIO.openstream
函數(shù)。
此類可以與任何普通流或迷你流一起使用,具體取決于偏移量、扇區(qū)大小和文件分配表參數(shù)。文章來源地址http://www.zghlxwxcb.cn/news/detail-732021.html
伍、項(xiàng)目
- 軟件(exe打包:
pyinstaller run.spec
):GitHub v0.1.0.3 或者 Gitee,失效看發(fā)行版:GitHub 或者 Gitee - GitHub:https://github.com/Minuhy/python_docx_export
- 碼云:https://gitee.com/Minuhy/python_docx_export
- 博客園:https://www.cnblogs.com/minuhy/p/17447849.html
- CSDN:https://blog.csdn.net/XiaoYuHaoAiMin/article/details/130979264
陸、參考文檔
- python-oletools項(xiàng)目:https://github.com/decalage2/oletools
- 如何使用Python嵌入附件到Word文檔中:https://github.com/zhutong/Embbed-files-into-docx-with-Python
- Python提取Word文檔中的圖片視頻教程:https://www.bilibili.com/video/BV14s4y1A7gY?p=14
- 編碼轉(zhuǎn)文字工具:https://the-x.cn/encodings/Hex.aspx
- 文本框的讀取和修改:https://blog.csdn.net/weixin_42636075/article/details/129010740
- OLE文件介紹:https://baike.baidu.com/item/OLE/2139114
- OLE文件結(jié)構(gòu):https://www.cnblogs.com/AspDotNetMVC/p/3810839.html
- Python tkinter:http://c.biancheng.net/tkinter/
到了這里,關(guān)于【Python】導(dǎo)出docx格式Word文檔中的文本、圖片和附件等的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!