1С-Битрикс. Добавляем в морфологический индекс поиска возможность искать по части слова
Наверняка, многие знают, что в битриксе есть морфологический поиск. И вроде бы он даже худо-бедно работает. Но если мы торгуем товарами, и хотим, чтобы поиск искал и по части чего-то специфического, например, артикула товара (пример: артикул «Р6543», подстрокой будет «6543»), то ничего из этого не выйдет. Поиск тупо ничего не найдёт.
В этой заметке покажу способ, как можно эту проблему решить.
Суть
В момент индексации элемента будем добавлять в таблицу b_search_content_stem наши части слов. То есть, в нашем случае, для товара с артикулом «Р6543» мы добавим «6543» в поисковый индекс. Запросы «654» и «65» так же будут отдавать товар с артикулом «Р6543».
Обработчики — наше всё
Как обычно, решать мы будем обработчиками. Оффтоп: иногда вижу проекты вообще без файлов init.php и своих кастомных модулей. Ребята, как вам удаётся без этого сайты собирать?
Будем слушать 3 события: BeforeIndex, OnBeforeIndexUpdate и OnAfterIndexAdd.
- BeforeIndex — вызывается перед индексацией элемента методом CSearch::Index()
- OnBeforeIndexUpdate — вызывается перед обновлением поискового индекса
- OnAfterIndexAdd — вызывается после добавления новых данных в поисковый индекс.
На событие BeforeIndex мы проверим, что в индекс летит нужная нам запись и сохраним в статическую переменную данные, которые нам понадобятся в последующих событиях. По другому их там не достать.
На события OnBeforeIndexUpdate и OnAfterIndexAdd запишем в таблицу b_search_content_stem то, что нам нужно. API для этого нет, так что будем писать чистые SQL-запросы. Проверял на MySQL с InnoDB таблицей. На остальных не факт, что будет корректно работать
Код обработчика
Сохраняем код в файле /local/php_interface/classes/handlers/search/stemming.php
В свойстве $allowedIblockId класса необходимо указать ID инфоблоков, для которых нужен функционал.
/** * Created by olegpro.ru * User: Oleg Maksimenko <oleg.39style@gmail.com> */ namespace Olegpro\Classes\Handlers\Search; use Bitrix\Main\Loader; use Bitrix\Main\Application; use Bitrix\Main\DB\SqlQueryException; class Stemming { /** * @var array */ protected static $allowedIblockId = array( 1 ); /** * @var */ private static $element; /** * @param array $arFields * @return mixed */ public static function beforeIndex($arFields) { static $allowedIblockId = null; if ($allowedIblockId === null) { $allowedIblockId = array_flip(self::$allowedIblockId); } if ( $arFields['MODULE_ID'] == 'iblock' && isset($allowedIblockId[$arFields['PARAM2']]) && strlen($arFields['ITEM_ID']) > 0 && substr($arFields['ITEM_ID'], 0, 1) != 'S' && Loader::includeModule('iblock') ) { if ($obElement = \CIBlockElement::GetList( array(), array('ID' => $arFields['ITEM_ID']), false, false, array() )->GetNextElement() ) { $element = $obElement->GetFields(); $element['PROPERTIES'] = $obElement->GetProperties(); self::$element = $element; } } return $arFields; } /** * Events: OnBeforeIndexUpdate and OnAfterIndexAdd * @param $indexId * @param $arFields */ public static function beforeIndexUpdate($indexId, $arFields) { if ( isset(self::$element) && is_array(self::$element) && isset(self::$element['PROPERTIES']['ARTICLE']) && strlen(trim(self::$element['PROPERTIES']['ARTICLE']['VALUE'])) && preg_match('~^.*?([0-9.,]+).*?$~', trim(self::$element['PROPERTIES']['ARTICLE']['VALUE']), $m) ) { $word = ToUpper($m[1]); $stemId = \CSearch::RegisterStem($word); if ($stemId > 0) { $connection = Application::getConnection(); $sqlHelper = $connection->getSqlHelper(); try { $thereIs = $connection->queryScalar( sprintf( "SELECT 1 FROM b_search_content_stem WHERE SEARCH_CONTENT_ID = '%s' AND STEM = '%s'", $sqlHelper->forSql($indexId), \CSearch::RegisterStem($word) ) ); if ($thereIs === null) { $connection->query(sprintf( "INSERT IGNORE INTO `b_search_content_stem` (`SEARCH_CONTENT_ID`, `LANGUAGE_ID`, `STEM`, `TF`, `PS`) VALUES ('%s', '%s', '%s', '%s', '%s')", $sqlHelper->forSql($indexId), 'ru', \CSearch::RegisterStem($word), number_format(0.2, 4, ".", ""), number_format(1, 4, ".", "") )); } } catch (SqlQueryException $e) { AddMessage2Log(sprintf("\\%s:\n%s", __METHOD__, $e->getMessage())); } } } self::$element = null; } }
Регистрируем обработчики и добавляем класс в автозагрузку
Добавить в /local/php_interface/init.php
\Bitrix\Main\EventManager::getInstance()->addEventHandler('search', 'BeforeIndex', array('\Olegpro\Classes\Handlers\Search\Stemming', 'beforeIndex') ); \Bitrix\Main\EventManager::getInstance()->addEventHandler('search', 'OnBeforeIndexUpdate', array('\Olegpro\Classes\Handlers\Search\Stemming', 'beforeIndexUpdate') ); \Bitrix\Main\EventManager::getInstance()->addEventHandler('search', 'OnAfterIndexAdd', array('\Olegpro\Classes\Handlers\Search\Stemming', 'beforeIndexUpdate') ); \Bitrix\Main\Loader::registerAutoLoadClasses(null, array( '\Olegpro\Classes\Handlers\Search\Stemming' => '/local/php_interface/classes/handlers/search/stemming.php', ));
На этом всё. Осталось только запустить полную переиндексацию и теперь можно не тратить жизнь на вписывание в поиск артикулов полностью.
P.S.
Для своих частных случаев код придётся, конечно, поправить под себя.
24 комментария
Пытаюсь воспользоваться описанным решением проблемы морфологического поиска. При запуске переиндексации она просто зависает. Подскажите, с чем это может быть связано? может быть, я не так понял, где нужно указывать ID инфоблоков... Указывал здесь: protected static $allowedIblockId = array( 1 );
Александр, что значит зависает?
Смотрите логи ошибок веб-сервера.
После добавления файла Добавить в /local/php_interface/init.php
сайт выпадает в белый экран.
Что делать?
Смотреть логи ошибок веб-сервера.
Указал собственный инфоблок (вместо 1) и вместо ['ARTICLE'] собственный код артикула. Переиндексировал.
Результата нет - по части артикула не ищет. Может еще что-то изменить?
Напишите через обратную связь на странице «Контакты» — посмотрим.
А почему написано Запросы «653» и «65» так же будут отдавать товар с артикулом «Р6543»
Может "654"?
Да, опечатка. Спасибо, поправил.
Вот такой артикул J001.DР.ЕR.20.91.CC.
сам битрикс например J001 находит, а короче - уже нет. Какая тут регулярка нужна , не могу понять.
Что-то вроде: "найти каждый набор символов равный или длиннее 2"? Или какой?
Экспериментируйте.
Возможно будет достаточно разбить по точке и оставить части, у которых длина больше 2.
не , по точке битрикс сам разбивает.
но у него кажется лимит - ищет только если в сроке 4 знака (точки не учитывает).
долго вы регулярки изучали? чтобы разобраться в них - сколько времени надо.
и да, про эксперименты - ну а как понять, каждый раз переиндексацию делать?
чем пользуетесь для отладки регулярок?
Сколько бы не изучал, иногда всё равно приходиться лезть в документацию
скорее всего (но не факт)
Обычно прямо в процессе и отлаживаю. В закладках есть http://regexr.com/
А вообще странная на сайте с которым я вожусь ситуация: по части артикула ищет, но только если это от 4 знаков, причем точки не учитываются. Т.е. как будто уже сделана настройка поиска по части, но сделан какой-то лимит на количество. Это вообще обычная ситуация для битрикса или нет? Большой у вас опыт в этом деле? А то сверху придумвать еще какой-то функционал, если это уже реализовано - как то неправильно. Как будто квест разгадываешь.
Похоже на то, что ваша регулярка не работает
https://yadi.sk/i/qlxosEto3E7NLt
Поиск по части слова с морфологией есть, но он не всегда хорошо работает. Собственно, про то как можно это ситуацию подправить для себя и написана заметка.
Работает :) https://yadi.sk/i/9_ImMaPv3E7RN5
запустил на опенсервер - скрипт один результаты разные
https://yadi.sk/i/8MysT72v3E7bCj
У меня такие же результаты, как у вас на последнем скриншоте.
Существует ли исчерпывающий список того, что нужно поправить. Т.е. понятно, что поле артикула ['PROPERTIES']['ARTICLE']['VALUE']
Может что-то еще?
Только самое первое условие тут \Olegpro\Classes\Handlers\Search::beforeIndexUpdate
Потестировал на локалном - работает, кроме этой части: "Запросы «654» и «65» так же будут отдавать товар с артикулом «Р6543».
непонятно, а за счет чего так должно работать? Это в битриксе настраивается?
Код установил. Вижу что добавились строки как надо в таблицу b_search_content_stem после переиндексации. Но понять не могу, на сайте стоит shpinx. В нем в логах тоже вижу что запросы идут норм. Но не ищет на сайте все равно. Например: у меня артикул G41045, в таблице добавилась еще запись 41045. Но поиск не дает результата. Использую компоненты: search.title и search.page
Переключил поиск на MySQL - заработало в компоненте search.title. А в компоненте search.page - не ищет.
Можете подсказать, какую регулярку написать, чтобы можно было делить просто длинные слова без цифр, например: тенгизшевроил нужно сделать, чтобы можно было найти это слово по тенгиз и подобным. Не подскажете как такое можно реализовать, не могу придумать. Заранее спасибо
Такое регуляркой не решить.
Гуглите алгоритмы, которые решают такую задачу и реализовывайте :)