PHP以数组格式从PDF中提取数据

时间:2016-11-11 14:07:06

标签: php parsing

我有以下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...  
  );
?>

2 个答案:

答案 0 :(得分:7)

TL; DR 您几乎肯定不会单独使用图书馆

  

更新:下面编写了一个有效的解决方案(不是一个完美的解决方案!),请参阅实践中的#39;。它要求:

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

为什么不容易

PDF文件包含排版基元,可提取文本;有时差异很小,你可以通过,但通常只有易读格式的可提取文本,意味着文件看起来有点错误&#34;在美学上,因此创造了最好的&#34;用于文本提取的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)字母,你就会得到安德鲁·斯梅先生&#34;,但是你需要知道< em> where 这些字母 与页面和数据网格相关。您还需要注意空格。在上面,有一个明确的空格字符,括号内,&#34; Mr&#34;和&#34;安德鲁&#34 ;;但如果你删除这样的空格并修正了以下所有字母的偏移量,你仍然可以阅读&#34; Andrew Smee先生&#34;并保存两个字符。 一些PDF&#34;优化器&#34;将尝试做那个,而不考虑抵消,&#34;文本&#34;该实体的字符串只是&#34; MrAndrewSmee&#34;。

这就是为什么大多数文本提取库无法轻松管理字符偏移(他们使用&#34;文本行&#34;以及大体上他们不关心网格)给你类似的东西

Mr Andrew Smee 505738 12/04/54 (61

或者,对于&#34;优化&#34;文本,

MrAndrewSmee50573812/04/54(61

仍然给出危险错误,可以用正则表达式进行解析 - 有时它是,有时它不是,有时它不起作用95%的时间,剩下的5%变成来自地狱的维护噩梦),但更重要的是,他们无法让你药物详情时间表的内容除以细胞

任何与空间相关的信息(例如,如果名称在左侧&#34;从&#34;或在右侧&#34; To&#34;框中),则名称具有不同的含义要么丢失,要么难以重建。

有PDF&#34;保护&#34;利用抵消文本能力的方案,并将扰乱字符串。有了抵消,你可以写:

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

并且PDF查看器将向您显示&#34; Hello World&#34 ;;但是直接阅读文字,你会得到&#34; ldlHeloWor&#34;或更糟。您可以添加恶意文本并将其放在页面外,或者用透明颜色书写,以恶作剧成功删除PDF文件的轻松删除的可选复制粘贴保护。大多数图书馆会愉快地将恶作剧文本与好文本一起吸收。

尝试使用大多数库,以及它可能起作用的原因(但可能不是)

XPDF(及其包装器phpxpdf,pdf2html等)等库将为您提供一个简单的调用,例如

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

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

和你的&#34;文字&#34;将包含所有内容,并且类似于:

...
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,一堆正则表达式,你可以在半天内免费回家。你呢!请记住,任何&#34;小&#34;然后,项目的添加可能不可能。添加了两个在PDF中分开的数字;他们是128和361,或12和8361,或1283和61?您$text获得的所有内容均为128361

因此,如果您这样做,清楚地记录并避免可能难以维护的期望。你的初始项目可能运作得如此之快,如此之快,如此之快,而不是你不知道的加入 - 然后你需要做不可能的事情。解释why the first 95% was easy and the subsequent 5% very hard可能比你的工作更有价值。

一个难以实现的方法,这对我有用

但你能做同样的事吗&#34;手工和#34;?毕竟,通过查看在PDF上,您知道您所看到的内容。机器可以完成同样的事情吗? (this仍然适用)。当然,在这个 - 毕竟 - 明确划分的计算机视觉问题,你很可能。它只是快速而简单。你需要:

  • 一个非常低级别的库(或者自己阅读PDF;你只需要先解压缩它,并且有一些工具,例如pdftk)。您需要使用坐标恢复文本。 &#34; C&#34;为&#34;住院&#34;什么都不值得。 &#34; C,495.2,882.7&#34;加上您网格的坐标告诉您2015年10月13日的住院治疗 - 是您所追求的信息!
  • 耐心(或工具)输入文本区域的坐标。你需要告诉系统 2015年10月13日......以及所有其他日子。例如: //单元名称X1 Y1 X2 Y2文本 [&#39; PatientName&#39;,60,760,300,790,&#39;&#39; ] [&#39; PatientNumber&#39;,310,760,470,790,&#39;&#39; ] ... [&#39; Grid01Y01X01&#39;,90,1020,110,1040,&#39;&#39; ] ...

请注意,您可以通过编程方式计算出很多这些值:一旦您有左上角并知道一个单元格的大小,其他值或多或少可以计算得非常轻微错误。您不需要输入六个网格,每周六个网格,每个网格每周六天,每周七天。

您可以使用相同的结构创建一个带红色区域的PNG,以指示您已覆盖的单元格。这对于视觉检查你没有忘记任何事情是有用的。

此时您解析PDF,每次在坐标(x1,y1)处找到文本时,都会扫描所有单元格并确定文本的位置(使用XY二进制搜索有更快的方法)树)。如果您发现安德鲁先生S&#39;在66,765.2,你将它添加到PatientName。然后你会发现&#39; mee&#39;在109.2,765.2,你也将它添加到PatientName。现在读起来是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像素(任何尺寸都可以,只要您跟踪尺寸。下面,它们保存在& #39;宽度&#39;和&#39;身高&#39;)。通过这种方式,我可以直接从旧的PaintShop Pro状态栏中读取像素坐标: - )。

&#34;地址&#34;场地从73,161到837,193。

我的样本&#34;模板&#34;,只有三个字段,因此在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
    )

答案 1 :(得分:1)

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

将PDF安装到HTML库

composer require gufy/pdftohtml-php:~2

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

$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
    }
}

希望这可以帮助你,因为它帮助了我很多