documentformat.openxml 是读取 .docx 文件正文文本最轻量、无需 word 进程的方案,需正确遍历 paragraph→run→text 节点以避免格式干扰和内容遗漏。

用 DocumentFormat.OpenXml 读取 .docx 文件正文文本
OpenXML 是读取 .docx(不是 .doc)最轻量、无需 Word 进程的方案。它直接解析 ZIP 结构里的 XML,速度快、无依赖,但只支持 Office 2007+ 的格式。
常见错误是误以为能打开双击就报错的文件——其实那大概率是 .doc(OLE 复合文档),WordprocessingDocument 会直接抛 FileFormatException。
- 确认后缀是
.docx,且不是另存为时勾选了“兼容模式”(那可能仍是 .doc) - 引用 NuGet 包:
DocumentFormat.OpenXml(v2.19+ 支持 .NET 6/7/8) - 核心流程:用
WordprocessingDocument.Open()打开 → 取MainDocumentPart→ 遍历Body下所有Paragraph→ 拼接Text节点内容 - 注意:表格、文本框、页眉页脚里的文字默认不包含在
Body中,需单独处理HeaderPart/FooterPart/Table
using (var doc = WordprocessingDocument.Open(@"C:\test.docx", false))
{
var body = doc.MainDocumentPart.Document.Body;
var text = string.Join("\n",
body.Elements<paragraph>()
.Select(p => p.InnerText.Trim())
.Where(t => !string.IsNullOrEmpty(t)));
}</paragraph>
InnerText 和 Text 节点的区别与取值陷阱
InnerText 看起来省事,但它会把注释、删除线、高亮等格式标记全吞掉,还可能把换行符变成空格;而只遍历 Text 元素又容易漏掉加粗/斜体里的文字——因为它们被包在 Run 里,Text 只是 Run 的子节点。
真正稳定的做法是递归提取所有 Text 节点,跳过 CommentRangeStart、DeletedText 等非正文内容。
- 别用
Paragraph.InnerText做生产环境文本抽取,尤其当文档含修订痕迹或样式复杂时 - 正确路径:遍历每个
Paragraph→ 遍历其下所有Run→ 取每个Run内第一个Text节点(run.Elements<text>().FirstOrDefault()?.Text</text>) - 如果需要保留段落间换行,用
\r\n分隔Paragraph,而不是依赖InnerText自带的换行逻辑
读取表格和标题样式(Heading1 等)要额外处理
OpenXML 不像 Word UI 那样有“标题”概念,Heading1 实际是段落上的 ParagraphProperties 里一个 OutlineLevel 或命名样式引用。表格更是完全独立的结构树,不在 Body 的线性流里。
这意味着:只扫 Paragraph 会漏掉全部表格内容,也识别不出哪些段落是标题。
- 查标题:检查
Paragraph.ParagraphProperties?.ParagraphStyleId?.Val.Value是否等于"Heading1"(实际值取决于模板,可用StylesPart查映射) - 读表格:遍历
Body.Elements<table>(),再对每张表遍历 <code>TableRow→TableCell→Paragraph→Text - 性能提醒:嵌套深的表格 + 大量样式定义会让 DOM 遍历变慢,避免在循环里反复调用
StylesPart查样式名 - Interop 适合本地工具类小脚本(且确定只在 Win+桌面版 Office 环境运行)
- OpenXML 是服务器、CI、跨平台场景唯一靠谱选择
- 真要兼顾 .doc 和 .docx?别硬扛,用 LibreOffice headless 转成 .docx 再交给 OpenXML —— 比写两套解析逻辑省心
为什么不用 Microsoft.Office.Interop.Word?
Interop 看似简单——调 Application.Documents.Open() 然后读 Document.Content.Text。但它会在后台启动完整 Word 进程,Windows Server 上常因 DCOM 权限失败,Linux/macOS 根本跑不了,而且并发高时极易卡死或内存泄漏。
OpenXML 没这些问题,但代价是:你得自己处理 XML 结构细节。比如超链接是 Hyperlink 节点,页码是 SimpleField,这些都不会出现在 Text 节点里。
Run 里,一段标题可能由多个 Text 拼成,表格单元格里还能嵌套整个文档。别指望“读一次就全出来”,得按需分层提取。










