lucene - multiple - query_string elasticsearch
Búsqueda de nombre de archivo con ElasticSearch (3)
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
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"
}
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 docs ), 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 ngrama.
Por ejemplo, si indicas "abcd"
con ngrams de longitud 1 a 4, terminarás con estos tokens:
a b c d 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:
d c b a 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 ngrama 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 ngrama 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 aumentar 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
# }