Статус партнерства с 1С-Битрикс: Бизнес партнер
  • Маркетплейс
  • Документация
  • Блог
  • Новости
  • Софт
  • Профиль
  • Улучшаем сортировку результатов поиска Битрикс

    02.03.2017
    Актуальные версии на момент написания статьи
    • Поиск (search): 17.0.0

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

    Суть проблемы

    Чтобы воспроизвести данную проблему необходимо выполнить следующие действия:

    1. Добавляем элемент инфоболока с названием "Статья про оптимизацию сайта на Битрикс 1"
    2. Добавляем элемент инфоболока с названием "Статья про оптимизацию сайта на Битрикс 2"
    3. Осуществляем поиск по строке "Статья про оптимизацию сайта на Битрикс 2"

    В результате открывается страница с компонентом bitrix:search.page. В идеале элемент с названием совпадающим в точности с поисковой фразой должен быть первым в списке. Однако элементы отображаются, как правило, в порядке добавления.

    Решение

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

    Для решения этой задачи необходимо отказаться от использования стандартного компонента bitrix:search.page. Можно создать свой модуль либо для вывода использовать bitix:catalog.section с дополнительным параметром.

    Первым шагом находим искомые элементы ($word - поисковый запрос). На этом этапе добавлен важный момент: для каждого найденного элемента рассчитывается степень похожести поисковой фразы и заголовка. Для этого существуют различные алгоритмы в нашем случае используется функция similar_text http://php.net/manual/ru/function.similar-text.php. При этом важно, что производится выборка всех результатов поиска без разделения на страницы.

    $arSearchResult = ['RANKS' => [], 'IDS' => [], 'PAGINATOR' => '','WORD'=>$word];
    
    $arParams = [
    	'QUERY' => $word,
    	'SITE_ID' => LANG,
    	'MODULE_ID' => 'iblock'
    ];
    $arSort = [
    	'CUSTOM_RANK' => 'DESC',
    	'TITLE_RANK' => 'DESC',
    	'RANK' => 'DESC',
    	'DATE_CHANGE' => 'DESC'
    ]; 
    $obSearch = new CSearch;
    $obSearch->Search($arParams,$arSort);
    $arItems = [];
    while ($arSearch = $obSearch->Fetch()) {
    	$arSearch['TITLERANK'] = 0;
    	similar_text($arSearch['TITLE'],$_REQUEST['q'],$arSearch['TITLERANK']);
    	$arItems[$arSearch['ITEM_ID']] = $arSearch;
    }
    if (count($arItems) == 0) {
    	/** если не найдено с включенной морфологией - пробуем найти без нее */
    	$obSearch->Search($arParams,$arSort,['STEMMING' => false]);
    	while ($arSearch = $obSearch->Fetch()) {
    		$arSearch['TITLERANK'] = 0;
    		similar_text($arSearch['TITLE'],$_REQUEST['q'],$arSearch['TITLERANK']);
    		$arItems[$arSearch['ITEM_ID']] = $arSearch;
    	}
    }
    $obSearch->Statistic = new CSearchStatistic($obSearch->strQueryText, $obSearch->strTagsText);
    $obSearch->Statistic->PhraseStat($obSearch->NavRecordCount, $obSearch->NavPageNomer);
    

    Далее массив необходимо отсортировать в необходимом нам порядке.

    usort($arItems, "cmp");
    public static function cmp($a, $b){
    	if ($a['TITLERANK'] == $b['TITLERANK']) {
    		return 0;
    	}
    	return ($a['TITLERANK'] > $b['TITLERANK']) ? -1 : 1;
    }
    

    Далее необходимо отобрать только те элементы, которые необходимо вывести на текущей странице.

    use Bitrix\Main\Application;
    $request = Application::getInstance()->getContext()->getRequest();
    /** Текущая страница */
    $pageNum = intval($request->get('PAGEN_1'));
    if ($pageNum>0) --$pageNum;
    /** Элементов на страницу */
    $pageItems = 15;   
    
    $pageItemsTotal = count($arItems);
    $pageCount = ceil($pageItemsTotal / $pageItems);
       
    if ($pageNum > $pageCount) {
    	$pageNum = $pageCount;
    }
    $pageStart = $pageNum * $pageItems;
    if ($pageStart > $pageItemsTotal) {
    	$pageStart = $pageItemsTotal - $pageItems;
    }
    $pageStop = $pageStart + $pageItems;
    $arSearchResult['RANKS'] =[];
    $arRanks = array_splice($arItemsFound,$pageStart,$pageStop);
    foreach ($arRanks as $arItem){
    	$arSearchResult['IDS'][] = $arItem['ITEM_ID'];
    	$arSearchResult['RANKS'][$arItem['ITEM_ID']] = $arItem;
    }
    

    Формируем пагинацию

    if ($pageCount > 1) {
    	$nav = new \CDBResult();
    	$nav->NavStart($pageItems);
    	$nav->NavPageCount = $pageCount;
    	$nav->NavRecordCount = $pageItemsTotal;
    	$nav->NavPageNomer = $pageNum + 1;
    	$navComponentObject = null;
    	$arSearchResult['PAGINATOR'] = $nav->GetPageNavStringEx($navComponentObject, '', 'arrows', 'N');
    } else {
    	$arSearchResult['PAGINATOR'] = '';
    }
    

    На этом этапе собрано все необходимое для вывода результатов поиска. (Не забывайте про кеширование $arSearchResult). Следующим этапом вывод элементов.

    В результате вышеприведенного кода имеем массив с элементами:

    • IDS - идентификаторы найденных элементов, которые необходимо вывести на текущей странице
    • RANKS - информация об элементах из модуля поиска, а так же поле TITLERANK
    • PAGINATOR - HTML код пагинатора для вывода на странице

    Передаем $arSearchResult['IDS'] в фильтр для CIBlockElement::GetList или в фильтр компонента bitrix:catalog.section. Кроме того, в случае использования компонента, необходимо передать ему в качестве параметра для дальнейшего использования полученный массив $arSearchResult

    Далее необходимо отсортировать результаты выборки в нужном нам порядке. В случае использования компонента - в result_modifier.php используемого шаблона. Код ниже приводится на случай использования готового компонента.

    if (count($arResult['ITEMS']) > 0){
    	$arItems = $arParams['SEARCH_ARRAY']['RANKS'];	
    	foreach($arResult['ITEMS'] as $arItem){
    		$arItems[$arItem['ID']] = $arItem;
    	}
    	$arResult['ITEMS'] = $arItems;
    }
    

    Все записи

    © 2001-2024 Воробьев Александр