如何将PDF文件中的行提取到csv文件中?

sta*_*ck1 4 pdf

我想从这个PDF文件中获取美国所有大学的列表,并将其放入CSV文件中.然后我将CSV文件导入SQL服务器(这样我就可以轻松运行查询).

我尝试了几个在线pdf到csv转换器和基于Java的pdf到CSV教程.没有任何效果.今天我花了6-8个小时才完成这项工作并且失败了.当我导入csv时,我的csv文件搞砸了,我的数据库中有很多空值.我甚至尝试过搜索DHS api,它可以给我这个信息,但没有找到.

有人可以帮我提取大学,就像他们在pdf文件中显示的那样吗?

PS:你也可以看到所有大学使用这个网址.但是,您必须手动滚动以提取所有结果.这将花费太长时间,数据不会采用pdf文件中给出的格式.

mkl*_*mkl 9

正如在对该问题的评论中所声称的,

考虑到相当直接的页面内容流样式,数据应该可以使用不太复杂的自定义文本提取器来提取.

详细地:

页面内容流样式

常规表条目内容按条目逐项绘制,每个条目按字段按阅读顺序排列.因此,在浏览内容流时,我们不必尝试重新安排内容以建立该订单.这使得这项任务相当容易.

因此,主要工作是忽略非条目,即第一页上的标题,指示新的第一个字母开始位置的条和页码.

我们是这样做的

  • 忽略图形和非黑色文本,它们负责标题和第一个字母条;
  • 不接受不以SCHOOL NAME列中的数据开头的条目,该列负责处理仅存在于CAMPUS NAME列中的页码.

(其他方法也可以做到,例如忽略底页区域中的所有内容来处理页码.)

现在我们只需要将条目拆分为它们的字段.

文档结构再次有用,因为它是一个非常统一的文档,表格列在每个页面上具有相同的位置和尺寸.所以我们只需要在固定的x值下进行剖析.

只有一个绊脚石:在一些条目中,原子文本块包含不同列的内容.例如,有时将FM列的内容绘制为单个字符串,如"YN",并通过字符间距引入光学距离.

所以我们必须逐个字符地处理文本块,而不是整体.

示例实现

我在这里使用Java和PDF库iText(当前版本5.5.7开发快照).这完全不意味着它不能做一个使用不同的设置,这仅仅是建立我最习惯.

由于分离器我用的是制表符,因为其他可能的候选人还出现作为文本的一部分,我不希望有应对逃避它们.

这是RenderListener为了处理内容而引入的自定义类,如上所述:

public class CertifiedSchoolListExtractionStrategy implements RenderListener
{
    public CertifiedSchoolListExtractionStrategy(Appendable data, Appendable nonData)
    {
        this.data = data;
        this.nonData = nonData;
    }

    //
    // RenderListener implementation
    //
    @Override
    public void beginTextBlock() { }

    @Override
    public void endTextBlock() { }

    @Override
    public void renderImage(ImageRenderInfo renderInfo) { }

    @Override
    public void renderText(TextRenderInfo renderInfo)
    {
        try
        {
            Vector startPoint = renderInfo.getBaseline().getStartPoint();
            BaseColor fillColor = renderInfo.getFillColor();
            if (fillColor instanceof GrayColor && ((GrayColor)fillColor).getGray() == 0)
            {
                if (debug)
                    data.append(String.format("%4d\t%3.3f %3.3f\t%s\n", chunk, startPoint.get(I1), startPoint.get(I2), renderInfo.getText()));
                for (TextRenderInfo info : renderInfo.getCharacterRenderInfos())
                {
                    renderCharacter(info);
                }
            }
            else
            {
                if (debug)
                    nonData.append(String.format("%4d\t%3.3f %3.3f\t%s\n", chunk, startPoint.get(I1), startPoint.get(I2), renderInfo.getText()));
                if (currentField > -1)
                    finishEntry();
                entryBuilder.append(renderInfo.getText());
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        finally
        {
            chunk++;
        }
    }

    public void renderCharacter(TextRenderInfo renderInfo) throws IOException
    {
        Vector startPoint = renderInfo.getBaseline().getStartPoint();

        float x = startPoint.get(I1);

        if (currentField > -1)
        {
            if (isInCurrentField(x))
            {
                entryBuilder.append(renderInfo.getText());
                return;
            }
            if (isInNextField(x))
            {
                currentField++;
                entryBuilder.append('\t').append(renderInfo.getText());
                return;
            }
            finishEntry();
        }
        if (isInNextField(x))
        {
            finishEntry();
            currentField = 0;
        }
        entryBuilder.append(renderInfo.getText());
    }

    public void close() throws IOException
    {
        finishEntry();
    }

    boolean isInCurrentField(float x)
    {
        if (currentField == -1)
            return false;

        if (x < fieldstarts[currentField])
            return false;

        if (currentField == fieldstarts.length - 1)
            return true;

        return x <= fieldstarts[currentField + 1];
    }

    boolean isInNextField(float x)
    {
        if (currentField == fieldstarts.length - 1)
            return false;

        if (x < fieldstarts[currentField + 1])
            return false;

        if (currentField == fieldstarts.length - 2)
            return true;

        return x <= fieldstarts[currentField + 2];
    }

    void finishEntry() throws IOException
    {
        if (entryBuilder.length() > 0)
        {
            if (currentField == fieldstarts.length - 1)
            {
                data.append(entryBuilder).append('\n');
            }
            else
            {
                nonData.append(entryBuilder).append('\n');
            }

            entryBuilder.setLength(0);
        }
        currentField = -1;
    }

    //
    // hidden members
    //
    final Appendable data, nonData;
    boolean debug = false;

    int chunk = 0;
    int currentField = -1;
    StringBuilder entryBuilder = new StringBuilder();

    final int[] fieldstarts = {20, 254, 404, 415, 431, 508, 534};
}
Run Code Online (Sandbox Code Playgroud)

(CertifiedSchoolListExtractionStrategy.java)

我们可以像这样使用它:

@Test
public void testCertifiedSchoolList_9_16_2015() throws IOException
{
    try (   Writer data = new OutputStreamWriter(new FileOutputStream(new File(RESULT_FOLDER, "data.txt")), "UTF-8");
            Writer nonData = new OutputStreamWriter(new FileOutputStream(new File(RESULT_FOLDER, "non-data.txt")), "UTF-8")    )
    {
        CertifiedSchoolListExtractionStrategy strategy = new CertifiedSchoolListExtractionStrategy(data, nonData);
        PdfReader reader = new PdfReader("certified-school-list-9-16-2015.pdf");

        PdfReaderContentParser parser = new PdfReaderContentParser(reader);
        for (int page = 1; page <= reader.getNumberOfPages(); page++)
            parser.processContent(page, strategy);
        strategy.close();
    }
}
Run Code Online (Sandbox Code Playgroud)

(ExtractCertifiedSchoolList.java)

现在data.txt包含所有条目作为制表符分隔的行,并non-data.txt忽略所有内容.

在幕后

要了解这里发生了什么,首先必须知道如何在PDF文档页面内容的组织和如何(对于给定的样本代码)的iText上运行.

在PDF内部

PDF文档是从一些基本对象类型,一些基本类型(数字,字符串,...)和一些更复杂的(其他对象或流的数组或字典)建,构筑物.

PDF文档中的页面由这样的字典对象表示,该对象包含定义某些页面属性(如页面维度)的条目以及引用定义页面上绘制内容的对象的其他条目:内容流.

内容流本质上包含一系列操作,可以

  • 选择一种颜色(用于抚摸或填充),
  • 定义一条路径(移动到某个点,线到另一个点,曲线到另一个点,......),
  • 划动或填充这样的路径,
  • 在某处绘制一些位图图像,
  • 在某处画一些文字,或者
  • 做很多其他的事情.

对于手头的问题,我们主要对绘制文本所涉及的操作感兴趣.相较于文字处理器的操作并不需要这么长的文字串,并安排它作为一个段落,而是更原始地在这里移动文本位置,从这里得出这个短字符串,再移动文本位置,并绘制另一个字符串出现.

例如,在PDF示例中,绘制表头和第一个输入行的操作是:

/TT2 1 Tf
Run Code Online (Sandbox Code Playgroud)

选择大小为1的字体TT2.

9.72 0 0 9.72 20.16 687.36 Tm
Run Code Online (Sandbox Code Playgroud)

设置文本矩阵以将文本插入坐标移动到20.16,687.36,并按照因子9.72缩放所有内容.

0 g
Run Code Online (Sandbox Code Playgroud)

选择灰度填充颜色黑色

0 Tc
0 Tw
Run Code Online (Sandbox Code Playgroud)

选择其他字符和字间距为0.

(SCHOOL)Tj
Run Code Online (Sandbox Code Playgroud)

在这里画"学校".

/TT1 1 Tf
Run Code Online (Sandbox Code Playgroud)

选择字体TT1.

3.4082 0 TD
Run Code Online (Sandbox Code Playgroud)

在x方向上将文本插入点移动3.4082.

<0003>Tj
Run Code Online (Sandbox Code Playgroud)

绘制一个空格字符(当前字体使用不同的编码,每个字符使用16位,而不是8,这里以十六进制表示).

/TT2 1 Tf
.2261 0 TD
[(NAME)-17887.4(CAMPUS)]TJ
Run Code Online (Sandbox Code Playgroud)

选择字体,移动文本插入点,然后绘制字符串"NAME",然后绘制17887.4文本单位的间隙,然后绘制"CAMPUS".

/TT1 1 Tf
24.1809 0 TD
<0003>Tj
/TT2 1 Tf
.2261 0 TD
[(NAME)-8986.6(F)-923.7(M)-459.3(CITY)-6349.9(ST)-1390.2(CAMPUS)]TJ
/TT1 1 Tf
28.5147 0 TD
<0003>Tj
/TT2 1 Tf
.2261 0 TD
(ID)Tj
Run Code Online (Sandbox Code Playgroud)

绘制标题行的其余部分.

/TT4 1 Tf
-56.782 -1.3086 TD
Run Code Online (Sandbox Code Playgroud)

向左移动56.782并向下移动1.3086,即向第一个输入行的开头移动.

("I)Tj
/TT3 1 Tf
.6528 0 TD
<0003>Tj
/TT4 1 Tf
.2261 0 TD
(Am")Tj
/TT3 1 Tf
1.7783 0 TD
<0003>Tj
/TT4 1 Tf
.2261 0 TD
(School)Tj
/TT3 1 Tf
2.6919 0 TD
<0003>Tj
/TT4 1 Tf
.2261 0 TD
[(Inc.)-16894.2("I)]TJ
/TT3 1 Tf
18.9997 0 TD
<0003>Tj
/TT4 1 Tf
.2261 0 TD
(Am")Tj
/TT3 1 Tf
1.7783 0 TD
<0003>Tj
/TT4 1 Tf
.2261 0 TD
(School)Tj
/TT3 1 Tf
2.6919 0 TD
<0003>Tj
/TT4 1 Tf
.2261 0 TD
[(Inc.)-8239.9(Y)-1018.9(N)-576.7(Mount)]TJ
/TT3 1 Tf
15.189 0 TD
<0003>Tj
/TT4 1 Tf
.2261 0 TD
[(Shasta)-2423.3(CA)-2443.7(41789)]TJ
Run Code Online (Sandbox Code Playgroud)

并绘制第一个输入行.

如您所见,正如我上面提到的,表格内容按阅读顺序绘制.甚至多行列条目也按所需顺序排列,例如校园名称"AF West of Westlake Village":

[(Inc.)-7228.7(A)]TJ
/TT3 1 Tf
9.26 0 TD 
<0003>Tj
/TT4 1 Tf
.2261 0 TD
(F)Tj
/TT3 1 Tf
.4595 0 TD
<0003>Tj
/TT4 1 Tf
.2261 0 TD
(International)Tj
/TT3 1 Tf
5.2886 0 TD
<0003>Tj
/TT4 1 Tf
.2261 0 TD
(of)Tj
/TT3 1 Tf
.8325 0 TD
<0003>Tj
/TT4 1 Tf
.2261 0 TD
(Westlake)Tj
/TT3 1 Tf
3.7739 0 TD
<0003>Tj
/TT4 1 Tf
-11.8374 -1.3086 TD
Run Code Online (Sandbox Code Playgroud)

向下移动到列的第二行.

(Village)Tj
15.4938 1.3086 TD
Run Code Online (Sandbox Code Playgroud)

再次上移到条目的主线.

[(Y)-1018.9(N)-576.7(Westlake)]TJ 
Run Code Online (Sandbox Code Playgroud)

因此我们可以消化文本,不需要排序(内容可以以完全不同的方式排序).

但我们也看到没有明显的列起点和终点.因此,为了将文本与列相关联,我们必须计算每个字符的位置,并将它们与外部给定的列起始位置进行比较.

解析由图书馆支持

PDF库通常提供一些帮助解析此类内容流的机制.

对此有两种基本体系结构,库可以解析内容流

  • 作为一个整体,并提供它作为一个大的定位文本块或
  • 或者使用收听者模式分段和转发单独定位的文本块.

最初的变体似乎更容易处理,但可能有很大的资源需求(我遇到了多MB内容流),而第二个似乎有点难以处理,但内存需求较小.

我使用的库(iText)遵循后一种方法,但您的问题也可以使用前一个库来解决.

RenderListener是在这里实现的侦听器接口,这些renderText方法检索具有位置的个人文本块.

在上面的实现(CertifiedSchoolListExtractionStrategy)中,该renderText方法首先检查与块相关联的填充颜色,并且仅转发黑色文本以供进一步处理renderCharacter.该方法(和一些助手)依次检查文本所在的字段(通过硬编码的位置边界),并相应地导出制表符分隔值.这个逻辑同样也可以使用其他库实现.


col*_*yre 7

另一个解决方案没有太多努力来编写代码以获得pdf读取:有一个带有很好-layout标志的linux工具,如askubuntu中已经提到的那样.它叫做pdftotext:

$ pdftotext -layout <input.pdf> <output.txt>
Run Code Online (Sandbox Code Playgroud)

它对您提供的pdf文件非常有前景.当然,它不是解决您问题的完整解决方案.但是你要做的就是清理文本输出.这可能比其他解决方案的时间更短.

这是一个示例:

$ head -30 test.txt
                                                                                                          Updated
                                     SEVP Certified Schools                                      September 16, 2015
SCHOOL NAME                                     CAMPUS NAME                            F M CITY                     ST   CAMPUS ID
"I Am" School Inc.                              "I Am" School Inc.                     Y N Mount Shasta             CA     41789
424 Aviation                                    424 Aviation                           N Y Miami                    FL     103705
                                                            ? A ?
A F International School of Languages Inc.      A F International College              Y   N Los Angeles            CA      9538
A F International School of Languages Inc.      A F International of Westlake          Y   N Westlake Village       CA     57589
                                                Village
A. T. Still University of Health Sciences       Kirksville Coll of Osteopathic         Y   N Kirksville         MO         3606
                                                Medicine
Aaron School                                    Aaron School ? 30th Street             Y   N   New York             NY    159091
Aaron School                                    Aaron School                           Y   N   New York             NY    114558
ABC Beauty Academy, INC.                        ABC Beauty Academy, INC.               N   Y   Flushing             NY    95879
ABC Beauty Academy, LLC                         ABC Beauty Academy                     N   Y   Garland              TX    50677
Abcott Institute                                Abcott Institute                       N   Y   Southfield           MI    197890
Aberdeen Catholic School System                 Roncalli Primary                       Y   N   Aberdeen             SD    180510
Aberdeen Catholic School System                 Roncalli                               Y   N   Aberdeen             SD    21405
Aberdeen Catholic School System                 Roncalli Elementary                    Y   N   Aberdeen             SD    180511
Aberdeen School District 6?1                    Aberdeen Central High School           Y   N   Aberdeen             SD    36568
Abiding Savior Lutheran School                  Abiding Savior Lutheran School         Y   N   Lake Forest          CA     9920
Abilene Christian Schools                       Abilene Christian Schools              Y   N   Abilene              TX     8973
Abilene Christian University                    Abilene Christian University           Y   N   Abilene              TX     7498
Abington Friends School                         Abington Friends School                Y   N   Jenkintown           PA    20191
Above It All, Inc                               Benchmark Flight /Hawaii Flight        N   Y   Kailua?Kona          HI    24353
                                                Academy
Abraham Baldwin Agricultural College            Tifton Campus                          Y   N Tifton             GA         6931
Abraham Joshua Heschel School                   Abraham Joshua Heschel School          Y   N New York           NY        106824

ABT Jacqueline Kennedy Onassis School           ABT Jacqueline Kennedy Onassis         Y   Y New York               NY     52401
Run Code Online (Sandbox Code Playgroud)

因此,这会将您的文本输出转换为数据库可读的csv文件.也许你或其他人可能更喜欢这种做法.

  • 作为在 PDF 解析方面拥有丰富经验的人,这是迄今为止最好的方法,无需聘请孟加拉国自由职业者将数据手动输入 Excel (2认同)