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

Интерфейс для полнотекстового поиска

Использование ElasticSearch во frontend

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

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


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

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

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

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



Поиск



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

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

  • 1. Как создать индекс ElasticSearch
  • 2. Интерфейс для полнотекстового поиска

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

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


    Поделиться

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

Model

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

<?php
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.

<?php
namespace console\controllers;
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, который позволит вводить слова поиска и учитывать полученное условие при выдаче результатов. Но об этом в следующей статье.

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

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