Рубрики
Без рубрики

Как разбирать PDF в масштабе в Nodejs: что делать и что не делать

Томом, как разбираться в анализе PDFS в масштабе в Nodejs: что делать и что не делать шаг в программной архитектуре и научиться делать практическое решение для реальной деловой проблемы с потоками Nodejs с этой статьей. Ваша заинтересованная сторона, после того, как вы сохраняете их бесчисленные часы

Автор оригинала: FreeCodeCamp Community Member.

Tom.

Возьмите шаг в программную архитектуру и научитесь сделать практическое решение для реальной деловой проблемы с потоками Nodejs с этой статьей.

Объезд: механика жидкости

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

В простой механике жидкости, концепция оптимизировать Полезно для рассуждения о том, как частицы жидкости будут двигаться, а ограничения нанесены на них в различных точках в системе.

Например, скажем, у вас есть вода, протекающая через трубу равномерно. На полпути вниз по трубе, она ветви. Как правило, расход воды равномерно разделяется в каждую ветку. Инженеры используют абстрактную концепцию упорядочения оптимизации для рассуждения о свойствах воды, таких как его расход, для любого количества ветвей или сложных конфигураций трубопровода. Если вы спросили инженера, что он предположил, что скорость потока через каждую ветку будет, он будет справедливо ответить на «одну половину», интуитивно. Это расширяется к произвольному количеству тептики математически.

Потоки, концептуально, должны кодировать, какие упрочны являются слишком жидкими механиками. Мы можем рассуждать о данных в любой данной точке, рассмотрев его как часть потока. Вместо того, чтобы беспокоиться о деталях реализации между тем, как она хранится. Возможно, вы можете обобщить это с какой-либо универсальной концепцией трубопровода, которую мы можем использовать между дисциплинами. Ворон продаж приходит в голову, но это тангенциально, и мы охватим его позже. Лучший пример потоков, и один вы Абсолютно должен Ознакомьтесь с ли вы, если вы еще не были труб Unix:

cat server.log | grep 400 | less

Мы ласково называем |. характер трубы. На основании его функции мы подключаемся к выходу одной программы в качестве ввода другой программы. Эффективно создать трубопровод.

(Кроме того, это выглядит как труба.)

Если вы похожи на меня и удивляетесь в этот момент, почему это необходимо, спросите себя, почему мы используем трубопроводы в реальной жизни. Принципиально это структура, которая устраняет хранилище между точками обработки. Нам не нужно беспокоиться о хранении бочонков нефти, если он накачан.

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

Я не хочу изобретать колесо здесь. Так что теперь, когда я накрыл метафору потоков и обоснованное для их использования, имеет отличные Блог пост покрытие того, как они реализованы в узле. Займите до тех пор, пока вам нужно, чтобы там прикрыть основы, и когда вы будете готовы вернуться, и мы перейдем на использование случая.

Ситуация

Итак, теперь, когда у вас есть этот инструмент в вашей панели инструментов, рисую это:

Вы находитесь на работе, и ваш менеджер/юридический/HR/ваш клиент/(вставьте заинтересованную сторону здесь) подошел к вам с проблемой. Они проводят слишком длинные прорисования над структурированными PDF. Конечно, обычно люди не скажут вам такую вещь. Вы услышите: «Я провожу 4 часа, делая запись данных». Или «Я смотрю ценовые столы». Или, «Я заполняю правильные формы, чтобы мы получили нашу компанию фирменные карандаши каждый квартал».

Что бы это ни было, если их работа будет включать как (а) чтение структурированных документов PDF и (b) объемного использования этой структурированной информации. Затем вы можете наступить и сказать: «Эй, мы сможем автоматизировать это и освободить свое время на работу над другими вещами».

Таким образом, ради этой статьи давайте придумаем фиктивную компанию. Откуда я пришел, термин «манекен» относится к идиоту или соску ребенка. Итак, давайте представим эту поддельную компанию, которая производит Pacifiers. Пока мы на этом, давайте прыгаем в акулу и скажем, что они напечатаны 3D. Компания работает как этический поставщик Pacifiers для нуждающихся, которые не могут себе позволить себе премиум сами.

(Я знаю, как тупые это звучит, приостанавливают свое неверие, пожалуйста.)

Источники TODD Печатные материалы, которые входят в продукцию Dummeth, и должны обеспечить, чтобы они соответствовали трем ключевым критериям:

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

Проект

Так что легче следовать, я настроил Gitlab Reppo Вы можете клонировать и использовать. Убедитесь, что ваши установки узла и NPM имеют сегодня.

Основная архитектура: ограничения

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

Предположим, что продовольственный сорт материала оценивается по шкале от нуля до трех. С нулевым значением, запрещенным в калифорнии, богатых BPA пластмасс. Три смысла обычно используются не загрязняющие материалы, такие как полиэтилен низкой плотности. Это чисто для упрощения нашего кода. На самом деле нам придется каким-то карта текстовых описаний этих материалов (например: «ЛДПЭ») в продовольственный сорт.

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

Расположение, мы собираемся упростить и предположим, что это простое относительное расстояние, так как волна летит. На противоположном конце спектра есть чрезмерное решение: использование некоторых API (например.: Google Maps) для различения грубого расстояния поездки Данный материал будет путешествовать для достижения распределительного центра TODD. В любом случае, скажем, что мы даем это как значение (километры-к тому, что) в PDF TODD.

Кроме того, давайте рассмотрим контекст, в котором мы работаем. Тоддо эффективно работает как собиратель информации на динамическом рынке. Продукты входят и выходят, и их детали могут измениться. Это означает, что у нас есть произвольное количество PDF, которые могут измениться – или более удачно, быть обновленным – в любое время.

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

Основная архитектура: решения

Таким образом, у нас есть произвольное количество PDFS, а некоторые правила для того, чтобы их разобрать. Вот как мы можем сделать это:

  1. Установите объект потока, который может читать из некоторого ввода. Как HTTP-клиент, запрашивающий PDF загрузки. Или модуль, который мы написали, что читает файлы PDF из каталога в файловой системе.
  2. Настройте посредник Буфер Отказ Это как официант в ресторане, доставляющем готовое блюдо к предполагаемому клиенту. Каждый раз, когда полный PDF передается в поток, мы промыли эти куски в буфер, чтобы его можно было транспортировать.
  3. Официант (буфер) обеспечивает питание (PDF данные) клиенту (наша функция анализа). Клиент делает то, что они пожалуйста (преобразуйте в какой-то формат электронной таблицы).
  4. Когда клиент (Parser) выполнен, пусть официант (буфер) знает, что они свободны и могут работать над новыми заказами (PDF).

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

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

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

Так что в Гранд-схеме вещей это выглядит что-то подобное:

Внедрение зависимостей

Сейчас как отказ от ответственности, я должен добавить, что есть целый мир мыслей вокруг введения зависимостей в ваш код. Я хотел бы покрыть эту концепцию в другом посте. Тем временем позвольте мне просто сказать, что один из фундаментальных конфликтов в пьесе – это то, что между нашим желанием добиться нашей работы быстро (то есть.: Во избежание NIH синдром ), и наше желание избежать сторонний риск Отказ

Применяя это в наш проект, я решил разгрузить основную часть нашей обработки PDF для pdwreader модуль. Вот несколько причин, почему:

  • Он был недавно опубликован, что является хорошим признаком того, что репо актуальна.
  • У него одна зависимость – то есть это просто абстракция по другому модулю, которое регулярно поддерживается на Github. Это только это отличный знак. Более того, зависимость, модуль называется PDF2JSON Имеет сотни звезд, 22 участника, и многие глазные яблоки держат близкого глаз на нем.
  • Сопровождающий, Адриан Джоли , делает хорошее бухгалтерское дело в трекере выпуска Github и активно имеет тенденцию пользователей и вопросов разработчиков.
  • При аудитере через NPM (6.4.1) нет уязвимостей.

Так что все во всем, кажется, что безопасная зависимость включает.

Теперь модуль работает довольно простым способом, хотя его readme явно не описывает структуру ее выхода. Клифтные заметки:

  1. Это подвергает Pdwreader класс, который будет создан
  2. Этот экземпляр имеет два метода для анализа PDF. Они возвращают один и тот же выход и отличаются только на входе: Pdfreader.parsefileiTems Для имени файла и Pdfreader.parybuffer Из данных, которые мы не хотим ссылаться с файловой системы.
  3. Методы просят обратный вызов, который вызывается каждый раз, когда Pdwreader Находит то, что он обозначает как элемент PDF. Есть три вида. Во-первых, файловые метаданные, которые всегда являются первым элементом. Второй – метаданные страницы. Это действует как возвращение каретки для обработки координат текстовых элементов. Последние текстовые элементы, которые мы можем подумать как простые объекты/структуры с помощью текстового свойства и с плавающей точкой 2D Абб координаты на странице.
  4. Это зависит от нашего обратного вызова для обработки этих элементов в структуру данных нашего выбора, а также для обработки любых ошибок, брошенных на него.

Вот один фрагмент кода в качестве примера:

const { PdfReader } = require('pdfreader');
// Initialise the readerconst reader = new PdfReader();
// Read some arbitrarily defined bufferreader.parseBuffer(buffer, (err, item) =>; {
  if (err)    console.error(err);
  else if (!item)    /* pdfreader queues up the items in the PDF and passes them to     * the callback. When no item is passed, it's indicating that     * we're done reading the PDF. */    console.log('Done.');
  else if (item.file)    // File items only reference the PDF's file path.    console.log(`Parsing ${item.file && item.file.path || 'a buffer'}`)
  else if (item.page)    // Page items simply contain their page number.    console.log(`Reached page ${item.page}`);
  else if (item.text) {
    // Text items have a few more properties:    const itemAsString = [      item.text,      'x: ' + item.x,      'y: ' + item.y,      'w: ' + item.width,      'h: ' + item.height,    ].join('\n\t');
    console.log('Text Item: ', itemAsString);
  }
});

PDFS TODD

Вернемся в ситуацию в Тоддо, просто чтобы обеспечить некоторое контекст. Мы хотим хранить Pacifiers данных на основе трех ключевых критериев:

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

Я заканчивал простой скрипт, который рандомилизует некоторые фиктивные продукты, и вы можете найти его в /data Каталог компаньона REPO для этого проекта. Этот скрипт записывает, что рандомизированные данные на файлы JSON.

Там также там есть шаблон документа. Если вы знакомы с шаблонными двигателями, такими как Руль Тогда ты поймешь это. Есть онлайн-сервисы – или если вы чувствуете авантюристые, вы можете катиться своим собственным – что возьми данные JSON и заполните шаблон и вернуть его в качестве PDF. Может быть, для полноты, мы можем попробовать это в другом проекте. В любом случае: я использовал такую услугу для создания пустых PDF, которые мы будем разблокировать.

Вот что выглядит (дополнительный пробел был обрезан):

Мы хотели бы уступить от этого PDF немного JSON, который дает нам:

  • Идентификатор заявки и дата, для учебных целей,
  • SKU соску, для уникальной идентификации и
  • Свойства соску (имя, продовольственный сорт, цена на единицу и расстояние), поэтому TODD может фактически использовать их в своей работе.

как нам это сделать?

Чтение данных

Сначала давайте создадим функцию для чтения данных из одного из этих PDF, и извлекать pdwreader PDF элементы в используемую структуру данных. На данный момент давайте проведем массив, представляющий документ. Каждый элемент в массиве является объектом, представляющим коллекцию всех текстовых элементов на странице в индексе этого объекта. Каждое свойство в объекте страницы имеет значение Y для его ключа, и массив текстовых элементов, обнаруженных при этом Y-значении для его значения. Вот диаграмма, так что это проще понять:

readpdfpages Функция в /parser/index.js обрабатывает это, аналогично примеру, написанным выше:

/* Accepts a buffer (e.g.: from fs.readFile), and parses * it as a PDF, giving back a usable data structure for * application-specific, second-level parsing. */function readPDFPages (buffer) {  const reader = new PdfReader();
  // We're returning a Promise here, as the PDF reading  // operation is asynchronous.  return new Promise((resolve, reject) =>; {
    // Each item in this array represents a page in the PDF    let pages = [];
    reader.parseBuffer(buffer, (err, item) =>; {
      if (err)        // If we've got a problem, eject!        reject(err)
      else if (!item)        // If we're out of items, resolve with the data structure        resolve(pages);
      else if (item.page)        // If the parser's reached a new page, it's time to        // work on the next page object in our pages array.        pages.push({});
      else if (item.text) {
        // If we have NOT got a new page item, then we need        // to either retrieve or create a new "row" array        // to represent the collection of text items at our        // current Y position, which will be this item's Y        // position.
        // Hence, this line reads as,        // "Either retrieve the row array for our current page,        //  at our current Y position, or make a new one"        const row = pages[pages.length-1][item.y] || [];
        // Add the item to the reference container (i.e.: the row)        row.push(item.text);
        // Include the container in the current page        pages[pages.length-1][item.y] = row;
      }
    });  });
}

Так что теперь передавая PDF буфер в эту функцию, мы получим некоторые организованные данные. Вот то, что я получил от теста, и печатать его к JSON:

[ { '3.473': [ 'PRODUCT DETAILS REQUISITION' ],    '4.329': [ 'Date: 23/05/2019' ],    '5.185': [ 'Requsition ID: 298831' ],    '6.898': [ 'Pacifier Tech', 'Todd Lerr' ],    '7.754': [ '123 Example Blvd', 'DummEth Pty. Ltd.' ],    '8.61': [ 'Timbuktu', '1337 Leet St' ],    '12.235': [ 'SKU', '6308005' ],    '13.466': [ 'Product Name', 'Square Lemon Qartz Pacifier' ],    '14.698': [ 'Food Grade', '3' ],    '15.928999999999998': [ '$ / kg', '1.29' ],    '17.16': [ 'Location', '55' ] } ]

Если вы посмотрите внимательно, вы заметите, что в исходном PDF появляется ошибка правописания. «Реквизиция» написано как «рецидивирование». Красота нашего парсера заключается в том, что нам не особо заботиться о таких ошибках в наших входных документах. Пока они правильно структурированы, мы можем извлечь данные из них точно.

Теперь нам просто нужно организовать это во что-то более полезное для использования (как будто мы выставим его через API). Структура, которую мы ищем, это что-то вдоль линий этого:

{  reqID: '000000',  date: 'DD/MM/YYYY', // Or something else based on geography  sku: '000000',  name: 'Some String We Have Trimmed',  foodGrade: 'X',  unitPrice: 'D.CC',  // D for Dollars, C for Cents  location: 'XX',}

В сторону: целостность данных

Почему мы в том числе числа как строки? Он основан на риске расставания. Давайте просто скажем, что мы принудили все наши цифры на строки:

Цена и местоположение единицы будет в порядке – они должны быть счетными числами в конце концов.

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

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

Поэтому, поскольку мы хотим целостности данных при чтении PDFS, мы просто оставьте все как строку. Если код приложения хочет преобразовать некоторые поля в цифры, чтобы сделать их полезными для арифметических или статистических операций, то мы позволим принудительному принущению на этом слое. Здесь мы просто хотим что-то, что анализирует PDFS последовательно и точно.

Реструктуризация данных

Итак, теперь у нас есть информация Тодда, нам просто нужно организовать ее в использовании. Мы можем использовать различные функции манипуляции массива и объектов, а здесь MDN твой друг.

Это шаг, где у каждого есть свои предпочтения. Некоторые предпочитают метод, который просто выполняет работу и сводит к минимуму DEV Time. Другие предпочитают разведчиться для лучшего алгоритма для работы (например.: Сокращение времени итерации). Это хорошее упражнение, чтобы увидеть, сможете ли вы придумать способ сделать это и сравнить его с тем, что я получил. Я хотел бы видеть лучше, проще, быстрее или даже разные способы достичь той же цели.

Во всяком случае, вот как я это сделал: parsetoddpdf Функция в /parser/index.js Отказ

function parseToddPDF (pages) {
  const page = pages[0]; // We know there's only going to be one page
  // Declarative map of PDF data that we expect, based on Todd's structure  const fields = {    // "We expect the reqID field to be on the row at 5.185, and the    //  first item in that array"    reqID: { row: '5.185', index: 0 },    date: { row: '4.329', index: 0 },    sku: { row: '12.235', index: 1 },    name: { row: '13.466', index: 1 },    foodGrade: { row: '14.698', index: 1 },    unitPrice: { row: '15.928999999999998', index: 1 },    location: { row: '17.16', index: 1 },  };
  const data = {};
  // Assign the page data to an object we can return, as per  // our fields specification  Object.keys(fields)    .forEach((key) =>; {
      const field = fields[key];      const val = page[field.row][field.index];
      // We don't want to lose leading zeros here, and can trust      // any application / data handling to worry about that. This is      // why we don't coerce to Number.      data[key] = val;
    });
  // Manually fixing up some text fields so they're usable  data.reqID = data.reqID.slice('Requsition ID: '.length);  data.date = data.date.slice('Date: '.length);
  return data;
}

Мясо и картофель вот в foreach петля и как мы его используем. После извлечения положений Y каждая текстовый элемент ранее простым указывать каждое поле, которое мы хотим в качестве позиции в наших страницах объектом. Эффективно предоставляя карту для последующей деятельности.

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

После нескольких одноиналров, чтобы поставить несколько строковых полей, мы можем вернуть объект данных и мы находимся в гонках. Вот что похоже:

{ reqID: '298831',  date: '23/05/2019',  sku: '6308005',  name: 'Square Lemon Qartz Pacifier',  foodGrade: '3',  unitPrice: '1.29',  location: '55' }

Положить все это вместе

Теперь мы перейдем к созданию некоторого параллелизма для этого модуля анализа, поэтому мы можем работать в масштабе и распознавать некоторые важные барьеры для этого. Диаграмма выше значит для понимания контекста логики разборы. Это не значит для понимания того, как мы собираемся распараллелизировать его. Мы можем сделать лучше:

Тривиальный, я знаю, и, пожалуй, слишком уже слишком учебник – Y обобщел для нас практически использовать, но эй, это фундаментальная концепция формализации.

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

  • Это будет приложение командной строки?
  • Это будет последовательный сервер, с набором конечных точек API? Это имеет свой собственный состав вопросов – отдых или график, например?
  • Может быть, это просто скелетной модуль в более широкой кодовой базе – например, что, если мы обобщем наш анализ через набор двоичных документов и хотели разделить модель параллелизма из конкретного типа исходного файла и реализации анализа исходных файлов?

Для простоты, я собираюсь обернуть логику разборки в утилите командной строки. Это означает, что пришло время сделать кучу допущений:

  • Ожидается ли он файловые пути в качестве ввода, и они относительны или абсолютны?
  • Или вместо этого ожидают, что он будет включен в PDF-данные?
  • Он собирается выводить данные в файл? Потому что, если это так, то мы должны предоставить этот вариант в качестве аргумента для пользователя, чтобы указать …

Обработка ввода командной строки

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

node index file-1.pdf file-2.pdf … file-n.pdf

Или трубопровод к стандартному вводу в качестве нового разделенного списка путей файлов:

# read lines from a text file with all our pathscat files-to-parse.txt | node index# or perhaps just list them from a directoryfind ./data -name "*.pdf" | node index

Это позволяет процессу узла манипулировать порядком этих путей любым способом, который он видит, что позволяет нам масштабировать код обработки позже. Для этого мы собираемся прочитать список файлов, в зависимости от того, каким бы ни было предоставлено, и отключить их каким-то произвольным числом в подписи. Вот код, getterminalinput Метод ./input/index.js :

function getTerminalInput (subArrays) {
  return new Promise((resolve, reject) =>; {
    const output = [];      if (process.stdin.isTTY) {
      const input = process.argv.slice(2);
      const len = Math.min(subArrays, Math.ceil(input.length / subArrays));
      while (input.length) {        output.push(input.splice(0, len));      }
      resolve(output);
    } else {          let input = '';      process.stdin.setEncoding('utf-8');
      process.stdin.on('readable', () => {        let chunk;        while (chunk = process.stdin.read())          input += chunk;      });
      process.stdin.on('end', () => {        input = input.trim().split('\n');
        const len = Math.min(input.length, Math.ceil(input.length / subArrays));
        while (input.length) {          output.push(input.splice(0, len));        }
        resolve(output);      })        }      });
}

Почему divvy до списка? Допустим, у вас есть 8-ядерный процессор на потребительском оборудовании, и 500 PDF для анализа.

К сожалению для узла, даже если он фантастически обрабатывает асинхронный код, благодаря контуре события, он работает только на одном потоке. Для обработки этих 500 PDFS, если вы не используете код MultiThreaded (I.E: MULLY PLACE), вы используете только восьмую часть вашей обработки. Предполагая, что эффективность памяти не является проблемой, вы можете обрабатывать данные в восемь раз быстрее, воспользовавшись встроенным модулям параллелизма узлов.

Разделение нашего ввода в куски позволяет нам это сделать.

Кроме того, это, по сути, это привычный балансировщик нагрузки и четко предполагает, что рабочие нагрузки, представленные анализами каждого PDF, являются взаимозаменяемыми. То есть PDFS одинаковых размеров и удерживает ту же структуру.

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

Кластеризация нашего кода

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

Тот, который мы собираемся использовать, кластер Модуль, в основном позволяет процессу узла порождать копии себя и обработку баланса между ними, поскольку оно видит в форме.

Это построено на вершине Child_Process Модуль, который менее тесно связан с самими программами распараллелирования узлов и позволяет создавать другие процессы, такие как программы оболочки или другой исполняемый файл двоичных данных, а также интерфейс с использованием стандартного ввода, вывода et cetea.

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

Так что давайте пройдемся по коду. Вот оптом:

const cluster = require('cluster');const numCPUs = require('os').cpus().length;
const { getTerminalInput } = require('./input');
(async function main () {
  if (cluster.isMaster) {
    const workerData = await getTerminalInput(numCPUs);
    for (let i = 0; i < workerData.length; i++) {
      const worker = cluster.fork();      const params = { filenames: workerData[i] };
      worker.send(params);
    }
  } else {
    require('./worker');
  }
})();

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

Теперь основной метод на самом деле достаточно просто. На самом деле, мы могли бы сломать его на шаги:

  1. Если мы основной процесс, разделив вход, отправленный нам равномерно на количество ядер CPU для этой машины
  2. Для каждого работника – это нагрузка, порождайте рабочего по Cluster.fork И настройте объект, который мы можем отправить ему по каналу сообщения Module Module [Cluster] Module, и отправьте ему чертовски.
  3. Если мы на самом деле не на самом деле основной модуль, то мы должны быть рабочим – просто запустите код в нашем рабочем файле и вызовите его в день.

Здесь нет ничего сумасшедшего, и это позволяет нам сосредоточиться на реальном подъеме, что выясняется, как работник собирается использовать список файлов, которые мы даем ему.

Обмен сообщениями, async и Streams, все элементы питательной диеты

Во-первых, как указано выше, позвольте мне бросить код для вас. Поверьте мне, просматривая его первым, позволит вам пропустить любые объяснения, которые вы считаете тривиальными.

const Bufferer = require('../bufferer');const Parser = require('../parser');const { createReadStream } = require('fs');
process.on('message', async (options) =>; {
  const { filenames } = options;  const parser = new Parser();
  const parseAndLog = async (buf) => console.log(await parser.parse(buf) + ',');
  const parsingQueue = filenames.reduce(async (result, filename) =>; {
    await result;
    return new Promise((resolve, reject) =>; {
      const reader = createReadStream(filename);      const bufferer = new Bufferer({ onEnd: parseAndLog });
      reader        .pipe(bufferer)        .once('finish', resolve)        .once('error', reject)        });    }, true);
  try {    await parsingQueue;    process.exit(0);  } catch (err) {    console.error(err);    process.exit(1);  }
});

Теперь здесь есть несколько грязных хайков, поэтому будьте осторожны, если вы один из неиниктированных (только шутки). Давайте посмотрим, что происходит первым:

Шаг один – потребовать всех необходимых ингредиентов. Ум на вас, это основано на том, что делает сам код. Итак, позвольте мне просто сказать, что мы собираемся использовать на заказ поток пирета в пользовательском языке, который у меня энтуально называемый буферной буфер, обертка для нашей пассажирующей логики из последнего времени, а также сложным названием, анализаторами, а также старый добрый надежный создатель по течению от ФС модуль.

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

Таким образом, мы можем видеть прежде всего, что это асинхронно. Во-первых, мы извлекаем имена файлов из самого сообщения – если бы это был производственный код, я бы проверил их здесь. На самом деле, черт возьми, я бы проверял их в нашем коде обработки ввода ранее. Затем мы создали наш объект анализа – только один для всего процесса – это так, что мы можем разбирать несколько буферов с одним набором методов. Божественная забота заключается в том, что это управление памятью внутренне, а по отражению, это хорошая вещь для рассмотрения позже.

Тогда есть простая обертка, Parseandlog Вокруг анализа, которые регистрируют буфер PDF JSON-Ifeed с запятой, прилагаемым к нему, просто чтобы упростить жизнь для объединения результатов анализа нескольких PDF.

Наконец мясо материи, асинхронная очередь. Позволь мне объяснить:

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

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

Итак, теперь у нас есть все кусочки, мы просто передаем их вместе:

  1. Читаемый поток – файл PDF, трубы к буфереру
  2. Буферщик заканчивает и называет наш работник – ширину Parseandlog метод

Весь этот процесс завернут обещание, которое сам возвращается к функции снижения, которая она находится внутри. Когда он разрешится, уменьшение операции продолжается.

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

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

Так что это работает, но это полезно?

Так что у вас есть. Это было немного путешествий, и это, безусловно, работает, но, как и любой код, важно рассмотреть, каковы его сильные и слабые стороны. С верхней части моей головы:

  • Потоки должны быть сложены в буферах. Это, к сожалению, поражает цель использования потоков, а эффективность памяти страдает соответственно. Это необходимая протока-лента, предназначенная для работы с pdwreader модуль. Я хотел бы увидеть, есть ли способ потокотать данные PDF и разбирать его на более тонкозернистом уровне. Особенно, если модульная, функциональная логика разборки все еще может применяться к ней.
  • На этом детском этапе разборная логика также раздражает ломкостью. Просто подумайте, что если у меня есть документ, который длиннее страницы? Куча предположений вылетает окно и затруднит потребность в потоковых данных PDF еще сильнее.
  • Наконец, было бы здорово увидеть, как мы могли бы построить эту функциональность с помощью журнала и конечных точек API, чтобы обеспечить общественности – по цене, или PRO BOONO, в зависимости от контекста, в которых он используется.

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