Python:讀取 .doc、.docx
- 2020 年 1 月 21 日
- 筆記
概述
Python 中可以讀取 word 文件的庫有 python-docx 和 pywin32。
下表比較了各自的優缺點。
優點 | 缺點 | |
|---|---|---|
python-docx | 跨平台 | 只能處理 .docx 格式,不能處理.doc格式 |
pywin32 | 僅限 windows 平台 | .doc 和 .docx 都能處理 |
pywin32
這個庫很強大,不僅僅可以讀取 word,本文僅介紹其讀取 word 功能。網上介紹用 pywin32 讀取 .doc 的文章真不多,因為,真心不好用。
以下是 pywin32 讀取 .doc 的程式碼示例,但是讀取表格有問題,輸出全是空,原因不明,因為不打算用所以沒有深入研究。另外,如果表格中有縱向合併單元格,會報錯:「無法訪問此集合中單獨的行,因為表格有縱向合併的單元格。」
from win32com.client import Dispatch word = Dispatch('Word.Application') # 打開word應用程式 # word = DispatchEx('Word.Application') # 啟動獨立的進程 word.Visible = 0 # 後台運行,不顯示 word.DisplayAlerts = 0 # 不警告 path = r'E:abctest.doc' doc = word.Documents.Open(FileName=path, Encoding='gbk') for para in doc.paragraphs: print(para.Range.Text) for t in doc.Tables: for row in t.Rows: for cell in row.Cells: print(cell.Range.Text) doc.Close() word.Quit但是 pywin32 有另外一個功能,就是將 .doc 格式另存為 .docx 格式,這樣我們就可以使用 python-docx 來處理了。
# 將 .doc 文件轉成 .docx def doc2docx(path): w = win32com.client.Dispatch('Word.Application') w.Visible = 0 w.DisplayAlerts = 0 doc = w.Documents.Open(path) newpath = os.path.splitext(path)[0] + '.docx' doc.SaveAs(newpath, 12, False, "", True, "", False, False, False, False) doc.Close() w.Quit() os.remove(path) return newpathpython-docx
python-docx 可以按段落讀取 word,對於表格,可以單獨的提取,程式碼如下:
import docx fn = r'E:abctest.docx' doc = docx.Document(fn) for paragraph in doc.paragraphs: print(paragraph.text) for table in doc.tables: for row in table.rows: for cell in row.cells: print(cell.text)
對於縱向合併單元格,python-docx 的處理也很貼心。看下面的截圖:
word 表格截圖:

程式碼運行結果截圖:

綜上所述,對於大批量 word 文件的讀取,我建議使用 python-docx 庫,若是 .doc 文件,則用 pywin32 庫將其轉化為 .docx 文件,然後再調用 python-docx 庫讀取。
Word 未能引發事件
這是我遇到的一個實際問題,困擾了我半天時間。
我的爬蟲在爬取到 .doc 文件之後,就通過上面的方法將其轉為 .docx 格式,原本一切都好,下班掛機在跑,第二天來一看,報了這個錯:pywintypes.com_error: (-2147352567, '發生意外。', (0, 'Microsoft Word', 'Word 未能引發事件。', 'D:工具Microsoft OfficeOffice122052WDMAIN11.CHM', 25482, -2146822286), None)

我用報錯的文件單獨調試了 doc2docx 方法,並沒有報錯。網上查了這個錯誤,沒有啥收穫。
反覆測試後發現總是那個網頁報錯,說明 bug 可以重現,那麼問題到底出在哪裡?
我將程式碼一行行刪去,直到只留下執行到報錯所必須的程式碼:
def get_winningbid_detail(url, name): r = requests.get(url) r.encoding = 'utf-8' html = r.text soup = BeautifulSoup(html, 'lxml') ps = soup.find_all(text=re.compile('附件')) if len(ps) > 0: os.makedirs(os.path.join(download_dir, name), exist_ok=True) for p in ps: a_tab = p.find_next_sibling('a') if a_tab is not None: link = homepage + a_tab['href'] localfilename = os.path.join(download_dir, name, a_tab.text) # print(localfilename) with open(localfilename, 'wb+') as sw: sw.write(requests.get(link).content) if localfilename.endswith('.doc'): doc2docx(localfilename)反覆讀這段程式碼,並沒有發現什麼問題。
因為有些網頁的附件名稱是相同的,例如 "公告.doc",所以我按每個網頁的標題(在總覽頁面爬到的)分文件夾放置下載的文件,所以方法中傳了一個 name 參數,而如果 name 參數傳空,則不會報錯。
其實由此已經可以發現 bug 所在了,但我卻沒想到,又反覆折騰了很久才發現,原來是文件名太長了。
在 windows 下面,單個文件名的長度限制是 25,完整的路徑長度(如 E:abctest.doc )限制是 260。路徑最後有一個字元串結束符 '' 要佔掉一個字元,所以完整路徑實際限長是259。**

