elasticsearch, analizador spanish con filtro fonético

Pasos a seguir para crear un indice con un analizdor en español con reconocimiento de fonético.

Especialmente util si queremos buscar para un texto como este:

  • “Habia una vez un barquito chiquitito que navegaba ….”

palabras como

  • avia, nabegava

es decir, palabras mal escritas pero que se pronuncian igual.

Pasos previo necesarios

Instalar plugin

En cada nodo debemos instalar el plugin y reiniciar.

sudo bin/plugin install analysis-phonetic

Crear el indice y el mapping

curl -XDELETE http://localhost:9201/indice_fonetico

curl -XPUT http://localhost:9201/indice_fonetico

curl -XPOST http://localhost:9201/indice_fonetico/_close

curl -XPUT http://localhost:9201/indice_fonetico/_settings?pretty -d '{
  "analysis" : {
    "analyzer":{
      "spanish_no_accent": {
          "tokenizer": "standard",
          "filter":  [ "lowercase", "asciifolding", "spanish_stop", "filtro_fonetico", "spanish_stemmer" ]
          }
    },
    "filter" : {
            "spanish_stemmer" : {
                "type" : "stemmer",
                "name" : "spanish"
            },
            "spanish_stop": {
                             "type":        "stop",
                             "stopwords": [ "_spanish_" ]
      },
      "filtro_fonetico": {
          "type": "phonetic",
          "encoder": "beidermorse",
          "replace": false,
          "languageset": ["spanish"]
      }
          }
  }
}'

curl -XPOST http://localhost:9201/indice_fonetico/_open

curl -XPUT http://localhost:9201/indice_fonetico/_mapping/documento?pretty -d '{
"properties":{
  "documento":{
     "type":"string",
     "analyzer":"spanish_no_accent"
  }
 }
}'

Indexar datos y preparar una query

Indexamos un documento

PUT indice_fonetico/documento/1
{
  "documento": "Habia una vez un barquito chiquitito que navegaba ...."
}

Realizamos una búsqueda

  • avia

Query y respuesta

GET indice_fonetico/documento/_search
{
  "query": {
    "match": {
      "documento": {
        "query": "avia"
      }
    }
  }
}
{
  "took": 3,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 1.7499999,
    "hits": [
      {
        "_index": "indice_fonetico",
        "_type": "documento",
        "_id": "4",
        "_score": 1.7499999,
        "_source": {
          "documento": "Habia una vez un barquito chiquitito que navegaba ...."
        }
      }
    ]
  }
}

Términos generados por este analizador

En cualquier momento podemos ver los términos que genera este analizador, para entender mejor las búsquedas que podemos realizar con este analizador y el nuevo filtro fonético.

Invocación del analizador

POST prueba_fonetica/_analyze
{
  "analyzer": "spanish_no_accent",
  "text":     "barquito"
}

Términos generados por el analizador

{
  "tokens": [
    {
      "token": "barkit",
      "start_offset": 0,
      "end_offset": 8,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "borkit",
      "start_offset": 0,
      "end_offset": 8,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "varkit",
      "start_offset": 0,
      "end_offset": 8,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "vorkit",
      "start_offset": 0,
      "end_offset": 8,
      "type": "<ALPHANUM>",
      "position": 0
    }
  ]
}

Para entender mejor el comportamiento de este filtro fonético podemos crear un analizador donde solamente tengamos como filtro el filtro fonético.

curl -XPUT http://localhost:9201/indice_fonetico/_settings?pretty -d '{
  "analysis" : {
    "analyzer":{
      "spanish_no_accent": {
          "tokenizer": "standard",
          "filter":  [ "lowercase", "filtro_fonetico" ]
          }
    },
    "filter" : {
      "filtro_fonetico": {
          "type": "phonetic",
          "encoder": "beidermorse",
          "replace": false,
          "languageset": ["spanish"]
      }
    }
  }
}'
Anuncios

elasticsearch Cookbook, ver los términos generados por un analizador utilizando el API Java

Elasticsearch

Ya hemos visto en otro artículo cómo obtener los términos de un analizador utilizando el API Rest. Ahora vamos a ver lo mismo pero utilizando el API Java.

Como sabemos podemos obtener los términos generados por un analizador de dos formas distintas

  • invocando al API indicándole el nombre del analizador
  • invocando al API indicándole un nombre del campo para que utilice el analizador asociado a ese campo

En este artículo vamos a ver las dos opciones.

Invocando al API indicándole el nombre del analizador

public List<String> analyzer(String texto, String analyzer) {
    AnalyzeResponse aresp = client.admin().indices().analyze(new AnalyzeRequest(INDEX_NAME, texto).analyzer(analyzer)).actionGet();
    List<String> terms = new ArrayList<String>();
    if (aresp != null && aresp.getTokens() != null) {
        aresp.getTokens().forEach(token -> {
            terms.add(token.getTerm());
        });
    }
    return terms;
}

Invocando al API indicándole un nombre del campo

public List<String> analyzerByField(String texto, String field) {
    AnalyzeResponse aresp = client.admin().indices().analyze(new AnalyzeRequest(INDEX_NAME, texto).field(field)).actionGet();
    List<String> terms = new ArrayList<String>();
    if (aresp != null && aresp.getTokens() != null) {
        aresp.getTokens().forEach(token -> {
            terms.add(token.getTerm());
        });
    }
    return terms;
}

En ambos casos

  • client, es un objeto de la clase org.elasticsearch.client.Client obtenido utilizando el API Java de elasticsearch. Tanto con la clase NodeBuilder como TransportClient podemos obtener el cliente de acceso a elasticsearch.

    No vamos a ver como obtener este objeto client. Esperamos hacerlo en otro artículo que publicaremos más adelante.

elasticsearch Cookbook, ver los términos generados por un analizador

Elasticsearch

Es muchas ocasiones es útil saber qué términos devuelve un analizador tras analizar un texto. Esto se puede hacer con el API Rest de forma fácil y sencilla. No es necesario crear un índice para evaluar el comportamiento de un analizador.

Para utilizar este API solo tenemos que saber el nombre del analizador. Vamos a ver unos ejemplos.

Obtener los términos devueltos por el analizador spanish

# Petición
curl -XPOST 'http://localhost:9200/_analyze?analyzer=spanish&pretty' -d 'Oracion Oración Oraciones'

# Respuesta
{
   "tokens": [
      {
         "token": "oracion",
         "start_offset": 5,
         "end_offset": 12,
         "type": "<ALPHANUM>",
         "position": 1
      },
      {
         "token": "oracion",
         "start_offset": 13,
         "end_offset": 20,
         "type": "<ALPHANUM>",
         "position": 2
      },
      {
         "token": "oracion",
         "start_offset": 21,
         "end_offset": 30,
         "type": "<ALPHANUM>",
         "position": 3
      }
   ]
}

Obtener los términos devueltos por el analizador standard

# Petición
curl -XPOST 'http://localhost:9200/_analyze?analyzer=standard&pretty' -d 'Oracion Oración Oraciones'

# Respuesta
{
   "tokens": [
      {
         "token": "oracion",
         "start_offset": 5,
         "end_offset": 12,
         "type": "<ALPHANUM>",
         "position": 1
      },
      {
         "token": "oración",
         "start_offset": 13,
         "end_offset": 20,
         "type": "<ALPHANUM>",
         "position": 2
      },
      {
         "token": "oraciones",
         "start_offset": 21,
         "end_offset": 30,
         "type": "<ALPHANUM>",
         "position": 3
      }
   ]
}

Obtener los términos devueltos por el analizador asociado a un campo de un documento ya mapeado

El analizador utilizado es el asociado al campo nombre_campo del índice nombre_indice.

# Petición
curl -XPOST 'http://localhost:9200/nombre_indice/_analyze?field=nombre_campo&pretty' -d 'Oracion Oración Oraciones'

elasticsearch Cookbook, indexar varios campos de un documento en un solo campo utilizando copy_to

Elasticsearch

Hay ocasiones donde necesitamos tener en 1 solo campo, el valor indexado y analizado de varios campos del documento. El ejemplo típico es el de un documento con los campos nombre, apellido1 y apellido2, donde además de esos campos, necesitamos tenerlos indexados y analizados en un solo campo, como por ejemplo nombre_apellidos, que no viene en el documento de origen.

Para esos casos tenemos el parámetro copy_to.

Creamos el índice

PUT user

Creamos el mapping utilizando copy_to

POST user/_mapping/profile
{
  "profile": {
    "properties": {
      "nombre": {
        "type": "string",
        "analyzer": "spanish",
        "copy_to": "nombre_apellidos"
      },
      "apellido1": {
        "type": "string",
        "analyzer": "spanish",
        "copy_to": "nombre_apellidos"
      },
      "apellido2": {
        "type": "string",
        "analyzer": "spanish",
        "copy_to": "nombre_apellidos"
      },
      "nombre_apellidos": {
        "type": "string",
        "analyzer": "spanish"
      }
    }
  }
}

indexamos unos documentos

POST user/profile/1 
{ "nombre":"Antonio","apellido1":"Garcia","apellido2":"Sanchez"}
POST user/profile/2
{ "nombre":"Jose Manuel","apellido1":"Gutierrez","apellido2":"Garcia"}
POST user/profile/3 
{ "nombre":"David","apellido1":"Sanchez","apellido2":"Ortiz"}

Realizamos unas búsquedas sobre el nuevo campo

POST user/profile/_search
{
  "query" : {
    "query_string": {
      "default_field": "nombre_apellidos",
      "analyze_wildcard": true,
      "query": "garcia"
    }
  }
}