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

标签 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...  
  );
?>

最佳答案

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

Update: a working solution (not a perfect solution!) is coded below, see 'in practice'. It requires:

  • defining the areas where the text is;
  • the possibility of installing and running a command line tool, 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”,但是你需要知道这些字母与页面的相关位置,和数据网格。您还需要注意空格。上面,在“Mr”和“Andrew”之间有一个显式的空格字符,用括号括起来;但是,如果您删除了这些空格并修复了以下所有字母的偏移量,您仍然会阅读“安德鲁斯米先生”并保存两个字符。一些PDF“优化器”会尝试这样做,并且不考虑偏移,该实体的“文本”字符串将只是“MrAndrewSmee”。
这就是为什么大多数文本提取库无法轻松管理字符偏移(它们使用“文本行”,并且总的来说它们不关心网格)会给你类似的东西
Mr Andrew Smee 505738 12/04/54 (61
或者,在“优化”文本的情况下,
MrAndrewSmee50573812/04/54(61
(这仍然给了 危险的 可以用正则表达式解析的错觉——有时是,有时不是,大多数情况下它在 95% 的时间里工作,所以剩下的 5% 变成了维护噩梦from Hell),但更重要的是,他们将无法为您提供按单元格划分的药物详细时间表的内容。
任何与空间相关的信息(例如,如果一个名字写在左边的“发件人”或右边的“收件人”框中,则它具有不同的含义)要么丢失,要么难以重建。
有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 可能比你的工作更有值(value)。
一种困难的方法,对我有用
但是你能“手工”做同样的事情吗?毕竟,通过查看 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, ''],
    ...

  • 请注意,您可以通过编程方式计算这些值中的很多:一旦您拥有左上角并知道一个单元格的大小,其他单元格或多或少都可以计算出来,但会出现非常轻微的错误。您不必为自己输入 6 个 4 周的网格,每个网格 6 行,每周 7 天。
    您可以使用相同的结构创建带有红色区域的 PNG,以指示您覆盖了哪些单元格。这将有助于目视检查您没有忘记任何事情。
    在这一点上,您解析 PDF,每次在坐标 (x1,y1) 处找到文本时,您都会扫描所有单元格并确定文本的位置(使用 XY 二叉搜索树有更快的方法来做到这一点)。如果您在 66, 765.2 找到“Mr Andrew S”,请将其添加到 PatientName。然后您在 109.2, 765.2 处找到“mee”,并将其添加到 PatientName。现在读作“安德鲁·斯密先生”。
    如果水平距离高于某个阈值,则添加一个空格(或多个)。
    (对于非常小的文本,PDF 驱动程序有可能会乱序输出字母并通过字距调整进行更正,但这通常不是问题)。
    在整个周期结束时,您将剩下
        [ 'PatientName',    60, 760, 300, 790, 'Mr Andrew Smee' ],
        [ 'PatientNumber',  310, 760, 470, 790, '505738' ],
    
    等等。
    几年前,我为一个大型 PDF 导入项目做过这种工作,它的效果非常棒。现在,我认为大部分繁重的工作都可以用 TcLibPDF 来完成。
    痛苦的部分是手动记录,第一时间,网格的信息;可能有工具可以做到这一点,或者可以使用 Canvas 制作 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。
    我的示例"template"只有三个字段,因此在 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
        )
    
    )

    关于PHP 以数组格式从 PDF 中提取数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40549696/

    相关文章:

    php - Codeigniter CMS 的高级数据库搜索

    php - (#200) 用户未授权应用程序执行此操作 facebook php api 错误?

    java - 我的代码似乎效率低下并引发 jdbc 异常

    java - 如何替换xml文件中字符串的一部分?

    php - Elasticsearch范围查询(PHP客户端)

    php - 如何在php中的mysql获取结果之间插入数据?

    php - 在数据库更改时更新 PHP session 变量

    java - 将字符串值转换为类型

    python - ConfigNumParser值错误: invalid literal for int() with base 10: ''

    parsing - 在没有所有可用包或加载所有内容的情况下从 lisp 读取/解析常见的 lisp 文件