2012-02-23 5 views
26

Я хочу использовать ElasticSearch для поиска имен файлов (а не содержимого файла). Поэтому мне нужно найти часть имени файла (точное совпадение, нечеткий поиск).Поиск имени файла с ElasticSearch

Пример:
У меня есть файлы со следующими именами:

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 

Теперь я хочу, чтобы искать 2012.01.13, чтобы получить первые два файла.
Поиск file или ile должен возвращать все имена файлов, кроме последнего.

Как я могу достичь этого с помощью ElasticSearch?

Это то, что я испытал, но он всегда возвращает нулевой результат:

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 

ответ

128

У вас есть различные проблемы с тем, что вы вставили:

1) Некорректное отображения

При создании индекса необходимо указать:

"mappings": { 
    "files": { 

Но ваш тип на самом деле file, а не files. Если вы проверили отображение, вы бы увидели, что сразу:

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) Неправильное определение анализатора

Вы указали lowercase Tokenizer но удаляет все, что не письмо (см docs), поэтому ваши номера полностью удаляются.

Вы можете проверить это с analyze API:

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 по поиску

Вы включаете свой Ngram токенов фильтр в обоих анализатор индекса и анализатора поиска. Это хорошо для анализатора индексов, потому что вы хотите индексировать ngrams. Но когда вы выполняете поиск, вы хотите выполнить поиск по полной строке, а не по каждой ngram.

Например, если индекс "abcd" с ngrams длиной от 1 до 4, вы будете в конечном итоге с этими маркерами:

a b c d ab bc cd abc bcd 

Но если вы будете искать "dcba" (которые не должны совпадать) и вас также анализировать условия поиска с ngrams, то на самом деле вы ищете на:

d c b a dc cb ba dbc cba 

Так a, b, c и d будет соответствовать!

Решение

Во-первых, вам нужно выбрать правильный анализатор. Ваши пользователи, вероятно, будут искать слова, цифры или даты, но они, вероятно, не ожидают ile в соответствии с file. Вместо этого, вероятно, будет более полезно использовать edge ngrams, который привяжет ngram к началу (или концу) каждого слова.

Кроме того, почему исключить docx и т. Д.? Неужели пользователь может захотеть выполнить поиск по типу файла?

Так позволяет разбить имя каждый файл на более мелкие лексемы, удаляя все, что не является буква или цифра (с помощью pattern tokenizer):

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

Тогда для анализатора индекса, мы будем также использовать край ngrams по каждому из этих лексем:

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 

Мы создаем индекс следующим образом:

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" 
      } 
     } 
     } 
    } 
} 
' 

Теперь проверить, что наши анализаторы работают правильно:

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 - кажется, работает правильно.Поэтому давайте добавим некоторые документы:

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" 

И попробуйте поиск:

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 
# } 

успеха!

#### UPDATE ####

я понял, что поиск 2012.01 будет соответствовать как 2012.01.12 и 2012.12.01 поэтому я попытался изменить запрос, чтобы использовать text phrase запрос вместо. Однако это не сработало. Оказывается, что краевой ngram-фильтр увеличивает количество позиций для каждой nграммы (хотя я бы подумал, что позиция каждой ngram будет такой же, как и для начала слова).

Проблема, упомянутая в пункте (3) выше, является проблемой только при использовании запроса query_string, field или text, который пытается совместить любой токен. Однако для запроса text_phrase он пытается сопоставить ВСЕ токены и в правильном порядке.

Чтобы продемонстрировать выпуск, индекс другой документ с другой датой:

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" 

И сделать такой же поиск, как описано выше:

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 
# } 

Первый результат имеет дату 2012.12.01, который ISN» t лучшее совпадение для 2012.01. Таким образом, чтобы соответствовать только то, что точную фразу, мы можем сделать:

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 
# } 

Или, если вы все еще хотите, чтобы соответствовать все 3 файла (так как пользователь может вспомнить некоторые из слов в имени файла, но в неправильном порядке) , вы можете запустить оба запроса, но увеличить значение имени файла, который находится в правильном порядке:

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 
# } 
+16

Вау, это не просто решение. Это учебник, который я искал: D THX – Biggie

+0

Большое спасибо за это, очень полезно! – Robin

+5

Это замечательный ответ. Где кнопка +20 на SO? – Ben

0

Я считаю, что это из-за Tokenizer используется ..

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

в нижнем регистре токенизатор расколов так что 2012.01.13 будет индексироваться как «2012», «01» и «13». Поиск строки «2012.01.13», очевидно, не будет соответствовать.

Одним из вариантов было бы добавить токенизацию в поиске. Поэтому поиск «2012.01.13» будет обозначаться до тех же токенов, что и в индексе, и он будет соответствовать. Это также удобно, так как тогда вам не нужно всегда вводить строчные запросы в коде.

Второй вариант - использовать токенизатор n-gram вместо фильтра. Это будет означать, что он будет игнорировать границы слов (и вы также получите «_»), однако у вас могут возникнуть проблемы с несоответствиями в случае, что, по-видимому, является причиной того, что вы добавили маркер нижнего регистра в первую очередь.

+0

к 1-й вариант: Я думал, что мой filename_analyzer уже будет использоваться при индексации и поиска, потому что я не в явном виде используйте index_analyzer/search_analyzer. К второму варианту: я попробовал это так. Но поиск имеет только результаты, если я окружаю ключевые слова с помощью '' * "', например: '' * 2012 * "'. Более того, '' * doc * "' находит оба doc-файла, но '' * .doc * "' находит только файл docx. Есть идеи? – Biggie

-1

У меня нет опыта работы с ES, но в Solr вам нужно будет указать тип поля как текст. Ваше поле относится к типу string вместо текст. Строковые поля не анализируются, а сохраняются и индексируются дословно. Дайте ему выстрел и посмотрите, работает ли он.

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

ES просто использует тип 'string', и они анализируются по умолчанию. Если вы хотите, чтобы они хранились дословно, вам нужно добавить '{" index ":" not_analyzed "}' к отображению – DrTech

Смежные вопросы