Skip to content

programmers_guide_rus

Bogatenkova Anastasiya edited this page Dec 17, 2020 · 2 revisions

Опишем общую схему работу пайплайна:

Общий пайплайн устроен следующим образом:

  1. Пользователь отправляет файл и дополнительные параметры через пост-запрос.
  2. Модуль API сохраняет файл в временную директорию и вызывает менеджера (код в файле dedoc/api/dedoc_api.py)
  3. Менеджер переименовывает файл, сохраняя расширение. Это необходимо для того, чтобы в имени файла не осталось пробелов, не ascii символов, инъекций и других нежелательных вещей. После этого менеджер пытается сконвертировать файл с помощью FileConverter. Код менеджера находится в файле dedoc/manager/dedoc_manager.py
  4. FileConverter проверяет может ли он сконвертировать файл с таким расширением. Если может то выполняет конвертацию и возвращает имя сконвертированного файла, если не может то возвращает имя исходного файла. Код находится в dedoc_project/converters/file_converter.py
  5. После конвертации производится извлечение информации из документа, этим занимается DocParser. Он возвращает пару (UnstructuredDocument, может ли такой файл содержать вложения). Код находится в dedoc/readers/doc_parser.py
  6. StructureConstructor собирает структурированный файл, он принимает на вход UnstructuredDocument и возвращает DocumentContent. Пример можно найти в dedoc/structure_constructor/tree_constructor.py
  7. Документ обогащается метаданными, это делает MetadataExtractor, его код находится в dedoc/metadata_extractor/basic_metadata_extractor.py
  8. (опциональный шаг) Извлекаются и анализируются вложенные файлы. Это делает менеджер (каждый вложенный файл проходит пайплайн от шага 2 до шага 8)
  9. Пользователь получает результат в качестве респонса на его реквест.

Опишем подробнее каждый шаг пайплайна:

API

Отвечает за обработку запросов и отправку респонсов, так же содержит некоторые вспомогательные функции, например для работы с онлайн-документацией, отображения логотипа и так далее. Код находится в файле dedoc/api/dedoc_api.py

Manager

Менеджер выполняет основную часть работу, но, как и положено настоящему менеджеру, в основном делегирует работу своим подчинённым. В целом менеджер отвечает за все шаги пайплайна обработки запроса кроме получения файла и отправки респонса. Менеджер может обрабатывать как файл, полученный из запроса, так и файл, находящийся в локальной файловой системе. Конфигурация менеджера производится с помощью специального конфигурационного файла (он лежит в dedoc/manager_config.py) Код находится менеджера находится в dedoc/manager/dedoc_manager.py

FileConverter

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

DocParser

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

Результаты работы BaseReader

BaseReader используется для извлечения данных и метаинформации о содержимом документа (UnstructuredDocument), а так же информация может ли документ содержать вложенные файлы. UnstructuredDocument состоит из списка страниц и списка строк, каждая строка представлена объектом класса LineWithMeta.

LineWithMeta содержит текст, метаинформацию о тексте (такой как тип строки, номер строки и так далее), список аннотаций (аннотация содержит информацию об отдельный словах или частях текста), а так же HierarchyLevel необходимый для сворачивания документа.

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

ГЛАВНЫЙ ЗАГОЛОВОК (0, 0)

  1. Первый пункт (1, 0)

1.1 Первый подпункт (1, 1)

StructureConstructor

После чтения файла в DocParser содержимое документа представляет из себя плоский список строк (и таблицы). StructureConstructor превращает плоскую структуру в древовидную (или другую). Результатом его работы является DocumentContent. Для сворачивания плоского документа в древовидную структуру используется

BasicMetadataExtractor

Извлекает метоинформацию о документе, такую как размер, дата создания и изменения и так далее.

AttachmentsExtractor

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

Опишем структуры данных

Вход и выход API

API ожидает получить на вход POST запрос с вложенным файлом и словарём дополнительных параметров. Вы можете посмотреть пример отправки запроса в dedoc/examples/example_post.py

Можно заметить, что в запросе мы передаём вложенный файл и дополнительные параметры. DedocApi извлекает файл из запроса и передаёт его на обработку в DedocManager.

Вход и выход DedocManager

Менеджер ожидает получить на вход FileStorage (werkzeug.datastructures.FileStorage) и дополнительные параметры в виде словаря {"имя параметра": "значение параметра"}

Результатом работы менеджера будет объект типа ParsedDocument

Вы можете посмотреть пример в dedoc/examples/example_manager_input.py

Вход и выход FileConverter

FileConverter получает на вход путь до директории с временными файлами (например /tmp в Linux), и имя файла, который надо сконвертировать. Конвертер может не опасаться того, что в название файла будут пробелы или другие нежелательные символы т.к. имя менеджер переименовывает файлы перед сохранением.

FileConverter возвращает имя сконвертированного файла (он должен находиться в той же директории, что и исходный).

Пример можно посмотреть в dedoc/converters/concrete_converters/docx_converter/DocxConverter.py

Вход и выход DocParser

Если вы предпочитаете смотреть код, то стоит посмотреть dedoc/readers/docx_reader/docx_reader/DocxReader.py и dedoc/examples/create_unstructured_document.py

DocParser выполняет основную работу, так что про него стоит написать подробнее. Предположим что мы хотим прочитать файл dedoc/examples/example.docx и представить его в виде следующего дерева: example_tree

Кроме того в документе есть таблица, её надо извлечь:

N Second name Name Organization Phone Notes
1 Ivanov Ivan ISP RAS 8-800

Первым делом DocParser находит подходящего исполнителя для этой работы, в данном случае это будет dedoc/readers/docx_reader/docx_reader.py, ему будет делегирована задача сформировать UnstructuredDocument и определить может ли пришедший файл содержать вложения (вообще, а не этот конкретный).

DocxReader знает что документы в формате docx могут содержать вложения, так что второе значение будет True, теперь надо сформировать UnstructuredDocument.

Формируем таблицу

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

Из массива ячеек и метаинформации создаём объект класса Table.

Формируем список строк.

Каждая строка документа представлена объектом класса LineWithMeta, который в свою очередь состоит из

  • line - текст строки
  • hierarchy_level - параметр, отвечающий за формирование древовидного представления документа, о нём мы напишем подробнее.
  • metadata - метаинформация о строке, такая как номер строки, тип строки, номер страницы, на которой находится строка и так далее.
  • annotations - список аннотаций, с их помощью можно отметить особенности текста строки (такие как жирность, курсив и так далее), для отдельных частей текста от start до end
Определяем вложенность строки

Нам необходим способ задать иерархическую структуру в плоском списке строк, в этом нам помогает HierarcyLevel. HierarchyLevel состоит из следующих параметров:

  • level_1 и level_2 - уровень иерархии, задающийся парой чисел. При сравнение двух HierarchyLevel они сравниваются по паре (level_1, level_2), чем меньше уровень иерархии тем "важнее" строка, например (0, 0) соответствует корню документа.
  • paragraph_type - тип строки (используется для объединения многострочных заголовков)
  • can_be_multiline - некоторые элементы документа (такие как название) могут состоять из нескольких строк. В таком случае надо выставить can_be_multiline = True, тогда при формирование древовидного представления несколько подряд идущих строк с одинаковым level_1, level_2 и paragraph_type они будут объединены в один элемент.

Может возникнуть вопрос зачем задавать уровень иерархии двумя числами? В документе мы можем встретить списки типа

  1. 1.1. 1.2.1.1 и так далее. Тогда мы можем задать уровни иерархии следующим образом:
  • HierarchyLevel(1, 1) для 1.
  • HierarchyLevel(1, 2) для 1.1.
  • HierarchyLevel(1, 4) для 1.2.1.1. и так далее

Читаем строки и определяем их вложенность.

Добавляем новый тип документов

Подробнее можно посмотреть тут

Предположим что мы хотим добавить обработку возможность обработки документов в формате pdf/djvu с текстовым слоем. Мы не хотим разбираться с двумя форматами, тем более что djvu хорошо конвертируется в pdf. Давайте опишем что нам нужно сделать для этого.

Краткое содержание

  • Пишем свой конвертер из djvu в pdf.
  • Пишем свой PdfReader.
  • Пишем свой PdfAttachmentsExtractor.
  • Добавляем всё в конфиг менеджера.

Теперь подробнее опишем каждый шаг:

Пишем свой конвертер из djvu в pdf DjvuConverter.

Для этого нам надо создать класс-наследник BaseConverter и реализовать два метода do_convert и can_convert

can_convert сообщает можем ли мы наш новый конвертер обработать файл, например мы можем возвращать True если расширение файла .djvu

do_convert выполняет преобразование файла, например мы можем использовать утилиту ddjvu и вызывать её через os.system. Мы можем не опасаться того, что в имени файла встретятся пробелы или другие нежелательные символы т.к. файл был переименован менеджером.

Пишем свой PdfReader.

Нам надо сделать наш класс наследником класса BaseReader и реализовать два метода can_read и read

'can_read' проверяет можем ли мы обработать пришедший файл, мы можем использовать для этого путь до файла, информацию о расширении файла, mime и тип документа (передаётся пользователем в запросе, например можем обрабатывать только документы-научные статьи). Старайтесь сделать этот метод быстрым т.к. он будет вызываться часто, в том числе на не pdf документы.

read не мне вас учить как читать текст из pdf, важно что в итоге необходимо сформировать UnstructuredDocument. Метод так же должен сообщить может ли данный документ содержать вложения (pdf может так что стоит вернуть True, либо проверить содержит ли данный файл вложения).

Пишем свой PdfAttachmentsExtractor.

Нам необходимо сделать наш класс наследником BaseAttachmentsExtractor и реализовать два метода can_extract и get_attachments Первый проверяет можем ли наш экстрактор извлечь вложения из файла такого типа (т.е. что это pdf), а второй непосредственно занимается извлечением, он должен вернуть List[AttachedFile] - список вложенных файлов.

Добавляем всё в конфиг менеджера.

dedoc/manager_config.py - конфиг находится тут, если вы хотите использовать свой конфиг, то используйте set_manager_config до того, как создать объект DedocManager.

В новом конфиге нам необходимо изменить следующие поля:

  • converters добавить DjvuConverter
  • readers добавить PdfReader
  • attachments_extractors добавить PdfAttachmentsExtractor