用PHP在ElasticSearch中创建和设置索引,以进行日本语搜索
这篇文章是关于在ElasticSearch中创建和设置索引以进行日语搜索的。
环境
-
- Elasticsearch はローカル開発環境については Laradock
-
- Dev環境と本番環境については AWS Elasticsearch Service を使います
-
- Elasticsearch のバージョンは 6.7とし、これは2019/8/2時点での AWS Elasticsearch Service の最新版です
-
- elasticsearch-php のバージョンは 7.1
- PHP のバージョンは 7.2 (これも現時点での ElasticBeanstalk の対応最新版です)
插件和其他环境配置
在本地开发环境的Laradock中,我们需要在DockerFile中编写插件的安装方法。
$ cat elasticsearch/Dockerfile
// ここのバージョンを書き換えてます
FROM docker.elastic.co/elasticsearch/elasticsearch:6.7.0
// 以下のプラグインを2つ入れます。日本語解析のためのものです。
RUN elasticsearch-plugin install analysis-kuromoji
RUN elasticsearch-plugin install analysis-icu
// 以下変更なし
EXPOSE 9200 9300
由于Laradock中包含Kibana和Elasticsearch,只需指定参数即可安装。
$ docker-compose up -d nginx mysql workspace elasticsearch kibana
AWS ElasticSearch Service 似乎已经内置了大部分的插件和功能。
所以,如果在控制台上叮咚叮咚地连接上了,就没有什么特别的事要做。关于这一点,会在另一篇文章中进行详细介绍,所以在这里不写。
关于elasticsearch-php
安装
为了将 PHP 与 Elasticsearch 连接起来,可以使用此选项。
参考链接:https://github.com/elastic/elasticsearch-php
安装方法等可以参考 README,非常清楚明了,所以我就不多赘述了。
用法手冊
在这里,不仅有顶部的README文档,还有另外一份docs文档,并且有很多资料。此外,Elasticsearch的官方文档也有简明易懂的写作方式,建议也可以参考一下。
https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/quickstart.html
创建索引和设置
我会同时进行索引创建和设置操作。
// typeをtextにしたいリスト。analyzerを指定するときに同時にtypeが必須だったのでここにtextのものだけリストアップしている。
$types = [
'hoge' => 'text',
'fuga' => 'text',
];
$mappingProperties = [];
foreach ($types as $key => $type) {
$mappingProperties[$key] = [
'search_analyzer' => 'my_ja_analyzer',
'analyzer' => 'my_ja_analyzer',
'type' => $type
];
}
// indexの作られていない初回のみ作成とsettingを投げ込む
// settingは初回だけなので別の管理にする案もあったが、Index内容と密接に関係にしているのでここに一緒に書いておきたい
$indexCheckResponse = $this->getClient()->indices()->get([
'index' => 'your_index_name',
'client' => ['ignore' => 404]
]);
if (isset($indexCheckResponse['status']) && $indexCheckResponse['status'] == '404') {
$this->getClient()->indices()->create([
'index' => 'your_index_name',
'body' => [
'settings' => [
"analysis" => [
"analyzer" => [
"my_ja_analyzer" => [
"type" => "custom",
"tokenizer" => "kuromoji_tokenizer",
"char_filter" => [
"icu_normalizer",
"kuromoji_iteration_mark"
],
"filter" => [
"kuromoji_baseform",
"kuromoji_part_of_speech",
"ja_stop",
"kuromoji_stemmer"
]
]
]
]
],
'mappings' => [
// この type 指定はelasticsearch 7系からは削除されています。あんまり意味のないパラメータのように見えるので適当な値を入れています。
'_doc' => [
'properties' =>
$mappingProperties
]
]
]
]);
}
导入这段代码
$documentBatch 是从数据库中提取的数据集合。
例如,它具有以下的结构。
[
'id' = 1,
'fields' => [
'hoge' => 'ここにテキスト1',
'fuga' => 'ここにテキスト2'
]
]
我們將使用批量導入的端點一次性將其加入。
foreach ($documentBatch as $document) {
$params['body'][] = [
'index' => [
'_index' => 'your_index_name',
// この type 指定はelasticsearch 7系からは削除されています。あんまり意味のないパラメータのように見えるので適当な値を入れています。
'_type' => '_doc',
'_id' => $document['id']
]
];
$params['body'][] = $document['fields'];
}
$this->getClient()->bulk($params);
单词搜索代码
public function search(string $words)
{
// ここについてはコード後述
$queryString = $this->getWordQueryString($words);
$searchResults = $this->getClient()->search([
'index' => 'your_index_name',
'from' => 0,
// 設定可能上限値なので実質無制限扱い
'size' => 10000,
'body' => [
'query' => [
'query_string' => [
// prefixで検索するためにワイルドカードを許可
'analyze_wildcard' => true,
// この設定は tokenize された後の文字断片の間の関係についてにも使われているっぽい(推測)
// tokenizer により意図しない分割をされた単語においては"OR"検索をされると無関係な場所までマッチしてしまうことになるので、デフォルトはANDにしておいたほうが良い。例えば(架空の会社名) "日伸" は "日"と"伸"になるが、これがORだと意図しない"日"へのマッチが紛れ込んでしまう
// ここで文字列として作成する queryString の中においては AND と OR は明示的に設定されるので影響しない
'default_operator' => "AND",
'query' => $queryString
]
],
]
]);
$idList = [];
foreach ($searchResults['hits']['hits'] as $result) {
$idList[] = $result['_id'];
}
return $idList;
}
private function getWordQueryString(string $words): string
{
// 全角スペースを半角スペースに変換してexplodeでの分割がうまくいくようにしている
// 使うためには composer で ext-mbstring をインストールしておいてください
$spaceConvertedWords = mb_convert_kana($words, 's');
$splittedWords = explode(' ', $spaceConvertedWords);
$queries = array_map(function ($splittedWords) {
$trimmedWord = $this->trimEscapedChar($splittedWords);
return "${trimmedWord}*";
}, $splittedWords);
$queryString = implode(' AND ', $queries);
return $queryString;
}
private function trimEscapedChar($word)
{
return str_replace(['\\', "'"], '', $word);
}
参考于Qiita
我们在处理Kuromoji解析周围的事情时,大多都是依赖于这里(指的是https://qiita.com/shin_hayata/items/41c07923dbf58f13eec4)提供的帮助。