PHP

Поиск с учетом морфологии русского языка

Семантический поиск текста с учетом морфологии - серьезная задача, с которой даже крупные поисковики справились с разной степенью успеха. Но частично реализовать поиск с учетом разных форм слов русского языка вы можете самостоятельно. Для этого лучше всего воспользоваться PHP-классом phpMorphy. Это очень удобное средство для организации поиска с учетом морфологии русского, английского, украинского, эстонского или немецкого языков. Словари для каждого языка суммарно занимают около 10 Мб, а сам подключаемый скрипт - чуть меньше 30 Кб. При этом не требуется устанавливать на сервер дополнительное программное обеспечение, все будет работать на самом обычном хостинге.

Начнем с подключения phpMorphy. Это делается очень просто. Сперва скачайте дистрибутив, словари, распакуйте все это в отдельную папку, соблюдая структуру вложенных каталогов. Словари разместите в папке dict. Теперь откроем файл примера из папки examples и посмотрим настройки:
// подключение движка phpMorphy
require_once('src/common.php');
 
// настройки движка
$opts = array(
    ...
);
 
// подключение словарей русского языка
$dir = 'dicts';
$dict_bundle = new phpMorphy_FilesBundle($dir, 'rus');
 
// создание класса
$morphy = new phpMorphy($dict_bundle, $opts);

В настройках используется важный параметр storage, он может принимать одно из трех значений: PHPMORPHY_STORAGE_FILE (не загружать файлы словарей в память целиком, это самый медленный вариант, но самый экономный в плане работы с ресурсами сервера), PHPMORPHY_STORAGE_SHM (загружать файл словаря целиком в shared-память, требуется расширение PHP shmop) или PHPMORPHY_STORAGE_MEM (также загружать файл в память целиком если не используется shmop, по скорости работы ничем не отличается от предыдущего). На виртуальном хостинге, скорее всего, придется использовать первый вариант, а на выделенном сервере для большей скорости лучше применять варианты с использованием памяти. Выберите вариант под свои задачи. Остальные настройки и их значения подробно расписаны в официальной документации.

Словари загружены, скрипт подключен, можно пробовать его в деле. Допустим, что надо найти текст "Примерная строка поиска":
$bulk_words = array('ПРИМЕРНАЯ', 'СТРОКА', 'ПОИСКА');
// Получить нормализованные слова
$base_form = $morphy->getBaseForm($bulk_words);
// Получить все словоформы
$all_forms = $morphy->getAllForms($bulk_words);
// Получить корни слов
$pseudo_root = $morphy->getPseudoRoot($bulk_words);

Обратите внимание, что перед передачей phpMorphy поисковая строка переводится в верхний регистр и разделяется на отдельные слова. Вот что у нас получается. Нормализованные слова (массив $base_form):
Array (
[СТРОКА] => Array (
[0] => СТРОКА
)

[ПРИМЕРНАЯ] => Array (
[0] => ПРИМЕРНЫЙ
)

[ПОИСКА] => Array (
[0] => ПОИСК
)
)

Все словоформы (массив $all_forms):
Array (
[СТРОКА] => Array (
[0] => СТРОКА
[1] => СТРОКИ
[2] => СТРОКЕ
[3] => СТРОКУ
[4] => СТРОКОЙ
[5] => СТРОКОЮ
[6] => СТРОК
[7] => СТРОКАМ
[8] => СТРОКАМИ
[9] => СТРОКАХ
)

[ПРИМЕРНАЯ] => Array (
[0] => ПРИМЕРНЫЙ
[1] => ПРИМЕРНОГО
[2] => ПРИМЕРНОМУ
[3] => ПРИМЕРНЫМ
[4] => ПРИМЕРНОМ
[5] => ПРИМЕРНАЯ
[6] => ПРИМЕРНОЙ
[7] => ПРИМЕРНУЮ
[8] => ПРИМЕРНОЮ
[9] => ПРИМЕРНОЕ
[10] => ПРИМЕРНЫЕ
[11] => ПРИМЕРНЫХ
[12] => ПРИМЕРНЫМИ
[13] => ПРИМЕРЕН
[14] => ПРИМЕРНА
[15] => ПРИМЕРНО
[16] => ПРИМЕРНЫ
[17] => ПРИМЕРНЕЕ
[18] => ПРИМЕРНЕЙ
[19] => ПОПРИМЕРНЕЕ
[20] => ПОПРИМЕРНЕЙ
)

[ПОИСКА] => Array (
[0] => ПОИСК
[1] => ПОИСКА
[2] => ПОИСКУ
[3] => ПОИСКОМ
[4] => ПОИСКЕ
[5] => ПОИСКИ
[6] => ПОИСКОВ
[7] => ПОИСКАМ
[8] => ПОИСКАМИ
[9] => ПОИСКАХ
)
)

Корни слов (массив $pseudo_root):
Array (
[СТРОКА] => Array (
[0] => СТРОК
)

[ПРИМЕРНАЯ] => Array (
[0] => ПРИМЕР
)

[ПОИСКА] => Array (
[0] => ПОИСК
)
)

Как нам это поможет в поиске? Тут есть несколько вариантов. Например, при загрузке или редактировании статьи ее текст при помощи phpMorphy разбирается на нормализованные слова (то есть начальная форма слова) и они сохраняются в базу. Это может быть дополнительное поле в таблице статей, для которого создается индекс FULLTEXT.
SELECT * FROM `articles`
WHERE MATCH (`article_text_index`) AGAINST ('+СТРОКА +ПРИМЕРНЫЙ +ПОИСК')


Еще один вариант я встречал в движке форума punBB. Там текст каждого сообщения разделялся на отдельные слова, а затем они сохранялись в таблицу со ссылкой на сообщение. Поисковая строка также сперва разбивается по отдельным словам, и поиск в базе выполняется уже по этим данным, а не по исходному тексту. В нашем случае можно облегчить задачу и повысить точность поиска, если каждое слово перед занесением в базу и слова из поисковой строки будут преобразоваться к начальной форме.
SELECT DISTINCT(`index_article_id`) FROM `index_articles`
WHERE `index_word` IN ('СТРОКА', 'ПРИМЕРНЫЙ', 'ПОИСК')

Релевантность поиска в этом случае можно считать по количеству входящих слов, добавив в запрос команду GROUP.

Для совсем небольших сайтов, где не планируется большая нагрузка, можно извлечь корни из слов искомой строки, а затем использовать эти данные в запросе:
SELECT * FROM `articles`
WHERE UPPER(`article_text`) LIKE ‘%СТРОК%’
OR UPPER(`article_text`) LIKE ‘%ПРИМЕР%’
OR UPPER(`article_text`) LIKE ‘%ПОИСК%’

Искать по всем словоформам в этом случае не имеет смысла, точность результата будет такая же, а нагрузка на базу возрастет.

Для серьезных проектов, конечно, потребуется и серьезная поисковая мощность, например, Sphinx - Open Source Search Server. Но это уже выходит за рамки данной статьи.
Добавлено: 13 Сентября 2013 01:14:33 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...