6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Elixir Livebook で Algolia からデータ検索してみる

Last updated at Posted at 2023-02-17

はじめに

Algolia は簡単に使える全文検索サービスです

ざっくり言うと、テキスト検索や条件による絞り込み機能を自分の Web サイトに簡単に組み込めます

例えば、以下のようなオンラインストア風画面を簡単に実装できます

algolia_temp.png

とりあえず少ないデータでやる分には無料で試せます

以下は Algolia のデモサイトです

大体どんなことをするものか分かったと思います

というわけで、 Elixir から Algolia のデータを検索してみます

今回も Livebook を使います

ゆくゆくは LiveView に組み込んでいきます

実装したノートブックはこちら

Algolia のアカウント作成

2023/02/17 現在の方法です

Algolia のトップページ右上の START FREE をクリックします

スクリーンショット 2023-02-17 15.04.00.png

メールアドレス、パスワードを入力し、利用規約への同意、ロボットではないことの証明をしてサインアップします

スクリーンショット 2023-02-17 15.05.34.png

必要な項目を埋めていきます

スクリーンショット 2023-02-17 15.07.29.png

メール検証をするように言われるので、自分のメールアドレスに届いている検証用メールから Verify Your Email のボタンをクリックします

スクリーンショット 2023-02-17 15.28.38.png

ダッシュボードが表示されればアカウント作成は完了です

スクリーンショット 2023-02-17 15.34.49.png

データの準備

今回はお試し用に Algolia の公開データセットを使います

色々データはありますが、ecommerce = Eコマース(電子商取引) = EC = オンラインストア用データを使ってみます

以下のデータ本体(products.json)とデータの設定(products_configuration.json)をダウンロードしておきます

インデックスの作成

Algolia に戻って、インデックス(データを入れておくもの)を作成して先程用意したデータを格納します

ダッシュボードの左メニュー下側にあるロケットアイコン :rocket: をクリックします

すると、下画像のように最初のインデックスを作る画面が表示されます

products と入力して Create index をクリックします

スクリーンショット 2023-02-17 15.43.11.png

続いて、作成したインデックスにデータをインポートします

Upload your records |> Upload file と選択し、用意しておいた products.json をアップロードします

スクリーンショット 2023-02-17 15.49.05.png

データがインポートされるとインデックスの画面に遷移します(遷移しなかったらダッシュボードの一覧下にインデックスへのリンクがあるのでそこから遷移します)

画面中央あたりにある Manage index |> Import Configuration をクリックします

スクリーンショット 2023-02-17 15.52.20.png

用意しておいた products_configuration.json を選択し、 Settings と Rule の両方にチェックを付けてテキストボックスに IMPORT を入力してから Import Configuration をクリックします

スクリーンショット 2023-02-17 15.52.45.png

これでデータと設定が準備できました

APIキーの取得

左メニュー上側の家アイコン :house: からダッシュボードに戻ります

画面中央あたりにある API Keys をクリックします

スクリーンショット 2023-02-17 15.34.49.png

API キーの画面に遷移します

スクリーンショット 2023-02-17 16.11.37.png

ここで表示される Application ID と Admin API Key を後で使います

Algolia 用の Elixir モジュール

elixir algolia で Google 検索すると、二つのモジュールがヒットします

2023年2月17日現在、それぞれ最終更新日は以下のようになっていました

  • algolia-elixir 5年前
  • Algoliax 6ヶ月前

というわけで、比較的更新されている Algoliax の方を使います

セットアップ

Livebook で新しいノートブックを開き、セットアップを実行します

Mix.install([
  {:algoliax, "~> 0.7"},
  {:req, "~> 0.3"},
  {:kino, "~> 0.8"}
])

Req はデータセットの設定情報をダウンロードしてくるのに使います

Kino はデータを見やすく表示するために使います

本来であれば Mix.installconfig を以下のように設定します

config: [
    algoliax: [
      api_key: <APIキー>,
      application_id: <アプリケーションID>
    ]
  ]

しかし、 Livebook に秘密情報などを残したくないので、後から環境変数で指定します

認証情報の設定

アプリケーションIDは見えても良い情報なので、 Kino.Input.text でテキストボックスを作成します

application_id = Kino.Input.text("Application ID")

表示されたテキストボックスに Algolia の API キー画面からアプリケーションIDをコピー&ペーストします

次のコードを実行することで環境変数にアプリケーションIDをセットします

application_id
|> Kino.Input.read()
|> then(&System.put_env("ALGOLIA_APPLICATION_ID", &1))

APIキーは秘密情報なので、 Kino.Input.password で入力値が隠れるテキストボックスを作成します

Algolia の API キー画面から Admin API KEY をコピー&ペーストします

次のコードを実行することで環境変数にAPIキーをセットします

api_key
|> Kino.Input.read()
|> then(&System.put_env("ALGOLIA_API_KEY", &1))

インデックス設定の取得

Algoliax の README に従うと、インデックスにアクセスするためのモジュールを定義するようになっています

Usage

defmodule People do
  use Algoliax.Indexer,
    index_name: :algoliax_people,
    object_id: :reference,
    algolia: [
      attributes_for_faceting: ["age"],
      searchable_attributes: ["full_name"],
      custom_ranking: ["desc(updated_at)"]
    ]

  defstruct reference: nil, last_name: nil, first_name: nil, age: nil
end

ここで引数 algolia にインデックスの設定情報を入れています

このモジュールで指定しているインデックス設定と、 Algolia 上のインデックス設定が異なる場合、 Algoliax はモジュールの内容で Algolia の内容を上書きに行きます

もし引数 algolia を指定しない場合、設定が全て nil になって消されてしまいます

なので、必ず正しい設定情報を指定する必要があります

また、設定情報を読み書きできる権限が必要なため、 Admin API KEY を使う必要があります

というわけで、先程ダウンロードしていた products_configuration.json を Elixir でも読み込みます

configuration =
  "https://raw.githubusercontent.com/algolia/datasets/master/ecommerce-federated/products_configuration.json"
  |> Req.get!()
  |> then(&Jason.decode!(&1.body))

Algoliax で使える形に整形します

settings =
  configuration["settings"]
  |> Enum.into([], fn {key, value} ->
    {
      key |> Inflex.underscore() |> String.to_atom(),
      value
    }
  end)

結果は以下のようになります

[
  advanced_syntax: true,
  alternatives_as_exact: ["ignorePlurals", "singleWordSynonym"],
  attribute_for_distinct: nil,
  ...
  snippet_ellipsis_text: "",
  typo_tolerance: "strict",
  unretrievable_attributes: nil
]

モジュールの定義

インデックス名を :products に指定して Products モジュールを定義します

defmodule Products do
  use Algoliax.Indexer,
    index_name: :products,
    algolia: settings
end

検索

ここまで準備すると後は単純で、 search にテキストを入れるだけで検索できます

{:ok, %Algoliax.Response{response: response}} = Products.search("Nike")

response

このとき、デバッグ情報としてインデックスの設定を上書きしているのが見えます(初回実行時のみ)

07:31:01.027 [debug] CONFIGURE_INDEX: PUT https://xxxxxxxxxx.algolia.net/1/indexes/products/settings, body: %{"queryLanguages" => ["en"], "removeWordsIfNoResults" => "none", "snippetEllipsisText" => "", ...}

07:31:01.783 [debug] GET_SETTINGS: GET https://xxxxxxxxxx-dsn.algolia.net/1/indexes/products/settings

07:31:02.146 [debug] SEARCH: POST https://xxxxxxxxxx.algolia.net/1/indexes/products/query, body: %{query: "Shirt"}

結果は以下のようになります

%{
  "exhaustive" => %{"nbHits" => true, "typo" => true},
  "exhaustiveNbHits" => true,
  "exhaustiveTypo" => true,
  "hits" => [
  ...
  "serverTimeMS" => 3
}

非常に項目が多いので、一部を切り出します

  • hitsPerPage: 1ページあたりの件数
  • nbHits: ヒットした件数
  • nbPages: ページ数
  • page: 現在のページ番号
{
  response["hitsPerPage"],
  response["nbHits"],
  response["nbPages"],
  response["page"],
}

結果は以下のようになります

{20, 339, 17, 0}

ページ指定する場合は以下のようにします

Products.search("Shirt", %{page: 1})

また、1ページあたりの件数を指定することもできます

Products.search("Shirt", %{hits_per_page: 100})

ヒットしたデータの1件を見てみましょう

項目が非常に多いので、ツリー表示します

response["hits"]
|> List.first()
|> Kino.Tree.new()

結果は以下のようになります

スクリーンショット 2023-02-17 17.01.08.png

brandname などはヒットした商品の属性情報です

_ から始まる情報はメタデータになっています

ハイライト

_highlightResult には、検索したテキストがどの項目にヒットしたのか、という情報が格納されています

対象はインデックスの設定情報で attributes_to_highlight に指定した項目です

response["hits"]
|> List.first()
|> then(& &1["_highlightResult"])

値は以下のようになっています

%{
  "brand" => %{"matchLevel" => "none", "matchedWords" => [], "value" => "Polo Ralph Lauren"},
  "list_categories" => [
    %{"matchLevel" => "none", "matchedWords" => [], "value" => "Men"},
    %{"matchLevel" => "none", "matchedWords" => [], "value" => "Clothing"},
    %{
      "fullyHighlighted" => false,
      "matchLevel" => "full",
      "matchedWords" => ["shirt"],
      "value" => "<em>Shirt</em>s"
    }
  ],
  "name" => %{
    "fullyHighlighted" => false,
    "matchLevel" => "full",
    "matchedWords" => ["shirt"],
    "value" => "<em>Shirt</em> Polo Ralph Lauren pink"
  }
}

brand ブランド名ではヒットしていません

list_categories カテゴリリストの中では ShirtsShirt の部分がヒットしています

name 商品名は Shirt Polo Ralph Lauren pink の中から Shirt の部分がヒットしています

スニペット

_snippetResult にも、検索したテキストがどの項目にヒットしたのか、という情報が格納されています

対象はインデックスの設定情報で attributes_to_snippet に指定した項目です

response["hits"]
|> Enum.filter(& &1["_snippetResult"]["description"]["value"] != "")
|> List.first()
|> then(& &1["_snippetResult"])

値は以下のようになります

%{
  "description" => %{
    "matchLevel" => "full",
    "value" => "Ralph Lauren has interpreted the slim-fit <em>shirt</em> with an"
  }
}

description に含まれる shirt という単語が完全一致しています

まとめ

Algolia からデータ検索することができました

もっとモジュールが充実するように、とにかく使ってみて Issue を上げたりしたいですね

6
4
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
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?