Búsqueda de nombre de archivo con ElasticSearch

Deseo usar ElasticSearch para buscar nombres de archivo (no el contenido del archivo). Por lo tanto, necesito encontrar una parte del nombre de archivo (coincidencia exacta, búsqueda no 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 debería 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 

Tienes varios problemas con lo que pegaste:

1) mapeo incorrecto

Al crear el índice, usted especifica:

 "mappings": { "files": { 

Pero tu tipo es en realidad file , no files . Si marcó la asignación, lo verá de inmediato:

 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 tokenizador en lowercase pero eso elimina cualquier cosa que no sea una letra (ver documentos ), por lo que sus números se eliminan por completo.

Puede verificar 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 en la búsqueda

Incluye el filtro de token de ngram tanto en el analizador de índice como en el analizador de búsqueda. Eso está bien para el analizador de índices, porque quiere que los ngrams se indexen. Pero cuando busca, desea buscar en la cadena completa, no en cada ngtwig.

Por ejemplo, si indicas "abcd" con ngrams de longitud 1 a 4, terminarás con estos tokens:

 abcd ab bc cd abc bcd 

Pero si busca en "dcba" (que no debe coincidir) y también analiza los términos de búsqueda con ngrams, entonces en realidad está buscando:

 dcba dc cb ba dbc cba 

¡Entonces a , b , d coincidirán!

Solución

Primero, debes elegir el analizador correcto. Sus usuarios probablemente buscarán palabras, números o fechas, pero probablemente no esperen que el file coincida. En cambio, probablemente sea más útil usar ngramos de borde , que anclarán el ngtwig al inicio (o final) de cada palabra.

Además, ¿por qué excluir docx etc.? Seguramente un usuario puede querer buscar en el tipo de archivo?

Así que desglosemos cada nombre de archivo en fichas más pequeñas quitando todo lo que no sea una letra o un número (usando el tokenizador de patrones ):

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

Luego, para el analizador de índices, también utilizaremos ngulos 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 de la siguiente manera:

 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, compruebe que nuestros analizadores funcionan 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 agreguemos 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" 

Y prueba 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!

#### ACTUALIZACIÓN ####

Me di cuenta de que una búsqueda para 2012.01 coincidiría tanto con 2012.01.12 como con 2012.12.01 así que traté de cambiar la consulta para usar una consulta de frase de texto . Sin embargo, esto no funcionó. Resulta que el filtro de borde de ngulo incrementa el recuento de posiciones para cada ngram (mientras que yo hubiera pensado que la posición de cada ngtwig sería la misma que para el comienzo de la palabra).

El problema mencionado en el punto (3) anterior es solo un problema cuando se utiliza una query_string , field o consulta de text que intenta hacer coincidir CUALQUIER token. Sin embargo, para una consulta text_phrase , intenta hacer coincidir TODOS los tokens, y en el orden correcto.

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 haz la misma búsqueda que arriba:

 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 . Entonces, para hacer coincidir solo 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 bien, si aún desea hacer coincidir 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 boost la importancia del nombre de 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 # } 

Creo que esto se debe a que se usa el tokenizer …

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

El tokenizador en minúsculas se divide en los límites de las palabras, por lo que 2012.01.13 se indexará como “2012”, “01” y “13”. La búsqueda de la cadena “2012.01.13” obviamente no coincidirá.

Una opción sería agregar la tokenización en la búsqueda también. Por lo tanto, la búsqueda de “2012.01.13” se convertirá en tokens a los mismos símbolos que en el índice y coincidirá. Esto también es útil, ya que no necesita siempre minúsculas de sus búsquedas en el código.

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

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

 properties": { "filename": { "type": "string", "analyzer": "filename_analyzer" }