如何使用PDFbox抽取分列的PDF页面

在尝试抽取PDF文档的内容时,了解到了Java下面有个PDFbox的工具包,整体使用的效果还是不错的。在解决本文的主要问题前,先了解一下PDF这种半结构化的文档。

PDF Structure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|----------------------------+
| --------------------------|
| | Header | | <-----文件头,表示版本.%PDF-1.M
| | | |
| --------------------------|
| | | |
| | Body | | <-----文件体,由一系列PDF对象组成
| | | |
| | | |
| --------------------------|
| | Cross-reference | | <-----交叉引用表,包含指向所有间接
| | table | | 对象的文件位置索引的列表
| | (xref) | |
| --------------------------|
| | Trailer | | <-----包含文件的根节点信息和
| | | | 文件解析的起点信息
| --------------------------|
+----------------------------+

本文主要解决的问题是针对有两列或者多列的页面,提取其中的文字。这一点根据 @mkl的提示,实现了基本的demo。
在Stack Overflow上有一个也是关于PDF文档多列抽取的问题,PDF文档的处理是一项复杂的工程,现在还尚未有非常成熟的一套解决方案。Java平台上的开源工具 PDFbox和商业服务iText相对而言做的比较完整,但对于的文档还需要定义额外的提取规则。这里是 stackoverflow 下的问题及 @mkl 的回答。extract PDF text by columns

PDFBox does not analyze the page content to recognize columns. If you do the analysis, though, it allows you to extract text column by column if you provide the column rectangles as reguions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
StringBuilder pdfText = new StringBuilder();
// 重写 writeString(String, List<TextPosition>)方法以加载更多文本的信息,
// 增加使用字体的名称来扩展文本信息
PDFTextStripperByArea stripper = new PDFTextStripperByArea();

stripper.setSortByPosition(true);
// 定位到左边一栏的页面
Rectangle rectLeft = new Rectangle(10, 60, 320, 820);
// 定位到右边一栏的页面
Rectangle rectRight = new Rectangle(330, 60, 320, 820);

stripper.addRegion("leftColumn", rectLeft);

stripper.addRegion("rightColumn", rectRight);

PDPageTree allPages = document.getDocumentCatalog().getPages();
int pageNumber = document.getNumberOfPages();


String leftText = "";
String rightText = "";

for (int i = 0; i < pageNumber; i++) {

PDPage page = (PDPage) allPages.get(i);

stripper.extractRegions(page);
leftText = stripper.getTextForRegion("leftColumn");
rightText = stripper.getTextForRegion("rightColumn");

pdfText.append(leftText);
pdfText.append(rightText);

System.out.println("Page number "+ i);

}

其中这里的列的边界,是不断尝试获取的。

在做毕业设计的时候,看到一篇关于 寻找PDF文档区域边界的方法。论文题目是 Improving the Extraction of Text in PDFs by Simulating the Human Reading Order。通篇读下来,感觉这种方法还是很可靠地,近期会对论文中提到的算法进行复现。


很多同学对于上面代码中的 PDFTextStripperByArea类的重写比较感兴趣,我在这里贴一下代码吧。这里主要实现的是 把抽取出来的文本根据字体以及字号的不同打上标签,便于内容的提取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
PDFTextStripper stripper = new PDFTextStripper() {

String prevBaseFont = "";
float prevFontSize = 0;

protected void writeString(String text, List<TextPosition> textPositions) throws IOException {
StringBuilder builder = new StringBuilder();

for (TextPosition position : textPositions) {

// 获得该区域文本的字体名以及字体大小,根据这两点进行抽取
float fontSize = position.getFontSize();

String baseFont = position.getFont().getName();
if (baseFont != null && !baseFont.equals(prevBaseFont) && !(fontSize==prevFontSize)) {

builder.append('[').append(baseFont).append('|').append(fontSize).append(']');
prevBaseFont = baseFont;
prevFontSize = fontSize;
}

builder.append(position.getUnicode());

}

writeString(builder.toString());
}
};