Игорь Чакрыгин Игорь Чакрыгин

У любой задачи существует по крайней мере одно очевидное и невероятно простое для понимания неправильное решение

Sphinx - Настраиваем поиск

Несомненно, одной из основных причин, почему вам может потребоваться использовать Sphinx, является возможность полнотекстового поиска. Благодаря большому количеству настроек, можно отрегулировать поиск таким образом, чтобы он максимально удовлетворял потребностям каждой конкретной задачи. В этой статье я постараюсь рассказать про некоторые из этих настроек более подробно, а также приведу примеры их использования.

Опция «charset_table»

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

Опция charset_table задаёт таблицу допустимых символов. Если несколько допустимых символов в тексте идут один за другим, то Sphinx воспринимает их как одно слово. Если символ не указан в таблице символов и, соответственно, не считается допустимым, то Sphinx воспринимает его как разделитель.

По умолчанию для индексов с кодировкой utf-8 опция charset_table выглядит следующим образом:

charset_table = 0..9, A..Z->a..z, _, a..z, \
    U+410..U+42F->U+430..U+44F, U+430..U+44F, U+401->U+451, U+451

Допустимые символы в таблице символов указываются через запятую. Записи 0..9 и a..z обозначают, что все символы идущие между указанными символами считаются допустимыми. Можно также указывать отдельные символы, как это сделано с символом _.

Запись A..Z->a..z обозначает, что символы A..Z считаются допустимыми, но при обработке заменяются символами a..z, т.е. по сути приводятся к нижнему регистру. Это необходимо, чтобы поиск не был чувствителен к регистру.

Наконец, вторая строка по аналогии определяет правила обработки символов для кирилицы, которые заданы в формате U+xxx, что обязательно для всех символов, код которых больше 127. По сути это означает следующее:

charset_table = 0..9, A..Z->a..z, _, a..z, \
    А..Я->а..я, а..я, Ё->ё, ё

Пример

По умолчанию Sphinx воспринимает символы «Е» и «Ё» как различные. Мы можем изменить это поведение, чтобы, например, иметь возможность по запросу «сгущёнка» также находить документы со словом «сгущенка». Для этого в таблице символов необходимо изменить правила для символов «Ё» (U+401) и «ё» (U+451) тамим образом, чтобы эти символы отображались на символ «е» (U+0435).

charset_table = 0..9, A..Z->a..z, _, a..z, \
    U+410..U+42F->U+430..U+44F, U+430..U+44F, U+401->U+0435, U+451->U+0435

Теперь во всех запросах символы «Е» и «Ё» будут считаться одинаковыми.

select * from recipes where match('сгущёнка');

Опция «blend_chars»

Опция blend_chars похожа на опцию charset_table, но задаёт смешанные символы, которые будут считаться и допустимыми, и разделитеелями одновременно. При нахождении таких символов в тексте во время индексации, Sphinx будет индексировать все возможные варианты. Например, если символ «&» считается смешанным, а в индексируемом документе встретится текст «Рога&Копыта», то проиндексированы будут три слова: «рога», «копыта» и «рога&копыта». При поиске же всегда будет использоваться вариант, в котором все смешанные символы считаются допустимыми. Например, если поисковой запрос будет содержать слово «рога&копыта», то поиск будет производиться именно по нему, а не по словам «рога» и «копыта».

Опцию blend_chars можно использовать вместе со связанной опцией blend_mode, которая задаёт, какие комбинации допустимых и смешанных символов следует проиндексировать. Например, можно индексировать слова со смешанными символами в начале слова, отбросив при этом смешанные символы в конце, или наоборот. При этом можно указать несколько значений, проиндексировав тем самым несколько различных комбинаций.

Пример

Предположим, что мы хотим организовать поиск книг по названию и найти книни по языкам «C#» или «C++»:

select * from book where match('C#');
select * from book where match('C++');

Поскольку символы «#» и «+» не являются допустимым (не указаны в таблице символов), то Sphinx считает их разделителями и производит поиск только по слову «C». Добавить эти символы в таблицу символов было бы неправильно. Вместо этого, укажем их в опции blend_chars. Обратите внимание, что символ «#» указан как «U+23», поскольку иначе он бы считался началом комментария.

blend_chars = +, U+23

Обновив индекс и повторив поиск, мы убедимся в том, что теперь Sphinx ищет именно по словам «C#» и «C++».

select * from book where match('C#');
select * from book where match('C++');

Опция «ignore_chars»

Опция ignore_chars задаёт символы, которые Sphinx будет игнорировать, как будто их вообще нет в тексте. Таким образом, если два слова разделены только игнорируемым символом, они «склеятся» и будут считаться одним словом. Обычно, эту опцию следует использовать, если в индексируемом тексте присутствуют символы мягкого переноса, поэтому чаще всего её использование выглядит следующим образом:

ignore_chars = U+AD

Опция «morphology»

После того, как обрабатываемый текст разбит на слова, в дело вступают морфологические алгоритмы (если они, конечно, включены), которые заменяют все найденные слова на их нормализованную форму, позволяя тем самым, например, по запросу «книга» находить документы, содержащие слова «книги», «книгу» или «книгой». В Sphinx могут использоваться три типа морфологических алгоритмов: стеммер (stemmer), лемматайзер (lemmatizer) или фонетические алгоритмы. Для использования одного из них необходимо в настройках индекса указать опцию morphology.

Стеммер

Стеммер является наиболее простым и быстрым алгоритмом, позволяющим найти основу слова (часть слова, которая является одинаковой для разных его форм) без использования дополнительных словарей и основываясь только на определённых правилах удаления суффиксов и окончаний для конкретного языка. Основным минусом стеммера является то, что он далеко не всегда позволяет точно определить основу слова. Например, слова «девушка» и «девушек» будут считаться различными, поскольку после обработки стеммером первое слово превратится в «девушк», а второе не изменится. Возможна и обратная ситуация, когда два морфологически различных слова приводятся стеммером к одной и той же основе и поэтому считаются одинаковыми.

Вот пример того, как стеммер нормализует слова «девушка», «девушки» и «девушек»:

Для использования стеммера для русского и английского языков необходимо в опции morphology указать значение stem_enru.

morphology = stem_enru

Лемматайзер

Лемматайзер (появившийся только в версии 2.1.1-beta), в отличие от стеммера, использует морфологические словари, поэтому позволяет не просто находить основу слова, а приводить его к нормальной (словарной) форме. Поиск с использованием лемматайзера более точен, но за всё приходится платить - работает он немного медленнее, чем стеммер.

Вот пример того, как слова «девушка», «девушки» и «девушек» нормализует лемматайзер:

Поддержка лемматайзера в настоящее время реализована только для русского языка, реализация для английского языка скорее всего будет доступна в следующих версиях Sphinx. Для использования лемматайзера в первую очередь необходимо скачать морфологические словари с сайта sphinxsearch.com и в файле конфигурации в блоке indexer указать путь к папке со словарями при помощи опции lemmatizer_base.

indexer
{
    ...
    lemmatizer_base = c:/sphinx/data/dict/ 
}

После этого в опции morphology нужно указать значение lemmatize_ru или lemmatize_ru_all. При использовании опции lemmatize_ru_all, Sphinx будет индексировать все нормальные формы слова, если их несколько. Например, для слова «чайку» нормальной формой может быть как «чайка», так и «чаёк».

morphology = lemmatize_ru

Фонетические алгоритмы

Помимо стеммера и лематайзера Sphinx также поддерживает два фонетических алгоритма: Soundex и Metaphone, которые, однако, работают только для английского яыка. Эти алгоритмы заменяют слова на их фонетический код таким образом, что разные по написанию, но схожие по звучанию слова будут считаться одинаковыми. Наиболее полезными фонетические алгоритмы могут оказаться, например, для поиска по фамилиям.

Для использования фонетических алгоритмов необходимо в опции morphology указать значение soundex или metaphone.

morphology = metaphone

Опция «wordforms»

Опция wordforms задаёт путь к пользовательскому файлу словоформ, который имеет два основных назначения.

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

девушек => девушк

Обратите внимание, что при использовании стеммера после знака => должна идти именно основа слова («девушк», а не «девушка»), т.к. именно по основе слова впоследствии будет производится поиск. Также обратите внимание, что если вы используете индекс в кодировке utf-8, то файл словоформ тоже обязательно должен быть сохранён в этой же кодировке.

В результате слова «девушка» и «девушек» станут приводиться к одной основе «девушк» и будут считаться одинаковыми при поиске.

Во-вторых, при помощи файла словоформ можно организовать словарь поисковых синонимов. Например, если слову «Эппл» необходимо находить документы, содержащие слово «Яблоко», то в файл словоформ нужно добавить одну из следующих строк (в зависимости от того, пользуетесь вы стеммером или лемматайзером):

эппл => Яблок  # Стеммер
эппл => Яблоко # Лемматайзер

Такие результаты будут получены при поиске по слову «Эппл» для стеммера:

А такие для лемматайзера

Опция «stopwords»

Опция stopwords задаёт путь к файлу стоп-слов, которые игнорируются при индексации и поиске. Обычно в этот файл рекомендуется помещать слова, которые встречаются в индексе настолько часто, что никак не влияют на результат. Как правило это различные предлоги или союзы.

Например, если мы хотим, чтобы союз «и» игнорировался при поиске, мы можем добавить его в список стоп-слов. Тогда при поиске он будет игнорироваться:

Заключение

Если подытожить, то Sphinx имеет достаточно большое количество опций, правильная настройка которых может значительно влиять на качество поиска. Помимо опций, описанных в этой статье, он также поддерживает поиск по точному совпадению и по подстроке, а также поиск в html-документах.

А в следующей статье про Sphinx я планирую немного рассказать про распределённый поиск и привести несколько примеров.

32 комментария

  1. Подскажите, пожалуйста, а как запустить использование лемматайзера для rt-индекса, для которого не определяется блок indexer?

    ОтветитьУдалить
  2. А блок indexer ни для какого индекса не определяется =)
    Этот блок один на весь конфиг, как и блок searchd, и нужен только чтобы путь к словарям указать, поэтому там абсолютно всё тоже самое.
    В блоке index ставим morphology = lemmatize_ru, делаем insert в rt индекс и вставляемые данные будут обрабатываться лемматайзером (только для полей, само собой).

    ОтветитьУдалить
  3. Ага, спасибо, заведу этот блок, а то из-за того, что одни rt-индексы используются, его вообще нет в конфиге.

    ОтветитьУдалить
  4. В транке поддерживается лемматизатор для английского и немецкого языков (вдобавок к русскому).
    + появилась в конфиге секция common. Сейчас в неё кладётся путь к словарям лемматизатора (перемещён туда из indexer) и ещё семь разных параметров, связанных с RLP и JSON.
    (соответственно, транковая версия при наличии в старом конфиге lemmatizer_base не заведётся, нужно будет строчку перенести в common)

    ОтветитьУдалить
  5. Кажись в примере указывать строчную "ё" ("U+451") в конце не надо, она и так входит в объявленный диапазон "а-я".

    ОтветитьУдалить
  6. Не входит, это можно проверить по таблице символов. Буква "я" имеет код U+44F, а "ё" - U+451, т.е. идёт после через несколько других символов.

    ОтветитьУдалить
  7. О, не знал, спасибо. Тогда значит дефолтные настройки charset_table (как в документации) лишают людей слов с буквой "ё"? И как разработчиков ещё не замучили жалобами...

    ОтветитьУдалить
  8. В документации была ошибка, в последних версиях её уже поправили, кажется.

    ОтветитьУдалить
  9. Я бы через wordforms попробовал, наверное.

    ОтветитьУдалить
  10. Я пробовал через wordforms "с++ > c++", но похоже что замена не происходит. После переиндексации вижу две разные выдачи. До этого было через exceptions, но blend_chars с ними конфликтуют. Т.к. exceptions регистрозависимый то там получается много писать нужно. С blend_chars решение красивее, вот только бы еще эту проблему победить)

    ОтветитьУдалить
  11. У меня файл создан в vi, первая строчка обрабатывается номально. Там в этом файле уже есть правила, они обрабатываются хорошо. А вот С++ не хочет воспринимать. Наверно да, стоит спросить у разработчиков.

    ОтветитьУдалить
  12. newart godofspectrum2 июля 2014 г. в 21:26

    Подскажите как получить пример с девушками? У меня выдает пустой результат. Эти слова должны быть в базе данных проиндекированной сфинксом?

    mysql> call keywords('девушка девушки девушек', 'testrt');

    Empty set (0.00 sec)

    mysql> call keywords('девушка девушки девушек', 'test1');

    Empty set (0.00 sec)

    mysql> call keywords('черный', 'test1');

    Empty set (0.01 sec)

    mysql> call keywords('черный', 'testrt');

    Empty set (0.00 sec)

    mysql>

    ОтветитьУдалить
  13. newart godofspectrum5 июля 2014 г. в 23:12

    Кодировку выставил. Версия mysql новая (Centos 6.5). Версия сфинкса 2.1.8. Через php русские слова находятся. Вот только morphology = lemmatize_ru_all не работает. (словарь скачан и прописан)То есть ищется всегда точное совпадение, а словоформы не находятся.

    ОтветитьУдалить
  14. Я бы проверил сделующее: перестроить индекс, перезапустить сфинкс, проверить, нет ли ошибок в логах при запуске, возможно словарь не там лежит. А может это просто баг и нужно написать в их баг-трекер, и такое бывает.

    ОтветитьУдалить
  15. newart godofspectrum6 июля 2014 г. в 14:32

    Проблема оказалась в следующем. 1. морфологию нужно указывать для обоих интедксов (я зыбал для первого). 2. мофрология letimizzer_ru_all работает только в бете. В не бете работает просто ru. Было бы полезно увидеть конфиг файл с подробным описанием каждой строки. Я например не очень понимаю чем sql_QUERY отличается от sql_auery_info

    ОтветитьУдалить
  16. В других статьях есть ссылки на архивы с конфигами.
    sql_query_info ни разу не испрользовал, а значит он скорее всего не нужен.

    ОтветитьУдалить
  17. Подскажите, пожалуйста. Вот есть база фильмов. В ней, соответственно, названия фильмов, которые в индексе сфинкса. Как можно сделать, чтобы запросы типа "Терминатор 2" или "Поворот не туда 5" использовали числительное в поиске? Если есть настройка, позволяющая их прилеплять в к словам? или стоит в индекс загонять уже слепленные числительные с предыдущим словом? Но тогда, как я понимаю, накроется морфология. Либо способ отранжировать такие вещи наверх? То есть слеплять при покладке в индекс, и считать crc32. Может есть другое решение какой-нибудь хитрой настройкой?

    ОтветитьУдалить
  18. Что значит "использовали числительное в поиске"? Не очень понял. Можно примеры запросов и результатов, которые нужно получить?

    ОтветитьУдалить
  19. Индекс строится по полю "название фильма" и еще дополнительным полям с меньшем весом.
    Используется следующий ранкер: SPH_RANK_SPH04 с добавлением вынесения в топ записей с точным совпадением.
    $sphinx->SetRankingMode(SPH_RANK_EXPR, "sum((4*lcs+2*(min_hit_pos==1)+exact_hit)*user_weight+if(origtitlecrc==$querycrc,1000,0)+if(titlecrc==$querycrc,1000,0))*1000+bm25");

    Если ищем строку "Терминатор 2", то выдается "Терминатор: Да придет спаситель", "Терминатор 3: Восстание машин", "Терминатор","Терминатор 2: Судный день"...
    Хотелось бы видеть "Терминатор 2" первым в поиске. Слова подряд, с первого символа, что может быть лучше? Как я понимаю, это связано с тем, что числительное из одного символа (короткое), и не учитывается при поиске. Может ошибаюсь, ибо ставил min_word_len = 1 - не помогло. Что я делаю не так?

    charset_table = 0..9, A..Z->a..z, _, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F, U+401->U+0435, U+451->U+0435

    ОтветитьУдалить
  20. Спасибо за ответ. Я тут другими делами отвлекся.
    Я сразу не сказал почему-то, но индекс строится из нескольких полей, не только заголовок, но и синопсис фильма и год там присутствуют, так что "2" там есть, но где-то много дальше.
    Mysql сейчас попробую. Еще не пробовал так никогда.

    ОтветитьУдалить
  21. Подскажите, как организовать поиск слов, содержащих символы "!" . При поиске выдает ошибку: ERROR 1064 (42000) syntax error, unexpected $end near ''. Добавление в blend_chars, в charset_table не помогло, все равно поиск вылетает с ошибкой

    ОтветитьУдалить
  22. Возможно его надо экранировать как-то так: \! или \\! (или возможно даже так \\\\!, точно не помню)

    ОтветитьУдалить
  23. спасибо огромное Вам. \\! помогло, ошибка больше не появляется

    ОтветитьУдалить
  24. Спасибо тебе, добрый человек!

    ОтветитьУдалить
  25. Здравствуйте, подскажите, пожалуйста как сделать следующее:
    Есть каталог товаров, например товар называется IRF740PBF , он находится, все ок, нет никаких проблем.


    Но нужно сделать так, что бы этот товар находился по запросам, например: IRF 740 PBF, IRF 740PBF
    что бы игнорировались пробелы, но при этом есть опасение, что весь текст "слипнется" и индекс будет гигантским

    Пробовал сделать через
    ignore_chars = U+20 - не помогло


    Подсознательно подозреваю, что надо делать через
    blend_chars = U+20, но тоже не помогает (

    ОтветитьУдалить
  26. Нет готового механизма, насколько знаю.
    Единственный вариант - при индексации искать слова содержащие буквы и цифры и заменять их на все возможные комбинации.
    Или попробовать regexp_filter, который вроде есть не во всех сборках.
    http://sphinxsearch.com/docs/latest/conf-regexp-filter.html
    Разбивать через него слова содержащие и буквы и цыфры на отдельные слова

    ОтветитьУдалить
  27. Понял 8)) Спасибо большое за ответ 8))

    ОтветитьУдалить
  28. Здравствуйте, нужна помощь в такой задачке:

    В БД есть таблица сотрудников, необходим поиск по фамилиям.

    Есть такой случай: ищем "Иванова", но первые в результате находятся:
    Алексеев Иван,
    Абрамов Иван и т.д.

    Я понял, что это из-за настроек конфига, а точнее морфологии: morphology = stem_ru
    Если его убрать, то всё норм, но вот в чём загвоздка. Мне нужно реализовать выпадающий список найденных сотрудников, скажем 10 результатов. Т.е. начинаем вводить "Ива" формируется такой список:
    Иванов Иван Иванович
    Иванов Алексей Сергеевич
    Иванов Владимир Петрович
    и т.д.

    Но такого не получается сделать, т.к. отключена морфология и происходит поиск целого
    слова. Помогите, как правильно настроить sphinx для такой задачи?

    ОтветитьУдалить
  29. Включите
    http://sphinxsearch.com/docs/latest/conf-index-exact-words.html

    http://sphinxsearch.com/docs/latest/conf-expand-keywords.html

    тогда точны совпадения будут иметь больший вес.


    Поиск по части слова включается через конфиг в зависимости от версии (в последних включен по умолчанию)


    Фамилию выгружайте в отдельное поле, и при поиске задавайте ему больший вес, тогда в запросе
    http://sphinxsearch.com/docs/latest/sphinxql-select.html

    можно указать field_weights, чтобы совпадения с фамилией опять же имело больший вес.

    ОтветитьУдалить
  30. lemmatizer_base = /srv/lemitaizer_sphinx/

    подключается в блоке common{}

    долго копал в чем у вас в статье ошибка

    ОтветитьУдалить
  31. Это не ошибка. Просто примеры в статье для версии сфинкса 2.1.1. В ней не было блока common =)

    ОтветитьУдалить

© Игорь Чакрыгин. Все права защищены при помощи чёрной магии. Технологии Blogger.