【问题标题】:PHP Extract data from PDF in array formatPHP以数组格式从PDF中提取数据
【发布时间】:2017-03-25 18:50:57
【问题描述】:

我有以下 pdf 文件 Marsheet PDF m 试图提取示例中显示的数据,我尝试过 PDFParsePDFtoText 等....但无法正常工作有什么解决方案或示例吗?

<?php 
//Output something like this or suggest me if u have any better option 
$data_array = array( 
        array( "name" => "Mr Andrew Smee", 
          "medicine_name" => "FLUOXETINE 20MG CAPS",
          "description" => "TAKE ONE ONCE DAILY FOR LOW MOOD. CAUTION:YOUR DRIVING REACTIONS MAY BE IMPAIRED",
          "Dose" => '9000',
          "StartDate" => '28/09/15',
          "period" => '28',
          "Quantity" => '28'
        ),

        array( "name" => "Mr Andrew Smee", 
          "medicine_name" => "SINEMET PLUS 125MG TAB",
          "description" => "TAKE ONE TABLET FIVE TIMES A DAY FOR PD
                            (8am,11am,2pm,5pm,8pm)
                            THIS MEDICINE MAY COLOUR THE URINE. THIS IS
                            HARMLESS. CAUTION:REACTIONS MAY BE IMPAIRED
                            WHILST DRIVING OR USING TOOLS OR MACHINES.",
          "Dose" => '0800,1100,1400,1700,2000',
          "StartDate" => '28/09/15',
          "period" => '28',
          "Quantity" => '140'
        ), etc...  
  );
?>

【问题讨论】:

  • 是的,我已经尝试过了,但如果有任何其他解决方案,请告诉我...谢谢@Hiren-Modi
  • 通过PDF =&gt; EXCELEXCEL to PHP Arrays实现它
  • 可能有很多方法可以实现这一点,并且可能需要一些先前的分析和深入的工作才能获得您喜欢的正确解决方案。您可能想聘请一个人通过演出来实现这一目标。
  • 您好,我知道这有点离题,但您考虑过其他语言吗?我个人使用 NodeJS 来满足您对这个神奇的库的要求:npmjs.com/package/pdf2json。在任何情况下,正如@alariva 所说,您将需要处理任何库中的内容,以使其看起来像您所展示的那样。

标签: php parsing


【解决方案1】:

TL;DR 您几乎肯定不会单独使用库来做到这一点

更新:一个可行的解决方案(不是一个完美的解决方案!)如下编码,请参阅“实践中”。它需要:

  • 定义文本所在的区域;
  • 安装和运行命令行工具的可能性,pdf2json

为什么不容易

PDF 文件包含排版原语,不是可提取的文本;有时差异很小,您可以通过,但通常只有可提取的文本,易于访问的格式,这意味着文档在美学上看起来“有点错误”,因此为文本提取创建“最佳”PDF的生成器是也少用。

存在一些嵌入排版层和不可见文本层的生成器,允许看到漂亮的文本并拥有好的文本。以 PDF 大小为代价,您猜对了。

在您的示例中,文件中只有漂亮的文本,而网格的存在意味着文本需要正确排版。

所以,在里面,真正要读的是这个。注意圆括号内的字母:

/R8 12 Tf
0.99941 0 0 1 66 765.2 Tm
[(M)2.51003(r)2.805( )-2.16558(A)-3.39556(n)
-4.33056(d)-4.33056(r)2.805(e)-4.33056(w)11.5803
( )-2.16558(S)-3.39556(m)-7.49588(e)-4.33117(e)556]TJ
ET

如果你在里面组装 (s)(i)(n)(g)(l)(e) 字母,你会得到“Mr Andrew Smee”,但是你需要知道在哪里 em> 这些字母 与页面和数据网格相关。您还需要注意空格。上面,在“先生”和“安德鲁”之间有一个明确的空格字符,用括号括起来;但是如果您删除这些空格并修复所有以下字母的偏移量,您仍然会阅读“Mr Andrew Smee”并保存两个字符。 一些 PDF“优化器”会尝试这样做,不考虑偏移,该实体的“文本”字符串将只是“MrAndrewSmee”。

这就是为什么大多数无法轻松管理字符偏移的文本提取库(它们使用“文本行”,而且总体上不关心网格)会给你类似的东西

Mr Andrew Smee 505738 12/04/54 (61

或者,在“优化”文本的情况下,

MrAndrewSmee50573812/04/54(61

(这仍然给出了用正则表达式解析的危险错觉——有时是,有时不是,大多数时候它可以工作 95%时间,这样剩下的 5% 就变成了地狱的维护噩梦),但更重要的是,他们将无法获得您除以单元格的药物详细时间表的内容

任何与空间相关的信息(例如,如果将名称写在左侧的“From”或右侧的“To”框中,则具有不同的含义)要么会丢失,要么难以重构。

有一些PDF“保护”方案利用了偏移文本的能力,并且会打乱字符串。使用偏移量,您可以编写:

9 l 10 d 4 l 5 1 H 2 e 3 l o 6 W 7 o 8 r

PDF 查看器会显示“Hello World”;但是直接阅读文本,你会得到“ldlHeloWor”,或者更糟。您可以添加恶意文本并将其放置在页面之外,或者以透明颜色书写,以恶作剧成功删除 PDF 文件的易于删除的可选复制粘贴保护的人.大多数图书馆会愉快地把恶作剧文本和好文本一起吸走。

尝试使用大多数库,以及为什么它可能有效(但可能无效)

诸如 XPDF(及其包装器 phpxpdf、pdf2html 等)之类的库将为您提供这样的简单调用

// open PDF
$pdfToText->open('PDF-book.pdf');

// PDF text is now in the $text variable
$text = $pdfToText->getText();
$pdfToText->close();

您的“文本”将包含所有内容,类似于:

...
START DATE START DAY
WEEK 1 WEEK 2 WEEK 3 WEEK 4
DATE 28 29 30 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
19/10/15
Medication Details
Commencing
D.O.B
Doctor
Hour:Dose 1 2 3 4 5 6 7 1 2 3 4 5 6 7 1 2 3 4 5 6 7 1 2 3 4 5 6 7
Patient
Number
Period
MEDICATION ADMINISTRATION RECORD SHEETS Pharmacy No.
Document No.
02392 731680
28
0900 1
TAKE ONE ONCE DAILY FOR LOW MOOD.
CAUTION:YOUR DRIVING REACTIONS MAY BE IMPAIRED.
28
FLUOXETINE 20MG CAPS
Received Quantity returned quant. by destroyed quant. by

所以,阅读以上内容,问问自己 - 28 秒是多少?不看PDF能分辨是收货量、退货量、毁损量吗?当然,如果只有一个数字,很可能是收到的数量。变成了赌注。

02392 731680 是文件号吗?看起来是(不是)。

另请注意,在 PDF 中,药物名称位于注释之前。在提取的文本中,它是之后。通过查看 PDF 中的偏移量,您就明白了原因,这甚至是一个不错的决定——但是查看提取的文本,就不是那么容易了。

所以,自动分析看起来很诱人,但正如我所说,这是一项风险很大的业务。它是脆弱:有人在文档的某处输入了错误的(对您而言)文本,有时甚至不按顺序填写字段,都会生成视觉上的 PDF正确,同时,无法解释。 你要告诉你的用户什么?

有时,可用信息的一个子集足够稳定,您可以完成工作。在那种情况下,XPDF 或 PDF2HTML,一堆正则表达式,半天后你就可以回家了。耶你!请记住,对项目的任何“小”添加都可能不可能。添加了两个在 PDF 中分离良好的数字;它们是 128 和 361,还是 12 和 8361,还是 1283 和 61?你在$text 中得到的只有128361

因此,如果您这样做,请清楚地记录它,并避免可能难以维持的期望。您的初始项目可能运行得如此之好、如此之快、如此之短,以至于在您不知情的情况下接受了添加——然后您需要完成不可能的事情。解释why the first 95% was easy and the subsequent 5% very hard 可能比你的工作更有价值。

一种困难的方法,它对我有用

但是你可以“手动”做同样的事情吗?毕竟,通过查看 PDF,您知道您所看到的。机器可以做同样的事情吗? (this 仍然适用)。当然,在这个——毕竟——明确界定的计算机视觉问题中,你很可能可以。它不会是快速和容易的。你需要:

  • 一个非常低级的库(或自己阅读 PDF;您只需要先解压缩它,并且有相应的工具,例如 pdftk)。您需要恢复文本坐标。 “住院”的“C”一文不值。 “C, 495.2, 882.7”加上您的网格坐标告诉您 2015 年 10 月 13 日住院 - 是您所追求的信息!
  • 耐心(或工具)来输入文本区域的坐标。您需要告诉系统哪个区域 是 2015 年 10 月 13 日...以及其他所有日子。例如: // 单元格名称 X1 Y1 X2 Y2 文本 ['患者姓名', 60, 760, 300, 790, '' ], ['患者编号', 310, 760, 470, 790, '' ], ... ['Grid01Y01X01', 90, 1020, 110, 1040, ''], ...

请注意,您可以通过编程方式计算其中的许多值:一旦您知道左上角并知道一个单元格的大小,其他值或多或少可以计算,但误差很小。您无需为自己输入六个四个星期的网格,每个六行,每周 7 天。

您可以使用相同的结构创建一个带有红色区域的 PNG,以指示您已经覆盖了哪些单元格。这将有助于直观地检查您是否忘记了任何事情。

此时您解析 PDF,每次在坐标 (x1,y1) 处找到文本时,您都会扫描所有单元格并确定文本应该在哪里(使用 XY 二进制搜索有更快的方法来做到这一点树)。如果您在 66, 765.2 找到“Mr Andrew S”,请将其添加到 PatientName。然后您在 109.2、765.2 找到“mee”,并将其添加到 PatientName。现在读作“Mr Andrew Smee”。

如果水平距离高于某个阈值,则添加一个空格(或多个)。

(对于 非常 小文本,PDF 驱动程序输出的字母可能会乱序并通过字距调整进行更正,但通常这不是问题)。

在整个周期结束时,您将剩下

    [ 'PatientName',    60, 760, 300, 790, 'Mr Andrew Smee' ],
    [ 'PatientNumber',  310, 760, 470, 790, '505738' ],

等等。

几年前,我为一个大型 PDF 导入项目做过这种工作,而且效果很好。如今,我认为大部分繁重的工作都可以通过TcLibPDF 完成。

痛苦的部分是手工记录,第一次,网格的信息;可能有相应的工具,或者可以使用画布创建一个 HTML5/AJAX 编辑器。

在实践中

大部分工作已经由出色的pdf2json 工具完成,该工具使用“Andrew Smee”PDF,输出如下内容:

[ 
    {
        "height" : 1263,
        "width" : 892
        "number" : 1,
        "pages" : 1,
        "fonts" : [
            {
                "color" : "#000000",
                "family" : "Times",
                "fontspec" : "0",
                 "size" : "15"
            },
            ...
        ],
        "text" : [ 
            { "data" : "12/04/54",
              "font" : 0,
              "height" : 17,
              "left" : 628,
              "top" : 103,
              "width" : 70
            },
            { "data" : "28/09/15",
              "font" : 0,
              "height" : 17,
              "left" : 105,
              "top" : 206,
              "width" : 70
            },
            { "data" : "AQUARIUS",
              "font" : 0,
              "height" : 17,
              "left" : 99,
              "top" : 170,
              "width" : 94
            },
            { "data" : " ",
              "font" : 0,
              "height" : 17,
              "left" : 193,
              "top" : 170,
              "width" : 5
            },
            { "data" : "NURSING",
              "font" : 0,
              "height" : 17,
              "left" : 198,
              "top" : 170,
              "width" : 83
            },
            ...

为了简单起见,我将 Andrew Smee PDF 转换为 PNG 并将其重新采样为 892 x 1263 像素(任何尺寸都可以,只要您跟踪尺寸即可。下面,它们保存在 '宽度”和“高度”)。这样我就可以直接从旧 PaintShop Pro 的状态栏读取像素坐标:-)。

“地址”字段是从 73,161 到 837,193。

我的示例“模板”只有三个字段,因此在 PHP 5.7 中(使用短数组语法,[ ] 而不是 Array()

<?php

function template() {
    $template = [
        'Address' => [ 'x1' => 73, 'y1' => 161, 'x2' => 837, 'y2' => 193 ],
        'Medicine1' => [ 'x1' => 1, 'y1' => 283, 'x2' => 251, 'y2' => 299 ],
        'Details1'  => [ 'x1' => 1, 'y1' => 302, 'x2' => 251, 'y2' => 403 ],
    ];
    foreach ($template as $fieldName => $candidate) {
        $template[$fieldName]['elements'] = [ ];
    }
    return $template;
}
// shell_exec('/usr/local/bin/pdf2json "Andrew-Smee.pdf" andrew-smee.json');

$parsed = json_decode(file_get_contents('ann-underdown.json'), true);

$pout   = [ ];
foreach ($parsed as $page) {
    $template   = template();
    foreach ($page['text'] as $text) {
        // Will it blend?
        foreach ($template as $fieldName => $candidate) {
            if ($text['top'] > $candidate['y2']) {
                continue; // Too low.
            }
            if (($text['top']+$text['height']) < $candidate['y1']) {
                continue; // Too high.
            }
            if ($text['left'] > $candidate['x2']) {
                continue;
            }
            if (($text['left']+$text['width']) < $candidate['x1']) {
                continue;
            }
            $template[$fieldName]['elements'][] = $text;
        }
    }


    // Now I must reassemble all my fields
    foreach ($template as $fieldName => $data) {
        $list = $data['elements'];
        usort($list, function($txt1, $txt2) {
            for ($r = 8; $r >= 1; $r /= 2) {
                if (($txt1['top']/$r) < ($txt2['top']/$r)) {
                    return -1;
                }
                if (($txt1['top']/$r) > ($txt2['top']/$r)) {
                    return 1;
                }
                if (($txt1['left']/$r) < ($txt2['left']/$r)) {
                    return -1;
                }
                if (($txt1['left']/$r) > ($txt2['left']/$r)) {
                    return 1;
                }
            }
            return 0;
        });
        $text   = '';
        $starty = false;
        foreach ($list as $data) {
            if ($data['top'] > $starty + 5) {
                if ($starty > 0) {
                    $text .= "\n";
                }
            } else {
                // Add space
                // $text .= ' ';
            }
            $starty = $data['top'];
            // Add text to current line
            $text .= $data['data'];
        }
        // Remove extra spaces
        $text   = preg_replace('# +#', ' ', $text);
        $template[$fieldName]   = $text;
    }
    $paged[] = $template;
}

print_r($paged);

结果(在多页 PDF 上)

Array
(
[0] => Array
    (
        [Address] => AQUARIUS NURSING HOME 4-6 SPENCER ROAD, SOUTHSEA PO4 9RN
        [Medicine1] => ATORVASTATIN 40MG TABS
        [Details1] =>  take ONE tablet at NIGHT
    )

[1] => Array
    (
        [Address] => AQUARIUS NURSING HOME 4-6 SPENCER ROAD, SOUTHSEA PO4 9RN
        [Medicine1] => SOTALOL 80MG TABS
        [Details1] =>  take ONE tablet TWICE each day
DO NOT STOP TAKING UNLESS YOUR DOCTOR TELLS
YOU TO STOP.
    )

[2] => Array
    (
        [Address] => AQUARIUS NURSING HOME 4-6 SPENCER ROAD, SOUTHSEA PO4 9RN
        [Medicine1] => LAXIDO ORANGE SF 13.8G SACHETS
        [Details1] =>  ONE to TWO when required
DISSOLVE OR MIX WITH WATER BEFORE TAKING.
NOT IN CASSETTE
    )

)

【讨论】:

  • Hiii 专家告诉我,iTextSharp 非常适合阅读 marsheet,但它只支持 .NET 和 JAVA 无论如何我们可以在 php 中检查它......?
  • 我猜你看过stackoverflow.com/questions/4390675/…。大概可以像他们说的那样做。我自己实际上会尝试创建一个 Java 服务,它使用 PDF 并输出一个 JSON 数组 { pageNo, x1, y1, x2, y2, text }。然后我会解析那个 JSON。这样的二进制文件已经存在,这里:github.com/flexpaper/pdf2json
  • 为了过去,我已经尝试实现上述算法。有一些细微之处(例如处理跨越可变区域的医学字段,也需要检查字体索引)甚至可能不需要,并留作练习;-)
  • 非常感谢你所做的一切,如果你已经实现了演示nhscare.net/Ann%20Underdown.pdf,如果它返回了你上面提到的正确结果,请帮我一个忙检查这个pdf,然后开始实施非常感谢你再次感谢您的出色努力...@LSerni
  • Marsheet 是由 pro-script 软件开发的,在英国几乎所有的药房公司都使用这个软件来生成 mar-sheet pdf。
【解决方案2】:

有时很难使用某些库或工具直接将 pdf 提取为所需的格式/输出。我最近遇到了同样的问题,我有 1600 多个 pdf,我需要提取这些数据并将其存储在 db 中。我尝试了几乎所有的库、工具,但没有一个对我有帮助。所以,我尝试了一些手动工作来找到一个模式并使用 php 处理它们。为此,我使用了这个 php 库 PDF TO HTML

安装 PDF TO HTML 库

composer require gufy/pdftohtml-php:~2

这会将您的 pdf 转换为 html 代码,其中每个 标记代表页面和 标签表示 titles 及其 values。现在使用 p 标签,如果您可以识别常见模式,并且不难将其放入处理所有 pdf 并将它们转换为 csv/xls 或其他任何内容的逻辑中。因为在我的情况下,在每个 11 标签之后,模式都在重复,所以我使用了这个。

$pdf = new Gufy\PdfToHtml\Pdf('<PDF_FILE_PATH>');

// get total no pages
$total_pages = $pdf->getPages();

// Iterate through each page and extract the p tags
for($i = 1; $i <= $total_pages; $i++){
    // This will convert pdf to html
    $html = $pdf->html($i);
    // Create a dom document 
    $domOb = new DOMDocument();
    // load html code in the dom document 
    $domOb->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
    // Get SimpleXMLElement from Dom Node
    $sxml = simplexml_import_dom($domOb);

    // here you have the p tags
    foreach ($sxml->body->div->p as $pTag) {
        // your logic
    }
}

希望对你有帮助,因为它对我有很大帮助

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-11
    • 2023-03-23
    • 2022-10-09
    • 2019-10-10
    • 2018-09-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多