Recherche de nom de fichier avec ElasticSearch
Je veux utiliser ElasticSearch pour rechercher des noms de fichiers (pas le contenu du fichier). Donc j'ai besoin de trouver une partie du nom de fichier (correspondance exacte, pas de recherche floue).
Exemple:
J'ai des fichiers avec les noms suivants:
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
Maintenant, je veux rechercher 2012.01.13
pour obtenir les deux premiers fichiers.
Une recherche pour file
ou ile
devrait renvoyer tous les noms de fichiers sauf le dernier.
Comment puis-je accomplir cela avec ElasticSearch?
C'est ce que j'ai testé, mais toujours renvoie zéro résultat:
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
3 réponses
Vous avez divers problèmes avec ce que vous avez collé:
1) mappage Incorrect
Lors de la création de l'index, vous spécifiez:
"mappings": {
"files": {
Mais votre type est en fait file
, Pas files
. Si vous avez vérifié le mappage, vous le verrez immédiatement:
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) définition incorrecte de l'analyseur
Vous avez spécifié le lowercase
tokenizer mais cela supprime tout ce qui n'est pas une lettre, (voir docs ), donc vos numéros sont complètement enlevé.
Vous pouvez vérifier cela avec l'API analyze :
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 sur la recherche de
Vous incluez votre filtre de jeton ngram dans l'analyseur d'index et l'analyseur de recherche. C'est bien pour l'analyseur d'index, car vous voulez que les ngrams soient indexés. Mais lorsque vous recherchez, vous voulez rechercher sur la chaîne complète, pas sur chaque ngram.
Par exemple, si vous indexez "abcd"
avec des ngrams de longueur 1 à 4, vous finirez avec ces jetons:
a b c d ab bc cd abc bcd
Mais si vous recherchez sur "dcba"
(ce qui ne devrait pas correspondre) et que vous analysez également vos termes de recherche avec ngrams, alors vous recherchez réellement sur:
d c b a dc cb ba dbc cba
, de Sorte a
,b
,c
et d
trouvera!
Solution
Tout d'abord, vous devez choisir le bon analyseur. Vos utilisateurs rechercheront probablement des mots, des chiffres ou des dates, mais ils ne s'attendront probablement pas à ce que ile
corresponde à file
. Au lieu de cela, il sera probablement plus utile à utilisez edge ngrams , qui ancrera le ngram au début (ou à la fin) de chaque mot.
Aussi, pourquoi exclure docx
, etc? Sûrement un utilisateur peut bien vouloir rechercher sur le type de fichier?
Permet donc de diviser chaque nom de fichier en jetons plus petits en supprimant tout ce qui n'est pas une lettre ou un nombre (en utilisant le tokenizer pattern):
My_first_file_2012.01.13.doc
=> my first file 2012 01 13 doc
Ensuite, pour l'analyseur d'index, nous utiliserons également des ngrams edge sur chacun de ces jetons:
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
Nous créons l'index comme suit:
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"
}
}
}
}
}
'
Maintenant, testez que nos analyseurs fonctionnent correctement:
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"
Nom_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 - semble fonctionner correctement. Ajoutons donc quelques docs:
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"
Et essayez une recherche:
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
# }
Succès!
#### mise à JOUR ####
J'ai réalisé qu'une recherche pour 2012.01
correspondrait à la fois 2012.01.12
et 2012.12.01
alors j'ai essayé de changer la requête pour utiliser un phrase de texte requête à la place. Toutefois, cela n'a pas fonctionné. Il s'avère que le bord ngram filtre incrémente la position de chaque décompte ngram (alors que j'aurais pensé que la position de chaque ngram serait le même que pour le début du mot).
Le problème mentionné dans le point (3) ci-dessus n'est qu'un problème lors de l'utilisation d'un query_string
, field
, ou text
requête qui essaie de faire correspondre n'IMPORTE quel jeton. Cependant, pour une requête text_phrase
, elle essaie de faire correspondre tous les jetons, et dans le bon ordre.
Pour démontrer le problème, indexez un autre document avec une date différente:
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"
Et faites la même recherche que ci-dessus:
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
# }
Le premier résultat a une date 2012.12.01
qui n'est pas la meilleure correspondance pour 2012.01
. Donc, pour ne correspondre qu'à cette phrase exacte, nous pouvons faire:
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
# }
Ou, si vous voulez toujours faire correspondre les 3 fichiers (parce que l'utilisateur peut se souvenir de certains des mots dans le nom de fichier, mais dans le mauvais ordre), vous pouvez exécuter les deux requêtes mais augmenter l'importance de le nom du fichier qui est dans le bon ordre:
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
# }
Je crois que c'est à cause du tokenizer utilisé..
Http://www.elasticsearch.org/guide/reference/index-modules/analysis/lowercase-tokenizer.html
Le tokenizer en minuscules se divise sur les limites des mots de sorte que 2012.01.13 sera indexé comme "2012", "01"et " 13". La recherche de la chaîne "2012.01.13" ne correspondra évidemment pas.
Une option serait d'ajouter la tokenisation sur la recherche ainsi. Par conséquent, la recherche de "2012.01.13" sera tokenisée jusqu'à les mêmes jetons que dans l'index et il correspondra. Ceci est également pratique car vous n'avez pas besoin de toujours minuscules vos recherches dans le code.
La deuxième option serait d'utiliser un tokenizer n-gram au lieu du filtre. Cela signifie qu'il ignorera les limites des mots (et vous obtiendrez également les"_"), mais vous pourriez avoir des problèmes avec les discordances de cas, ce qui est probablement la raison pour laquelle vous avez ajouté le tokenizer en minuscules en premier lieu.
Je n'ai aucune expérience avec ES, mais dans Solr, vous devez spécifier le type de champ en tant que texte. Votre champ est de type string au lieu de text. Champs de chaîne, ne sont pas analysés, mais stockés et indexés verbatim. Donner un coup de feu et voir si cela fonctionne.
properties": {
"filename": {
"type": "string",
"analyzer": "filename_analyzer"
}