国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【Python】導(dǎo)出docx格式Word文檔中的文本、圖片和附件等

這篇具有很好參考價(jià)值的文章主要介紹了【Python】導(dǎo)出docx格式Word文檔中的文本、圖片和附件等。希望對大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

【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)容,主界面如下:

python 輸出docx,Python,電腦,python,word,開發(fā)語言

界面左右分欄,左邊是待導(dǎo)出的docx文件列表,右邊是一些導(dǎo)出選項(xiàng)。配置好之后點(diǎn)擊“導(dǎo)出”按鈕開始導(dǎo)出相關(guān)內(nèi)容,導(dǎo)出界面如下:

python 輸出docx,Python,電腦,python,word,開發(fā)語言

導(dǎo)出成功后可以保存一份json格式的導(dǎo)出報(bào)告,這份報(bào)告中記錄了導(dǎo)出的文件和原始文件的關(guān)系,導(dǎo)出報(bào)告界面如下:

python 輸出docx,Python,電腦,python,word,開發(fā)語言

功能就這樣了,簡簡單單,剩下的識(shí)別作業(yè),自動(dòng)批改作業(yè)的代碼還沒整理,拿不出手,哈哈(>__<)。

貳、方案

下面介紹軟件細(xì)節(jié)相關(guān)內(nèi)容和部分探索的過程:

使用Python處理Word文檔,經(jīng)過搜索,發(fā)現(xiàn)有如下幾個(gè)主流方案:

  1. python-docx:python-docx是一個(gè)用于創(chuàng)建和更新Word(.docx)文件的python庫,目前只支持docx。
  2. pywin32:能處理doc和docx文檔,但是只能在Windows平臺(tái)上用,而且使用的時(shí)候需要電腦有安裝Office或者WPS。
  3. 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☆
python 輸出docx,Python,電腦,python,word,開發(fā)語言
在Word文檔中插入了一段文字、一張圖片和一個(gè)作為附件的壓縮包,附件是以“文件附件”的形式嵌入到文檔中的(即打包到了文檔中,換臺(tái)電腦也能正常打開),這樣可以模擬學(xué)生提交的實(shí)驗(yàn)報(bào)告文檔的大致情況。

2. Word文檔的結(jié)構(gòu)

經(jīng)過簡單的嘗試,發(fā)現(xiàn)docx是一個(gè)壓縮包,可以直接用7z打開:
(?ˉ?ˉ?)python 輸出docx,Python,電腦,python,word,開發(fā)語言
于是將其解包,得到如下目錄:

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中的所有圖片都可以到這里解壓出來。
python 輸出docx,Python,電腦,python,word,開發(fā)語言
另外我們發(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ù)。
( ̄︶ ̄)↗
python 輸出docx,Python,電腦,python,word,開發(fā)語言
除了上面兩個(gè)文檔,我還發(fā)現(xiàn)/word/_rels/document.xml.rels中保存了一份文件清單,這份文件清單有我們嵌入的壓縮文檔(Id=rId5)和插入的圖片(Id=rId4),看了下rId6指向的/word/media/image2.emf,這個(gè)是我們嵌入的附件圖標(biāo)圖片。
python 輸出docx,Python,電腦,python,word,開發(fā)語言
為啥這里強(qiáng)調(diào)它們的ID,一個(gè)原因是這里的ID是除了路徑外的另外一個(gè)文件識(shí)別標(biāo)記,另一個(gè)原因是我在另外一個(gè)文檔(/word/document.xml)中發(fā)現(xiàn)了它們:
python 輸出docx,Python,電腦,python,word,開發(fā)語言
這個(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ù):
(~ ̄▽ ̄)~
python 輸出docx,Python,電腦,python,word,開發(fā)語言

3. 實(shí)現(xiàn)目標(biāo)

了解了docx文檔的大致結(jié)構(gòu)后,我們可以開始嘗試使用python-docx去獲取上面的一些數(shù)據(jù)了。大致需要實(shí)現(xiàn)如下功能:

  1. 所有文字轉(zhuǎn)存為文本文件。
  2. 所有圖片轉(zhuǎn)存為單獨(dú)的圖片文件,具體的格式為Word能插入的圖片格式。
  3. 所有插入的附件轉(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)行簡單的分析:
python 輸出docx,Python,電腦,python,word,開發(fā)語言
可以發(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 對象下的:
python 輸出docx,Python,電腦,python,word,開發(fā)語言
于是我們再導(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 對象分析:
python 輸出docx,Python,電腦,python,word,開發(fā)語言
我們可以發(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è)文件:
python 輸出docx,Python,電腦,python,word,開發(fā)語言
成功!
(??????) ?
嘗試修改文件,插入更多的圖片、附件,依然能導(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é)果:
python 輸出docx,Python,電腦,python,word,開發(fā)語言
導(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)重的問題:

  1. 文件名不支持中文
  2. 我們無法得知它導(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_fileoleobj.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

和這些文件:
python 輸出docx,Python,電腦,python,word,開發(fā)語言
中文亂碼的問題已被解決!而且我們也在export_files中能看到所有的導(dǎo)出的文件了,至此滿足了大部分的需求。
ヾ(≧▽≦*)o

9. 從OLE嵌入對象中提取原始附件(改進(jìn)提取方式:避免多次寫入磁盤)

其實(shí)這部分在看oleobj.py的源碼的時(shí)候就發(fā)現(xiàn)oleobj.py的作者也提供了類似的方法,oleobj.process_fileoleobj.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è)文件:
python 輸出docx,Python,電腦,python,word,開發(fā)語言
成功!至此,達(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é)果:
python 輸出docx,Python,電腦,python,word,開發(fā)語言
也可以把這個(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.OleNativeStreamoleobj.OleNativeStream.parse、oleobj.read_zero_terminated_stringoleobj.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.listdirolefile.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ǔ)的文件)

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)目

  1. 軟件(exe打包:pyinstaller run.spec):GitHub v0.1.0.3 或者 Gitee,失效看發(fā)行版:GitHub 或者 Gitee
  2. GitHub:https://github.com/Minuhy/python_docx_export
  3. 碼云:https://gitee.com/Minuhy/python_docx_export
  4. 博客園:https://www.cnblogs.com/minuhy/p/17447849.html
  5. CSDN:https://blog.csdn.net/XiaoYuHaoAiMin/article/details/130979264

陸、參考文檔

  1. python-oletools項(xiàng)目:https://github.com/decalage2/oletools
  2. 如何使用Python嵌入附件到Word文檔中:https://github.com/zhutong/Embbed-files-into-docx-with-Python
  3. Python提取Word文檔中的圖片視頻教程:https://www.bilibili.com/video/BV14s4y1A7gY?p=14
  4. 編碼轉(zhuǎn)文字工具:https://the-x.cn/encodings/Hex.aspx
  5. 文本框的讀取和修改:https://blog.csdn.net/weixin_42636075/article/details/129010740
  6. OLE文件介紹:https://baike.baidu.com/item/OLE/2139114
  7. OLE文件結(jié)構(gòu):https://www.cnblogs.com/AspDotNetMVC/p/3810839.html
  8. 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)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Java POI導(dǎo)出富文本的內(nèi)容到word文檔

    當(dāng)創(chuàng)建使用富文本編輯器,操作完的數(shù)據(jù),傳輸?shù)胶笈_(tái)都是帶有html標(biāo)簽的。 如:h1標(biāo)題頭/h1h2第二個(gè)標(biāo)題/h2a href=\\\"www.baidu.com\\\"百度搜索/a 我們想把富文本數(shù)據(jù)轉(zhuǎn)換為Word內(nèi)容。 Word是完全支持html標(biāo)簽的,但是我們獲取到的富文本內(nèi)容并不是完整的html代碼,所有我們需要先補(bǔ)全

    2024年02月09日
    瀏覽(19)
  • word導(dǎo)出為HTML格式教程,同時(shí)也導(dǎo)出圖片

    word導(dǎo)出為HTML格式教程,同時(shí)也導(dǎo)出圖片

    在寫文檔教程時(shí),有時(shí)需要借鑒人家的專業(yè)文檔內(nèi)容,一般都是word格式文檔。word直接復(fù)制里面的內(nèi)容,帳帖到網(wǎng)站編輯器會(huì)有很多問題,需要二次清楚下格式才行,而且圖片是沒辦法直接復(fù)制到編輯器內(nèi)的。所以最方便的辦法是將word導(dǎo)出為HTML格式,同時(shí)也導(dǎo)出圖片,把wo

    2024年02月10日
    瀏覽(28)
  • python之python-docx:操作 office word 文檔

    在Python中,有一個(gè)名為 python-docx 的庫,它提供了豐富的功能,可以方便地創(chuàng)建、修改和讀取Word文檔。 本文將詳細(xì)介紹 python-docx 庫的使用,并提供一些示例來演示其中的功能。為了更好地理解,我們將分為以下幾個(gè)方面進(jìn)行討論: 安裝 python-docx 創(chuàng)建和保存Word文檔 修改現(xiàn)有

    2024年02月12日
    瀏覽(26)
  • Python 實(shí)現(xiàn) PDF 到 Word 文檔的高效轉(zhuǎn)換(DOC、DOCX)

    Python 實(shí)現(xiàn) PDF 到 Word 文檔的高效轉(zhuǎn)換(DOC、DOCX)

    PDF(Portable Document Format)已成為一種廣泛使用的電子文檔格式。PDF的主要優(yōu)勢是跨平臺(tái),可以在不同設(shè)備上呈現(xiàn)一致的外觀。然而,當(dāng)我們需要對文件內(nèi)容進(jìn)行編輯或修改,直接編輯PDF文件會(huì)非常困難,而且效果也不理想。將PDF文件轉(zhuǎn)換為Word文檔(doc、docx)再進(jìn)行編輯是一

    2024年02月03日
    瀏覽(30)
  • 【python】使用docx獲取word文檔的標(biāo)題等級、大綱等級和編號(hào)等級

    在Microsoft Word中: 【標(biāo)題X】是一個(gè)樣式,一般來說,【標(biāo)題1】樣式的大綱級別是1級。 大綱級別一般用于頁面導(dǎo)航和生成目錄??梢杂益I文字-段落里查看/設(shè)置大綱的級別。設(shè)置成【x級】后左側(cè)導(dǎo)航欄就會(huì)顯示。 編號(hào)等級就是大家熟知的項(xiàng)目編號(hào),常用于正文。 基本沒有一

    2024年02月03日
    瀏覽(22)
  • vue導(dǎo)出word文檔(含ECharts,多圖片,表格等)

    vue導(dǎo)出word文檔(含ECharts,多圖片,表格等)

    package.json 安裝文件包 ? ? ?1.導(dǎo)入插件包 ? 2.初始化echarts圖表時(shí)? 將echarts圖片轉(zhuǎn)為base64格式(為后續(xù)導(dǎo)出準(zhǔn)備) ? 3.導(dǎo)出echarts圖片,格式轉(zhuǎn)換,官方自帶,不需要修改 ?4.導(dǎo)出word 具體實(shí)現(xiàn)方法(包含發(fā)郵件,上傳到服務(wù)器) 5.word文檔模板 效果圖: ? ? ? 1.如果有將文件流

    2024年02月13日
    瀏覽(49)
  • python-docx:將excel爬取題庫轉(zhuǎn)化為word格式便于瀏覽

    POE的GPT4.0錯(cuò)誤太多難以吐槽。 似乎段落和運(yùn)行的刪除一直是失敗的,所以在第一次添加的時(shí)候設(shè)置好所有格式 大綱等級設(shè)置失敗了

    2024年02月12日
    瀏覽(26)
  • 【導(dǎo)出Word】如何使用Java+Freemarker模板引擎,根據(jù)XML模板文件生成Word文檔(只含文本內(nèi)容的模板)

    【導(dǎo)出Word】如何使用Java+Freemarker模板引擎,根據(jù)XML模板文件生成Word文檔(只含文本內(nèi)容的模板)

    這篇文章,主要介紹如何使用Java+Freemarker模板引擎,根據(jù)XML模板文件生成Word文檔。 目錄 一、導(dǎo)出Word文檔 1.1、基礎(chǔ)知識(shí) 1.2、制作模板文件 1.3、代碼實(shí)現(xiàn) (1)引入依賴 (2)創(chuàng)建Freemarker工具類 (3)測試案例代碼 (4)運(yùn)行效果 Word文件有兩種后綴格式,分別是:doc和docx,

    2024年02月13日
    瀏覽(29)
  • java實(shí)現(xiàn)word導(dǎo)入導(dǎo)出富文本(含圖片)-附完整測試用例

    java實(shí)現(xiàn)word導(dǎo)入導(dǎo)出富文本(含圖片)-附完整測試用例

    1、解決富文本導(dǎo)入導(dǎo)出依賴兼容問題 2、處理富文本和非富文本內(nèi)容 3、解決webp格式通過java下載不了問題,如果要用到富文本導(dǎo)出,將來勢必是會(huì)碰到的bug,這里提前給提出來并解決,測試用例中有給圖片測試。 4、在原有方法上優(yōu)化,比如處理等比縮小圖片、將圖片本地路

    2024年02月03日
    瀏覽(27)
  • 100天精通Python丨辦公效率篇 —— 12、Python自動(dòng)化操作 office-word(word轉(zhuǎn)pdf、轉(zhuǎn)docx、段落、表格、標(biāo)題、頁面、格式)

    100天精通Python丨辦公效率篇 —— 12、Python自動(dòng)化操作 office-word(word轉(zhuǎn)pdf、轉(zhuǎn)docx、段落、表格、標(biāo)題、頁面、格式)

    本文收錄于 《100天精通Python專欄 - 快速入門到黑科技》專欄 ,是由 CSDN 內(nèi)容合伙人丨全站排名 Top 4 的硬核博主 不吃西紅柿 傾力打造。 基礎(chǔ)知識(shí)篇以理論知識(shí)為主 ,旨在幫助沒有語言基礎(chǔ)的小伙伴,學(xué)習(xí)我整理成體系的精華知識(shí),快速入門構(gòu)建起知識(shí)框架; 黑科技應(yīng)用篇

    2023年04月18日
    瀏覽(41)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包