You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#使用PdfPig等免费库逐行提取PDF文本的问题求解

C# 免费方案:基于PdfPig实现PDF文本逐行拆分

原代码失效原因

  • 直接按BoundingBox.Bottom精确值分组无法覆盖所有行:PDF排版中同一行的文字会受字体、字号、上下标、对齐偏移影响,Y轴坐标存在小幅浮动,精确匹配必然出现漏判、错判。
  • 直接提取\r\n拆分的思路不成立:PDF是排版格式而非流式文档格式,不存在内置的硬换行标记,所有换行都需要通过文字坐标计算得到,GetWords()本身不会返回换行符。

修复方案

核心逻辑是引入行高容差做Y坐标聚类,而非精确匹配坐标值:

  1. 单页内先计算所有文字的平均高度,取平均字高的20%~40%作为同一条行的判定容差
  2. 按Y坐标从页面顶部到底部遍历所有单词,Y轴差值在容差范围内的单词判定为同一行
  3. 同一行内的单词按X坐标从左到右排序后拼接,避免文字顺序错乱
  4. 所有页面处理完成后统一写入纯文本文件

完整实现代码

using UglyToad.PdfPig;
using UglyToad.PdfPig.Content;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System;

// 路径配置
const string pdfInputPath = @"fileName.pdf";
const string txtOutputPath = @"extracted_text.txt";
// 行容差系数:数值越大越容易把相邻行合并,数值越小越容易把同一行拆分,常规文档取0.3即可
const double lineToleranceRatio = 0.3;

List<string> allExtractedLines = new List<string>();

using (PdfDocument document = PdfDocument.Open(pdfInputPath))
{
    foreach (Page page in document.GetPages())
    {
        IReadOnlyList<Word> pageWords = page.GetWords();
        if (pageWords.Count == 0)
        {
            allExtractedLines.Add(string.Empty);
            continue;
        }

        // 动态计算当前页的行判定容差
        double avgWordHeight = pageWords.Average(w => w.BoundingBox.Height);
        double allowedYDeviation = avgWordHeight * lineToleranceRatio;

        // 第一步:按Y坐标聚类拆分出行
        List<List<Word>> lineClusters = new List<List<Word>>();
        // PDF坐标原点在页面左下角,Y值越大位置越靠上,因此按Y降序遍历
        List<Word> wordsSortedByY = pageWords.OrderByDescending(w => w.BoundingBox.Bottom).ToList();
        
        List<Word> currentLineWords = new List<Word> { wordsSortedByY[0] };
        double currentLineBaseY = wordsSortedByY[0].BoundingBox.Bottom;

        for (int i = 1; i < wordsSortedByY.Count; i++)
        {
            Word currentWord = wordsSortedByY[i];
            double yDifference = Math.Abs(currentWord.BoundingBox.Bottom - currentLineBaseY);
            
            if (yDifference <= allowedYDeviation)
            {
                currentLineWords.Add(currentWord);
            }
            else
            {
                lineClusters.Add(currentLineWords);
                currentLineWords = new List<Word> { currentWord };
                currentLineBaseY = currentWord.BoundingBox.Bottom;
            }
        }
        lineClusters.Add(currentLineWords);

        // 第二步:行内按X坐标从左到右排序,拼接为文本
        foreach (List<Word> line in lineClusters)
        {
            string lineContent = string.Join(" ", line
                .OrderBy(w => w.BoundingBox.Left)
                .Select(w => w.Text));
            allExtractedLines.Add(lineContent.Trim());
        }
    }
}

// 写入纯文本文件
File.WriteAllLines(txtOutputPath, allExtractedLines);

调参说明

如果提取结果存在行拆分错误,可微调lineToleranceRatio参数:

  • 出现同一行被拆为多条:适当调大参数,最高不超过0.5
  • 出现上下相邻行被合并为一条:适当调小参数,最低不小于0.1
    该方案仅适用于可编辑文本PDF,扫描版图片PDF需要先接入OCR能力再做行拆分。

内容的提问来源于stack exchange,提问作者Ben.S

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 13:45:32