ARTICLE · 1096500
用 Python 一键提取 Word 文档全部内容,文字、表格、图片全都有
平时写公众号、整理资料、做知识库,最烦的就是从 Word 里往外抠内容。
复制文字吧,表格全乱;想存图片吧,得一张张右键另存;要是几十个文档,手都点废。
后来我用 Python 写了个小工具,不管 Word 里有多少段落、表格、图片,跑一遍全部提取出来,还能按原顺序整理好。今天把方法分享给你,代码可以直接拿去用。
一、为什么选 python-docx
Python 处理 Word 的库有好几个,但最省心的是 python-docx。
它不用你装 Office,也不依赖 COM 组件,纯 Python 解析 .docx 文件。安装就一行:
pip install python-docx注意:它只支持 .docx,老版 .doc 不行。如果你手头是 .doc,先用 Word 另存为 .docx。
二、提取文字:别只会 doc.paragraphs
很多人上手就写:
from docx import Documentdoc = Document("demo.docx")for p in doc.paragraphs:print(p.text)
这样确实能拿到文字,但有两个坑:
表格里的文字拿不到,因为表格不在
doc.paragraphs里。顺序会乱。如果文档是“段落 → 表格 → 段落”,
doc.paragraphs会把所有段落先列出来,表格全跑到后面去。
正确做法是直接遍历文档底层的 XML 节点,按真实顺序走:
from docx.oxml.table import CT_Tblfrom docx.oxml.text.paragraph import CT_Pfrom docx.table import Table, _Cellfrom docx.text.paragraph import Paragraphfrom docx.document import Document as _Documentdef iter_blocks(parent):"""按文档真实顺序,依次产出 Paragraph 或 Table"""if isinstance(parent, _Document):elm = parent.element.bodyelif isinstance(parent, _Cell):elm = parent._tcelse:raise TypeError("不支持的容器")for child in elm.iterchildren():if isinstance(child, CT_P):yield Paragraph(child, parent)elif isinstance(child, CT_Tbl):yield Table(child, parent)
这样你就能按顺序拿到每一个段落和表格。
三、识别标题和样式
提取文字时,如果能顺便把标题级别也识别出来,后面整理成 Markdown 或公众号文章就轻松多了。
Word 的标题样式名一般是 Heading 1、Heading 2,中文版是 标题 1、标题 2。写个正则兼容一下:
import reHEADING_RE = re.compile(r'^(?:Heading|标题)\s*([1-6])$', re.I)def get_heading_level(paragraph):style_name = (paragraph.style.name or '').strip()m = HEADING_RE.match(style_name)if m:return int(m.group(1))if style_name in ('Title', '标题'):return 1return None
拿到级别后,你就可以决定输出成 #、##,或者公众号的 <h1>、<h2>。
四、提取表格:转成二维列表
表格提取的核心是 table.rows 和 row.cells。但要注意,合并单元格会导致同一个单元格被重复读取,这里先不做复杂去重,一般够用。
def extract_table(table):data = []for row in table.rows:cells = []for cell in row.cells:# 单元格内可能有多个段落,用换行拼起来text = '\n'.join(p.text.strip() for p in cell.paragraphs if p.text.strip())cells.append(text)data.append(cells)return data
拿到二维列表后,你想存 CSV、转 Markdown、还是直接写进公众号表格,都随你。
五、提取图片:Word 其实是个压缩包
这是最容易被忽略的部分。
.docx 文件本质是个 zip 包,图片全藏在 word/media/ 目录里。段落里通过一个 rId 去引用图片。所以思路是:找到段落里的图片引用,拿到 rId,再去文档部件里取二进制数据。
import osfrom docx.oxml.ns import qndef save_images_from_run(run, doc, out_dir, counter):"""抽出一个 run 里所有图片,保存到本地,返回文件名列表"""names = []for blip in run._element.findall('.//' + qn('a:blip')):rid = blip.get(qn('r:embed'))if not rid:continuepart = doc.part.related_parts.get(rid)if part is None:continueext = os.path.splitext(str(part.partname))[1] or '.png'counter[0] += 1name = f"img_{counter[0]:03d}{ext}"with open(os.path.join(out_dir, name), 'wb') as f:f.write(part.blob)names.append(name)return names
然后在遍历段落时,对每个 run 调用这个函数,就能把图片按顺序存下来。
六、完整代码:一键提取所有内容
把上面的拼起来,写一个 extract_word.py:
# -*- coding: utf-8 -*-"""extract_word.py提取 Word 文档中的文字、表格、图片,按原顺序输出为结构化数据用法:python extract_word.py demo.docx"""import osimport reimport shutilfrom docx import Documentfrom docx.oxml.ns import qnfrom docx.oxml.table import CT_Tblfrom docx.oxml.text.paragraph import CT_Pfrom docx.table import Table, _Cellfrom docx.text.paragraph import Paragraphfrom docx.document import Document as _DocumentHEADING_RE = re.compile(r'^(?:Heading|标题)\s*([1-6])$', re.I)def iter_blocks(parent):"""按文档真实顺序依次产出 Paragraph / Table"""if isinstance(parent, _Document):elm = parent.element.bodyelif isinstance(parent, _Cell):elm = parent._tcelse:raise TypeError("不支持的容器")for child in elm.iterchildren():if isinstance(child, CT_P):yield Paragraph(child, parent)elif isinstance(child, CT_Tbl):yield Table(child, parent)def get_heading_level(paragraph):style_name = (paragraph.style.name or '').strip()m = HEADING_RE.match(style_name)if m:return int(m.group(1))if style_name in ('Title', '标题'):return 1return Nonedef extract_table(table):data = []for row in table.rows:cells = []for cell in row.cells:text = '\n'.join(p.text.strip() for p in cell.paragraphs if p.text.strip())cells.append(text)data.append(cells)return datadef save_images_from_run(run, doc, out_dir, counter):names = []for blip in run._element.findall('.//' + qn('a:blip')):rid = blip.get(qn('r:embed'))if not rid:continuepart = doc.part.related_parts.get(rid)if part is None:continueext = os.path.splitext(str(part.partname))[1] or '.png'counter[0] += 1name = f"img_{counter[0]:03d}{ext}"with open(os.path.join(out_dir, name), 'wb') as f:f.write(part.blob)names.append(name)return namesdef extract_docx(docx_path, img_dir='images'):doc = Document(docx_path)if os.path.isdir(img_dir):shutil.rmtree(img_dir)os.makedirs(img_dir, exist_ok=True)counter = [0]blocks = []for block in iter_blocks(doc):if isinstance(block, Paragraph):text = block.text.strip()has_img = bool(block._element.findall('.//' + qn('a:blip')))imgs = []for run in block.runs:imgs.extend(save_images_from_run(run, doc, img_dir, counter))if not text and not has_img:continueblocks.append({'type': 'paragraph','text': text,'heading_level': get_heading_level(block),'images': imgs,})elif isinstance(block, Table):blocks.append({'type': 'table','data': extract_table(block),})return blocksif __name__ == '__main__':import syssrc = sys.argv[1] if len(sys.argv) > 1 else 'demo.docx'result = extract_docx(src)for b in result:if b['type'] == 'paragraph':prefix = '#' * b['heading_level'] if b['heading_level'] else ''print(f"{prefix}{b['text']}")for img in b['images']:print(f" [图片] {img}")elif b['type'] == 'table':print("[表格]")for row in b['data']:print(" | " + " | ".join(row) + " |")
python extract_word.py 我的文档.docx你会看到文字按顺序打印出来,表格变成 | 分隔的行,图片存进 images/ 文件夹。
七、提取完怎么用?几个常见场景
1. 转成 Markdown 发公众号
拿到 blocks 后,写个简单的转换函数:
def to_markdown(blocks, img_dir='images'):lines = []for b in blocks:if b['type'] == 'paragraph':if b['heading_level']:lines.append('#' * b['heading_level'] + ' ' + b['text'])elif b['text']:lines.append(b['text'])for img in b['images']:lines.append(f'')elif b['type'] == 'table':for i, row in enumerate(b['data']):lines.append('| ' + ' | '.join(row) + ' |')if i == 0:lines.append('| ' + ' | '.join(['---'] * len(row)) + ' |')return '\n\n'.join(lines)
Markdown 可以直接粘进支持 Markdown 的编辑器,也可以再用工具转成公众号 HTML。
2. 批量提取多个文档
把 extract_docx 套个循环,遍历文件夹里所有 .docx,结果写进 JSON 或数据库,就能做知识库了。
3. 只提取图片做素材库
如果你只想要图片,把文字部分跳过,只调 save_images_from_run 就行。
八、几个容易踩的坑
1. 老版 .doc 不支持
python-docx 只认 .docx。遇到 .doc 先用 Word 批量另存为 .docx,或者用 win32com 调 Office 转换。
2. 合并单元格会重复读
row.cells 遇到合并单元格时,同一个单元格会被多个位置返回。如果表格合并很复杂,需要额外判断 cell._tc 是否相同来去重。
3. 图片可能重复
如果同一张图在文档里被引用多次,按 rId 存会得到多个副本。想要去重的话,加一个 rid -> filename 的字典缓存即可。
4. 页眉页脚不在 doc.paragraphs 里
页眉页脚属于 section.header 和 section.footer,需要单独遍历。一般公众号文章用不到,知道有这回事就行。
写在最后
用 Python 提取 Word 内容,核心就三件事:按顺序遍历、识别样式、抠出图片。
代码不长,但能省下大量复制粘贴的时间。你可以直接拿上面的脚本去改,比如加上页眉页脚提取、支持批量处理、或者接上公众号草稿箱接口一键发文。
工具的意义就是让重复劳动自动化,把时间留给真正重要的内容创作。