Elasticsearch Bazoj

Elasticsearch estas serĉilo kun json rest api, uzante Lucene kaj skribita en Java. Priskribo de ĉiuj avantaĝoj de ĉi tiu motoro estas havebla ĉe oficiala retejo. En kio sekvas ni aludos al Elasticsearch kiel ES.

Similaj motoroj estas uzataj por kompleksaj serĉoj en dokumenta datumbazo. Ekzemple, serĉu konsiderante la morfologion de la lingvo aŭ serĉu laŭ geokoordinatoj.

En ĉi tiu artikolo mi parolos pri la bazaĵoj de ES uzante la ekzemplon de indeksado de blogaĵoj. Mi montros al vi kiel filtri, ordigi kaj serĉi dokumentojn.

Por ne dependi de la operaciumo, mi faros ĉiujn petojn al ES per CURL. Ekzistas ankaŭ kromaĵo por google chrome nomita senso.

La teksto enhavas ligilojn al dokumentaro kaj aliaj fontoj. Fine estas ligiloj por rapida aliro al la dokumentaro. Difinoj de nekonataj terminoj troviĝas en terminaroj.

Instalante ES

Por fari tion, ni unue bezonas Java. Programistoj rekomendi instalu Java-versiojn pli novajn ol Java 8 ĝisdatigo 20 aŭ Java 7 ĝisdatigo 55.

La distribuo de ES haveblas ĉe programejo. Post malpakado de la arkivo vi devas kuri bin/elasticsearch. Ankaŭ havebla pakoj por apt kaj yum... estas oficiala bildo por docker. Pli pri instalado.

Post instalado kaj lanĉo, ni kontrolu la funkciojn:

# для удобства запомним адрес в переменную
#export ES_URL=$(docker-machine ip dev):9200
export ES_URL=localhost:9200

curl -X GET $ES_URL

Ni ricevos ion tian:

{
  "name" : "Heimdall",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "2.2.1",
    "build_hash" : "d045fc29d1932bce18b2e65ab8b297fbf6cd41a1",
    "build_timestamp" : "2016-03-09T09:38:54Z",
    "build_snapshot" : false,
    "lucene_version" : "5.4.1"
  },
  "tagline" : "You Know, for Search"
}

Indeksado

Ni aldonu afiŝon al ES:

# Добавим документ c id 1 типа post в индекс blog.
# ?pretty указывает, что вывод должен быть человеко-читаемым.

curl -XPUT "$ES_URL/blog/post/1?pretty" -d'
{
  "title": "Веселые котята",
  "content": "<p>Смешная история про котят<p>",
  "tags": [
    "котята",
    "смешная история"
  ],
  "published_at": "2014-09-12T20:44:42+00:00"
}'

servilo respondo:

{
  "_index" : "blog",
  "_type" : "post",
  "_id" : "1",
  "_version" : 1,
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "created" : false
}

ES aŭtomate kreita indekso blogo kaj speco post. Ni povas desegni kondiĉan analogion: indekso estas datumbazo, kaj tipo estas tabelo en ĉi tiu datumbazo. Ĉiu tipo havas sian propran skemon − surĵeto, same kiel interrilata tabelo. Mapado estas generita aŭtomate kiam la dokumento estas indeksita:

# Получим mapping всех типов индекса blog
curl -XGET "$ES_URL/blog/_mapping?pretty"

En la servila respondo, mi aldonis la valorojn de la kampoj de la indeksita dokumento en la komentoj:

{
  "blog" : {
    "mappings" : {
      "post" : {
        "properties" : {
          /* "content": "<p>Смешная история про котят<p>", */ 
          "content" : {
            "type" : "string"
          },
          /* "published_at": "2014-09-12T20:44:42+00:00" */
          "published_at" : {
            "type" : "date",
            "format" : "strict_date_optional_time||epoch_millis"
          },
          /* "tags": ["котята", "смешная история"] */
          "tags" : {
            "type" : "string"
          },
          /*  "title": "Веселые котята" */
          "title" : {
            "type" : "string"
          }
        }
      }
    }
  }
}

Indas noti, ke ES ne diferencas inter unu valoro kaj tabelo da valoroj. Ekzemple, la titolkampo simple enhavas titolon, kaj la etikedkampo enhavas tabelon da ŝnuroj, kvankam ili estas reprezentitaj en la sama maniero en mapado.
Ni parolos pli pri mapado poste.

Petoj

Prenante dokumenton per ĝia id:

# извлечем документ с id 1 типа post из индекса blog
curl -XGET "$ES_URL/blog/post/1?pretty"
{
  "_index" : "blog",
  "_type" : "post",
  "_id" : "1",
  "_version" : 1,
  "found" : true,
  "_source" : {
    "title" : "Веселые котята",
    "content" : "<p>Смешная история про котят<p>",
    "tags" : [ "котята", "смешная история" ],
    "published_at" : "2014-09-12T20:44:42+00:00"
  }
}

Novaj ŝlosiloj aperis en la respondo: _version и _source. Ĝenerale, ĉiuj klavoj komencante per _ estas klasifikitaj kiel oficialaj.

Ключ _version montras la dokumentversion. Ĝi estas necesa por ke la optimisma ŝlosa mekanismo funkcii. Ekzemple, ni volas ŝanĝi dokumenton, kiu havas version 1. Ni sendas la ŝanĝitan dokumenton kaj indikas, ke tio estas redakto de dokumento kun versio 1. Se iu alia ankaŭ redaktis dokumenton kun versio 1 kaj sendis ŝanĝojn antaŭ ni, tiam ES ne akceptos niajn ŝanĝojn, ĉar ĝi konservas la dokumenton kun versio 2.

Ключ _source enhavas la dokumenton, kiun ni indeksis. ES ne uzas ĉi tiun valoron por serĉaj operacioj ĉar Indeksoj estas uzataj por serĉado. Por ŝpari spacon, ES konservas kunpremitan fontdokumenton. Se ni bezonas nur la identigilon, kaj ne la tutan fontdokumenton, tiam ni povas malŝalti fontan stokadon.

Se ni ne bezonas pliajn informojn, ni povas ricevi nur la enhavon de _fonto:

curl -XGET "$ES_URL/blog/post/1/_source?pretty"
{
  "title" : "Веселые котята",
  "content" : "<p>Смешная история про котят<p>",
  "tags" : [ "котята", "смешная история" ],
  "published_at" : "2014-09-12T20:44:42+00:00"
}

Vi ankaŭ povas elekti nur iujn kampojn:

# извлечем только поле title
curl -XGET "$ES_URL/blog/post/1?_source=title&pretty"
{
  "_index" : "blog",
  "_type" : "post",
  "_id" : "1",
  "_version" : 1,
  "found" : true,
  "_source" : {
    "title" : "Веселые котята"
  }
}

Ni indeksu kelkajn pliajn afiŝojn kaj faru pli kompleksajn demandojn.

curl -XPUT "$ES_URL/blog/post/2" -d'
{
  "title": "Веселые щенки",
  "content": "<p>Смешная история про щенков<p>",
  "tags": [
    "щенки",
    "смешная история"
  ],
  "published_at": "2014-08-12T20:44:42+00:00"
}'
curl -XPUT "$ES_URL/blog/post/3" -d'
{
  "title": "Как у меня появился котенок",
  "content": "<p>Душераздирающая история про бедного котенка с улицы<p>",
  "tags": [
    "котята"
  ],
  "published_at": "2014-07-21T20:44:42+00:00"
}'

Ordigado

# найдем последний пост по дате публикации и извлечем поля title и published_at
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
  "size": 1,
  "_source": ["title", "published_at"],
  "sort": [{"published_at": "desc"}]
}'
{
  "took" : 8,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : null,
    "hits" : [ {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "1",
      "_score" : null,
      "_source" : {
        "title" : "Веселые котята",
        "published_at" : "2014-09-12T20:44:42+00:00"
      },
      "sort" : [ 1410554682000 ]
    } ]
  }
}

Ni elektis la lastan afiŝon. size limigas la nombron de eldonendaj dokumentoj. total montras la totalan nombron de dokumentoj kongruaj kun la peto. sort en la eligo enhavas tabelon de entjeroj per kiuj ordigo estas farita. Tiuj. la dato estis konvertita al entjero. Pliaj informoj pri ordigo troveblas en dokumentado.

Filtriloj kaj demandoj

ES ekde versio 2 ne distingas inter filtriloj kaj demandoj, anstataŭe la koncepto de kuntekstoj estas enkondukita.
Demanda kunteksto diferencas de filtrila kunteksto, ke la demando generas _poentaron kaj ne estas kaŝmemorigita. Mi montros al vi kia _poentaro estas poste.

Filtru laŭ dato

Ni uzas la peton gamo en la kunteksto de filtrilo:

# получим посты, опубликованные 1ого сентября или позже
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
  "filter": {
    "range": {
      "published_at": { "gte": "2014-09-01" }
    }
  }
}'

Filtri per etikedoj

Ni uzas termino demando serĉi dokumentojn enhavantajn difinitan vorton:

# найдем все документы, в поле tags которых есть элемент 'котята'
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
  "_source": [
    "title",
    "tags"
  ],
  "filter": {
    "term": {
      "tags": "котята"
    }
  }
}'
{
  "took" : 9,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 1.0,
    "hits" : [ {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "1",
      "_score" : 1.0,
      "_source" : {
        "title" : "Веселые котята",
        "tags" : [ "котята", "смешная история" ]
      }
    }, {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "3",
      "_score" : 1.0,
      "_source" : {
        "title" : "Как у меня появился котенок",
        "tags" : [ "котята" ]
      }
    } ]
  }
}

Plenteksta serĉo

Tri el niaj dokumentoj enhavas la jenajn en la enhavkampo:

  • <p>Смешная история про котят<p>
  • <p>Смешная история про щенков<p>
  • <p>Душераздирающая история про бедного котенка с улицы<p>

Ni uzas kongrua demando serĉi dokumentojn enhavantajn difinitan vorton:

# source: false означает, что не нужно извлекать _source найденных документов
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
  "_source": false,
  "query": {
    "match": {
      "content": "история"
    }
  }
}'
{
  "took" : 13,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : 0.11506981,
    "hits" : [ {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "2",
      "_score" : 0.11506981
    }, {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "1",
      "_score" : 0.11506981
    }, {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "3",
      "_score" : 0.095891505
    } ]
  }
}

Tamen, se ni serĉas "rakontojn" en la enhavkampo, ni trovos nenion, ĉar La indekso enhavas nur la originalajn vortojn, ne iliajn radikojn. Por fari altkvalitan serĉon, vi devas agordi la analizilon.

kampo _score montras graveco. Se la peto estas efektivigita en filtrila kunteksto, tiam la _score-valoro ĉiam estos egala al 1, kio signifas kompletan kongruon al la filtrilo.

Analiziloj

Analiziloj necesas por konverti la fonttekston en aron da ĵetonoj.
Analiziloj konsistas el unu Tokenizer kaj pluraj laŭvolaj Tokenfiltriloj. Tokenizer povas esti antaŭita de pluraj CharFilters. Tokeniziloj rompas la fontŝnuron en ĵetonojn, kiel ekzemple spacoj kaj interpunkciaj signoj. TokenFilter povas ŝanĝi ĵetonojn, forigi aŭ aldoni novajn, ekzemple, lasi nur la radikon de la vorto, forigi prepoziciojn, aldoni sinonimojn. CharFilter - ŝanĝas la tutan fontĉenon, ekzemple, fortranĉas html-etikedojn.

ES havas plurajn normaj analiziloj. Ekzemple, analizilo russian.

Ni profitu api kaj ni vidu kiel la normaj kaj rusaj analiziloj transformas la ĉenon "Amuzaj rakontoj pri katidoj":

# используем анализатор standard       
# обязательно нужно перекодировать не ASCII символы
curl -XGET "$ES_URL/_analyze?pretty&analyzer=standard&text=%D0%92%D0%B5%D1%81%D0%B5%D0%BB%D1%8B%D0%B5%20%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D0%B8%20%D0%BF%D1%80%D0%BE%20%D0%BA%D0%BE%D1%82%D1%8F%D1%82"
{
  "tokens" : [ {
    "token" : "веселые",
    "start_offset" : 0,
    "end_offset" : 7,
    "type" : "<ALPHANUM>",
    "position" : 0
  }, {
    "token" : "истории",
    "start_offset" : 8,
    "end_offset" : 15,
    "type" : "<ALPHANUM>",
    "position" : 1
  }, {
    "token" : "про",
    "start_offset" : 16,
    "end_offset" : 19,
    "type" : "<ALPHANUM>",
    "position" : 2
  }, {
    "token" : "котят",
    "start_offset" : 20,
    "end_offset" : 25,
    "type" : "<ALPHANUM>",
    "position" : 3
  } ]
}
# используем анализатор russian
curl -XGET "$ES_URL/_analyze?pretty&analyzer=russian&text=%D0%92%D0%B5%D1%81%D0%B5%D0%BB%D1%8B%D0%B5%20%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D0%B8%20%D0%BF%D1%80%D0%BE%20%D0%BA%D0%BE%D1%82%D1%8F%D1%82"
{
  "tokens" : [ {
    "token" : "весел",
    "start_offset" : 0,
    "end_offset" : 7,
    "type" : "<ALPHANUM>",
    "position" : 0
  }, {
    "token" : "истор",
    "start_offset" : 8,
    "end_offset" : 15,
    "type" : "<ALPHANUM>",
    "position" : 1
  }, {
    "token" : "кот",
    "start_offset" : 20,
    "end_offset" : 25,
    "type" : "<ALPHANUM>",
    "position" : 3
  } ]
}

La norma analizilo disfendis la ŝnuron per spacoj kaj konvertis ĉion al minusklo, la rusa analizilo forigis negravajn vortojn, konvertis ĝin al minusklo kaj lasis la radikon de la vortoj.

Ni vidu kiujn Tokenizer, TokenFilters, CharFilters uzas la rusa analizilo:

{
  "filter": {
    "russian_stop": {
      "type":       "stop",
      "stopwords":  "_russian_"
    },
    "russian_keywords": {
      "type":       "keyword_marker",
      "keywords":   []
    },
    "russian_stemmer": {
      "type":       "stemmer",
      "language":   "russian"
    }
  },
  "analyzer": {
    "russian": {
      "tokenizer":  "standard",
      /* TokenFilters */
      "filter": [
        "lowercase",
        "russian_stop",
        "russian_keywords",
        "russian_stemmer"
      ]
      /* CharFilters отсутствуют */
    }
  }
}

Ni priskribu nian analizilon bazitan sur la rusa, kiu eltranĉos html-etikedojn. Ni nomu ĝin defaŭlta, ĉar analizilo kun ĉi tiu nomo estos uzata defaŭlte.

{
  "filter": {
    "ru_stop": {
      "type":       "stop",
      "stopwords":  "_russian_"
    },
    "ru_stemmer": {
      "type":       "stemmer",
      "language":   "russian"
    }
  },
  "analyzer": {
    "default": {
      /* добавляем удаление html тегов */
      "char_filter": ["html_strip"],
      "tokenizer":  "standard",
      "filter": [
        "lowercase",
        "ru_stop",
        "ru_stemmer"
      ]
    }
  }
}

Unue, ĉiuj HTML-etikedoj estos forigitaj de la fonta ĉeno, tiam la tokenizer normo dividos ĝin en ĵetonojn, la rezultaj ĵetonoj moviĝos al minusklo, sensignifaj vortoj estos forigitaj, kaj la ceteraj ĵetonoj restos la tigo de la vorto.

Kreante Indekson

Supre ni priskribis la defaŭltan analizilon. Ĝi aplikiĝos al ĉiuj kordaj kampoj. Nia afiŝo enhavas aron da etikedoj, do la etikedoj ankaŭ estos prilaboritaj de la analizilo. Ĉar Ni serĉas afiŝojn laŭ ĝusta kongruo al etikedo, tiam ni devas malŝalti analizon por la kampo de etikedoj.

Ni kreu indeksan blogon2 kun analizilo kaj mapado, en kiu la analizo de la kampo de etikedoj estas malŝaltita:

curl -XPOST "$ES_URL/blog2" -d'
{
  "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": {
    "post": {
      "properties": {
        "content": {
          "type": "string"
        },
        "published_at": {
          "type": "date"
        },
        "tags": {
          "type": "string",
          "index": "not_analyzed"
        },
        "title": {
          "type": "string"
        }
      }
    }
  }
}'

Ni aldonu la samajn 3 afiŝojn al ĉi tiu indekso (blogo2). Mi preterlasos ĉi tiun procezon ĉar... ĝi similas al aldoni dokumentojn al la bloga indekso.

Plenteksta serĉo kun esprimo-subteno

Ni rigardu alian specon de peto:

# найдем документы, в которых встречается слово 'истории'
# query -> simple_query_string -> query содержит поисковый запрос
# поле title имеет приоритет 3
# поле tags имеет приоритет 2
# поле content имеет приоритет 1
# приоритет используется при ранжировании результатов
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
  "query": {
    "simple_query_string": {
      "query": "истории",
      "fields": [
        "title^3",
        "tags^2",
        "content"
      ]
    }
  }
}'

Ĉar Ni uzas analizilon kun rusa deveno, tiam ĉi tiu peto resendos ĉiujn dokumentojn, kvankam ili enhavas nur la vorton 'historio'.

La peto povas enhavi specialajn signojn, ekzemple:

""fried eggs" +(eggplant | potato) -frittata"

Peti sintakson:

+ signifies AND operation
| signifies OR operation
- negates a single token
" wraps a number of tokens to signify a phrase for searching
* at the end of a term signifies a prefix query
( and ) signify precedence
~N after a word signifies edit distance (fuzziness)
~N after a phrase signifies slop amount
# найдем документы без слова 'щенки'
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
  "query": {
    "simple_query_string": {
      "query": "-щенки",
      "fields": [
        "title^3",
        "tags^2",
        "content"
      ]
    }
  }
}'

# получим 2 поста про котиков

referencoj

PS

Se vi interesiĝas pri similaj artikoloj-lecionoj, havas ideojn por novaj artikoloj aŭ havas proponojn por kunlaboro, tiam mi ĝojos ricevi mesaĝon en persona mesaĝo aŭ retpoŝte. [retpoŝte protektita].

fonto: www.habr.com