Сегодня я вам расскажу как я улучшал стандартный функционал поиска на bitrix. Я покажу функции которые позволяют исправлять грамматические ошибки поисковых запросов, искать по части слова и по свойствам товаров.
Для начала я вам расскажу что я использовал для своей задачи.
- Во-первых, я сразу же отказался от стандартного метода поиска и поставил на свой хостинг Sphinx. Для своих сайтов я использую хостинг Beget, где в качестве дополнений можно установить данный модуль.
- Во-вторых, для исправления грамматических ошибок я буду использовать сервис Спеллер https://yandex.ru/dev/speller/ . Данный сервис получает текст с ошибкой через GET или POST запрос и возвращает исправленный текст в json формате.
Установка Sphinx сразу же решает проблему с индексацией по «части слова». Для того чтобы установить Sphinx необходимо перейти в Настройки->Настройки модулей->Поиск->Морфология и в поле «Полнотекстовый поиск с помощь Sphinx.
Конечно у меня не сразу получилось это сделать, но для решения своей проблемы я обратился в тех поддержку и он мне помогли.
Логика состоит в следующем — я буду делать первый запрос на получение товаров интернет магазина через поиск, если товары не приходят проверяют текст запроса на ошибки и делаю доп запрос.
Будут проверятся следующие виды ошибок:
- Неполные слова: див (диван), кров (кровать), бар (барная стойка)
- По свойству товара: желтый, синий
- По наличию буквы «Ё«.
На bitrix такая проблема что жёлтый и желтый это два разных ответа, первый возвращал 2 товара, второй больше - Грамматические ошибки: крАвать, жОлтый
- Сложные запросы: желтый диван, желтая кровать
- Сложные ошибочные запросы: жОлтая крАвать
- По названию: лофт
- Слова в английской транскрипции: ;tknsq, lbdfy
Теперь давайте рассмотрим сам код. Код у нас будет только на языке PHP.
Первым делом я напишу класс в котором будут записаны основные методы для обработки поисковых запросов.
Сразу замечу что фраза для поискового запроса хранится в $_REQUEST[«q»] поэтому перед каждым новым запросом я буду записывать новую поисковую строку именно в эту переменную.
class DopSearchClass {
/* ДЕЛАЕМ ЗАПРОС НА ПРОВЕРКУ ГРАММАТИКИ */
public function checkValueSearch($textSearch) {
$textQuery = urlencode($textSearch);
$url = "https://speller.yandex.net/services/spellservice.json/checkText?text=".$textQuery;
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$resultCheck = curl_exec($curl);
$resultCheck = json_decode($resultCheck, true);
if($resultCheck) {
/* массив в который будут записываться обработанные значения */
$listCurrentText = array();
/* если у нас только одно ошибочное слово */
if(count($resultCheck) == 1) {
/* записываем вариант ошибки */
$falseText = $resultCheck[0]["word"];
/* преобразуем ассоциативный массив в обычный */
$arrValidWord = array_values($resultCheck[0]["s"]);
/* удаляем однокоренные слова */
$validWord = $this->clearBySixFirstLetter($arrValidWord);
foreach($validWord as $word) {
$newTextSearch = str_replace($falseText, $word, $textSearch);
array_push($listCurrentText, $newTextSearch);
}
}
/* если несколько ошибочных слов */
else if(count($resultCheck) > 1) {
foreach($resultCheck as $arrWord) {
$falseText = $arrWord["word"];
$textSearch = str_replace($falseText, $arrWord["s"][0], $textSearch);
}
array_push($listCurrentText, $textSearch);
}
return $listCurrentText;
}
}
/* УДАЛЯЕМ ОДНОКОРЕННЫЕ СЛОВА */
public function clearBySixFirstLetter($array) {
/* записываем корни слов */
$has = [];
return array_filter(
$array,
function ($word) use (&$has) {
$sixLetters = mb_substr($word, 0, 6);
if (!in_array($sixLetters, $has)) {
array_push($has, $sixLetters);
return true;
}
return false;
}
);
}
/* МЕНЯЕМ "Ё" НА "Е" */
public function alternateSymbol($str) {
$str = strtolower($str);
return str_replace("ё", "е", $str);
}
}
Давайте я немного расскажу по каждому методу…
- checkValueSearch — делает запрос на проверку грамматики. Если есть ошибка в одном слове, то мы берём все варианты правильного значения и в дальнейшем делаем перезапрос с новыми словами. Если у нас НЕСКОЛЬКО ОШИБОЧНЫХ СЛОВ, то для упрощения с сделал так чтобы доставался 1 правильный вариант по каждому слову и формировалась новая строка запроса. Для улучшения вы можете составить комбинации из всех значений и пройтись по всем, мне же достаточно и этого!
- clearBySixFirstLetter — удаляет однокоренные слова из полученных значений от Яндекс Спеллер. Дело в том что в «битриксовском» поиске уже есть возможность использовать морфологический анализ для получения результата, поэтому однокоренные слова будут возвращать одинаковый результат.
- alternateSymbol — ищет в строке запроса букву «Ё» и если она есть, то добавляет значения с вариантом «Е». Получается что слово «жёлтый» вернёт значения для себя и для слова «желтый»
Ниже в этом же файле я запускаю работу основного скрипта.
Для начала я просто делаю запрос и проверяю есть ли результат. Проводить все доп запросы я буду только если ответ пустой или имеет букву «Ё» в слове.
/* вначале делаем поисковой запрос без преобразования */
$searchResult = $APPLICATION->IncludeComponent(
"bitrix:search.page",
"ajax",
array(
"AJAX_MODE" => "N",
"AJAX_OPTION_ADDITIONAL" => "",
"AJAX_OPTION_HISTORY" => "N",
"AJAX_OPTION_JUMP" => "N",
"AJAX_OPTION_STYLE" => "N",
"CACHE_TIME" => "3600",
"CACHE_TYPE" => "A",
"CHECK_DATES" => "Y",
"DEFAULT_SORT" => "rank",
"DISPLAY_BOTTOM_PAGER" => "Y",
"DISPLAY_TOP_PAGER" => "N",
"FILTER_NAME" => "",
"PAGER_SHOW_ALWAYS" => "N",
"PAGER_TEMPLATE" => "",
"PAGER_TITLE" => "Результаты поиска",
"PAGE_RESULT_COUNT" => "500",
"RESTART" => "Y",
"SHOW_WHEN" => "N",
"SHOW_WHERE" => "N",
"USE_LANGUAGE_GUESS" => "N",
"USE_SUGGEST" => "N",
"USE_TITLE_RANK" => "Y",
"arrFILTER" => array(
0 => "iblock_catalog",
),
"arrWHERE" => array(
0 => "iblock_catalog",
),
"COMPONENT_TEMPLATE" => "ajax",
"arrFILTER_iblock_catalog" => array(
0 => "5",
)
),
false
);
Если результат есть, то попробуем поискать букву Ё в словах. Если Ё есть то делаем новый запрос и сливаем массивы.
/* если товары есть, то проверяем значение запроса на наличие буквы "Ё" */
if(count($searchResult) > 0) {
if(strpos($_REQUEST["q"], "ё") !== false) {
$newSearchCheck = new DopSearchClass();
$_REQUEST["q"] = $newSearchCheck->alternateSymbol($_REQUEST["q"]);
/* в нижний регистр */
$str = strtolower($_REQUEST["q"]);
$_REQUEST["q"] = str_replace("ё", "е", $str);
$thisSearchResult = $APPLICATION->IncludeComponent(
"bitrix:search.page",
"ajax",
array(
"AJAX_MODE" => "N",
"AJAX_OPTION_ADDITIONAL" => "",
"AJAX_OPTION_HISTORY" => "N",
"AJAX_OPTION_JUMP" => "N",
"AJAX_OPTION_STYLE" => "N",
"CACHE_TIME" => "3600",
"CACHE_TYPE" => "A",
"CHECK_DATES" => "Y",
"DEFAULT_SORT" => "rank",
"DISPLAY_BOTTOM_PAGER" => "Y",
"DISPLAY_TOP_PAGER" => "N",
"FILTER_NAME" => "",
"PAGER_SHOW_ALWAYS" => "N",
"PAGER_TEMPLATE" => "",
"PAGER_TITLE" => "Результаты поиска",
"PAGE_RESULT_COUNT" => "500",
"RESTART" => "Y",
"SHOW_WHEN" => "N",
"SHOW_WHERE" => "N",
"USE_LANGUAGE_GUESS" => "N",
"USE_SUGGEST" => "N",
"USE_TITLE_RANK" => "Y",
"arrFILTER" => array(
0 => "iblock_catalog",
),
"arrWHERE" => array(
0 => "iblock_catalog",
),
"COMPONENT_TEMPLATE" => "ajax",
"arrFILTER_iblock_catalog" => array(
0 => "5",
)
),
false
);
/* если результат есть, то сливаем его с полным результатом */
if($thisSearchResult) {
$searchResult = array_merge($searchResult, $thisSearchResult);
}
}
}
Если первый запрос не вернул результат то вначале опять проверяем запрос на букву «Ё», но только в этот раз мы проверяем когда пришёл пустой ответ, а потом если опять результатов не будет то делаем запрос на проверку грамматических ошибок.
/* если пришёл пустой результат */
else if(count($searchResult) < 1) {
$newSearchCheck = new DopSearchClass();
/* если ошибка только в букве "Ё" */
if(strpos($_REQUEST["q"], "ё") !== false) {
$_REQUEST["q"] = $newSearchCheck->alternateSymbol($_REQUEST["q"]);
$str = strtolower($_REQUEST["q"]);
$_REQUEST["q"] = str_replace("ё", "е", $str);
$thisSearchResult = $APPLICATION->IncludeComponent(
"bitrix:search.page",
"ajax",
array(
"AJAX_MODE" => "N",
"AJAX_OPTION_ADDITIONAL" => "",
"AJAX_OPTION_HISTORY" => "N",
"AJAX_OPTION_JUMP" => "N",
"AJAX_OPTION_STYLE" => "N",
"CACHE_TIME" => "3600",
"CACHE_TYPE" => "A",
"CHECK_DATES" => "Y",
"DEFAULT_SORT" => "rank",
"DISPLAY_BOTTOM_PAGER" => "Y",
"DISPLAY_TOP_PAGER" => "N",
"FILTER_NAME" => "",
"PAGER_SHOW_ALWAYS" => "N",
"PAGER_TEMPLATE" => "",
"PAGER_TITLE" => "Результаты поиска",
"PAGE_RESULT_COUNT" => "500",
"RESTART" => "Y",
"SHOW_WHEN" => "N",
"SHOW_WHERE" => "N",
"USE_LANGUAGE_GUESS" => "N",
"USE_SUGGEST" => "N",
"USE_TITLE_RANK" => "Y",
"arrFILTER" => array(
0 => "iblock_catalog",
),
"arrWHERE" => array(
0 => "iblock_catalog",
),
"COMPONENT_TEMPLATE" => "ajax",
"arrFILTER_iblock_catalog" => array(
0 => "5",
)
),
false
);
/* если результат есть, то сливаем его с полным результатом */
if($thisSearchResult) {
$searchResult = array_merge($searchResult, $thisSearchResult);
}
}
/* если исправление буквы "Ё" не помогло и опять вернулся пустой ответ */
if(count($searchResult) < 1) {
/* проверям грамматику */
$arrValidWord = $newSearchCheck->checkValueSearch($_REQUEST["q"]);
/* если такого слова не существует */
if(count($arrValidWord) == 0) {
$errorMessageSearch = "К сожалению по данному запросу мы не чего не нашли!";
}
else if(count($arrValidWord) > 0) {
/* делаем запрос по каждому варианту ИСПРАВЛЕННОГО слова */
foreach($arrValidWord as $word) {
$_REQUEST["q"] = $word;
$thisSearchResult = $APPLICATION->IncludeComponent(
"bitrix:search.page",
"ajax",
array(
"AJAX_MODE" => "N",
"AJAX_OPTION_ADDITIONAL" => "",
"AJAX_OPTION_HISTORY" => "N",
"AJAX_OPTION_JUMP" => "N",
"AJAX_OPTION_STYLE" => "N",
"CACHE_TIME" => "3600",
"CACHE_TYPE" => "A",
"CHECK_DATES" => "Y",
"DEFAULT_SORT" => "rank",
"DISPLAY_BOTTOM_PAGER" => "Y",
"DISPLAY_TOP_PAGER" => "N",
"FILTER_NAME" => "",
"PAGER_SHOW_ALWAYS" => "N",
"PAGER_TEMPLATE" => "",
"PAGER_TITLE" => "Результаты поиска",
"PAGE_RESULT_COUNT" => "500",
"RESTART" => "Y",
"SHOW_WHEN" => "N",
"SHOW_WHERE" => "N",
"USE_LANGUAGE_GUESS" => "N",
"USE_SUGGEST" => "N",
"USE_TITLE_RANK" => "Y",
"arrFILTER" => array(
0 => "iblock_catalog",
),
"arrWHERE" => array(
0 => "iblock_catalog",
),
"COMPONENT_TEMPLATE" => "ajax",
"arrFILTER_iblock_catalog" => array(
0 => "5",
)
),
false
);
/* если результат есть, то сливаем его с полным результатом */
if($thisSearchResult) {
$searchResult = array_merge($searchResult, $thisSearchResult);
}
/* проверяем слово запроса н наличие буквы "Ё" и заменяем её на "Е" */
if(strpos($word, "ё") !== false) {
$_REQUEST["q"] = $newSearchCheck->alternateSymbol($word);
$thisSearchResult = $APPLICATION->IncludeComponent(
"bitrix:search.page",
"ajax",
array(
"AJAX_MODE" => "N",
"AJAX_OPTION_ADDITIONAL" => "",
"AJAX_OPTION_HISTORY" => "N",
"AJAX_OPTION_JUMP" => "N",
"AJAX_OPTION_STYLE" => "N",
"CACHE_TIME" => "3600",
"CACHE_TYPE" => "A",
"CHECK_DATES" => "Y",
"DEFAULT_SORT" => "rank",
"DISPLAY_BOTTOM_PAGER" => "Y",
"DISPLAY_TOP_PAGER" => "N",
"FILTER_NAME" => "",
"PAGER_SHOW_ALWAYS" => "N",
"PAGER_TEMPLATE" => "",
"PAGER_TITLE" => "Результаты поиска",
"PAGE_RESULT_COUNT" => "500",
"RESTART" => "Y",
"SHOW_WHEN" => "N",
"SHOW_WHERE" => "N",
"USE_LANGUAGE_GUESS" => "N",
"USE_SUGGEST" => "N",
"USE_TITLE_RANK" => "Y",
"arrFILTER" => array(
0 => "iblock_catalog",
),
"arrWHERE" => array(
0 => "iblock_catalog",
),
"COMPONENT_TEMPLATE" => "ajax",
"arrFILTER_iblock_catalog" => array(
0 => "5",
)
),
false
);
/* если результат есть, то сливаем его с полным результатом */
if($thisSearchResult) {
$searchResult = array_merge($searchResult, $thisSearchResult);
}
}
}
}
}
}
После использования данного способа у вас не получится сделать полностью идеальный поиск, так как здесь есть ещё над чем поработать, но в любом случае данные преобразования гораздо улучшат ваш поиск.
Я понимаю что данный способ использует в своей работе запросы на компонент поиска через цикл, чего я категорически не рекомендую делать, так как это сильно нагружает сайт, но в данном случае я не нашёл другого выхода как можно добиться результата.
Не смотря на это вы всегда можете записать полученные значения в кэш и вытаскивать их при повторном запросе. Если у меня будут время, я обязательно поделюсь тем как это можно сделать используя базовый функционал битрикса.