W przypadku, gdy żaden z wbudowanych analizer-ów nie spełnia naszych wymagań. ElasticSearch daje nam możliwość zbudowania własnych.
Jednak jeśli mamy już stworzony indeks to dodanie nowego analizer-a wymaga odrobiny gimnastyki. Mianowicie konieczne jest zamknięcie indeksu:
POST /nazwa_indeksu/_close
Po czym dodajemy nowy analizer lub modyfikujemy istniejący, gdy zakończymy prace to otwieramy indeks:
POST /nazwa_indeksu/_open
W poniższych przykładach pominę ten proces i skupię się na pokazaniu procesu tworzenia analizera. Więc nie dziwcie się, że każdy przykład będzie miał nową nazwę indeksu. Dzięki temu będziecie mogli skopiować przykłady bez obaw o błąd resource_already_exists_exception
:
{
"error": {
"root_cause": [
{
"type": "resource_already_exists_exception",
"reason": "index [my_index/jgjij-zPS86T_lINGGvAUg] already exists",
"index_uuid": "jgjij-zPS86T_lINGGvAUg",
"index": "my_index"
}
],
"type": "resource_already_exists_exception",
"reason": "index [my_index/jgjij-zPS86T_lINGGvAUg] already exists",
"index_uuid": "jgjij-zPS86T_lINGGvAUg",
"index": "my_index"
},
"status": 400
}
Definiując nowy analizer, będziemy podawać trzy parametry wynikające ze sposobu analizy danych przez ElasticSearch-a. Bowiem proces analizy sprowadza się do trzech kroków:
Dlatego tworząc analizer podajemy następujące parametry:
Zdefiniujmy nasz pierwszy analizer, który będzie usuwał znaczniki html, wykorzysta standardowy tokenizer. I na sam koniec zmieni tokeny na pisane małymi literami. Więc nasz zapis będzie wyglądał następująco:
"custom_analyzer_1": {
"type": "custom",
"char_filter": ["html_strip"]
"tokenizer": "standard",
"filter": ["lowercase"]
}
Mamy tutaj dwa elementy o których nie wspominałem wcześniej, jednak łatwo wywnioskować czym są ;) custom_analyzer_1
to nazwa naszego analizera, zaś type
określa typ i w naszym przypadku jest to typ custom
. Jednak analizer musimy gdzieś przypisać i najlepszym miejscem będzie indeks. W indeksie znajduje się klucz settings
gdzie mamy ustawienia indeksu, zaś w nim znajdziemy klucz analysis
. I pod tym kluczem znajdziemy kolejny klucz o znajomej nazwie analyzer
, gdzie dodajemy nasze analizery. Taka struktura może wyglądać następująco:
{
"settings": {
"analysis": {
"analyzer": {
"custom_analyzer_1": {
"type": "custom",
"char_filter": ["html_strip"]
"tokenizer": "standard",
"filter": ["lowercase"]
}
}
}
}
}
Teraz pozostaje jedynie utworzenie nowego indeksu z naszym analizerem:
curl -XPUT 'localhost:9200/my_index_1?pretty' -H 'Content-Type: application/json' -d'{
"settings": {
"analysis": {
"analyzer": {
"custom_analyzer_1": {
"type": "custom",
"char_filter": ["html_strip"],
"tokenizer": "standard",
"filter": ["lowercase"]
}
}
}
}
}'
Skoro mamy własny analizer czas go przetestować, może nie robi on zbyt wiele jednak zobaczmy jak zadziała:
curl -XPOST 'localhost:9200/my_index_1/_analyze?pretty' -H 'Content-Type: application/json' -d '{
"analyzer": "custom_analyzer_1",
"text": "Pierwszy <b>analizer</b>, który usuwa znaczniki HTML !!!"
}'
Rezultatem działania będzie wyświetlenie poniższej listy tokenów:
[pierwszy, analizer, który, usuwa, znaczniki, html]
W porównaniu do wzorcowego tekstu widzimy, że zostały usunięte znaczniki html-a oraz przecinek i wykrzykniki. Analizer zadziałał zgodnie z naszymi oczekiwaniami.
Nasz prosty analizer z poprzedniego przykładu jest idealnym wyjście do czegoś bardziej zaawansowanego. Zróbmy więc analizer, który będzie wyciągał adresy e-mail. Zapewne zastanawiasz się cóż w tym takiego zaawansowanego, otóż jeśli wykorzystamy standardowy mechanizm:
curl -XPOST 'localhost:9200/my_index_2/_analyze?pretty' -H 'Content-Type: application/json' -d '{
"analyzer": "standard",
"text": "Mój adres email: marcin.lewandowski@czterytygodnie.pl"
}'
Rezultatem działania będą tokeny:
[mój, adres, email, marcin.lewandowski, czterytygodnie.pl]
Jak widzisz, nie znajdziemy tutaj pełnego adresu e-mail. Został on rozbity na dwa tokeny, które mogą być przydatne jednak sam adres email także byłby mile widziany. W tym przypadku problemem jest tokenizer, który dzieli adres email na dwie części. Rozwiązanie było by użycie tokenizera uax_url_email
.
curl -XPOST 'localhost:9200/my_index_2/_analyze?pretty' -H 'Content-Type: application/json' -d '{
"tokenizer": "uax_url_email",
"text": "Mój adres email: marcin.lewandowski@czterytygodnie.pl"
}'
Rezultatem działania będą tokeny:
[mój, adres, email, marcin.lewandowski@czterytygodnie.pl]
Więc jeśli chcielibyśmy mieć analizer uwzględniający adresy email to powinien wyglądać on następująco:
curl -XPUT 'localhost:9200/my_index_3?pretty' -H 'Content-Type: application/json' -d'{
"settings": {
"analysis": {
"analyzer": {
"email_analyzer": {
"type": "custom",
"char_filter": ["html_strip"],
"tokenizer": "uax_url_email",
"filter": ["lowercase"]
}
}
}
}
}'
Jednak dodatkowo chcielibyśmy, aby nasz analizer rozbijał nam adres email na dwie części jak to było robione wcześniej. I tu pojawia się problem bowiem nie ma tokenizera czy filtra, który potrafił by taką operację przeprowadzić. Jednak możemy zdefiniować własne filtry i to będzie rozwiązaniem naszego problemu.
Właściwie to nie do końca tworzymy filtry, raczej je konfigurujemy tak samo jak to ma miejsce w przypadku analizerów. Z tą jednak różnicą, że filtry nie mają stałej struktury. Jest ona uzależniona od opcji konfiguracji jakie są udostępnione przez dany filtr. I tak filtr uppercase
nie posiada żadnych opcji tym samym nie ma sensu tworzyć na jego bazie własnego filtra. Za to filtr pattern_capture
już posiada kilka opcji, które pozwalają na odpowiednie skonfigurowanie tego filtra do naszych wymagań.
Struktura dla nowego filtra pattern_capture
będzie wyglądała następująco:
"my_filter": {
"type": "pattern_capture",
"preserve_original": true,
"patterns": [
"([^@]+)@",
"@(.+)"
]
}
Mamy tutaj nazwę naszego filtra my_filter
. Następnie pod kluczem type
znajdziemy nazwę filtra, na bazie którego tworzymy nasz filtr. Pozostałe opcje to indywidualne ustawienia dla filtra pattern_capture
.
Gdy mamy już odpowiednią strukturę to możemy ją dodać do listy filtrów. Lista ta znajduje się podobnie jak analizery w ustawieniach indeksu settings
. Pod kluczem analysis
znajdziemy klucz filter
, gdzie zapisujemy nasze filtry.
Zapis filtra może wyglądać następująco:
curl -XPUT 'localhost:9200/my_index_4?pretty' -H 'Content-Type: application/json' -d'{
"settings": {
"analysis": {
"filter": {
"my_filter": {
"type": "pattern_capture",
"preserve_original": true,
"patterns": [
"([^@]+)@",
"@(.+)"
]
}
}
}
}
}'
Teraz czas na połączenie go z analizerem i weryfikację czy wszystko działa według naszych założeń.
curl -XPUT 'localhost:9200/my_index_5?pretty' -H 'Content-Type: application/json' -d'{
"settings": {
"analysis": {
"filter": {
"email": {
"type": "pattern_capture",
"preserve_original": true,
"patterns": [
"([^@]+)@",
"@(.+)"
]
}
},
"analyzer": {
"email_analyzer": {
"type": "custom",
"char_filter": ["html_strip"],
"tokenizer": "uax_url_email",
"filter": ["lowercase", "email"]
}
}
}
}
}'
Mając tak przygotowany analizer zobaczmy jak zostanie przetworzony tekst:
curl -XPOST 'localhost:9200/my_index_5/_analyze?pretty' -H 'Content-Type: application/json' -d '{
"analyzer": "email_analyzer",
"text": "Mój adres email: marcin.lewandowski@czterytygodnie.pl"
}'
W rezultacie dostajemy listę tagów:
[mój, adres, email, marcin.lewandowski@czterytygodnie.pl, marcin.lewandowski, czterytygodnie.pl]
I to jest dokładnie to czego oczekiwaliśmy :) Może nie jest to najbardziej zaawansowany przykład na świecie, jednak pokazuje ideę jaka przyświeca tworzeniu analizer-ów oraz filtrów.
Możliwość tworzenia własnych analizer-ów oraz filtrów daje nam ogromne możliwości. Kiedy dodatkowo połączymy je z mapper-ami co pozwoli na analizę określonych pól poprzez nasze analizery i filtry. Zyskamy pełną kontrolę nad tym jak powstają tokeny, dlatego w kolejnym wpisie skupię się na mapper-ach.