C#/Blazor项目中PPT转PDF文本提取至Elasticsearch的布局保留方案咨询
C#/Blazor项目中PPT转PDF文本提取至Elasticsearch的布局保留方案咨询
我太懂这种糟心的情况了——PPT转的PDF因为排版逻辑和原生PDF不一样,用普通的文本提取工具确实会把原本清晰的标题、项目符号全拆成零散的行,存到Elasticsearch里搜起来、读起来都费劲。结合你现在用的iText和Blazor场景,给你几个可行的方向:
1. 改用iText的结构化文本提取策略
你现在用的PdfTextExtractor.GetTextFromPage()默认是用SimpleTextExtractionStrategy,它只会按文本的读取顺序拼接,完全忽略布局信息。其实iText提供了LocationTextExtractionStrategy,可以获取到每个文本块的位置、字体大小、缩进等元数据,你可以基于这些信息来重建布局:
- 字体大小明显更大的文本块,大概率是PPT里的标题,你可以给它加上加粗标记;
- 有明显左侧缩进的文本块,就是PPT里的项目符号列表,你可以给它加上
-前缀;
给你调整后的代码示例,核心是用带位置信息的提取策略:
using var reader = new PdfReader(file.Path); using var pdf = new PdfDocument(reader); var strategy = new LocationTextExtractionStrategy(); for (var i = 1; i <= pdf.GetNumberOfPages(); i++) { var page = pdf.GetPage(i); PdfTextExtractor.GetTextFromPage(page, strategy); var textChunks = strategy.GetResultantTextChunks(); var processedText = new StringBuilder(); float? lastFontSize = null; foreach (var chunk in textChunks) { // 这里可以根据字体大小判断标题(比如大于14号的是标题) if (chunk.FontSize > 14 && !string.IsNullOrWhiteSpace(chunk.Text.Trim())) { processedText.AppendLine($"**{chunk.Text.Trim()}**"); lastFontSize = chunk.FontSize; } // 根据缩进判断列表项(比如左侧偏移大于50的是列表) else if (chunk.GetStartLocation().Get(Vector.I1) > 50) { processedText.AppendLine($"- {chunk.Text.Trim()}"); } else { // 普通段落,和上一段同字体就拼接,不同就换行 if (lastFontSize.HasValue && lastFontSize != chunk.FontSize) { processedText.AppendLine(); } processedText.Append(chunk.Text); lastFontSize = chunk.FontSize; } } await elasticService.WriteAsync(new ElasticWriteModel { DocId = document.Id, Page = i, Text = processedText.ToString(), AccessLevel = document.AccessLevel == AccessLevel.Private ? 0 : 1, Timestamp = DateTime.UtcNow }, cancellationToken); // 重置策略,处理下一页 strategy.Reset(); }
2. 利用PDF的结构化标签(如果存在)
有些PPT转PDF时(比如用Microsoft Office导出),会保留PDF的结构化标签,你可以直接遍历PDF的结构树来获取层级化的内容,而不是纯文本:
using var reader = new PdfReader(file.Path); using var pdf = new PdfDocument(reader); var structTree = new PdfStructTreeController(pdf); for (var i = 1; i <= pdf.GetNumberOfPages(); i++) { var page = pdf.GetPage(i); var structRoot = structTree.GetStructRoot(); var processedText = new StringBuilder(); // 递归遍历结构树节点 void TraverseStructNode(PdfStructElem node) { if (node.GetRole() == PdfName.H1) { processedText.AppendLine($"# {node.GetKidsContent()}"); } else if (node.GetRole() == PdfName.L) { // 处理列表节点 foreach (var kid in node.GetKids()) { if (kid is PdfStructElem listItem) { processedText.AppendLine($"- {listItem.GetKidsContent()}"); } } } else if (node.GetRole() == PdfName.P) { processedText.AppendLine(node.GetKidsContent()); } // 递归处理子节点 foreach (var kid in node.GetKids()) { if (kid is PdfStructElem childNode) { TraverseStructNode(childNode); } } } if (structRoot != null) { TraverseStructNode(structRoot); } // 写入Elasticsearch的逻辑和你原来的一致 }
这个方法的前提是你的PDF保留了结构化标签,如果是用轻量工具转的PDF可能没有,这时候就需要用第一种方法。
3. 对提取后的纯文本做后处理
如果上面两种方法对你来说有点复杂,也可以对已提取的纯文本做简单的后处理,比如:
- 识别开头有多个空格的行,当成列表项,替换成
-前缀; - 识别短且全大写的行,当成标题,加上加粗标记;
示例代码片段:
var pageText = PdfTextExtractor.GetTextFromPage(pdf.GetPage(i)); var lines = pageText.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); var processedLines = new List<string>(); foreach (var line in lines) { var trimmedLine = line.TrimStart(); var indentLength = line.Length - trimmedLine.Length; // 缩进超过4个空格,判定为列表项 if (indentLength > 4) { processedLines.Add($"- {trimmedLine}"); } // 短文本且全大写,判定为标题 else if (trimmedLine.Length < 30 && trimmedLine.All(c => char.IsUpper(c) || char.IsWhiteSpace(c))) { processedLines.Add($"**{trimmedLine}**"); } else { processedLines.Add(trimmedLine); } } var structuredText = string.Join(Environment.NewLine, processedLines); // 然后用structuredText写入Elasticsearch
额外提醒
PPT转PDF的工具会直接影响提取效果,尽量用Microsoft Office原生导出PDF,而不是第三方轻量工具——原生导出的PDF会保留更多排版元数据,不管用哪种提取方法效果都更好。
内容来源于stack exchange
相关产品推荐
相关产品推荐

