DevGang
Авторизоваться

Анализ PDF-файлов в Node.js

Синтаксический анализ PDF-файлов необходим в различных приложениях, особенно тех, которые связаны с обработкой документов и извлечением данных. Для синтаксического анализа PDF доступно множество онлайн-инструментов. Таким образом, решение о том, какой пакет использовать, может оказаться непростой задачей.

Эта статья предоставит исчерпывающее руководство по разбору PDF-файлов в Node.js, углубляясь в интеграцию пакетов Node, таких как pdf-parse и pdf-reader. Мы расскажем об уникальных преимуществах, использовании и проблемах, которые представляет каждый пакет. Мы также рассмотрим, почему разработчики могут предпочесть создавать пользовательские анализаторы, адаптируя решения к потребностям своих проектов.

Давайте начнем!

Настройка проекта Node.js

Для начала мы создадим простое Node.js приложение. Перейдите к терминалу и выполните следующую команду:

npm init -y

Затем создайте папку с именем uploads, которая будет использоваться для хранения образцов PDF-файлов, используемых для тестирования пакетов.

Для тестирования используются два файла PDF. Один содержит только страницу текстового содержимого и может быть найден в uploads/test.pdf. Второй файл содержит текст с таблицей в нем и может быть найден в uploads/table.pdf.

Вот как выглядит test.pdf файл:

  И вот как выглядит table.pdf файл:  

Популярные библиотеки синтаксического анализа

Давайте рассмотрим некоторые из самых популярных пакетов Node с открытым исходным кодом для синтаксического анализа файлов.

pdf-parse

pdf-parse — популярный пакет синтаксического анализа среди разработчиков благодаря своему удобному интерфейсу. Его стабильность обусловлена независимостью от других фреймворков синтаксического анализа, что способствует уменьшению количества ошибок.

Чтобы установить pdf-parse, выполните следующую команду:

npm i pdf-parse

Затем создайте файл с именем pdf-parse.mjs в корневом каталоге проекта и добавьте следующее:

import pdf from "pdf-parse-debugging-disabled";

const data = await pdf("./uploads/test.pdf");
console.log(data)

Это базовая настройка, демонстрирующая, как интегрировать pdf-parse в ваш рабочий процесс. Простой вызов pdf() метода в третьей строке и передача пути к PDF-файлу — это все, что необходимо для обработки содержимого файла.

Примерный результат выглядит следующим образом:

{
  numpages: 1,
  numrender: 1,
  info: {
    PDFFormatVersion: '1.4',
    IsAcroFormPresent: false,
    IsXFAPresent: false,
    Title: 'Test',
    Producer: 'Skia/PDF m123 Google Docs Renderer'
  },
  metadata: null,
  text: '\n\nTypescript,Serverless.',
  version: '1.10.100'
}

Свойство numpages показывает количество страниц в PDF. В нашем примере это одна страница. text содержит содержимое PDF. info содержит дополнительную информацию о документе, такую как его название, версия формата PDF и способ создания PDF. Наконец, metadata содержит метаданные PDF.  

Вывод в формате PDF с таблицами

Теперь давайте посмотрим на пример PDF-файла, содержащего таблицу:

import pdf from "pdf-parse-debugging-disabled";

const data = await pdf("./uploads/table.pdf");
console.log(data)

Результирующая полезная нагрузка выглядит следующим образом:

{
  numpages: 1,
  numrender: 1,
  info: {
    PDFFormatVersion: '1.6',
    IsAcroFormPresent: false,
    IsXFAPresent: false,
    Author: 'Mary',
    Creator: 'Acrobat PDFMaker 9.0 for Word',
    Producer: 'Adobe PDF Library 9.0',
    CreationDate: "D:20110123144232-05'00'",
    ModDate: "D:20140304212414-05'00'"
  },
  metadata: Metadata {
    _metadata: [Object: null prototype] {
      'xmp:modifydate': '2014-03-04T21:24:14-05:00',
      'xmp:createdate': '2011-01-23T14:42:32-05:00',
      'xmp:metadatadate': '2014-03-04T21:24:14-05:00',
      'xmp:creatortool': 'Acrobat PDFMaker 9.0 for Word',
      'xmpmm:documentid': 'uuid:4a18570c-d5bf-445d-9e0e-2efeb989eeb1',
      'xmpmm:instanceid': 'uuid:813474a4-22b0-4180-9415-bb67674d2b7b',
      'xmpmm:subject': '3',
      'dc:format': 'application/pdf',
      'dc:creator': 'Mary',
      'pdf:producer': 'Adobe PDF Library 9.0',
      'pdfx:sourcemodified': 'D:20110123172633'
    }
  },
  text: '\n' +
    '\n' +
    'Example table \n' +
    'This is an example of a data table. \n' +
    'Disability \n' +
    'Accuracy Time to \n' +
    'complete \n' +
    'Blind 5 1 4 34.5%, n=1 1199 sec, n=1 \n' +
    'Low Vision 5 2 3 98.3% n=2 \n' +
    '(97.7%, n=3) \n' +
    '1716 sec, n=3 \n' +
    '(1934 sec, n=2) \n' +
    'Dexterity 5 4 1 98.3%, n=4 1672.1 sec, n=4 \n' +
    'Mobility 3 3 0 95.4%, n=3 1416 sec, n=3 \n' +
    ' ',
  version: '1.10.100'
}

Как мы можем наблюдать, pdf-parse не сохраняет структуру таблицы. Вместо этого он обрабатывает все это как строку. Пакет pdf-parse полезен, если вы собираетесь извлекать только текст из PDF и не беспокоитесь о структуре файла.

pdf2json

pdf2json — это модуль, который преобразует PDF-файлы из двоичного формата в формат JSON, используя pdf.js для своей основной функциональности. Он также включает поддержку элементов интерактивной формы, что повышает его полезность при обработке и интерпретации содержимого PDF.

Чтобы установить pdf2json, выполните следующую команду:

npm i pdf2json

Затем создайте файл с именем pdf2json.mjs в корневой папке проекта и вставьте следующее:  

import/span><span> fs </import<span>из</span><span>"fs"</span><span>;</span><span>импортируйте</span><span>PDFParser</span><span>из</span><span>"pdf2json"</span><span>;</span><span>const</span><span> pdfParser </span><span>=</span><span>new</span><span>PDFParser</span><span>(</span><span>this</span><span>,</span><span>1</span><span>);</span><span>const</span><span> filename </span><span>=</span><span>"./uploads/table.pdf"</span><span>;</span><span>
pdfParser</span><span>.</span><span>on</span><span>(</span><span>"pdfParser_dataError"</span><span>,</span><span>(</span><span>errData</span><span>)</span><span>=></span><span>
 консоль</span><span>.</span><span>ошибка</span><span>(</span><span>errData</span><span>.</span><span>parserError</span><span>)</span><span>);</span><span>
pdfParser</span><span>.</span><span>on</span><span>(</span><span>"pdfparser_datarady"</span><span>,</span><span>(</span><span>pdfData</span><span>)</span><span>=></span><span>{</span><span>
 консоль</span><span>.</span><span>журнал</span><span>(</span><span>pdfData</span><span>);</span><span>});</span><span>

Это базовая настройка, показывающая, как интегрировать этот пакет в ваш рабочий процесс. on("pdfParser_dataReady") вызывается, когда анализатор завершает обработку содержимого PDF.

Примерный результат выглядит следующим образом:

{
  Transcoder: 'pdf2json@3.0.5 [https://github.com/modesty/pdf2json]',
  Meta: {
    PDFFormatVersion: '1.6',
    IsAcroFormPresent: false,
    IsXFAPresent: false,
    Author: 'Mary',
    Creator: 'Acrobat PDFMaker 9.0 for Word',
    Producer: 'Adobe PDF Library 9.0',
    CreationDate: "D:20110123144232-05'00'",
    ModDate: "D:20140304212414-05'00'",
    Metadata: {
      'xmp:modifydate': '2014-03-04T21:24:14-05:00',
      'xmp:createdate': '2011-01-23T14:42:32-05:00',
      'xmp:metadatadate': '2014-03-04T21:24:14-05:00',
      'xmp:creatortool': 'Acrobat PDFMaker 9.0 for Word',
      'xmpmm:documentid': 'uuid:4a18570c-d5bf-445d-9e0e-2efeb989eeb1',
      'xmpmm:instanceid': 'uuid:813474a4-22b0-4180-9415-bb67674d2b7b',
      'xmpmm:subject': '3',
      'dc:format': 'application/pdf',
      'dc:creator': 'Mary',
      'pdf:producer': 'Adobe PDF Library 9.0',
      'pdfx:sourcemodified': 'D:20110123172633'
    }
  },
  Pages: [
    {
      Width: 38.25,
      Height: 49.5,
      HLines: [],
      VLines: [],
      Fills: [Array],
      Texts: [Array],
      Fields: [],
      Boxsets: []
    }
  ]
}

Свойство Pages содержит содержимое PDF-файла, в то время как Meta содержит метаданные PDF.

Чтобы получить исходное содержимое файла, замените pdfParser_dataReady прослушиватель на этот:

pdfParser.on("pdfParser_dataReady", (pdfData) => {
  console.log({ textContent: pdfParser.getRawTextContent() });
});

Вот результат:

{
  textContent: 'Typescript,Serverless.\r\n----------------Page (0) Break----------------\r\n'
}

Далее давайте обновим образец PDF-файла до того, в котором будет таблица.

Замените приведенный выше код следующим:

import fs from "fs";
import PDFParser from "pdf2json";
const pdfParser = new PDFParser(this, 1);
const filename = "./uploads/table.pdf";
pdfParser.on("pdfParser_dataError", (errData) =>
  console.error(errData.parserError)
);
pdfParser.on("pdfParser_dataReady", (pdfData) => {
  console.log({ textContent: pdfParser.getRawTextContent() });
});
pdfParser.loadPDF(filename);

Вот выходные данные в консоли:

{
  textContent: 'Example table \r\n' +
    'This is an example of a data table. \r\n' +
    'Disability \r\n' +
    'Category Participants \r\n' +
    'Ballots \r\n' +
    'Completed \r\n' 
     
     
     
     
    +'Ballots \r\n'+'Incomplete/ \r\n'+'Terminated \r\n'+'Results \r\n'+'Accuracy Time to \r\n' +
    'complete \r\n' +
    'Blind 5 1 4 34.5%, n=1 1199 sec, n=1 \r\n' +
    'Low Vision 5 2 3 98.3% n=2 \r\n' +
    '(97.7%, n=3) \r\n' +
    '1716 sec, n=3 \r\n' +
    '(1934 sec, n=2) \r\n' +
    'Dexterity 5 4 1 98.3%, n=4 1672.1 sec, n=4 \r\n' +
    'Mobility 3 3 0 95.4%, n=3 1416 sec, n=3 \r\n' +
    ' \r\n' +
    '----------------Page (0) Break----------------\r\n'
}

Используя pdf2json, нет существенной разницы между PDF-файлом с таблицами и без них.

pdfreader

pdfreader — это еще один инструмент, который преобразует PDF-файлы из двоичного формата в формат JSON. В основе он использует pdf2json. В отличие от пакетов, которые мы видели до сих пор, которые не поддерживают табличные данные, этот пакет делает это с автоматическим определением столбцов и синтаксическим анализом на основе правил.

Чтобы установить pdfreader, выполните следующую команду:

npm install pdfreader

Затем создайте файл с именем pdfreader.mjs в корневой папке проекта и вставьте следующее:  

import { PdfReader } from "pdfreader";
const filename = "./uploads/test.pdf";
var rows = {}; // indexed by y-position
function printRows() {
  Object.keys(rows) // => array of y-positions (type: float)
    .sort((y1, y2) => parseFloat(y1) - parseFloat(y2)) // sort float positions
    .forEach((y) => console.log((rows[y] || []).join("")));
}
new PdfReader().parseFileItems(filename, function (err, item) {
  if (!item || item.page) {
    // end of file, or page
    printRows();
    item?.page && console.log("PAGE:", item.page);
    rows = {}; // clear rows for next page
  } else if (item.text) {
    // accumulate text items into rows object, per line
    (rows[item.y] = rows[item.y] || []).push(item.text);
  }
});

Метод parseFileItems возвращает обратный вызов, содержащий обработанный файл. В приведенном выше примере кода мы присваиваем этот файл переменной с именем item в девятой строке.

Объект item может соответствовать одному из следующих объектов:

  • null — это означает, что синтаксический анализ завершен или произошла ошибка.
  • File metadata — пример объекта:{file:{path:string}}. Это происходит при открытии PDF-файла и всегда является первым элементом.
  • Page metadata — пример объекта: {page:integer, width:float, height:float}. Это означает, что анализируется новая страница. Указывается номер страницы, начинающийся с индекса 1.
  • Text — образец объекта: {text:string, x:float, y:float, w:float, ...}. Он содержит text свойство и плавающие 2D координаты AABB на странице.

Результат выглядит следующим образом:

PAGE: 1
Typescript,Serverless.

Пример полезной нагрузки для PDF-файла с таблицами

Далее мы заменим PDF-файл, который мы использовали выше, на PDF-файл, содержащий таблицу.

Замените приведенный выше код следующим:

import { PdfReader, TableParser } from "pdfreader";
const filename = "./uploads/table.pdf";
const nbCols = 2;
const cellPadding = 40;
const columnQuantitizer = (item) => parseFloat(item.x) >= 20;
// polyfill for String.prototype.padEnd()
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
if (!String.prototype.padEnd) {
  String.prototype.padEnd = function padEnd(targetLength, padString) {
    targetLength = targetLength >> 0; //floor if number or convert non-number to 0;
    padString = String(padString || " ");
    if (this.length > targetLength) {
      return String(this);
    } else {
      targetLength = targetLength - this.length;
      if (targetLength > padString.length) {
        padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed
      }
      return String(this) + padString.slice(0, targetLength);
    }
  };
}
const padColumns = (array, nb) =>
  Array.apply(null, { length: nb }).map((val, i) => array[i] || []);
const mergeCells = (cells) => (cells || []).map((cell) => cell.text).join("");
const formatMergedCell = (mergedCell) =>
  mergedCell.substr(0, cellPadding).padEnd(cellPadding, " ");
const renderMatrix = (matrix) =>
  (matrix || [])
    .map(
      (row, y) =>
        "| " +
        padColumns(row, nbCols)
          .map(mergeCells)
          .map(formatMergedCell)
          .join(" | ") +
        " |"
    )
    .join("\n");
var table = new TableParser();
new PdfReader().parseFileItems(filename, function (err, item) {
  if (err) console.error(err);
  else if (!item || item.page) {
    // end of file, or page
    console.log(renderMatrix(table.getMatrix()));
    item?.page && console.log("PAGE:", item.page);
    table = new TableParser(); // new/clear table for next page
  } else if (item.text) {
    // accumulate text items into rows object, per line
    table.processItem(item, columnQuantitizer(item));
  }
});

Просматривая зарегистрированные данные в консоли, мы видим вот что:

pdfreader отличается от других анализаторов, которые мы рассмотрели выше, своей способностью сохранять структуры таблиц. Хотя он может и не обеспечивать идеальной точности, как мы можем видеть на изображении выше, он предлагает более эффективное решение для обработки сложных макетов документов.

Сравнение пакетов синтаксического анализа

Каждый пакет, который мы рассмотрели выше, имеет свои сильные стороны и недостатки. Давайте сравним их аспекты, чтобы вы могли легко определить наиболее подходящий вариант для вашего проекта.:

Пакет Функциональность Совместимость
pdf-parse Позволяет выполнять чтение из указанного пути к файлу (поддерживает как локальные, так и внешние источники) и из буфера памяти Node версии 14 и выше
pdfreader Поддерживает файлы, защищенные паролем, путь к файлу и буфер файла, PDF-файлы с таблицами и использование CLI Node версии 14 и выше. Зависит от pdf2json
pdf2json Упрощает чтение из пути к файлу или буфера памяти, наряду с возможностями интерфейса командной строки Node версии 14 и выше. Зависит от pdf.js

Необходимость в пользовательских парсерах

Часто требования проекта могут требовать чего-то сверх того, что может предоставить большинство пакетов, отсюда необходимость внедрения инноваций и создания пользовательского анализатора.

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

Заключение

В этом руководстве мы рассмотрели, как разбирать PDF-файлы в Node.js используя несколько пакетов npm. Мы расширили наши знания, сравнив пакеты и изучив проблемы, с которыми они сталкиваются. Вы также могли бы еще больше расширить свое понимание, изменив предоставленные примеры кодов, чтобы найти новые приложения для этих пакетов.

Надеюсь, вам понравилась эта статья и вы узнали о новом способе обработки содержимого вашего PDF-файла. Спасибо за чтение!

Источник:

#NodeJS #Подборка
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу