Búsqueda de nombres de archivo con ElasticSearch


Quiero usar ElasticSearch para buscar nombres de archivo (no el contenido del archivo). Por lo tanto, necesito encontrar una parte del nombre del archivo (coincidencia exacta, sin búsqueda difusa).

Ejemplo:
Tengo archivos con los siguientes nombres:

My_first_file_created_at_2012.01.13.doc
My_second_file_created_at_2012.01.13.pdf
Another file.txt
And_again_another_file.docx
foo.bar.txt

Ahora quiero buscar 2012.01.13 para obtener los dos primeros archivos.
Una búsqueda de file o ile debe devolver todos los nombres de archivo excepto el último.

¿Cómo puedo lograr eso con ElasticSearch?

Esto es lo que he probado, pero siempre devuelve cero resultados:

curl -X DELETE localhost:9200/files
curl -X PUT    localhost:9200/files -d '
{
  "settings" : {
    "index" : {
      "analysis" : {
        "analyzer" : {
          "filename_analyzer" : {
            "type" : "custom",
            "tokenizer" : "lowercase",
            "filter"    : ["filename_stop", "filename_ngram"]
          }
        },
        "filter" : {
          "filename_stop" : {
            "type" : "stop",
            "stopwords" : ["doc", "pdf", "docx"]
          },
          "filename_ngram" : {
            "type" : "nGram",
            "min_gram" : 3,
            "max_gram" : 255
          }
        }
      }
    }
  },

  "mappings": {
    "files": {
      "properties": {
        "filename": {
          "type": "string",
          "analyzer": "filename_analyzer"
        }
      }
    }
  }
}
'

curl -X POST "http://localhost:9200/files/file" -d '{ "filename" : "My_first_file_created_at_2012.01.13.doc" }'
curl -X POST "http://localhost:9200/files/file" -d '{ "filename" : "My_second_file_created_at_2012.01.13.pdf" }'
curl -X POST "http://localhost:9200/files/file" -d '{ "filename" : "Another file.txt" }'
curl -X POST "http://localhost:9200/files/file" -d '{ "filename" : "And_again_another_file.docx" }'
curl -X POST "http://localhost:9200/files/file" -d '{ "filename" : "foo.bar.txt" }'
curl -X POST "http://localhost:9200/files/_refresh"


FILES='
http://localhost:9200/files/_search?q=filename:2012.01.13
'

for file in ${FILES}
do
  echo; echo; echo ">>> ${file}"
  curl "${file}&pretty=true"
done
Author: user, 2012-02-24

3 answers

Tienes varios problemas con lo que pegaste:{[38]]}

1) Mapeo incorrecto

Al crear el índice, se especifica:

"mappings": {
    "files": {

Pero tu tipo es en realidad file, no files. Si usted comprueba la asignación, usted vería que inmediatamente:

curl -XGET 'http://127.0.0.1:9200/files/_mapping?pretty=1' 

# {
#    "files" : {
#       "files" : {
#          "properties" : {
#             "filename" : {
#                "type" : "string",
#                "analyzer" : "filename_analyzer"
#             }
#          }
#       },
#       "file" : {
#          "properties" : {
#             "filename" : {
#                "type" : "string"
#             }
#          }
#       }
#    }
# }

2) Definición incorrecta del analizador

Ha especificado el lowercase tokenizador, pero que elimina todo lo que no es una letra, (ver docs ), por lo que sus números están siendo completamente eliminado.

Puedes comprobar esto con la API de análisis :

curl -XGET 'http://127.0.0.1:9200/_analyze?pretty=1&text=My_file_2012.01.13.doc&tokenizer=lowercase' 

# {
#    "tokens" : [
#       {
#          "end_offset" : 2,
#          "position" : 1,
#          "start_offset" : 0,
#          "type" : "word",
#          "token" : "my"
#       },
#       {
#          "end_offset" : 7,
#          "position" : 2,
#          "start_offset" : 3,
#          "type" : "word",
#          "token" : "file"
#       },
#       {
#          "end_offset" : 22,
#          "position" : 3,
#          "start_offset" : 19,
#          "type" : "word",
#          "token" : "doc"
#       }
#    ]
# }

3) Ngrams on search

Incluye su filtro de token ngram tanto en el analizador de índices como en el analizador de búsqueda. Eso está bien para el analizador de índices, porque quieres que los ngrams sean indexados. Pero cuando buscas, quieres buscar en la cadena completa, no en cada ngram.

Por ejemplo, si indexas "abcd" con ngrams de longitud 1 a 4, terminarás con estas fichas:

a b c d ab bc cd abc bcd

Pero si busca en "dcba" (que no debería coincidir) y también analiza sus términos de búsqueda con ngrams, entonces realmente está buscando en:

d c b a dc cb ba dbc cba

So a,b,c y d coincidirá!

Solución

Primero, debe elegir el analizador correcto. Tus usuarios probablemente buscarán palabras, números o fechas, pero probablemente no esperarán que ile coincida con file. En su lugar, probablemente será más útil use ngrams de borde, que anclará el ngram al comienzo (o al final) de cada palabra.

También, ¿por qué excluir docx etc? Seguramente un usuario puede querer buscar en el tipo de archivo?

Así que dividamos cada nombre de archivo en tokens más pequeños eliminando cualquier cosa que no sea una letra o un número (usando el tokenizador de patrón ):

My_first_file_2012.01.13.doc
=> my first file 2012 01 13 doc

Luego, para el analizador de índices, también usaremos ngrams de borde en cada uno de esos tokens:

my     => m my
first  => f fi fir firs first
file   => f fi fil file
2012   => 2 20 201 201
01     => 0 01
13     => 1 13
doc    => d do doc

Creamos el índice como sigue:

curl -XPUT 'http://127.0.0.1:9200/files/?pretty=1'  -d '
{
   "settings" : {
      "analysis" : {
         "analyzer" : {
            "filename_search" : {
               "tokenizer" : "filename",
               "filter" : ["lowercase"]
            },
            "filename_index" : {
               "tokenizer" : "filename",
               "filter" : ["lowercase","edge_ngram"]
            }
         },
         "tokenizer" : {
            "filename" : {
               "pattern" : "[^\\p{L}\\d]+",
               "type" : "pattern"
            }
         },
         "filter" : {
            "edge_ngram" : {
               "side" : "front",
               "max_gram" : 20,
               "min_gram" : 1,
               "type" : "edgeNGram"
            }
         }
      }
   },
   "mappings" : {
      "file" : {
         "properties" : {
            "filename" : {
               "type" : "string",
               "search_analyzer" : "filename_search",
               "index_analyzer" : "filename_index"
            }
         }
      }
   }
}
'

Ahora, prueba que nuestros analizadores están funcionando correctamente:

Filename_search:

curl -XGET 'http://127.0.0.1:9200/files/_analyze?pretty=1&text=My_first_file_2012.01.13.doc&analyzer=filename_search' 
[results snipped]
"token" : "my"
"token" : "first"
"token" : "file"
"token" : "2012"
"token" : "01"
"token" : "13"
"token" : "doc"

Filename_index:

curl -XGET 'http://127.0.0.1:9200/files/_analyze?pretty=1&text=My_first_file_2012.01.13.doc&analyzer=filename_index' 
"token" : "m"
"token" : "my"
"token" : "f"
"token" : "fi"
"token" : "fir"
"token" : "firs"
"token" : "first"
"token" : "f"
"token" : "fi"
"token" : "fil"
"token" : "file"
"token" : "2"
"token" : "20"
"token" : "201"
"token" : "2012"
"token" : "0"
"token" : "01"
"token" : "1"
"token" : "13"
"token" : "d"
"token" : "do"
"token" : "doc"

OK - parece estar funcionando correctamente. Así que vamos a añadir algunos documentos:

curl -X POST "http://localhost:9200/files/file" -d '{ "filename" : "My_first_file_created_at_2012.01.13.doc" }'
curl -X POST "http://localhost:9200/files/file" -d '{ "filename" : "My_second_file_created_at_2012.01.13.pdf" }'
curl -X POST "http://localhost:9200/files/file" -d '{ "filename" : "Another file.txt" }'
curl -X POST "http://localhost:9200/files/file" -d '{ "filename" : "And_again_another_file.docx" }'
curl -X POST "http://localhost:9200/files/file" -d '{ "filename" : "foo.bar.txt" }'
curl -X POST "http://localhost:9200/files/_refresh"

E intenta una búsqueda:

curl -XGET 'http://127.0.0.1:9200/files/file/_search?pretty=1'  -d '
{
   "query" : {
      "text" : {
         "filename" : "2012.01"
      }
   }
}
'

# {
#    "hits" : {
#       "hits" : [
#          {
#             "_source" : {
#                "filename" : "My_second_file_created_at_2012.01.13.pdf"
#             },
#             "_score" : 0.06780553,
#             "_index" : "files",
#             "_id" : "PsDvfFCkT4yvJnlguxJrrQ",
#             "_type" : "file"
#          },
#          {
#             "_source" : {
#                "filename" : "My_first_file_created_at_2012.01.13.doc"
#             },
#             "_score" : 0.06780553,
#             "_index" : "files",
#             "_id" : "ER5RmyhATg-Eu92XNGRu-w",
#             "_type" : "file"
#          }
#       ],
#       "max_score" : 0.06780553,
#       "total" : 2
#    },
#    "timed_out" : false,
#    "_shards" : {
#       "failed" : 0,
#       "successful" : 5,
#       "total" : 5
#    },
#    "took" : 4
# }

Éxito!

#### ACTUALIZAR ####

Me di cuenta de que una búsqueda para 2012.01 coincidiría con 2012.01.12 y 2012.12.01 así que intenté cambiar la consulta para usar un text phrase consulta en su lugar. Sin embargo, esto no funcionó. Resulta que el filtro ngram de borde incrementa el conteo de posiciones para cada ngram (mientras que yo hubiera pensado que la posición de cada ngram sería la misma que para el comienzo de la palabra).

La cuestión mencionada en el punto 3) anterior es sólo un problema cuando se utiliza un query_string, field, o text consulta que intenta hacer coincidir CUALQUIER token. Sin embargo, para una consulta text_phrase, intenta hacer coincidir TODOS los tokens, y en el orden.

Para demostrar el problema, indexe otro documento con una fecha diferente:

curl -X POST "http://localhost:9200/files/file" -d '{ "filename" : "My_third_file_created_at_2012.12.01.doc" }'
curl -X POST "http://localhost:9200/files/_refresh"

Y hacer la misma búsqueda que la anterior:

curl -XGET 'http://127.0.0.1:9200/files/file/_search?pretty=1'  -d '
{
   "query" : {
      "text" : {
         "filename" : {
            "query" : "2012.01"
         }
      }
   }
}
'

# {
#    "hits" : {
#       "hits" : [
#          {
#             "_source" : {
#                "filename" : "My_third_file_created_at_2012.12.01.doc"
#             },
#             "_score" : 0.22097087,
#             "_index" : "files",
#             "_id" : "xmC51lIhTnWplOHADWJzaQ",
#             "_type" : "file"
#          },
#          {
#             "_source" : {
#                "filename" : "My_first_file_created_at_2012.01.13.doc"
#             },
#             "_score" : 0.13137488,
#             "_index" : "files",
#             "_id" : "ZUezxDgQTsuAaCTVL9IJgg",
#             "_type" : "file"
#          },
#          {
#             "_source" : {
#                "filename" : "My_second_file_created_at_2012.01.13.pdf"
#             },
#             "_score" : 0.13137488,
#             "_index" : "files",
#             "_id" : "XwLNnSlwSeyYtA2y64WuVw",
#             "_type" : "file"
#          }
#       ],
#       "max_score" : 0.22097087,
#       "total" : 3
#    },
#    "timed_out" : false,
#    "_shards" : {
#       "failed" : 0,
#       "successful" : 5,
#       "total" : 5
#    },
#    "took" : 5
# }

El primer resultado tiene una fecha 2012.12.01 que no es la mejor coincidencia para 2012.01. Así que para coincidir solo con esa frase exacta, podemos hacer:

curl -XGET 'http://127.0.0.1:9200/files/file/_search?pretty=1'  -d '
{
   "query" : {
      "text_phrase" : {
         "filename" : {
            "query" : "2012.01",
            "analyzer" : "filename_index"
         }
      }
   }
}
'

# {
#    "hits" : {
#       "hits" : [
#          {
#             "_source" : {
#                "filename" : "My_first_file_created_at_2012.01.13.doc"
#             },
#             "_score" : 0.55737644,
#             "_index" : "files",
#             "_id" : "ZUezxDgQTsuAaCTVL9IJgg",
#             "_type" : "file"
#          },
#          {
#             "_source" : {
#                "filename" : "My_second_file_created_at_2012.01.13.pdf"
#             },
#             "_score" : 0.55737644,
#             "_index" : "files",
#             "_id" : "XwLNnSlwSeyYtA2y64WuVw",
#             "_type" : "file"
#          }
#       ],
#       "max_score" : 0.55737644,
#       "total" : 2
#    },
#    "timed_out" : false,
#    "_shards" : {
#       "failed" : 0,
#       "successful" : 5,
#       "total" : 5
#    },
#    "took" : 7
# }

O, si todavía desea que coincidan los 3 archivos (porque el usuario puede recordar algunas de las palabras en el nombre del archivo, pero en el orden incorrecto), puede ejecutar ambas consultas, pero aumentar la importancia de el nombre del archivo que está en el orden correcto:

curl -XGET 'http://127.0.0.1:9200/files/file/_search?pretty=1'  -d '
{
   "query" : {
      "bool" : {
         "should" : [
            {
               "text_phrase" : {
                  "filename" : {
                     "boost" : 2,
                     "query" : "2012.01",
                     "analyzer" : "filename_index"
                  }
               }
            },
            {
               "text" : {
                  "filename" : "2012.01"
               }
            }
         ]
      }
   }
}
'

# [Fri Feb 24 16:31:02 2012] Response:
# {
#    "hits" : {
#       "hits" : [
#          {
#             "_source" : {
#                "filename" : "My_first_file_created_at_2012.01.13.doc"
#             },
#             "_score" : 0.56892186,
#             "_index" : "files",
#             "_id" : "ZUezxDgQTsuAaCTVL9IJgg",
#             "_type" : "file"
#          },
#          {
#             "_source" : {
#                "filename" : "My_second_file_created_at_2012.01.13.pdf"
#             },
#             "_score" : 0.56892186,
#             "_index" : "files",
#             "_id" : "XwLNnSlwSeyYtA2y64WuVw",
#             "_type" : "file"
#          },
#          {
#             "_source" : {
#                "filename" : "My_third_file_created_at_2012.12.01.doc"
#             },
#             "_score" : 0.012931341,
#             "_index" : "files",
#             "_id" : "xmC51lIhTnWplOHADWJzaQ",
#             "_type" : "file"
#          }
#       ],
#       "max_score" : 0.56892186,
#       "total" : 3
#    },
#    "timed_out" : false,
#    "_shards" : {
#       "failed" : 0,
#       "successful" : 5,
#       "total" : 5
#    },
#    "took" : 4
# }
 135
Author: DrTech,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-13 21:46:38

Creo que esto se debe al tokenizador que se está utilizando..

Http://www.elasticsearch.org/guide/reference/index-modules/analysis/lowercase-tokenizer.html

El tokenizador en minúsculas se divide en límites de palabras por lo que 2012.01.13 se indexará como "2012","01" y "13". Buscar la cadena" 2012.01.13 " obviamente no coincidirá.

Una opción sería añadir la tokenización en la búsqueda también. Por lo tanto, la búsqueda de "2012.01.13" se tokenizará hasta los mismos tokens que en el índice y coincidirá. Esto también es útil ya que entonces no necesita siempre minúsculas sus búsquedas en código.

La segunda opción sería usar un tokenizador n-gram en lugar del filtro. Esto significará que ignorará los límites de las palabras (y obtendrá los "_"'s también), sin embargo, puede tener problemas con los desajustes de casos, que es presumiblemente la razón por la que agregó el tokenizador en minúsculas en primer lugar.

 0
Author: Chris Rode,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-16 05:44:37

No tengo experiencia con ES, pero en Solr tendría que especificar el tipo de campo como texto. Su campo es de tipo string en lugar de texto. Los campos de cadena, no se analizan, sino que se almacenan e indexan textualmente. Inténtalo y mira si funciona.

properties": {
        "filename": {
          "type": "string",
          "analyzer": "filename_analyzer"
        }
 -2
Author: Mikos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-24 02:22:08