Pull to refresh

Parse it!

Reading time4 min
Views7.6K
Какое-то время назад мне по работе пришлось провести небольшое исследование. Суть его состояла в поиске наилучшего pdf-парсера реализованного на java.

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

Долгое время документы парсились при помощи библиотеки PDFBOX, работа которой не вызвала ни у кого радости: долго и со сбоями.
В итоге были выбраны 4 библиотеки, сравнением которых я занялся: PDFBOX, JPod, iText и Acrobat.

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

Оценка времени:


Размер файла

PDFBOX

Acrobat

JPOD

iText

74,1 KB 00:02.591 00:03.155 00:01.683 00:00.963
257,5 KB 00:01.680 00:03.191 00:00.116 00:00.78
1,6 MB 00:05.805 00:02.884 00:02.532 00:02.79
28,1 MB E 01:10.983 00:43.815 E
13,6 MB 00:05.218 00:04.331 00:00.599 00:00.77
1,9 MB 00:02.782 00:14.506 00:00.608 00:00.707
1,6 MB 00:06.182 00:02.98 00:00.906 00:02.413
8,9 MB 00:05.98 00:03.894 00:00.680 00:00.647
2,4 MB 00:14.15 00:07.893 00:02.826 E
604,7 KB 00:03.342 00:04.721 00:00.551 00:01.222
100,6 KB 00:01.819 00:04.212 00:00.84 00:00.456
1,6 MB 00:05.633 00:03.99 00:00.883 00:02.18
10,3 MB 00:22.311 00:22.145 00:27.663 E
1,9 MB 00:06.943 00:14.736 00:01.200 E
2,1 MB 00:02.573 E 00:00.498 00:00.475
111,0 KB 00:01.956 00:02.846 00:00.705 00:00.300
814,3 KB 00:02.552 00:04.221 00:00.306 00:00.900
2,0 MB 00:06.319 00:07.128 00:01.821 00:02.796
338,7 KB 00:01.950 00:03.684 00:00.79 00:00.415
12,9 MB 00:15.932 00:13.628 00:04.989 E
7,3 MB E 00:17.275 00:16.377 E
97,2 MB 00:27.291 00:01.994 00:05.739 E
5,2 MB 00:07.773 00:11.108 00:01.964 E
 
Total:
Best 2 14 7
Middle 12 7 8 8
Worst (including errors) 11 14 1 8
 
Errors 2 1 8

Красным и зеленым цветами выделены худшее и лучшее время соответственно. Буква «Е» обозначает состояние перманентного коллапса, настигшее процесс вследствие переполнения буфера либо каких-либо иных ошибок.
В сравнении времени объективным победителем стал JPod. Порадовало отсутствие ошибок при парсинге.

Оценка качества:


Оценка качества была довольно субъективна и делилась всего на 3 категории: Best, Middle и Worst. Также еще есть оценка Empty, которая выставлялась, если в процессе парсинга наступал коллапс или просто парсер не находил текста внутри документа.
Оценивалось сходство полученного текста с оригиналом, но не очень критично, т.к. текст нужен был для индекса, а не для вывода.

Размер файла

PDFBOX

Acrobat

JPOD

iText

74,1 KB Best Middle Best Best
257,5 KB Best Middle Best Empty
1,6 MB Best Empty Empty Worst
28,1 MB Empty Middle Best Empty
13,6 MB Empty Empty Empty Empty
1,9 MB Best Middle Worst Middle
1,6 MB Best Empty Worst Worst
8,9 MB Best Middle Middle Best
2,4 MB Best Middle Worst Empty
604,7 KB Best Middle Middle Middle
100,6 KB Best Middle Best Empty
1,6 MB Best Empty Worst Worst
10,3 MB Best Best Best Empty
1,9 MB Best Best Best Empty
2,1 MB Best Best Best Empty
111,0 KB Best Best Best Best
814,3 KB Best Worst Best Best
2,0 MB Best Middle Best Best
338,7 KB Middle Middle Best Empty
12,9 MB Best Best Best Empty
7,3 MB Empty Best Best Empty
97,2 MB Best Best Middle Empty
5,2 MB Best Best Best Empty
 
Total:
Best 19 8 14 5
Middle 1 10 3 2
Worst (including empty) 3 5 6 16
 
Empty 3 4 2 13


Характерные особенности парсинга:
Acrobat очень часто парсит текст в одну строку. Пробелы между словами и предложениями сохраняются, поэтому в принципе это не критично для индексации.
iText не понимает «не-английские» символы. В тестировании применялись документы на английском, немецком и французском языках. Поэтому все их умляуты пошли лесом. Даже не просто пошли лесом – вместо подобных символов я получил вопросики. Возможно, это где-то и настраивается, но остальные понимали подобные символы без плясок с бубном.
PDFBOX по качеству нареканий не вызвал.
JPod – тоже все ок. За исключением одной особенности, которая заставила повозиться с ним довольно долго. В некоторых случаях документ полностью или частями парсился по одной букве в строку – для индекса такой парсинг бесполезен.

В итоге, победителем был объявлен JPod, несмотря на его особенность парсить по букве в строку.
Предстояло разобраться с этим.

Часть вторая. Внутри JPod.


На ковыряние исходников JPod ушло немало времени. Письма, форум проекта. В итоге было установлено, что подобное поведение парсера вызвано тем, какую ориентацию имеют страницы документа. Книжная ориентация проходит нормально, а альбомная – нет. Попытки поковырять параметры ничего не дали. Свойства класса, ответственные за ориентацию страницы были бесполезны.
В общем, в один из моментов я решил просто удалить из классов всяческую работу со шрифтами. Все равно для индексации текста они не нужны. Это помогло, т.к. блоки текста рассчитывались неверно, и это было вызвано именно шрифтами.
Тут бы я и остановился, но Egiptyanin настоял на том, что необходимо все-таки дойти до конца. Дальше я почти не участвовал.
Решение было найдено, и в этом виде используется: была переопределена матрица аффинных преобразований. Вместо динамической матрицы была установлена статическая. Класс CSPlainTextExtractor использовался вместо CSTextExtractor. Новый класс имеет такой вид:

public class CSPlainTextExtractor extends CSTextExtractor {
public void textSetTransform(float a, float b, float c, float d, float e, float f) {
super.textSetTransform(1, 0, 0, 1, 0, 0);
}
}

Конечно, это не панацея и очень редко парсер не добавляет переносы в необходимые места, но для индексации это не важно.

Собственно, все. Спасибо за внимание =)

P.S. Это моя первая более менее серьезная статья, надеюсь на объективную критику.

Upd Особо внимательные читатели нашли в таблицах несоответствия — пофиксил.
Tags:
Hubs:
Total votes 40: ↑39 and ↓1+38
Comments13

Articles