Улучшаем стандартный поиск на bitrix search.page — PROG-TIME

Улучшаем стандартный поиск на bitrix search.page

07.07.2021
Содержание:

Сегодня я вам расскажу как я улучшал стандартный функционал поиска на 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);
                    }
                }
            }
        }
    }

}

После использования данного способа у вас не получится сделать полностью идеальный поиск, так как здесь есть ещё над чем поработать, но в любом случае данные преобразования гораздо улучшат ваш поиск.

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

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