Подтвердить что ты не робот

Поиск имени файла с помощью 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
4b9b3361

Ответ 1

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

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

Вы можете проверить это с помощью анализа 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 при поиске

Вы включаете свой маркер токенов как в анализаторе индексов, так и в анализаторе поиска. Это прекрасно для анализатора индексов, потому что вы хотите индексировать 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. Вместо этого, вероятно, будет более полезно использовать краевые ngrams, которые привяжут ngram к началу (или концу) каждого слова.

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

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

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"

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

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, поэтому я попытался изменить запрос, чтобы вместо него использовать текстовую фразу. Однако это не сработало. Оказывается, что фильтр ngram edge увеличивает количество позиций для каждой 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
# }

Ответ 2

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

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

Ответ 3

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

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