Sphinx, часть 2: Perl API

Date December 31st, 2009 Author Vitaly Agapov

perlsphinxВ прошлой статье «Sphinx: начало» я описал процедуру установки Sphinx и показал, как создавать полнотекстовые индексы на примере базы данных с книгами. Теперь нам надо понять, как использовать эти индексы в Perl-приложении. Это может быть любое Perl-приложение, но в основном Sphinx Perl API находит применение в Web CGI. Почему именно в Perl? Да потому что про PHP и так написано везде и помногу, и, кроме того, Perl – это наше всё.

В качестве API будем использовать перловый модуль Sphinx::Search, легко добывающийся с cpan.org:

cpan -i Sphinx::Search

Модуль обладает всей необходимой функциональностью, отлично работает и вообще всячески радует. На момент написания актуальной версией является 0.22, которую можно найти здесь – http://search.cpan.org/~jjschutz/Sphinx-Search-0.22/lib/Sphinx/Search.pm.

Итак, для нашего примера с индексом book_search и кодировкой utf-8 в базе и на сайте для вывода результатов поиска нужен примерно такой вот код:

use Sphinx::Search;
my $q = $input{query}; 			# Получаем строку поиска
my $string = decode('utf-8', $q); 	# Преобразуем строку для поиска из utf-8 во внутренний формат Perl
my $sphinx = Sphinx::Search->new(); 	# Вызываем конструктор
my %weights=(name=>3, annotation=>1);
my $results = $sphinx->SetMatchMode(SPH_MATCH_ANY)	# Режим поиска совпадений
                      ->SetSortMode(SPH_SORT_RELEVANCE)	# Режим сортировки
		      ->SetFieldWeights(\%weights)	# Вес полей
                      ->Query($string, 'book_search');	# Поисковый запрос
my $found=$results->{total_found};	# Получаем количество найденных совпадений
my $attrs=$results->{words};
for (my $x=0; $x<$found; $x++)
{
	$doc=$results->{matches}->[$x]->{doc};
	...
	# читаем из таблицы book строку с id = $doc и выводим нужные поля
	... 
}

Всё достаточно просто. Но для полноты картины расскажу о кастомизации и возможностях модуля.

Некоторые методы объекта Sphinx::Search

$error = $sphinx->GetLastError;
Получить строку с сообщением о последней ошибке

$warning = $sphinx->GetLastWarning;
Получить строку с сообщением о последнем предупреждении

$sphinx->SetServer($host, $port);
Установить хост и порт в случае, если searchd запущен на другом хосте

$sphinx->SetConnectTimeout($timeout)
Установить таймаут соединения в секундах

$sphinx->SetMaxQueryTime($millisec);
Установить ограничение на вермя запроса в миллисекундах. 0 – отсутствие ограничения.

$sphinx->SetMatchMode($mode);
Установить режим поиска совпадений. Возможные аргументы:

  • SPH_MATCH_ALL – Совпадение по всем словам
  • SPH_MATCH_ANY – Совпадение по любому из слов
  • SPH_MATCH_PHRASE – Точное совпадение фразы
  • SPH_MATCH_BOOLEAN – Запрос с использованием логических операторов AND (&), OR (|), NOT (!,-)

$sphinx->SetSortMode(SPH_SORT_RELEVANCE);
$sphinx->SetSortMode($mode, $sortby);
Установить режим сортировки результатов. Возможны аргументы:

  • SPH_SORT_RELEVANCE – сортировка по релевантности
  • SPH_SORT_ATTR_DESC, SPH_SORT_ATTR_ASC – сортировка по убыванию/возрастанию атрибута $sortby
  • SPH_SORT_TIME_SEGMENTS – сортировка по временному атрибуту $sortby по убыванию
  • SPH_SORT_EXTENDED – сортировка по аналогии с синтаксисом SQL, где $sortby определяет режим

$sphinx->SetFieldWeights(\%weights);
Установить вес для полей. Хэш определяет численные значения для соответствующих полей таблиц.

$sphinx->SetIndexWeights(\%weights);
Установить вес для индексов в том случае если для поиска используется более одного индекса.

$sphinx->SetIDRange($min, $max);
Установить диапазон значений id для записей.

$sphinx->SetFilter($attr, \@values);
$sphinx->SetFilter($attr, \@values, $exclude);
Установить фильтры по атрибутам. Если установлен $exclude, то фильтр будет удалять соответствующие записи.

$sphinx->SetRetries($count, $delay);
Установить количество повторных попыток и задержку между попытками.

$results = $sphinx->Query($query, $index);
Подключиться к searchd и выполнить поиск данного запроса в данном индексе. Если $index=”*”, то поиск ведётся по всем индексам. Возвращает хэш с такими ключами:

  • matches – массив с хэшами о найденных записях (ключи “doc”, “weight”, “group”, “stamp” )
  • total – количество совпадений
  • total_found – количество найденных записей
  • time – время поиска
  • words – хэш с отдельными словами запроса

$results = $sphinx->AddQuery($query, $index);
Добавить запрос в последовательность запросов. Механизм позволяет оптимизировать сложны или многократные запросы, добавляя возможность поиска в найденных результатах.

$sphinx->RunQueries
Выполнить последовательность запросов.


Поисковые сниппеты

Sphinx::Search позволяет генерировать сниппеты с контекстом, в котором встретилось ключевое слово. В своей терминологии здесь используется понятие excerpts.
Для генерирования сниппета используется метод BuildExcerpts:

$excerpts = $spinx->BuildExcerpts($docs, $index, $words, $opts)

Здесь:

  • $docs – ссылка на массив строк с содержимым документа
  • $index – строка с именем индекса для стемминга
  • $words – строка, содержащая ключевые слова для из выделения
  • $opts – ссылка на хэш с полями, определяющими параметры выделения ключевых слов
    • before_match – строка для вставки перед ключевым словом, по умолчанию “<b>”
    • after_match – строка для вставки после ключевого слова, по умолчанию “</b>”
    • chunk_separator – строка для вставки между кусками сниппета, по умолчанию ” … “
    • limit – максимальное количество символов в сниппете, по умолчанию 256
    • around – сколько слов выделять вокруг каждого совпадения, по умолчанию 5
    • exact_phrase – выделять ли только конкретные совпадения, по умолчанию false
    • single_passage – выделять ли только одно лучшее совпадение, по умолчанию false
    • use_boundaries
    • weight_order

Для пущей понятности принципа получения сниппетов приведу примерный кусок кода, как раз этим и занимающийся:

use Sphinx::Search;
use DBI;
# Подключаемся к базе
my $dbh = DBI->connect("DBI:mysql:database=db_name;host=localhost","user","pwd",{'RaiseError' => 1});
        $dbh->do("SET NAMES utf8");
        $dbh->do("SET CHARACTER SET utf8");
        $dbh->do("SET character_set_connection=utf8");
        $dbh->do("SET character_set_client=utf8");
        $dbh->do("SET character_set_server=utf8");
        $dbh->do("SET character_set_results=utf8");
# Здесь пропустим кусок, занимающийся вспомогательными делами
my $q = $input{query};              # Получаем строку поиска
my $string = decode('utf-8', $q);   # Преобразуем строку для поиска из utf-8 во внутренний формат Perl
my $sphinx = Sphinx::Search->new(); # Вызываем конструктор
my $results = $sphinx->SetMatchMode(SPH_MATCH_ANY)       # Режим поиска совпадений
                      ->SetSortMode(SPH_SORT_RELEVANCE)  # Режим сортировки
                      ->Query($string, 'index_name');    # Поисковый запрос
my $found=$results->{total_found};    # Получаем количество найденных совпадений
my $opts = {       # Задаём опции для BuildExcerpts
    'after_match' => '</strong>',
    'before_match' => '<strong>'
};
for (my $x=0; $x<$found; $x++)
{
    my $doc = $results->{matches}->[$x]->{doc};   # Получаем id найденного документа
    my $sql_query="SELECT text FROM table_name WHERE id=".$doc;  # Получаем текст документа
    my $sth = $dbh->prepare($sql_query);
    $sth->execute;
    if (my $ref = $sth->fetchrow_hashref())
    {
        my $tmp = decode('utf-8', $ref->{text});   # Преобразуем текст во внутренний формат Perl
        my $excerpts = $sphinx->BuildExcerpts([$tmp], 'index_name', $string, $opts);  # Ищем совпадения
        $ref->{text} = encode('utf-8', $excerpts->[0]);	  # Преобразуем полученную строку обратно в UTF-8
        print $ref->{text};  # Выводим на экран сниппет с подсвеченными ключевыми словами
    }
}

Tags: , ,
Category: MySQL, Perl, Web-dev | 4 Comments »

Comments

4 комментариев на “Sphinx, часть 2: Perl API”

  1. Сергей

    Добрый день, очень интересно если у меня несколько индексов, то как предать их в BuildExcerpts? через запятую и пробел не получается возвращает ноль.

  2. МультиКот

    Маленькое замечание: запрос к базе делать построчно в цикле – 20 результатов = 20 запросов, нерационально (стр. 26).
    А статья очень пригодилась спасибо.

  3. Mir

    Добрый день!

    Имеем задачу сделать поиск по базе запчастей от поставщиков, она заключается в следующем:

    Наша компания занимается ремонтом мобильной техники и нам необходимо когда звонит клиент
    по виду услуги и виду модели делать поиск по базе поставщиков (которые уже залиты и
    проиндексированы sphinx'ом)

    Пример:

    iPhone 5s Замена дисплея

    Мы обращаемся в нашу базу тегов где прописаны на услугу Замена дисплея следующие теги:
    (диспл, тачскрин, сборе, lcd, touch, digitizer и так далее скажем штук 10 тегов)

    Дальше мы дробим название бренда и модели по пробелам на теги – то есть получается массив
    тегов:

    {iPhone, 5s, диспл, тачскрин, сборе, lcd, touch, digitizer}

    Нам нужно, чтобы поиск был по тегам, и выводились результаты ТОП 100 подходящих по
    релевантности.

    Даже если скажем у нас в базе 300 строк, из них если искать по тегам чтобы все находились
    теги в строках – даже если их будет 50 штук, то остальные 50 в любом случае должны быть
    показаны (даже если не найден хотябы 1 тег) – то есть нужен вывод по релевантности. Если
    например сделаем вывод 300 строк – то будут выведены все строки с БД, но показаны будут
    по релевантности (количеству вхождений тегов, то есть Строка с наличием 10 тегов будет на
    1 месте, с 9 на 2 и так далее.)

    Никак не можем понять как настроить конфиг.

    Очень просим помочь, заранее больше спасибо!

  4. agapoff.name | IT блог » Blog Archive » Sphinx, часть 3. Real-Time индексы

    […] Sphinx, часть 2: Perl API […]

Leave a comment

 Comment Form