1
1

More than 5 years have passed since last update.

Elasticsearchでsearchkick gemを使ってレストランデータを検索する

Last updated at Posted at 2016-08-31

searchkickgemをつかってライブドアのレストランデータセットを様々なユースケースで検索してみます。
レストランのデータはActiveRecordを介してすでにデータベースにロードされているとします。

Elasticsearchへのインポートと更新

RestaurantsデータをElasticsearchに入れるには以下のようにします。

Restaurant.reindex

データが更新された際のElasticsearchへの更新方法は同期、非同期と2種類あります。デフォルトは同期です。また関連しているレコードはデフォルトでは更新されません。更新する倍は、after_commitで該当の関連先インスタンスをreindexします。

複数の条件で絞り込む

image

食べログだと上記のような複数条件の検索です。今回は以下で絞り込みます。

  • nameもしくはdescription: ラーメンを含む。ただし、nameを10ブーストする
  • エリア: 新宿・代々木(area_id = 3)
  • 閉店していない: closed = 0
  • 夜あいている: open_late = 1
restaurants = Restaurant.search "ラーメン", fields: ["name^10", "description"], where: { area_id: 3, closed: 0, open_late: 1 }, limit: 5, order: { access_count: :desc }

# 結果は以下
[27] pry(main)> restaurants.map(&:name)
  Restaurant Load (2.7ms)  SELECT `restaurants`.* FROM `restaurants` WHERE `restaurants`.`id` IN (9575, 7690, 11777, 674, 393412)
=> ["どうとんぼり神座", "ラーメン二郎", "ラーメン若月", "一心ラーメン", "ラーメン大"]

特定の店のレビュー数の統計を取る

image

Amazonで言うと上記のようなイメージです。今回は「どうとんぼり神座(id = 9575)」の結果をとってみます。

ratings = Rating.search "*", where: { restaurant_id: 9575 }, aggs: { total: { order: { "_term" => "asc" } } }, limit: 5

# ratingに対する評価数
[14] pry(main)> ratings.aggs["total"]["buckets"]
=> [{"key"=>0, "doc_count"=>1}, {"key"=>1, "doc_count"=>4}, {"key"=>2, "doc_count"=>17}, {"key"=>3, "doc_count"=>45}, {"key"=>4, "doc_count"=>28}, {"key"=>5, "doc_count"=>2}]

# 平均評価
[44] pry(main)> ratings.aggs["total"]["buckets"].map { |e| e["key"] * e["doc_count"] }.sum / ratings.aggs["total"]["doc_count"].to_f
=> 3.0412371134020617

検索でのオートコンプリーション

image

Googleサーチでも出てくる検索候補です。ここらへんはある程度データがたまってきたらユーザの検索クエリをデータベースに入れておいてそれを検索すればよさそうです。

restaurants = Restaurant.search "ラーメン", limit: 5

# 結果
[42] pry(main)> restaurants.map(&:name)
  Restaurant Load (0.4ms)  SELECT `restaurants`.* FROM `restaurants` WHERE `restaurants`.`id` IN (12742, 14108, 18345, 366402, 388797)
=> ["ラーメンバトルコロシアム", "ラーメンドラゴン", "ラーメンパンダ", "ラーメンショップ", "ラーメンショップ"]

もしかして検索

image

単語を打ち間違えたときに、あっていそうなものを出してくるGoogle Searchでもおなじみのものです。

こちら日本語検索だと、デフォルトのsearchkickのsuggestionsだとうまくいかないので、http://qiita.com/wapa5pow/items/8a19241d39579d939ade で調べてみました。

ユーザの特性に応じて独自のスコアをつける

省略。いずれ書きます。

デバッグ

# resopnseでElasticsearchからのレスポンスがみれる
[57] pry(main)> Restaurant.search("すきやばしこじろう", explain: true).response
  Restaurant Search (17.5ms)  curl http://localhost:9200/restaurants_development/_search?pretty -d '{"query":{"dis_max":{"queries":[{"match":{"name.word_start":{"query":"すきやばしこじろう","boost":10,"operator":"and","analyzer":"searchkick_word_search"}}},{"match":{"name.word_start":{"query":"すきやばしこじろう","boost":1,"operator":"and","analyzer":"searchkick_word_search","fuzziness":1,"prefix_length":0,"max_expansions":3,"fuzzy_transpositions":true}}}]}},"size":1000,"from":0,"explain":true,"fields":[]}'
=> {"took"=>15, "timed_out"=>false, "_shards"=>{"total"=>5, "successful"=>5, "failed"=>0}, "hits"=>{"total"=>0, "max_score"=>nil, "hits"=>[]}}

# 各種アナザイザーからの返却値
[58] pry(main)> Restaurant.searchkick_index.tokens("すきやばしこじろう", analyzer: "default_index")
=> ["す", "すき", "き", "きや", "や", "やば", "ば", "ばし", "し", "しこ", "こ", "こじ", "じ", "じろ", "ろ", "ろう", "う"]
[59] pry(main)> Restaurant.searchkick_index.tokens("すきやばしこじろう", analyzer: "searchkick_search")
=> ["すき", "きや", "やば", "ばし", "しこ", "こじ", "じろ", "ろう"]
[60] pry(main)> Restaurant.searchkick_index.tokens("すきやばしこじろう", analyzer: "searchkick_search2")
=> ["す", "き", "や", "ば", "し", "こ", "じ", "ろ", "う"]

参考

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1