Задать вопрос
alexsobolenko
@alexsobolenko
Программист

Как сортировать новости в результатах поиска по дате публикации?

Доброго времени суток!

На проекте для поиска новостей на сайте используется elasticsearch. Для формирования запросов используется библиотека https://github.com/ongr-io/ElasticsearchDSL .
Индекс следующий
[
            'index' => $name,
            'body' => [
                'settings' => [
                    'analysis' => [
                        'analyzer' => [
                            'ru' => [
                                'char_filter' => ['html_strip'],
                                'tokenizer' => 'standard',
                                'filter' => ['lowercase', 'hunspell_ru', 'stopwords_ru', 'custom_word_delimiter', 'stop'],
                            ],
                        ],
                        'filter' => [
                            'hunspell_ru' => [
                                'type' => 'hunspell',
                                'locale' => 'ru_RU',
                                'dedup' => true,
                            ],
                            'stopwords_ru' => [
                                'type' => 'stop',
                                'stopwords' => ['а', 'без', 'более', 'бы', 'был', 'была', 'были', 'было', 'быть', 'в', 'вам', 'вас', 'весь', 'во', 'вот', 'все', 'всего', 'всех', 'вы', 'где', 'да', 'даже', 'для', 'до', 'его', 'ее', 'если', 'есть', 'еще', 'же', 'за', 'здесь', 'и', 'из', 'или', 'им', 'их', 'к', 'как', 'ко', 'когда', 'кто', 'ли', 'либо', 'мне', 'может', 'мы', 'на', 'надо', 'наш', 'не', 'него', 'нее', 'нет', 'ни', 'них', 'но', 'ну', 'о', 'об', 'однако', 'он', 'она', 'они', 'оно', 'от', 'очень', 'по', 'под', 'при', 'с', 'со', 'так', 'также', 'такой', 'там', 'те', 'тем', 'то', 'того', 'тоже', 'той', 'только', 'том', 'ты', 'у', 'уже', 'хотя', 'чего', 'чей', 'чем', 'что', 'чтобы', 'чье', 'чья', 'эта', 'эти', 'это', 'я', 'a', 'an', 'and', 'are', 'as', 'at', 'be', 'but', 'by', 'for', 'if', 'in', 'into', 'is', 'it', 'no', 'not', 'of', 'on', 'or', 'such', 'that', 'the', 'their', 'then', 'there', 'these', 'they', 'this', 'to', 'was', 'will', 'with'],
                                'ignore_case' => true,
                            ],
                            'custom_word_delimiter' => [
                                'type' => 'word_delimiter',
                                'generate_word_parts' => true,
                                'generate_number_parts' => true,
                                'catenate_words' => true,
                                'catenate_numbers' => false,
                                'catenate_all' => true,
                                'split_on_case_change' => true,
                                'preserve_original' => true,
                                'split_on_numerics' => false,
                            ],
                        ],
                    ],
                ],
                'mappings' => [
                    'doc' => [
                        'properties' => [
                            'type' => [
                                'type' => 'keyword',
                            ],
                            'create_time' => [
                                'type' => 'text',
                            ],
                            'alias' => [
                                'type' => 'keyword',
                            ],
                            'title' => [
                                'type' => 'text',
                                'analyzer' => 'ru',
                            ],
                            'content' => [
                                'type' => 'text',
                                'analyzer' => 'ru',
                            ],
                            'intro_text' => [
                                'type' => 'text',
                                'analyzer' => 'ru',
                            ],
                            'publish_date' => [
                                'type' => 'date',
                                'format' => 'yyyyMMdd',
                            ],
                        ],
                    ],
                ],
            ],
        ]


Необходимо, чтобы результаты поиска сортировались с учётом даты публикации (результаты, которые имеют примерно одинаковый вес сортировались по убыванию.

Пробовал просто указывать `$search->addSort(new FieldSort('publish_date'), 'desc');`, но так предпочтение отдаётся более новым записям, а не более соответствующим поисковому запросу.

В результате чтения документации выявил, что можно использовать FunctionScoreQuery.
Далее уже пробовал читать разные статьи, задавать вопросы ChatGPT.
Первый более-менее рабочий вариант формулы был `Math.log(1 + doc['publish_date'].value.toInstant().toEpochMilli())`, но так довольно старые новости вылезли наверх. В результате я пришёл к следующему коду.
$referenceTimestamp = (new DateTimeImmutable())->getTimestamp() * 1000;
        $functionScoreSource = <<<EOL
            double t = doc['publish_date'].value.toInstant().toEpochMilli();
            double p = params.referenceDate;
            double d = Math.abs(7300.0 - ((p - t) / 86400000.0));
            double r = Math.log(d);

            return Math.max(r, 0);
        EOL;
        $functionScoreQuery = new FunctionScoreQuery($boolQuery, [
            'functions' => [
                [
                    'script_score' => [
                        'script' => [
                            'source' => $functionScoreSource,
                            'params' => [
                                'referenceDate' => $referenceTimestamp,
                            ],
                        ],
                    ],
                    'weight' => 2.5,
                ],
            ],
            'score_mode' => 'sum',
            'boost_mode' => 'sum',
        ]);
        $search->addQuery($functionScoreQuery);

7300 - это количество дней в 20 годах. Примерно за такое количество времени новости на сайте.
Но он всё равно ищет неидеально. Возможно ли как-то улучшить скрипт?
  • Вопрос задан
  • 128 просмотров
Подписаться 3 Простой Комментировать
Пригласить эксперта
Ответы на вопрос 1
Davidaa_WoW
@Davidaa_WoW
Интересный кейс. Я бы возможно вручную определил интервалы и давал по ним бусты.
Допустим:
новости за сегодня - буст = 10
новости за последнюю неделю - буст = 5
новости за последний месяц - буст = 3
новости за последний год - буст = 1
новости старше - буст = 0.2

При таком условии, наиболее точные совпадения, вероятнее всего всё равно будут выше, даже если они старше. Например, если текст названия полностью совпадает с новостью двухлетней давности.
Хотя, в данном случае надо смотреть ещё отдельно и тьюнить фильтры и метчеры, которые вы используете
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы