Личный опыт парсинга pdf с помощью PHP


Задача извлечения данных из pdf-документа с помощью PHP является довольно распространенной и решается с помощью готовых библиотек. Однако, в связи с тем, что формат такой, не самой однозначный так скажем, могут возникнуть некоторые сложности. В статье ниже я расскажу о процессе парсинга и решениях подробнее.

Задача

Необходимо из полисов pdf страховых компаний извлекать типовую информацию, например, номер полиса ОСАГО. Список компаний

  • Абсолют Страхование
  • АльфаСтрахование
  • Астро-Волга
  • БОРОВИЦКОЕ
  • АО ВСК
  • ЕВРОИНС
  • Ингосстрах
  • Макс
  • Ренессанс
  • Ресо Гарантия
  • РГС
  • Сбербанк
  • Совкомбанк
  • СОГАЗ
  • Согласие
  • Тинькоф
  • ЭНЕРГОГАРАН
  • Югория
Рассмотрим путь решения.

Библиотека Smalot

Подключать к проекту пришлось по старинке, многими инклудами, у меня получилась вот такая цепочка:

require_once('PdfParser/Config.php');
require_once('PdfParser/RawData/FilterHelper.php');
require_once('PdfParser/RawData/RawDataParser.php');
require_once('PdfParser/Encoding/PDFDocEncoding.php');
require_once('PdfParser/Document.php');
require_once('PdfParser/Header.php');
require_once('PdfParser/Element.php');
require_once('PdfParser/Element/ElementNumeric.php');
require_once('PdfParser/Element/ElementArray.php');
require_once('PdfParser/Element/ElementMissing.php');
require_once('PdfParser/Element/ElementXRef.php');
require_once('PdfParser/Element/ElementName.php');
require_once('PdfParser/Element/ElementBoolean.php');
require_once('PdfParser/Element/ElementString.php');
require_once('PdfParser/Element/ElementDate.php');
require_once('PdfParser/Element/ElementHexa.php');
require_once('PdfParser/Element/ElementStruct.php');
require_once('PdfParser/Element/ElementNull.php');
require_once('PdfParser/PDFObject.php');
require_once('PdfParser/Page.php');
require_once('PdfParser/XObject/Image.php');
require_once('PdfParser/XObject/Form.php');
require_once('PdfParser/Encoding.php');
require_once('PdfParser/Pages.php');
require_once('PdfParser/Font.php');
require_once('PdfParser/Parser.php');
Сам запуск тоже не совсем обычный, пришлось использовать магическую константу __FILE__

require_once  dirname(dirname(__FILE__))."/include/Smalot/all-pdf.php";
$parser = new \Smalot\PdfParser\Parser();
$pdf = $parser->parseFile('Ренесанс полис.pdf');
Все прекрасно работает, читает большинство файлов. Однако, появились и сложности.

Кодировка Smalot

У некоторые файлов Smalot не мог определить кодировку. Точнее, там была UTF-8, но вместо символов русского языка парсер выдавал крякозябры. Так получилось с полисами от АльфаБанка.

Манипуляции с текстом, который получался после парсера (функции mb_convert_encoding() и iconv()) ничего не дали, пришлось залезть в исходный код Smalot и поискать там. В файле PdfParser/Font.php в методе decodeContentByEncoding(string $text) я заменил

if (!mb_check_encoding($text, 'UTF-8')) {
            return mb_convert_encoding($text, 'UTF-8', 'Windows-1252');
}
На

if (!mb_check_encoding($text, 'UTF-8')) {
         $text = iconv('windows-1251', 'UTF-8', $text);
}
После этого полис Альфа Страхования стал парситься нормально.

Другие проблемы Smalot

Однако, дальше с некоторыми другими типами полисов также Smalot не смог справиться:

  • Согласие – защищенный pdf
  • Росгосстрах – вообще ничего не выводит
  • Сбербанк и Иногсстрах – проблемы с кодировкой
  • Макс – вообще непонятная ошибка
Что ж, пришло время поискать другую библиотеку для них

Библиотека Poppler

Целый комбайн. Легко ставится и прост в использовании. Попробовал у себя на сервере – читает и ингосстрах и согласие, все норм, можно работать. Возникло затруднение, состоящее в том, что у заказчика хостинг шаред и ставить туда Poppler проблематично. Пришлось набросать подобие апи к своему серверу. Отправка pdf и прием ответа выглядят так:

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);  

curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);  
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/pdf'));  
$file = new CURLFile($file_name_with_full_path,'application/pdf','MyFile');
curl_setopt($ch, CURLOPT_POSTFIELDS, ['pdf' => $file]);
$result = curl_exec($ch);
curl_close ($ch);
Само типа апи на PHP

copy("php://input", "1.pdf");
sleep(2);
$output = shell_exec('pdftotext 1.pdf 1.txt');
sleep(2);
echo file_get_contents("1.txt");
sleep(2);
unlink("1.pdf");
unlink("1.txt");
die();
Изо всех полисов кроме Макс удалось таким образом извлечь текст; Макс не хотел никак отдавать свою информацию. После нескольких проб было решено не тратить времени на него, в принципе мы уже обрабатывали таким образом больше 99 процентов поступавших полисов.

Но задача интересная, если будет время, то обязательно расковыряю Макс и напишу об этом.
Автор этого материала - я - Пахолков Юрий. Я оказываю услуги по написанию программ на языках Java, C++, C# (а также консультирую по ним) и созданию сайтов. Работаю с сайтами на CMS OpenCart, WordPress, ModX и самописными. Кроме этого, работаю напрямую с JavaScript, PHP, CSS, HTML - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.

тегизаметки, php, pdf, парсинг




Отправляя сообщение я подтверждаю, что ознакомлен и согласен с политикой конфиденциальности данного сайта.



Приключения послушника, сбежавшего из Святилища
Тест на JS и PHP на гитхабе
Непрямоугольная форма на C#