vorst.ru - Создание индекса ElasticSearch
Статьи из рубрики data-structure

Как создать индекс ElasticSearch

Полнотекстовый поиск

Один парень написал: "Нужно сделать поиск слов в таблице "articles" по столбцу "content". В результатах поиска сначала выводить те, которые содержат наибольшее количество слов в статье и далее по мере уменьшения."

Мысль летит) Но ясно, что нужен полнотекстовый поиск.


WordPress плагин для недвижимости

Без изменения структуры данных

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

Чтобы разобраться, как это возможно, я решил написать плагин для агенств недвижимости. Расширять структуру данных не хотелось - это уже будет не WordPress. Решил сохранять JSON-определения объектов недвижимости в поле post_content и преобразовывать эти определения "на лету" - Demo, GitHub


Как устроена структура данных Nested Set

Как сделать рубрикатор

Рубрикатор, как средство поиска интересных статей, имеет свои недостатки - трудно придумать иерархию, названия рубрик, трудно потом решить к какой конкретно рубрике относится статья. "Но, так принято" - скажите вы и будете правы.


Цепочки статей в блоге

Можно ли сделать блог удобнее?

Для группировки статей существует возможность помечать статьи метками или прикреплять к наперед заданной рубрике. Кроме того, в конце статьи есть навигация - предыдущая, следующая статья, ну и меню конечно. Вроде все удобно.



Поиск



Как создать индекс ElasticSearch

Полнотекстовый поиск

Один парень написал: "Нужно сделать поиск слов в таблице "articles" по столбцу "content". В результатах поиска сначала выводить те, которые содержат наибольшее количество слов в статье и далее по мере уменьшения."

Мысль летит) Но ясно, что нужен полнотекстовый поиск.


    Поделиться

Что мы имеем? Есть таблица с заголовками, статьями и прочими полями. Нужен индекс, который позволит проводить поиск по содержимому. Искать надо слово или набор слов, причем в любом количестве и быстро. Для подобной задачи подходит ElasticSearch и начать нужно с установки Java, ElasticSearch (который требует версии PHP >= 7.0) и расширения yii2-elasticsearch.

Model

Индекс нужно создавать для модели, содержащей статьи. Допустим это модель Post. В общем каталоге для моделей, то есть в папке common/models можно создать подкаталог elastic и определить в нем класс модели для индекса (yii2-elasticsearch doc, https://habr.com/post/280488/).

namespace common\models\elastic;
use yii\elasticsearch\ActiveRecord;
class Post extends ActiveRecord
{
    // DB name
    public static function index() 
    {  
        return 'blog'; 
    } 
    // Table name
    public static function type() 
    {
        return 'post';  
    }
    public function attributes()
    {
        return ['id', 'title', 'content'];
    }
    public function rules() 
    {   
        return [
            [$this->attributes(), 'safe']  
        ];
    }
    // Table definition
    public static function mapping()
    {
        return [
            static::type() => [
                'properties' => [
                    'id' => ['type' => 'long'],
                    'title' => ['type' => 'text'],
                    'content' => ['type' => 'text'],
                ]
            ],
        ];
    }
    public static function updateMapping()
    {
        $db = static::getDb();
        $command = $db->createCommand();
        $command->setMapping(static::index(), static::type(), static::mapping());
    }
    // Fill in by common/models/Post model
    public function fill($model, $setPrimaryKey = true) {
        if($setPrimaryKey)
            $this->primaryKey = $model->id;
        $this->attributes = [
            'title' => $model->title,
            'content'  => $model->content,
        ];
    }
    public static function createIndex()
    {
        $db = static::getDb();
        $command = $db->createCommand();
        if($command->indexExists(static::index()))
            $command->deleteIndex(static::index());
        $command->createIndex(static::index(), [
            'settings' => [
                'analysis' => [
                    'filter' => [
                        'ru_stop' => [
                            'type' => 'stop',
                            'stopwords' => '_russian_',
                        ],
                        'ru_stemmer' => [
                            'type' => 'stemmer',
                            'language' => 'russian',
                        ],
                    ],
                    'analyzer' => [
                        'default' => [
                            'char_filter' => [
                                'html_strip',
                            ],
                            'tokenizer' => 'standard',
                            'filter' => [
                                'lowercase',
                                'ru_stop',
                                'ru_stemmer',
                            ],
                        ],
                    ],
                ],
            ],
            'mappings' => static::mapping(),
        ]);
    }
}

Полей в индексе может быть больше. Собственно, можно указать все поля, где целесообразно вести поиск. В "боевой" версии я добавил еще excerpt.

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

Controller

Для выполнения поиска нужно предварительно сформировать индекс. Делается это просто. Перебираются все записи основной модели и сохраняются в индексе.

Контроллер можно определить консольным - для начала работы удобно формировать индекс из консоли.

Кроме того, при изменении записей в основной модели, индекс требует периодического полного обновления. Эту процедуру тоже удобно проводить из консоли, через cron.

namespace console\controllers;
use Yii;
use yii\console\Controller;
use common\models\Post;
use common\models\elastic\Post as ElasticPost;
class ElasticController extends Controller {
    public function actionCreateIndex() {
        ElasticPost::createIndex();
        foreach(Post::find()->where([
            'status' => Post::STATUS_PUBLISHED
        ])->all() as $post) {
            $elastic = new ElasticPost();
            $elastic->fill($post);
            $elastic->save();
        }
        Yii::info('The ElasticSearch index was created ('. 
            ElasticPost::index() .'/'. ElasticPost::type() .').', __METHOD__);
    }
}

Log message

Метод Yii::info() записывает сообщение в лог. Лог может быть не только файлом, но и email ящиком. В этом случае, после формирования индекса, можно получать извещение на нужный ящик.

Параметр categories в console/config.php определяет, что на ящик нужно отправлять только сообщения начинающиеся на указанную строку. В свою очередь строка задается в __METHOD__. Параметр logVars нужно обязательно обнулить, чтобы не получать кучу не нужной информации.

<?php
$params = array_merge(
    require(__DIR__ . '/../../common/config/params.php'),
    require(__DIR__ . '/../../common/config/params-local.php'),
    require(__DIR__ . '/params.php'),
    require(__DIR__ . '/params-local.php')
);
$mailer = require(__DIR__ . '/../../common/config/mailer.php');
return [
  'id' => 'app-console',
  'basePath' => dirname(__DIR__),
  'bootstrap' => ['log'],
  'controllerNamespace' => 'console\controllers',
  'components' => [
    'urlManager' => [
      'class' => 'yii\web\UrlManager',
        'enablePrettyUrl' => true,
        'showScriptName' => false,
        'baseUrl' => 'http://localhost/console',
    ],
    'mailer' => $mailer,
    'log' => [
      'targets' => [
        [
          'class' => 'yii\log\FileTarget',
          'levels' => ['error', 'warning'],
        ],
        [
          'class' => 'yii\log\EmailTarget',
          'levels' => ['info'],
          'categories' => ['console\controllers\ElasticController*'],
          'logVars' => [],
          'message' => [
            'from' => ['admin@sample.ru'],
            'to' => ['admin@sample.ru'],
            'subject' => 'ElasticSearch',
          ],
        ],
      ],
    ],
  ],
  'params' => $params,
];

Создание индекса ElasticSearch

Переходим в корень приложения, запускаем и ждем письма. Не забудьте определить mailer.

php yii elastic/create-index

Проверим, как работает индекс.

curl -XGET "localhost:9200/blog/post/_search?pretty" -d'
{
    "_source": ["title"],
    "query": {
        "match": {
            "content": "history"
        }
    }
}'

Заключение

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

Оставьте комментарий

Только зарегистрированные пользователи могут оставлять комментарии. Пожалуйста войдите или пройдите регистрацию.