Поиск по имени файла с ElasticSearch - PullRequest
28 голосов
/ 24 февраля 2012

Я хочу использовать 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

Ответы [ 3 ]

138 голосов
/ 24 февраля 2012

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

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, но он ничего не удаляетэто не буква (см. документы ), поэтому ваши номера полностью удаляются.

Вы можете проверить это с помощью анализа 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" с ngram длиной от 1 до 4, вы получите следующие токены:

a b c d ab bc cd abc bcd

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

d c b a dc cb ba dbc cba

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

Решение

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

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

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

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

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

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"

ОК - похоже, работает правильно.Итак, давайте добавим несколько документов:

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

Успех!

#### ОБНОВЛЕНИЕ ####

Я понял, что поиск по 2012.01 будет соответствовать как 2012.01.12, так и 2012.12.01, поэтому я попытался изменить запрос, чтобы вместо него использовать текстовую фразу .Однако это не сработало.Оказывается, фильтр грамм ngram увеличивает счетчик позиций для каждой ngram (хотя я бы подумал, что позиция каждой 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, которая не является лучшим соответствием для 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
# }
0 голосов
/ 24 февраля 2012

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

properties": {
        "filename": {
          "type": "string",
          "analyzer": "filename_analyzer"
        }
0 голосов
/ 24 февраля 2012

Я полагаю, это из-за того, что токенизатор используется ..

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-граммы вместо фильтра. Это будет означать, что он будет игнорировать границы слов (и вы также получите "_"), однако у вас могут возникнуть проблемы с несоответствиями регистра, что, вероятно, является причиной, по которой вы добавили токенайзер нижнего регистра. *

...