Blog. Just Blog

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

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

Начнем с подключения phpMorphy. Это делается очень просто. Сперва скачайте дистрибутив, словари, распакуйте все это в отдельную папку, соблюдая структуру вложенных каталогов. Словари разместите в папке dict. Теперь откроем файл примера из папки examples и посмотрим настройки:
  1. // подключение движка phpMorphy
  2. require_once('src/common.php');
  3.  
  4. // настройки движка
  5. $opts = array(
  6.     ...
  7. );
  8.  
  9. // подключение словарей русского языка
  10. $dir 'dicts';
  11. $dict_bundle = new phpMorphy_FilesBundle($dir'rus');
  12.  
  13. // создание класса
  14. $morphy = new phpMorphy($dict_bundle$opts);
В настройках используется важный параметр storage, он может принимать одно из трех значений: PHPMORPHY_STORAGE_FILE (не загружать файлы словарей в память целиком, это самый медленный вариант, но самый экономный в плане работы с ресурсами сервера), PHPMORPHY_STORAGE_SHM (загружать файл словаря целиком в shared-память, требуется расширение PHP shmop) или PHPMORPHY_STORAGE_MEM (также загружать файл в память целиком если не используется shmop, по скорости работы ничем не отличается от предыдущего). На виртуальном хостинге, скорее всего, придется использовать первый вариант, а на выделенном сервере для большей скорости лучше применять варианты с использованием памяти. Выберите вариант под свои задачи. Остальные настройки и их значения подробно расписаны в официальной документации.

Словари загружены, скрипт подключен, можно пробовать его в деле. Допустим, что надо найти текст "Примерная строка поиска":
  1. $bulk_words = array('ПРИМЕРНАЯ''СТРОКА''ПОИСКА');
  2. // Получить нормализованные слова
  3. $base_form $morphy->getBaseForm($bulk_words);
  4. // Получить все словоформы
  5. $all_forms $morphy->getAllForms($bulk_words);
  6. // Получить корни слов
  7. $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. Но это уже выходит за рамки данной статьи.

Поделиться ссылкой ВКонтакте
Просмотров: 20149 | Комментариев: 17

Метки: PHP
Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
ManHunter (29.05.2013 в 17:32):
Тут кагбэ больше одного примера кода, на базе которого можно что-то сделать. Я не вижу текста поискового запроса для трех слов, не вижу структуру таблиц базы. Так что хз в чем проблема, ищи сам.
Антон (29.05.2013 в 17:25):
Сделал поиск на базе вашего кода. Все работает нормально, однако, поиск идет толко по 1-2 словам. Если в запросе 3-и слова, то не ищет. В чем может быть проблема?
vladyka (03.12.2012 в 15:35):
Решил простым условием, чтоб не ковыряться в исходниках) Может пригодится таким, как я:
$pseudo_root = array();
foreach ($s_text_arr as $word){ $pseudo_root[$word] = $morphy->getPseudoRoot($word); if ($pseudo_root[$word] == '') {$pseudo_root[$word] = array($word); }}
ManHunter (03.12.2012 в 14:56):
Все исходники открыты, берешь и допиливаешь скрипт как надо.
vladyka (03.12.2012 в 14:52):
блин, раскладка... сори)

ManHunter, спасибо, попробую. Ещё такой вопрос: когда не находит корня, то выдаёт пустой результат. Можно ли как-нибудь заставить его выдавать исходное слово? Это, конечно, можно сделать посредством условий, но может есть просто какой-то параметр?
ManHunter (03.12.2012 в 14:40):
Получаешь корни не от целой фразы, а от каждого слова по очереди в нужном тебе порядке.
vladyka (03.12.2012 в 13:59):
Здравствуйте! А можно ли как-то сделать, чтоб getPseudoRoot не перемешивал слова? Изза єтого нарушается фраза и поиск відаёт неправильніе результаті.
ManHunter (11.01.2012 в 14:37):
Здесь эта проблема НЕ решается.
Олег (11.01.2012 в 14:32):
Вы с какой версией работали?
Алексей (06.11.2011 в 16:02):
Здрасти всем!
Сколько перечитал, так и не получилось настроить работу поиска php MySQL с помощью морф. словаря!
И мне кажется многие парятся как и я в этом вопросе.
На сайтах много статей на эту тему, но увы, я лично не допёр!
Отсюда следует: Дайте скачать базу и скрипт уже заточенный под неё, в рабочем состоянии. (для чайников) кому не жалко
oprosy09mail.ru
artmel (11.09.2011 в 21:24):
в первой версии моего поисковика тоже использовался phpMorphy.
потом перешел на яндекс.сервер + sphinx  .
но для небольших проектов phpMorphy самое то.
ManHunter (10.09.2011 в 17:02):
Все зависит от постановки задачи. Требуется максимальная синтаксическая точность поиска - придется хранить избыточные данные, требуется компактность и скорость - можно искать через обрезанные слова. И нельзя сказать, что какой-то из способов исключительно верный или ошибочный.

У меня на блоге используется комплексный способ поиска через LIKE с подсчетом релевантности, с подсказки Александра я усилил его обрезкой слов, точность попадания стала еще выше. Скорость тоже неплохая, по крайней мере на 400+ статьях никаких тормозов не наблюдается. Может быть попозже напишу статейку про свою реализацию релевантного поиска.
Кирилл Жуковский (09.09.2011 в 23:00):
Спасибо, очень полезный материал!
На данный момент из доступных средств поиска на php известен только Zend Lucene (имеется в виду поиск, полностью реализованный только средствами php), но и его многим приходится допиливать с помощью phpMorphy.
В то же время, насколько мне известно, разработчики phpMorphy уже давно не поддерживают проект.
Я не знаю, насколько правильно дублировать практически всю информацию, забивая в базу не только текст статьи, но и версию с нормализованными словами. Наиболее привлекательным вариантом кажется, представленный в комментария ранее - где режутся слова. Но непонятно что с точностью поиска.
Александр (16.08.2011 в 11:51):
Вы правы - надо резать не все слова ) Помню у меня была такая интересная ситуация: Клиент хотел, чтобы по поиску "hp" пользователю выходила техника от этого производителя. Страниц много и делать отдельный поиск по производителям клиент не хочет. Если использовать индексы FULLTEXT,то...натыкаешься на ограничение "Поисковая строка от 3-х букв" :) Конечно, можно было напрячь хостера, перекомпилировать MySQL и указать минимальное кол-во букв от 2, но...нельзя ) Самому пришлось писать ) Но результатом доволен.
ManHunter (09.08.2011 в 14:34):
Попробовал, весьма неплохой результат. Только я сделал двойную проверку: от слова длиннее 7 символов отрезаются две последние буквы, от слова длиннее 5 символов - одна буква. Более короткие слова без изменений. А для подсчета релевантности у меня свой алгоритм. Спасибо!
ManHunter (09.08.2011 в 14:03):
Хитрый ход, надо будет у себя попробовать.
Александр (09.08.2011 в 13:54):
Можно сделать маленько хитрее ) Например, у длинных слов резать последние две буквы и уже их искать по БД. Т.е. слово "ПРИМЕРНАЯ" уменьшиться до "ПРИМЕРН" и при запросе SELECT * FROM `articles` WHERE `article_text` LIKE ‘%ПРИМЕРН%’ выдача будет более точная (слово затрагивает 17 словоформ из 21). Да и так MySQL хоть как-то сможет кэшировать запросы. Алгоритм не идеален, но достаточен для большинства сайтов. Проблемнее будет отсортировать по релевантности

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.06 сек. / MySQL: 2 (0.0041 сек.) / Память: 4.5 Mb
Наверх