Поиск по сайту – необходиый инструмент при создании любого сайта (от визитки до социальной сети). Этому посвящено уже много статей, найти которые не составит для вас труда, но все они рассказывают об очень посредственном поиске. Я хочу рассказать о том как легко и без особых усилий вы можете сделать умный поиск на своем сайте. Под умным поиском я понимаю следующее:

1) проверка правописания в поисковой фразе
2) выделение корней значимых слов (stemming)
3) релевантный поиск по значимым словам

1) Проверку правописания выполним с помощью яндекс сервиса Speller (подробная документация)
Как мы видим в описании api yandex, нам необходимо отправить get запросом текст который нужно проверить в ответ мы получим данные по проверке (в документации все подробно описано поэтому не буду вдаваться в подробности). $str – поисковая фраза которую нужно проверить на правописание. Ответ от сервиса я получаю в формате json, если есть варианты исправления правописания то вывожу их на экран:

$checker=json_decode(file_get_contents("http://speller.yandex.net/services/spellservice.json/checkText?text=".urlencode($str)));
$checked_str=$str;
foreach($checker as $word) {
	$checked_str=str_replace($word->word,$word->s[0],$checked_str);
}
if(mb_strtolower($checked_str,'utf8')!=mb_strtolower($str,'utf8') && !empty($checked_str)) {
	echo "Возможно вы имели ввиду: <a href=\"/search/$checked_str\">$checked_str</a>";
}

2) Выделим корни значимых слов (stemming)
Для этого нам понадобится готовый класс стемминга на php, он использует стемммер Портера:
скачать stemming.zip
Код данного класса не мой, скачал с какого то форума немного подправил для работы в кодировке utf8, к сожалению не смог найти ссылку на источник.
Используем выше определенный класс для выделения корней слов, на выходе получаем фразу без окончаний и предлогов, а так же без вспомогательных слов таких как в, на и тд.

$stemming= new Stemming();
$stem_str=$stemming->stem_string($str);

3) Релевантный поиск организуем с помощью mysql и php. Генерируем запрос к базе в котором будем также рассчитывать коеффициент релевантности найденного результата. Коэффициент релевантности для каждой колонки указывается в массиве. Причем полное соответствие поисковому запросу будет иметь больший коэффициент релевантности чем найденное одно или несолько слов из запроса. Для колонки с названием продукта я поставил больший коэффициент чем за колонку с описанием, что тоже вполне логично чтоб поиск по сайту был более адекватным.

$search_req = " AND ( ";
$search_array = explode(" ", $stem_str);

$search_str=preg_replace("/[^а-яА-Яa-zA-z0-9\-]/ui","%",$stem_str); //заменяем все знаки кроме цифр и букв на % (любое кол-во любых символов)

// массив колонок по которым ищем и их коэффицент релевантности
$search_columns=array(
	'prod.title'=>'20',
	'prod.small_descr'=>'10',
	'prod.articul'=>'15',
	'prod.maker'=>'15',
	'sscat.title'=>'10',
	);

$select =", ( 0";
$search_req_arr=array();

foreach($search_columns as $col=>$coeff) {
	// полнотекстовый
	$select.= " + IF ($col LIKE '".$search_str."', $coeff*3, 0)";
	$search_req_arr[]= " $col LIKE '%".$search_str."%'";

	// для отдельного слова
	$word_coeff=round(($coeff/count($search_array)),2);

	foreach($search_array as $word) {
		$select .= "+ IF ($col LIKE '%".$word."%', ".$word_coeff.", 0)";
		$search_req_arr[] = "$col LIKE '%".$word."%'";
	}
}
$select.=") AS `relevant`";
$search_req .=implode(" OR ",$search_req_arr);
$search_req .= ")";

$query="SELECT prod.id, prod.title, prod.price, prod.description prod.isnew, prod.ishit, prod.small_descr $select
FROM ss_products prod
WHERE prod.status = '1' $search_req
ORDER BY relevant DESC LIMIT 0, 20";

В итоге получился вот такой запрос в базу данных:

SELECT prod.id, prod.title, prod.price, prod.description prod.isnew, prod.ishit, prod.small_descr ,
( 0 + IF (prod.title LIKE 'насос%вод', 20*3, 0)+ IF (prod.title LIKE '%насос%', 10, 0)+
IF (prod.title LIKE '%вод%', 10, 0) + IF (prod.small_descr LIKE 'насос%вод', 10*3, 0)+
IF (prod.small_descr LIKE '%насос%', 5, 0)+ IF (prod.small_descr LIKE '%вод%', 5, 0) +
IF (prod.articul LIKE 'насос%вод', 15*3, 0)+ IF (prod.articul LIKE '%насос%', 7.5, 0)+
IF (prod.articul LIKE '%вод%', 7.5, 0) + IF (prod.maker LIKE 'насос%вод', 15*3, 0)+
IF (prod.maker LIKE '%насос%', 7.5, 0)+ IF (prod.maker LIKE '%вод%', 7.5, 0)) AS `relevant`
FROM ss_products prod
WHERE prod.status = '1' AND (
prod.title LIKE '%насос%вод%' OR prod.title LIKE '%насос%' OR prod.title LIKE '%вод%' OR
prod.small_descr LIKE '%насос%вод%' OR prod.small_descr LIKE '%насос%' OR prod.small_descr LIKE '%вод%'
OR prod.articul LIKE '%насос%вод%' OR prod.articul LIKE '%насос%' OR prod.articul LIKE '%вод%'
OR prod.maker LIKE '%насос%вод%' OR prod.maker LIKE '%насос%' OR prod.maker LIKE '%вод%'
)
ORDER BY relevant DESC LIMIT 0, 20

Не бойтесь не смотря на много IF, запрос отрабатывает очень быстро и хорошо справляется с задачей организации релевантного поиска на сайте.

В моем случае в поисковом запросе я набрал фразу нососы для воды, специально сделав ошибку в одном слове. После прохождения через yandex speller скрипт мне предложил правильный вариант насосы для воды. Затем поисковая фраза прошла через stemming и на выходе отдала вразу насос вод. По этой фразе мы и выполняем релевантный поиск по сайту. В моем случае с увернностью можно сказать что в результате найдено то что искал пользователь, когда коэффициет релевантности больше 70.
Надеюсь теперь больше сайтов будут иметь адекватный красивый поиск.

Можно прочесть также:

10 комментариев “Релевантный поиск по сайту (Yandex API + PHP +MySql)”

  1. Аноним:

    Отличная статья, спасибо!
    С одной таблицей все отлично работает, но никак не разберусь как настроить ее для 2-х и более таблиц.
    Можете подсказать?

  2. admin:

    а какой у вас запрос для выборки данных из двух таблиц? помоему все также просто, джойним две или более таблиц прописываем им алиасы а дальше задаем в массив колонок нужные нам поля с alias.column_name и задаем им коэффициент релевантности

  3. Аноним:

    Задал вопрос и уехал )
    допустим 2 таблицы articles с полями title, text и таблица news с такими же полями title и text
    Джоин разве не добавляет столбцы слева (или справа)?

  4. Аноним:

    Использовал union, но смущает один момент.
    При выборке из 1-й таблицы запрос занимает 0,002 с
    А при выборке из 2-х таблиц с union уже 0,02с
    Есть какая-то статистика, при каком размере таблиц какая скорость выборки

  5. admin:

    понял что ты имеешь ввиду, с юнион действительно будет гораздо дольше. Если ты хочешь выборку из двух таблиц, типа новостей и продуктов то есть вариант попробовать создавать временную таблицу с нужными данными а потом по ней уже делать поиск. Сам не тестировал такой вариант но при больших объемах поиска думаю отработает быстрее

  6. Виталий:

    Здравствуйте.

    Спасибо за статью! Решил установить себе, но никак не могу разобраться в установке подобного поиска на свой сайт. Если вас не затруднит, выложите пожалуйста полный код, чтобы было все понятно. То есть код формы, код скрипта-обработчика и код вывода информации.

  7. Здравствуйте, хочу попробывать у себя на сайте. А можно получить исходники поиска?

  8. admin:

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

  9. Колупался кучу времени, и нашел только один нормальный пример здесь:
    http://wikikak.org/poisk_po_saytu_php_mysql
    просто, но доступно.

  10. Squirrel:

    Спасибо за код и статью :) Очень помогло в работе