Представление документа в виде простого текста понадобится для анализа его содержимого: индексирования и поиска, классификации, предварительной проверки.
В нашем случае, стояла задача предварительного анализа (скоринга) документов по их содержимому. Верхнеуровневый процесс обработки документов построен с использованием MS Power Automate, поэтому конвертор нужно было реализовать в виде некоего облачного сервиса, доступного через HTTP.
В результате получился очень компактный сервис экстракции текста из офисных файлов, который успешно работает у нас уже несколько месяцев. Под катом - краткое описание сервиса, ссылка на репозиторий и другие полезные статьи по теме.
Преобразование файлов
Выделение текстового содержимого из разнообразных офисных файлов разного формата - задача не новая. На Хабре уже опубликовано несколько хороших статей по теме конверсии PDF, RTF, например:
"Cага о пакетном конвертировании pdf в text" - тут хороша и статья, и обсуждения. Это была начальная точка входа в проблему конвертации файлов.
Серия статей "Текст любой ценой" (PDF, RTF) - это про то, как сделать вообще все своими руками. Статья сильно утвердила в понимании, что надо пользоваться готовыми библиотеками.
После некоторого исследования и тестов различных пакетов (PyPdf2, striprtf, doc2text), я остановился на textract. Пакет хорошо документирован в части использования и инсталляции необходимый базовых компонент.
Textract очень прост и компактен в использовании. И самое главное - он позволяет вытаскивать текст из различных форматов стандартных офисных файлов: .doc
, .docx
, .rtf
, .pdf
, .xlsx
, .ppt
import textract
file_name = "<path to the file>"
encoding = "utf-8"
text = textract.process(file_name).decode(encoding)
В процессе эксплуатации встречались случаи, когда файл с расширением .doc
на самом деле содержал rtf
. В этом случае конвертор antiword
выдавал соответствующую ошибку и все, что нужно было сделать - переименовать файл и подать его на обработку снова.
Сервис конвертации
Сервис конвертирования файлов реализован на базе FastAPI. Он упаковывается и запускается в Docker контейнере.
Сервис принимает исходный файл в виде multipart/form-data
и возвращает ответ в JSON формате, содержащем текст, статус конвертации и информацию об ошибках.
class ConvertResult(BaseModel):
result: str
text: str
text_length: int
file_name: str
messages: str
...
@app.post('/convert', response_model=ConvertResult)
async def convert_file(file: UploadFile = File("file_to_convert"),
encoding: str = "utf-8",
user_name: str = Depends(get_current_username)):
...
Авторизация
Компонент HTTP
MS Power Automate, который мы использовали для вызова сервиса, умеет авторизоваться с использованием многих схем: Basic Authenticate, Client Certificate, OAuth, Raw.
Поскольку сервис ничего не хранит на своей стороне и реализует не критичный сервис, авторизация запросов была реализована на базовом уровне с использованием Basic Authentication. Совместно с протоколом https такой подход видится достаточным. А на стороне MS Power Automate прамеры аутентификации можно сохранить в raw виде.
При развертывании контейнера компоненты авторизации (логин / пароль) можно установить через параметры окружения контейнера.
Установка зависимостей textract
При сборке контейнера, как описано в документации по textract,
нужно поставить утилиты, на которые пакет рассчитывает при работе. Чтобы не раздувать образ ставить можно только то, что реально планируется использовать.
Поскольку в нашем случае необходима была только конвертация из документов, из инсталляции удалены компоненты отвечающие за OCR и преобразование звуковых файлов.
Фрагмент Dockerfile
выглядит так:
...
RUN apt-get update && \
apt-get install -y antiword unrtf poppler-utils && \
rm -rf /var/lib/apt/lists/* && \
apt clean
...
Использование временных файлов
Определенной спецификой пакета textract
является то, что на вход он принимает только файл, а не байтовый поток. Это из-за того, что по расширению файла определяется необходимый конвертер. А конверторы, зачастую - внешние CLI утилиты.
Соответственно, сервис, после получения файла на конвертацию через вызов, обязан сохранить его локально во временный файл, с учетом исходного расширения файла. Далее временный файл отправляется в textract
на преобразование и после преобразования - удаляется.
Вопрос производительности в данном сервисе не является очень критичным, однако несколько улучшить ситуацию можно размещая область временных файлов в памяти, используя tmpfs
. Как это сделать для Docker описано в документации.
Использование сервиса
Вызов сервиса из MS Power Automate
Основное назначение сервиса - выделять текст из документа в рамках одного из шагов процесса. В этом случае сервис вызывается через компонент "HTTP".
Поскольку сервис принимает на вход файл в составе multipart\form-data
, отправка запроса должна быть описана в определенном формате в разделе Body
как показано на картинке.
При завершении вызова текст можно получить из поля ответа:
body('get_text')['text']
API сервиса
Для того, чтобы использовать сервис из различных приложений, вместе с сервисом разработан API пакет.
Пакет предоставляет классы синхронного и асинхронного клиентов для сервиса. Использование сервиса в этом случае выглядит примерно так:
from file_to_text import ConverterClient
client = ConverterClient('<url to your service>', '<username>', '<password>')
text = client.convert(filepath='<path to file to be converted>')
Репозиторий
Готовая к использованию реализация сервиса и API доступны в репозитории на GitHub.
Буду рад комментариям и улучшениям.
dmitryvolochaev
Документы Офиса 2007 и более поздних - это XML в зипе. Для них не надо специальных библиотек. Нужна только распаковка zip и парсинг XML. Дальше просто берем для корневого элемента innerText.
Вот так это выглядит на C#:
Есть, конечно, у такого подхода недостатки: InnerText не содержит пробелов на месте границ тегов. Т.е. абзацы склеены не просто в одну строку, а еще и без пробела.
Кроме того, грузить весь документ в память абсолютно незачем.
Обе проблемы решаются SAX-парсером.
А как у вашего конвертера потребление памяти зависит от размера документа?
serhit Автор
Да, все верно, поздние версии файлов MS Office, как и форматы Open Office, более открыты к извлечению текста. Однако, такие документы в нашем потоке составляют ~7% - остальное это pdf (~70%), и rtf с doc.
По поводу памяти и производительности. Как видно из постановки задачи (часть no/low-code процесса) и выбора средств (универсальный парсер-аггрегатор, основанный на внешних утилитах) - серьезная работа не проводилась.
Могу сказать, что при локальном запуске контейнера в Docker Desktop он при старте забирает ~25-26 Мб и дрейфует около этого значения (с учетом, что используется tmfs).
Тот же локальный экземпляр сервиса проводит конвертацию фалов (приведено к вреднему времени за 1000 символов выходного текста):
- docx - 5,3 ms
- pdf - 9.8 ms
- rtf - ~ 10 ms (но очень большой разброс в зависимости от файлов)
- doc - 5 ms