我想从这个PDF文件中获取美国所有大学的列表,并将其放入CSV文件中.然后我将CSV文件导入SQL服务器(这样我就可以轻松运行查询).
我尝试了几个在线pdf到csv转换器和基于Java的pdf到CSV教程.没有任何效果.今天我花了6-8个小时才完成这项工作并且失败了.当我导入csv时,我的csv文件搞砸了,我的数据库中有很多空值.我甚至尝试过搜索DHS api,它可以给我这个信息,但没有找到.
有人可以帮我提取大学,就像他们在pdf文件中显示的那样吗?
PS:你也可以看到所有大学使用这个网址.但是,您必须手动滚动以提取所有结果.这将花费太长时间,数据不会采用pdf文件中给出的格式.
正如在对该问题的评论中所声称的,
考虑到相当直接的页面内容流样式,数据应该可以使用不太复杂的自定义文本提取器来提取.
详细地:
常规表条目内容按条目逐项绘制,每个条目按字段按阅读顺序排列.因此,在浏览内容流时,我们不必尝试重新安排内容以建立该订单.这使得这项任务相当容易.
因此,主要工作是忽略非条目,即第一页上的标题,指示新的第一个字母开始位置的条和页码.
我们是这样做的
(其他方法也可以做到,例如忽略底页区域中的所有内容来处理页码.)
现在我们只需要将条目拆分为它们的字段.
文档结构再次有用,因为它是一个非常统一的文档,表格列在每个页面上具有相同的位置和尺寸.所以我们只需要在固定的x值下进行剖析.
只有一个绊脚石:在一些条目中,原子文本块包含不同列的内容.例如,有时将F和M列的内容绘制为单个字符串,如"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示例中,绘制表头和第一个输入行的操作是:
/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.该方法(和一些助手)依次检查文本所在的字段(通过硬编码的位置边界),并相应地导出制表符分隔值.这个逻辑同样也可以使用其他库实现.
另一个解决方案没有太多努力来编写代码以获得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文件.也许你或其他人可能更喜欢这种做法.
| 归档时间: |
|
| 查看次数: |
3262 次 |
| 最近记录: |