使用Elasticsearch,创建一个在30分钟内推测出口袋妖怪的搜索引擎
日本のゲーム会社エイチームの「引越し侍・エイチームコネクト Advent Calendar 2019」の22日目の担当は、@hinoraです!これが2回目です!
? 示范

? 这是什么?
我会根据输入的宝可梦名字推测并显示相似的宝可梦。
在示威活动中得出以下的结果。
机制
我要使用Elasticsearch的相似查询(More Like This Query)。
使用“更多相似结果”查询,可以从指定的文档或关键词中提取出相似的内容。
作为推荐系统,虽然精度不如机器学习等方法,但非常适合希望以一定精度简单实现”推荐文章”或”推荐商品”等场景!
✊ 亲自实践一下
准备Elasticsearch和Kibana。
请使用Docker Compose进行部署,需要注意它会占用相当多的内存。同时,我们也会安装kuromoji插件。
请您为音量相关的目录进行适当创建。
version: ‘2’
services:
elasticsearch:
build: elasticsearch
volumes:
– ./docker/es/data:/usr/share/elasticsearch/data
– ./docker/es/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
ports:
– 9200:9200
environment:
– discovery.type=single-node
– bootstrap.memory_lock=true
– “ES_JAVA_OPTS=-Xms512m -Xmx512m”
kibana:
image: docker.elastic.co/kibana/kibana:7.5.1
ports:
– 5601:5601
volumes:
elasticsearch-data:
driver: local
elasticsearch/Dockerfile:
FROM docker.elastic.co/elasticsearch/elasticsearch:7.5.1
RUN elasticsearch-plugin install analysis-kuromoji
2. 数据的获取和导入
本次我们将使用每只宝可梦的以下参数。
如果有数据集就好了,但是没有找到适合的,所以我克隆了PokeAPI并用docker-compose构建了它,从API中将数据取出来。
获得
我将使用JS进行fetch请求。
/** 指定したIDのポケモンの情報を取得する
* - これを800くらいまで取ってきます
* - jsonのArrayをJSON.stringifyしながらjoin("\n")するとあとで使いやすいです
*/
const fetchPokemonById = async id => {
const res = await fetch(`http://localhost/api/v2/pokemon-species/${id}`).then(res => res.json());
const { flavor_text: flavorText } = findByLanguage(res.flavor_text_entries);
const { name } = findByLanguage(res.names);
const { genus } = findByLanguage(res.genera);
return {
id,
name,
flavorText,
genus,
color: res.color.name,
eggGroup: res.egg_groups[0].name,
};
};
整形手术然后注入
本次支持的导入格式有JSON和CSV,但我们选择使用JSON。
不过要说JSON并不是普通的JSON文件,而是称为拥有换行分隔的JSON。
这种格式就如它的名字所示,由多个通过换行符分隔的JSON组成。
{"id":1,"name":"フシギダネ","flavorText":"~~~~~~~","genus":"たねポケモン","color":"green","eggGroup":"plant"}
{"id":2,"name":"フシギソウ","flavorText":"~~~~~~~","genus":"たねポケモン","color":"green","eggGroup":"plant"}
// ...
请进入Kibana的Machine Learning – Data Visualizer – Import data页面,导入之前创建的JSON文件。
选择文件后按下导入按钮,会询问如何处理索引,因此我们将创建一个名为”pokemon”的索引。
准备工作已经完成了!
3. 发出一个类推的查询
我打算试着去捕捉一个类似于”不可思议种子”的宝可梦。
事先在Kibana的Discover等页面上记下文档的ID。
当我复制了文档的ID之后,我将在Kibana的Dev Tools中尝试发送More Like This Query。
推测查询
使用”like”命令来指定与”fields”进行比较的参数名称。
GET pokemon/_search
{
"_source": "name",
"query": {
"more_like_this": {
"fields": [
"name",
"flavorText",
"color",
"eggGroup",
"genus"
],
"like": [
{
"_id": "Th1ALW8BEmAvY9Q5hJWt" // フシギソウのドキュメントID
}
]
,
"min_term_freq": 1,
"max_query_terms": 12
}
}
}
结果
{
// ~ 省略 ~
"hits" : [
{
"_index" : "pokemon",
"_type" : "_doc",
"_id" : "UB1ALW8BEmAvY9Q5hJWt",
"_score" : 23.025995,
"_source" : {
"name" : "フシギバナ"
}
},
{
"_index" : "pokemon",
"_type" : "_doc",
"_id" : "DR1ALW8BEmAvY9Q5hJa0",
"_score" : 22.985844,
"_source" : {
"name" : "キマワリ"
}
},
// ...
使用这个,「用于推测宝可梦的搜索引擎」已经完成了!如果在Web服务等中使用,只要从喜欢的客户端发送请求并显示即可。
顺便提一下,在工作中我们使用了一个叫做Chewy的Rails宝石。
它非常易于使用,我推荐使用!
额外内容:视角方面
这次我选择直接从前端实现,但因为有官方客户端,所以比我想象的要简单。
const es = require('elasticsearch');
const client = new es.Client({
host: 'localhost:9200',
});
// 全てのドキュメントを取得するクエリを投げる
function fetchAll() {
return client.search({
index: 'pokemon',
body: {
size: 50,
query: { match_all: {} },
},
});
}
已完成的产品

因为沙包和皮卡丘都属于老鼠类宝可梦,所以沙包比老鼠宝可梦皮丘得分高嘞,哈哈。
如果将类型作为比较参数的对象纳入考虑,可能会更加精确!
总结
当听到“推荐系统”这个词时,可能会给人一种很难的印象,但是如果使用“更多类似的查询”功能,就可以轻松地实现推荐。
此外,还可以通过修改max_query_terms等参数来调整以提高准确性。
请务必尝试一下!
明天轮到 @halkt 亲出场了!期待你的精彩表演喔!