尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

C#上位机PDF文本提取实战:PdfPig库从入门到踩坑

C#上位机PDF文本提取实战:PdfPig库从入门到踩坑 简介PdfPig是一个面向C#开发者的PDF处理库作为PdfBox的C#移植主要用于读取和提取PDF中的文本、几何形状及其他内容同时支持创建包含文本和形状的简单PDF。该库基于.NET Standard可在多种.NET平台上运行适合处理PDF文本抽取、版面分析、内容检索、文档结构化等任务尤其适合需要将PDF解析为HOCR、ALTO XML、Page XML等格式的文档分析和OCR流程。资源压缩包约27.03MB内含PdfPig的安装说明、快速上手示例以及从0.0.x迁移到0.1.x的版本迁移指南可帮助开发者熟悉API调用方式降低项目集成门槛。目前已有570人学习/下载。读者能够掌握利用PdfPig逐页读取文本、获取页面几何结构、生成简单PDF的方法并在文本挖掘、档案数字化、表单提取等场景中直接复用相关经验大幅减少重复开发和排错时间提升工作效率。 最近在做一个C#上位机项目客户丢过来一堆PDF格式的检测报告让我批量捞里面的测试项和结果。我第一反应是找现成的PDF解析库试了一圈发现坑不少有的库免费版有页数限制有的库版权协议绕不开最后在GitHub上翻到了PdfPig一个被称为“PdfBox的C#移植版”的开源库。断断续续用它做了好几个工具今天想把这块经验系统地整理出来给同样在C#里折腾PDF文本提取的同学做个参考。文章会覆盖选型思路、基础用法、图像提取、坐标定位、完整实操和踩坑记录尽量做到拿来就能用。1. 为什么选PdfPig先搞清楚它和PdfBox的关系1.1 PdfPig到底是什么PdfPig是GitHub上的一个开源项目由UglyToad社区维护核心定位是“在.NET环境下读取和提取PDF内容”。它最初的设计灵感确实来自Apache PdfBox所以不少人直接叫它“PdfBox的C#移植版”但实际上PdfPig并没有直接搬运PdfBox的Java代码而是用C#重新实现了PDF文件格式的解析层。这一点很重要因为它的API设计和PdfBox有相似之处但它完全遵循.NET生态的习惯用起来没有那种“从Java翻译过来”的生硬感。很多人会问市面上PDF解析库那么多为什么还要用PdfPig我个人的答案是它解决了“纯C#、免费、能商用、解析质量够好”这四个条件同时满足的问题。iTextSharp老版本还在用AGPL协议商用有风险Spire.PDF免费版有页数限制和页眉水印PdfiumViewer虽然性能好但它本质是对C库的封装部署的时候要额外带原生DLL。PdfPig是100%托管代码NuGet装完直接跑Apache 2.0协议商用很友好。1.2 它擅长做什么、不擅长做什么PdfPig擅长的是“读”不是“写”。它能稳定提取文字、图片、位置信息、注释、书签、表单值等但不太适合拿来做PDF编辑、修改内容和样式的活。我实际测试下来它对纯文本型PDF的提取效果非常好甚至能应对一些压缩过的内容流遇到扫描版PDF就无能为力了因为那本质是图像需要先走OCR路线。注意PdfPig不依赖任何外部组件不需要Adobe Reader也不需要在服务器上安装额外的运行时。这对部署在客户内网环境的上位机来说非常关键。2. 快速上手先把文本提取出来2.1 安装与第一个示例安装很简单Visual Studio的NuGet包管理器搜索UglyToad.PdfPig或者直接在包管理器控制台执行Install-Package UglyToad.PdfPig最新稳定版对.NET Standard 2.0和.NET Core 3.1都有支持所以传统.NET Framework 4.6.1的项目、.NET 6/8的项目都能用兼容性很广。第一个示例直接上代码从PDF里提取全部页面文本using UglyToad.PdfPig; using UglyToad.PdfPig.Content; string pdfPath C:\test\sample.pdf; using (PdfDocument document PdfDocument.Open(pdfPath)) { Console.WriteLine($PDF总页数: {document.NumberOfPages}); for (int i 1; i document.NumberOfPages; i) { using (Page page document.GetPage(i)) { Console.WriteLine($---- 第{i}页 ----); Console.WriteLine(page.Text); } } }这段代码看起来简单但有几个关键点必须说清楚。第一PdfDocument实现了IDisposable实际使用中一定要用using包起来否则文件句柄不会释放尤其是批量处理几百个PDF的时候不释放句柄会导致后续文件无法读写甚至内存暴涨。第二GetPage方法返回的Page对象同样需要释放虽然它内部只是引用解析器但养成随手using的习惯没有坏处。2.2 必须先理解的PDF文本结构第一次用PdfPig的人经常会疑惑为什么page.Text拿出来之后文字的排列顺序和PDF里看到的顺序不一样这还真不是PdfPig的bug而是PDF本身的特性。PDF里的文本不是像Word那样按段落存储的它是一堆“绘制指令”的集合。比如“提取PDF内容”这句话在PDF内容流里可能是“提”和“取”用了不同的字体指令或者“PDF”和“内容”之间是从右往左写的坐标变化。PdfPig的策略是尽可能按照内容流里的操作顺序把这些文字拼起来但如果PDF生成工具在写入指令时没有遵循合理的顺序提取出来的文字顺序就会很乱。我在分析一些国产报表软件生成的PDF时就经常遇到文本倒序、跨行混插的情况。遇到这种问题先别慌记住一个原则PdfPig把每个字符的位置信息都保留下来了。你完全可以自己写排序逻辑按坐标把字符重新组织成行和段落。这个技巧后面我会专门展开讲。3. 核心细节解析文本之外的三个实用功能3.1 按坐标区域提取指定位置的文本这个功能在做自动化工具的时候极其有用。比如客户发给我的检测报告页眉处是报告编号右下角是页码和日期正文区才是检测数据。如果直接提取全页文本我还得用正则去匹配内容但PdfPig提供了字符级的位置信息我可以直接按坐标“画个框”把框内的文字单独取出来。关键API是page.Letters它返回一个包含字符和几何信息的集合每个Letter对象都有StartX、StartY、Width、Height和Value属性。下面的代码展示了如何提取某个矩形区域内的文字using (Page page document.GetPage(1)) { var letters page.Letters; // 假设目标区域是X从100到300Y从500到600PDF坐标原点在左下角 var regionText string.Concat(letters .Where(l l.StartX 100 l.StartX 300) .Where(l l.StartY 500 l.StartY 600) .OrderBy(l l.StartY) // 先按行排 .ThenBy(l l.StartX) // 再按列排 .Select(l l.Value)); Console.WriteLine(regionText); }这里有个坐标系统的坑要提醒PDF的坐标系原点在页面左下角Y轴向上和Windows窗体屏幕坐标的左上角原点正好相反。所以如果你是从WinForm的控件上获取的鼠标坐标直接用会出问题需要做一个Y轴翻转yPdf page.Height - yScreen。我第一次做这个转换的时候翻了好几次最后看官方文档才确认坐标系的问题。3.2 提取页面中的图片PdfPig也能提取页面里的图片虽然它不支持把图片“渲染”成PDF页面那样复杂的效果但获取原始的图片字节完全没问题。代码方式如下using (Page page document.GetPage(1)) { var images page.GetImages(); int index 0; foreach (var image in images) { byte[] rawBytes image.RawBytes; string ext image.RawBytes.Length 2 image.RawBytes[0] 0xFF image.RawBytes[1] 0xD8 ? jpg : png; File.WriteAllBytes($C:\output\page1_img{index}.{ext}, rawBytes); index; } }RawBytes返回的是图片在PDF内部的实际编码字节可能是JPEG、PNG也可能是JPX2000格式。实际项目中可以直接根据文件头判断格式然后决定落盘的扩展名。这里要注意部分PDF的图片数据经过Flate压缩RawBytes拿到的是压缩后的数据直接保存可能无法打开。我遇到这种情况不多但真遇到了也别慌可以先用ICSharpCode.SharpZipLib之类的库做一次解压缩再保存。如果你只在简单的业务场景用建议先在自己负责的PDF样本上测一遍看看图片能否正常打开。3.3 处理加密PDF和带书签的文档企业内部的PDF很多都带打开密码PdfPig支持传入密码用法如下using (PdfDocument document PdfDocument.Open(pdfPath, new ParsingOptions { Password 123456 })) { // 正常读取 }如果你不知道密码Open会直接抛PdfDocumentEncryptedException这个异常在业务代码里值得单独捕获方便提示操作人员。PDF书签大纲的读取也很简单document.TryGetBookmarks(out var bookmarks)可以拿到文档的书签层级结构。我做合同批量归档工具的时候就用这个功能读取章节标题来生成文件名小试一把就把以前手工命名的活儿自动化了。4. 实操过程做一个批量PDF文本提取工具4.1 完整代码与流程光讲碎片API不过瘾我放一个完整的批量工具代码。功能是扫描某个文件夹下的所有PDF文件逐个提取文本、统计页数、记录图片数量最后汇总成一个文本报告。这个工具我实际在客户机器上跑过处理200个PDF大约两三分钟就能完成。using UglyToad.PdfPig; using UglyToad.PdfPig.Content; using System.Text; class BatchPdfExtractor { static void Main(string[] args) { string inputDir C:\pdf_reports; string outputDir C:\pdf_reports\output; Directory.CreateDirectory(outputDir); var pdfFiles Directory.GetFiles(inputDir, *.pdf); var summary new StringBuilder(); foreach (var file in pdfFiles) { try { using (var document PdfDocument.Open(file)) { int pageCount document.NumberOfPages; int imageCount 0; var textContent new StringBuilder(); for (int i 1; i pageCount; i) { using (var page document.GetPage(i)) { textContent.AppendLine($ Page {i} ); textContent.AppendLine(page.Text); imageCount page.GetImages().Count(); } } // 写文本文件 string txtFile Path.Combine(outputDir, Path.GetFileNameWithoutExtension(file) .txt); File.WriteAllText(txtFile, textContent.ToString()); // 汇总信息 summary.AppendLine(${Path.GetFileName(file)} | 页数: {pageCount} | 图片数: {imageCount}); } } catch (Exception ex) { summary.AppendLine(${Path.GetFileName(file)} | 错误: {ex.Message}); } } File.WriteAllText(Path.Combine(outputDir, summary.txt), summary.ToString()); Console.WriteLine(处理完成结果保存在: outputDir); } }这套代码有几个很实用的设计一是用了try-catch包住整个处理流程避免一个加密PDF导致全流程崩溃二是把文件信息和错误信息都写进了汇总文件方便后期排查三是每个Page都单独释放长时间大批量跑的时候稳定性和内存占用表现都很好。4.2 实操中的数据量和稳定性表现我在实际环境里测试过一些真实的PDF文件这里给出一些经验参考数据方便大家评估性能文件情况页数用时备注纯文本合同无图片50页0.8秒速度很快带20张图片的技术手册80页3.2秒图片解码占大头复杂表格混排的报表30页1.5秒文本顺序需要后处理扫描版PDF20页0.5秒提取不到文字纯图像可以看到PdfPig的性能对大多数业务场景完全够用。但要注意它解析PDF时是把整个文档的解析树读进来的遇到单个100MB以上的超大PDF时内存占用会明显上升。这时候建议分批处理或者简化逻辑只提取需要的页面。4.3 和搜狗PDF编辑器、PDF转Word工具对比聊到PDF处理很多人会想到搜狗PDF编辑器、在线PDF转Word这类工具。这些工具面向的是人的交互式阅读和编辑而PdfPig面向的是程序自动化。本质上不冲突但有一个场景需要提醒一下如果用户用PDF编辑器把文档改了然后你的程序再解析结果可能和编辑器里看到的不一致因为部分编辑器保存时会改写内容流。如果要长期用程序提取文本建议让上游系统直接导出原始PDF不要人工二次编辑。5. 常见问题与排查技巧实录5.1 常见问题速查表实操中遇到的坑我整理成了表格方便直接对照排查现象可能原因解决办法提取出来全是乱码PDF字体未嵌入字符映射表缺失检查上游生成工具尽量要求嵌入字体或配合OCR兜底文本顺序错乱内容流绘制顺序和阅读顺序不一致用Letters的坐标信息自行排序加密PDF打不开缺少密码或加密算法过旧传入密码无密码则捕获异常提示用户页面图片提取后打不开图片数据经过Flate压缩先解压再保存或者检查文件头是否完整PDF打开时内存飙高文件里面含有大量高清图片按页读取不要一次加载所有资源部分页面提取为空页面可能是扫描图或内容为空检查页面Images集合走OCR兜底5.2 一个卡了大半天的字符编码问题说来有点丢人有次我在提取PDF时发现文本里包含“\u0013”“\u0015”这类控制字符导致写入TXT后打开一片乱码。排查后发现是PDF文件里使用了一些字体特殊字符比如换行符和回车符在内容流里被编码成了不可见字符。解决办法是提取后做一次清洗过滤掉非打印字符var cleaned new string(page.Text .Where(c !char.IsControl(c) || c \n || c \r) .ToArray());这里要注意别把所有控制字符都删光了\n和\r是文本换行的重要标记得保留。5.3 中文内容的处理心得PdfPig对中文的支持总体不错只要PDF里嵌入了中文字体提取出来就是正常的中文文本。如果遇到中文乱码多半是字体没有嵌入或者是CID字体映射的问题。我处理过一个上游系统导出的PDF所有中文提取出来都变成空白后来改用PdfPig的page.Letters逐个字符看发现每个字符的Value都是空字符串最终定位到是字体子集映射缺失这个问题只能靠上游修复程序层面没有太好的解决办法。6. 我在几个真实项目里的使用体会最后聊点实际操作下来的感受。PdfPig最大的价值在于“轻装上阵”大部分PDF解析需求它都能覆盖而且没有授权风险。我现在的习惯是先用PdfPig做一版原型如果确实遇到它无法解决的特殊场景再去引入更重的渲染型库。一个真心建议不要只依赖page.Text这个属性做一切判断。PdfPig的字符级API才是它的精髓坐标提取、区域过滤、自定义排序这些能力能让你在文本顺序混乱的场景下依然拿到干净的数据。我后续做的几个工具都迁移到了坐标提取的方案上准确性明显提升。最后再分享一个小技巧如果你需要反复解析同一批PDF可以考虑用PdfPig的PdfDocument.Open配合文件流复用避免多次打开文件带来的IO开销。我在一个定时任务里就是用FileStream分包读取把上千个文件轮的耗时从原来的十几分钟降到了几分钟。这个优化点很不起眼但真实场景下收益非常明显。本文还有配套的精品资源点击获取
返回列表