![]() |
含蓄的薯片 · 将Db2 for ...· 7 月前 · |
![]() |
会搭讪的日记本 · PHP开发编码规范_51CTO博客_php编码· 1 年前 · |
![]() |
面冷心慈的红酒 · 近年中文顶刊使用机器学习文本分析论文 - 哔哩哔哩· 1 年前 · |
![]() |
高兴的太阳 · c# mvvm ...· 1 年前 · |
我需要解析一个包含表格数据的PDF文件。我使用 PDFBox 来提取文件文本,以便稍后解析结果(字符串)。问题是,对于表格数据,文本提取不能像我预期的那样工作。例如,我有一个包含这样的表的文件(7列:前两列总是有数据,只有一列复杂度列有数据,只有一列财务列有数据):
+----------------------------------------------------------------+
| AIH | Value | Complexity | Financing |
| | | Medium | High | Not applicable | MAC/Other | FAE |
+----------------------------------------------------------------+
| xyz | 12.43 | 12.34 | | | 12.34 | |
+----------------------------------------------------------------+
| abc | 1.56 | | 1.56 | | | 1.56|
+----------------------------------------------------------------+
然后我使用PDFBox:
PDDocument document = PDDocument.load(pathToFile);
PDFTextStripper s = new PDFTextStripper();
String content = s.getText(document);
这两行数据将像这样提取:
xyz 12.43 12.4312.43
abc 1.56 1.561.56
最后两个数字之间没有空格,但这不是最大的问题。问题是我不知道最后两个数字是什么意思:中,高,不适用?MAC/Other,FAE?我没有数字和它们的列之间的关系。
我不需要使用PDFBox库,所以使用其他库的解决方案就可以了。我想要的是能够解析文件,并知道每个解析的数字的含义。
我不熟悉PDFBox,但是你可以试试 itext 。即使主页上显示PDF生成,您也可以进行PDF操作和提取。看看它是否适合你的用例。
从PDF中提取数据肯定是充满问题的。这些文档是通过某种自动过程创建的吗?如果是这样的话,你可以考虑将PostScript转换成未压缩的PostScript (试试PDF),看看PDF中是否包含一些你可以利用的规则模式。
打印到图像并在其上进行OCR如何?
听起来非常低效,但实际上PDF的目的就是让文本无法访问,你必须做你想做的事情。
您需要设计一种算法来以可用的格式提取数据。无论您使用哪个PDF库,您都需要执行此操作。字符和图形是通过一系列有状态的绘制操作来绘制的,即移动到屏幕上的这个位置并绘制字符'c‘的字形。
我建议您扩展
org.apache.pdfbox.pdfviewer.PDFPageDrawer
并覆盖
strokePath
方法。从那里,您可以截取水平和垂直线段的绘制操作,并使用该信息来确定表的列和行位置。然后,只需设置文本区域并确定在哪个区域中绘制了哪些数字/字母/字符。由于您知道区域的布局,因此您将能够辨别提取的文本属于哪一列。
此外,在视觉上分隔的文本之间可能没有空格的原因是,PDF通常不会绘制空格字符。相反,文本矩阵被更新,并发出“移动”的绘制命令,以绘制下一个字符和与上一个字符分开的“空间宽度”。
祝好运。
http://swftools.org/ 这些人有一个pdf2swf组件。它们还可以显示表格。他们还给出了消息来源。所以你可以去看看。
对我来说可能太晚了,但我认为这并不难。您可以扩展PDFTextStripper类并覆盖writePage()和processTextPosition(...)方法。在您的例子中,我假设列标题总是相同的。这意味着您知道每个列标题的x坐标,并且可以将数字的x坐标与列标题的x坐标进行比较。如果它们足够接近(您必须测试以确定有多接近),那么您可以说该数字属于该列。
另一种方法是在编写每个页面后截取"charactersByArticle“矢量:
@Override
public void writePage() throws IOException {
super.writePage();
final Vector<List<TextPosition>> pageText = getCharactersByArticle();
//now you have all the characters on that page
//to do what you want with them
}
了解您的列之后,您可以对x坐标进行比较,以确定每个数字所属的列。
数字之间没有任何空格的原因是您必须设置单词分隔符字符串。
我希望这对你或其他可能正在尝试类似事情的人有用。
我已经成功地解析了由 pdftotext 实用程序(sudo apt-get install poppler-utils)生成的文本文件。
File convertPdf() throws Exception {
File pdf = new File("mypdf.pdf");
String outfile = "mytxt.txt";
String proc = "/usr/bin/pdftotext";
ProcessBuilder pb = new ProcessBuilder(proc,"-layout",pdf.getAbsolutePath(),outfile);
Process p = pb.start();
p.waitFor();
return new File(outfile);
}
您可以在PDFBox中按区域提取文本。如果您使用的是Maven,请参阅
pdfbox-examples
工件中的
ExtractByArea.java
示例文件。代码片段看起来像这样
PDFTextStripperByArea stripper = new PDFTextStripperByArea();
stripper.setSortByPosition( true );
Rectangle rect = new Rectangle( 464, 59, 55, 5);
stripper.addRegion( "class1", rect );
stripper.extractRegions( page );
String string = stripper.getTextForRegion( "class1" );
问题是首先要得到坐标。我已经成功地扩展了普通的
TextStripper
,覆盖了
processTextPosition(TextPosition text)
,打印出了每个字符的坐标,并确定了它们在文档中的位置。
但有一个简单得多的方法,至少如果你用的是Mac电脑。在预览中打开⌘I以显示检查器,选择裁剪选项卡并确保单位为点,从工具菜单选择矩形选择,然后选择感兴趣的区域。如果您选择一个区域,检查器将向您显示坐标,您可以将其舍入并馈送到
Rectangle
构造函数参数中。您只需使用第一种方法确认源在哪里。
要从pdf文件中读取表内容,您只需使用任何API(我已经使用了iText的PdfTextExtracter.getTextFromPage() )将pdf文件转换为文本文件,然后在读取完主要任务后由java program..now读取txt文件。你必须过滤你需要的数据。你可以继续使用String类的split方法,直到找到你感兴趣的记录。这是我的代码,我用它从一个PDF文件中提取部分记录,并将其写入.CSV文件中。PDF文件的Url是.. http://www.cea.nic.in/reports/monthly/generation_rep/actual/jan13/opm_02.pdf
代码:-
public static void genrateCsvMonth_Region(String pdfpath, String csvpath) {
try {
String line = null;
// Appending Header in CSV file...
BufferedWriter writer1 = new BufferedWriter(new FileWriter(csvpath,
true));
writer1.close();
// Checking whether file is empty or not..
BufferedReader br = new BufferedReader(new FileReader(csvpath));
if ((line = br.readLine()) == null) {
BufferedWriter writer = new BufferedWriter(new FileWriter(
csvpath, true));
writer.append("REGION,");
writer.append("YEAR,");
writer.append("MONTH,");
writer.append("THERMAL,");
writer.append("NUCLEAR,");
writer.append("HYDRO,");
writer.append("TOTAL\n");
writer.close();
// Reading the pdf file..
PdfReader reader = new PdfReader(pdfpath);
BufferedWriter writer = new BufferedWriter(new FileWriter(csvpath,
true));
// Extracting records from page into String..
String page = PdfTextExtractor.getTextFromPage(reader, 1);
// Extracting month and Year from String..
String period1[] = page.split("PEROID");
String period2[] = period1[0].split(":");
String month[] = period2[1].split("-");
String period3[] = month[1].split("ENERGY");
String year[] = period3[0].split("VIS");
// Extracting Northen region
String northen[] = page.split("NORTHEN REGION");
String nthermal1[] = northen[0].split("THERMAL");
String nthermal2[] = nthermal1[1].split(" ");
String nnuclear1[] = northen[0].split("NUCLEAR");
String nnuclear2[] = nnuclear1[1].split(" ");
String nhydro1[] = northen[0].split("HYDRO");
String nhydro2[] = nhydro1[1].split(" ");
String ntotal1[] = northen[0].split("TOTAL");
String ntotal2[] = ntotal1[1].split(" ");
// Appending filtered data into CSV file..
writer.append("NORTHEN" + ",");
writer.append(year[0] + ",");
writer.append(month[0] + ",");
writer.append(nthermal2[4] + ",");
writer.append(nnuclear2[4] + ",");
writer.append(nhydro2[4] + ",");
writer.append(ntotal2[4] + "\n");
// Extracting Western region
String western[] = page.split("WESTERN");
String wthermal1[] = western[1].split("THERMAL");
String wthermal2[] = wthermal1[1].split(" ");
String wnuclear1[] = western[1].split("NUCLEAR");
String wnuclear2[] = wnuclear1[1].split(" ");
String whydro1[] = western[1].split("HYDRO");
String whydro2[] = whydro1[1].split(" ");
String wtotal1[] = western[1].split("TOTAL");
String wtotal2[] = wtotal1[1].split(" ");
// Appending filtered data into CSV file..
writer.append("WESTERN" + ",");
writer.append(year[0] + ",");
writer.append(month[0] + ",");
writer.append(wthermal2[4] + ",");
writer.append(wnuclear2[4] + ",");
writer.append(whydro2[4] + ",");
writer.append(wtotal2[4] + "\n");
// Extracting Southern Region
String southern[] = page.split("SOUTHERN");
String sthermal1[] = southern[1].split("THERMAL");
String sthermal2[] = sthermal1[1].split(" ");
String snuclear1[] = southern[1].split("NUCLEAR");
String snuclear2[] = snuclear1[1].split(" ");
String shydro1[] = southern[1].split("HYDRO");
String shydro2[] = shydro1[1].split(" ");
String stotal1[] = southern[1].split("TOTAL");
String stotal2[] = stotal1[1].split(" ");
// Appending filtered data into CSV file..
writer.append("SOUTHERN" + ",");
writer.append(year[0] + ",");
writer.append(month[0] + ",");
writer.append(sthermal2[4] + ",");
writer.append(snuclear2[4] + ",");
writer.append(shydro2[4] + ",");
writer.append(stotal2[4] + "\n");
// Extracting eastern region
String eastern[] = page.split("EASTERN");
String ethermal1[] = eastern[1].split("THERMAL");
String ethermal2[] = ethermal1[1].split(" ");
String ehydro1[] = eastern[1].split("HYDRO");
String ehydro2[] = ehydro1[1].split(" ");
String etotal1[] = eastern[1].split("TOTAL");
String etotal2[] = etotal1[1].split(" ");
// Appending filtered data into CSV file..
writer.append("EASTERN" + ",");
writer.append(year[0] + ",");
writer.append(month[0] + ",");
writer.append(ethermal2[4] + ",");
writer.append(" " + ",");
writer.append(ehydro2[4] + ",");
writer.append(etotal2[4] + "\n");
// Extracting northernEastern region
String neestern[] = page.split("NORTH");
String nethermal1[] = neestern[2].split("THERMAL");
String nethermal2[] = nethermal1[1].split(" ");
String nehydro1[] = neestern[2].split("HYDRO");
String nehydro2[] = nehydro1[1].split(" ");
String netotal1[] = neestern[2].split("TOTAL");
String netotal2[] = netotal1[1].split(" ");
writer.append("NORTH EASTERN" + ",");
writer.append(year[0] + ",");
writer.append(month[0] + ",");
writer.append(nethermal2[4] + ",");
writer.append(" " + ",");
writer.append(nehydro2[4] + ",");
writer.append(netotal2[4] + "\n");
writer.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
有一个 PDFLayoutTextStripper 被设计用来保持数据的格式。
从自述文件中:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.apache.pdfbox.pdfparser.PDFParser;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.util.PDFTextStripper;
public class Test {
public static void main(String[] args) {
String string = null;
try {
PDFParser pdfParser = new PDFParser(new FileInputStream("sample.pdf"));
pdfParser.parse();
PDDocument pdDocument = new PDDocument(pdfParser.getDocument());
PDFTextStripper pdfTextStripper = new PDFLayoutTextStripper();
string = pdfTextStripper.getText(pdDocument);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
System.out.println(string);
}
如果PDF文件使用pdfbox 2.0.6“只有矩形表”,则可以很好地工作。不适用于任何其他表格,只适用于矩形表格。
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.PDFTextStripperByArea;
public class PDFTableExtractor {
public static void main(String[] args) throws IOException {
ArrayList<String[]> objTableList = readParaFromPDF("C:\\sample1.pdf", 1,1,6);
//Enter Filepath, startPage, EndPage, Number of columns in Rectangular table
public static ArrayList<String[]> readParaFromPDF(String pdfPath, int pageNoStart, int pageNoEnd, int noOfColumnsInTable) {
ArrayList<String[]> objArrayList = new ArrayList<>();
try {
PDDocument document = PDDocument.load(new File(pdfPath));
document.getClass();
if (!document.isEncrypted()) {
PDFTextStripperByArea stripper = new PDFTextStripperByArea();
stripper.setSortByPosition(true);
PDFTextStripper tStripper = new PDFTextStripper();
tStripper.setStartPage(pageNoStart);
tStripper.setEndPage(pageNoEnd);
String pdfFileInText = tStripper.getText(document);
// split by whitespace
String Documentlines[] = pdfFileInText.split("\\r?\\n");
for (String line : Documentlines) {
String lineArr[] = line.split("\\s+");
if (lineArr.length == noOfColumnsInTable) {
for (String linedata : lineArr) {
System.out.print(linedata + " ");
System.out.println("");
objArrayList.add(lineArr);
} catch (Exception e) {
System.out.println("Exception " +e);
return objArrayList;
}
您可以使用PDFBox的
PDFTextStripperByArea
类从文档的特定区域提取文本。您可以在此基础上通过标识表的每个单元格的区域来构建。这不是现成的,但示例
DrawPrintTextLocations
类演示了如何解析文档中单个字符的边界框(解析字符串或段落的边界框很好,但我还没有看到PDFBox中对此的支持-请参阅此
question
)。您可以使用此方法对所有接触边界框进行分组,以标识表格的不同单元格。要做到这一点,一种方法是维护一组
Rectangle2D
区域的
boxes
,然后对于每个解析的字符,找到与
DrawPrintTextLocations.writeString(String string, List<TextPosition> textPositions)
中一样的字符边界框,并将其与现有内容合并。
Rectangle2D bounds = s.getBounds2D();
// Pad sides to detect almost touching boxes
Rectangle2D hitbox = bounds.getBounds2D();
final double dx = 1.0; // This value works for me, feel free to tweak (or add setter)
final double dy = 0.000; // Rows of text tend to overlap, so no need to extend
hitbox.add(bounds.getMinX() - dx , bounds.getMinY() - dy);
hitbox.add(bounds.getMaxX() + dx , bounds.getMaxY() + dy);
// Find all overlapping boxes
List<Rectangle2D> intersectList = new ArrayList<Rectangle2D>();
for(Rectangle2D box: boxes) {
if(box.intersects(hitbox)) {
intersectList.add(box);
// Combine all touching boxes and update
for(Rectangle2D box: intersectList) {
bounds.add(box);
boxes.remove(box);
boxes.add(bounds);
然后,您可以将这些区域传递给
PDFTextStripperByArea
。
您还可以更进一步,分离出这些区域的水平和垂直组件,从而推断出所有表格单元格的区域,而不管是否包含任何内容。
我有理由执行这些步骤,并最终使用
PDFBox
编写了我自己的
PDFTableStripper
类。我已经以
gist on GitHub
的形式分享了我的代码。
main
method
提供了一个如何使用该类的示例:
try (PDDocument document = PDDocument.load(new File(args[0])))
final double res = 72; // PDF units are at 72 DPI
PDFTableStripper stripper = new PDFTableStripper();
stripper.setSortByPosition(true);
// Choose a region in which to extract a table (here a 6"wide, 9" high rectangle offset 1" from top left of page)
stripper.setRegion(new Rectangle(
(int) Math.round(1.0*res),
(int) Math.round(1*res),
(int) Math.round(6*res),
(int) Math.round(9.0*res)));
// Repeat for each page of PDF
for (int page = 0; page < document.getNumberOfPages(); ++page)
System.out.println("Page " + page);
PDPage pdPage = document.getPage(page);
stripper.extractTable(pdPage);
for(int c=0; c<stripper.getColumns(); ++c) {
System.out.println("Column " + c);
for(int r=0; r<stripper.getRows(); ++r) {
System.out.println("Row " + r);
System.out.println(stripper.getText(r, c));
}
尝试使用TabulaPDF ( https://github.com/tabulapdf/tabula )。这是一个很好的库,可以从PDF文件中提取表格内容。这是非常符合预期的。
祝好运。:)
对于任何想做和OP一样的事情的人(和我一样),经过几天的研究, Amazon Textract 是最好的选择(如果你的数据量很小,空闲层可能就足够了)。
ObjectExtractor oe = new ObjectExtractor(document);
SpreadsheetExtractionAlgorithm sea = new SpreadsheetExtractionAlgorithm(); // Tabula algo.
Page page = oe.extract(1); // extract only the first page
for (int y = 0; y < sea.extract(page).size(); y++) {
System.out.println("table: " + y);
Table table = sea.extract(page).get(y);