Решение: при помощи программы pdftotext, утилиты find, скриптовых языков Bash и Perl, а также некоторой смекалки и использования мощи линуксовой консоли, решаем проблему.
В этой статье я начал рассказ про замечательную поисковую систему SWISH++ и о том, как её приспособить под такие специфичные нужды, как поиск внутри pdf-документов. Здесь я напишу о том, как это автоматизировать ещё больше.
Вариант решения задачи
Как говорят поклонники Perl, "there is more than one way to do it". Я решил её так, если кто укажет на более лёгкий / правильный / экономичный / красивый путь - просьба отписаться в комментариях.
Итак, вкратце алгоритм таков:
- утилитой find ищем все документы pdf и вытаскиваем из них текст утилитой pdftotext
- индексируем найденные файлы программой index++
- задаёмся порогом релевантности (подбираем опытным путём)
- ищем по ключевым словам программой search++
- найденные файлы pdf перемещаем в каталог со словами поиска
- при необходимости повторить :-)
Поиск документов pdf и вытаскивание текста
Собственно, для этого просим утилиту find найти все файлы по маске *.pdf и для каждого выполнить вытаскивание текста программой pdftotext без выдачи предупреждений и без вставки символов разрыва страницы. Это делается командой:
find -name '*.pdf' -exec pdftotext -nopgbrk -q {} \;С русским языком программа pdftotext дружит слабо, хотя и пытается что-то вытащить. Тем не менее, большинство статей на английском, так что это нам не страшно.
Построение индекса
Тут всё очень просто - просим index++ проиндексировать все текстовые файлы, начиная с текущего каталога:
index++ -e "text:*.txt" .Точка в конце и означает, что поиск ведётся с текущего каталога.
Поиск по ключевым словам скриптом
На десерт - мой скрипт на PERL, который выполняет автоматизированный поиск и сортировку. Просьба аксакалов перловедения ногами не пинать - "не ругайте баяниста, он играет, как умеет". Цветовая разметка, сделанная в Kate, приведена для удобства чтения, скрипт подробно прокомментирован (чтобы я сам потом не забыл, что да как).
#! /usr/bin/perl
use strict;
# use warnings;
`index++ -e "text:*.txt" .`; #Создание индексированного списка текстовых файлов
в текущей директории с подробной выдачей статистики пользователю
# ### <-------- Блок создания переменных и определения порога
my $kmvThreshold = 30; #Обрубать по порогу: практика показала, что ниже
порога релевантности 30-40 документы не соответствуют критериям поиска
my $ii;
my $iimax;
### <-------- Блок создания переменных и определения порога
# ### <-------- Блок ввода запроса и организация каталога для перемещнения
туда найденный файлов
print "Query to PDF search?", "\n"; #Выдать запрос на ввод поисковой фразы
my $kmvQuery =; #Поисковый запрос к базе индексированных файлов
chop $kmvQuery;#обрубить символ возврата каретки
my $kmvNewDirName = $kmvQuery; #Создаём переменную для имени каталога
$kmvNewDirName =~ s/\s+//xgi; #Замена в тексте запроса пробелов для
создания каталога, в который будут перемещены найденные файлы
`mkdir $kmvNewDirName`; #Создаём новый подкаталог в текущем каталоге,
в который будут перемещены найденные файлы
### <-------- Блок ввода запроса и организация каталога для перемещения
туда найденный файлов
# ### <-------- Блок обработки запроса
my @kmvSearchResultsRaw = `search++ $kmvQuery`; #Поиск по запросу и его результаты
$iimax = scalar @kmvSearchResultsRaw;#Размер массива результатов - сколько строк
### <-------- Блок обработки запроса
### <-------- Блок принятия решения о перемещении файлов в соответствии
с релевантностью
for ($ii=0;$ii<$iimax;$ii++) #перебираем в цикле строки
{
my $kmvCurrent = substr($kmvSearchResultsRaw[$ii],0,3);#берём первые три
символа - информация о релевантности (три - так как она может быть и 100)
if (int $kmvCurrent > 0 and $kmvCurrent > $kmvThreshold) #Если в строках
первое число и оно больше порога (может быть и не число, а количество найденных совпадений)
{
my ($kmvResultRelev,$kmvResultHomeName,$kmvResultFilesize,$kmvResultFilename) =
($kmvSearchResultsRaw[$ii] =~ m/(\S+)\s(\S+)\s(\S+)\s(\S+)/xgi);
#Эта строка стоила мне половину вечера курения книги по Перл.
Для "Не Достигших Полного Просветления на Пути Perl" объясню всю эту адскую конструкцию.
Строка $kmvSearchResultsRaw[$ii] разбирается на четыре переменные (которые слева)
по следующему сценарию: (символы_не_пробела) пробел (символы_не_пробела) пробел
(символы_не_пробела) пробел (символы_не_пробела) - поиск глобальный
(не до первого совпадения), допускает пробелы и не учитывает регистра.
$kmvResultFilename =~ s/.txt/.pdf/xgi; #Выданное имя файла txt заменяется на pdf
print "Moving ", $kmvResultFilename, "\n"; #напечатать сообщение о перемещении
найденного файла
`mv $kmvResultFilename $kmvNewDirName`; #перемещение найденного файла в
новый каталог
}
}
### <-------- Блок принятия решения о перемещении файлов в соответствии
с релевантностью
Что-то вроде этого. Может быть, в скрипте есть некоторые топорные решения, но мне не до изящества кода - надо архивы разгребать.
Плоды прогресса
Проведение натурных экспериментов показало достаточно высокую точность и адекватность сортировки. Запрашивая сначала по точным критериям, а потом по всё более общим (запросы два-три слова), я разгрёб 2400 документов за полтора часа. Задача для системы усложнялась тем, что тексты были однородны по тематике. Точность (после визуального сравнения) составляет где-то 60-70%. Конечно, отобранный материал пробегается глазами по заголовкам, то есть реализуется полуавтоматический режим.
Впереди ещё оптимизация поиска, надо будет подучить систему немного, и тогда уже вволю конкурировать с Гуглом и Яндексом.
Я ещё при предыдущей статье задумался о том, чтобы натравить енто дело на мои завалы, теперь вот попробую на досуге ;)
ОтветитьУдалитьШурик, дело стоящее - точность довольно высокая, а уж как это быстро всё делается... прямо душа поёт! :-)
ОтветитьУдалитьmy ($kmvResultRelev,$kmvResultHomeName,$kmvResultFilesize,$kmvResultFilename) =
ОтветитьУдалить($kmvSearchResultsRaw[$ii] =~ m/(\S+)\s(\S+)\s(\S+)\s(\S+)/xgi);
#Эта строка стоила мне половину вечера курения половины книги по Перл.
И все-таки... можно проще ;)
my ($kmvResultRelev,$kmvResultHomeName,$kmvResultFilesize,$kmvResultFilename = split(' ', $kmvSearchResultsRaw[$ii]);
2 Portnov
ОтветитьУдалитьОгромное спасибо! Поправлю скрипт обязательно - я в перле не силён, нашёл первый рецепт в книге и применил его. Сплит искал-искал, так и не отыскал. Так что спасибо!
После ввода команды
ОтветитьУдалитьfind -name '*.pdf' -exec pdftotext -nopgbrk -q{}\;
терминал выдаёт
find: отсутствует аргумент у `-exec'
Что я делаю не так?