Solr не объединяет слова в запросе

У меня проблемы с Solr. Я хочу, чтобы он объединял слова в поисковом запросе. Например, я хочу иметь возможность искать «ссылку на данные» и находить документ, содержащий «ссылку на данные» (Google делает это — так почему это так сложно с Solr?).

Вот настройки индекса и анализатора запросов из schema.xml:

<fieldType name="text_en_splitting" class="solr.TextField" positionIncrementGap="100" autoGeneratePhraseQueries="true">
  <analyzer type="index">
    <tokenizer class="solr.WhitespaceTokenizerFactory"/>
    <filter class="solr.StopFilterFactory"
            ignoreCase="true"
            words="lang/stopwords_en.txt"
            />
    <filter class="solr.WordDelimiterFilterFactory"
            generateWordParts="1" 
            generateNumberParts="1" 
            catenateWords="0" 
            catenateNumbers="0" 
            catenateAll="0" 
            splitOnCaseChange="1"
            />
    <filter class="solr.LowerCaseFilterFactory"/>
    <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
    <filter class="solr.PorterStemFilterFactory"/>
  </analyzer>
  <analyzer type="query">
    <tokenizer class="solr.WhitespaceTokenizerFactory"/>
    <filter class="solr.SynonymFilterFactory" 
            synonyms="synonyms.txt" 
            ignoreCase="true" 
            expand="true"
            />
    <filter class="solr.StopFilterFactory"
            ignoreCase="true"
            words="lang/stopwords_en.txt"
            />
    <filter class="solr.WordDelimiterFilterFactory"
            generateWordParts="1" 
            generateNumberParts="1" 
            catenateWords="1" 
            catenateNumbers="0" 
            catenateAll="0" 
            splitOnCaseChange="1"
            />
    <filter class="solr.LowerCaseFilterFactory"/>
    <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
    <filter class="solr.PorterStemFilterFactory"/>
  </analyzer>
</fieldType>

Это вывод debugQuery при поиске «ссылки данных»:

"rawquerystring": "\"data link\"\n",
"querystring": "\"data link\"\n",
"parsedquery": "PhraseQuery(text:\"data link\")",
"parsedquery_toString": "text:\"data link\"",

Это вывод debugQuery, когда я ищу "связь данных" - это также не соответствует "ссылке данных", хотя кажется, что она есть в MultiPhraseQuery? Может кто-нибудь объяснить?

"rawquerystring": "\"data-link\"\n",
"querystring": "\"data-link\"\n",
"parsedquery": "MultiPhraseQuery(text:\"(data datalink) link\")",
"parsedquery_toString": "text:\"(data datalink) link\"",

person John Thompson    schedule 03.03.2015    source источник


Ответы (2)


Это очень сложный вопрос, но большая часть того, что здесь происходит, похоже, связана с фильтром WordDelimiterFilter и его аргументами generateWordParts и concatentateWords. При индексации вы создаете части, но не объединяете их, а при запросе вы делаете и то, и другое.

При индексировании

  1. "Ссылка на данные": это должно было быть проиндексировано как два отдельных слова, несмотря ни на что. WordDelimiterFilter не угадывает, что это уже два отдельных слова.
  2. «Data-Link»: неоднозначно и может состоять из одного или двух слов. Поскольку в вашем анализе индексации указано generateWordParts, это индексируется как "ссылка данных".

При поиске

  1. «Связь с данными»: это два отдельных слова, и, как и во время индекса, время запроса WordDelimiterFilter не меняет этого. Это по-прежнему два слова.

  2. «Data-Link»: анализ поиска настраивается иначе, чем анализ индекса, поэтому выполняется поиск и как «ссылка на данные», и как «ссылка на данные».

Результат?

Ваш поиск «ссылка данных» действительно ищет термин «ссылка данных», но значения, которые вы индексируете, будут проиндексированы как «ссылка данных», только если они изначально написаны таким образом, потому что вы никогда не активируете concatenateWords, когда индексация.

Одна вещь, которую вы можете сделать, это выбрать concatenateWords или generateWordParts и активировать одно или другое для поля как при индексировании, так и при поиске. Другой вариант, который я выбрал, — использовать <copyField/> в schema.xml для копирования значения между полем, в котором генерируются части слова, и полем, в котором слова объединяются. По крайней мере, я обнаружил, что попытка выполнить как конкатенацию, так и части в одном поле приводит к хаосу при поиске фраз, поскольку номера позиций отдельных терминов выходят из строя, когда термины не могут быть четко подсчитаны.

Но если вы надеетесь получить документ, в котором слово ссылка на данные можно найти при поиске ссылка на данные, то WordDelimiterFilter не поможет, так как никогда не будет учитывать ссылка данных в качестве кандидата на роль двух слов или ссылка данных в качестве кандидата на роль одного слова. Его особенность заключается в неоднозначных обстоятельствах, вызванных такими вещами, как дефисы и апострофы.

Вместо этого вам может потребоваться создать собственный логический запрос. (Логические операторы работают в обработчике запросов Standard/Lucene и в обработчике запросов Extended-Dismax, но не в обработчике запросов Dismax.) Этот тип запроса может быстро усложниться, но что-то вроде (+data +link) OR datalink является хорошим первым шагом. Если включены три термина, становится трудно понять, какие из них потенциально могут быть объединены, поэтому в игру может вступить такой запрос, как datalinkcisco OR (+datalink +cisco) OR (+data +linkcisco) OR (+data +link +cisco). (И представьте себе его с четырьмя терминами!)

Это становится очень сложным, очень быстро, и если есть только несколько случаев терминов, которые могут быть объединены только иногда, вы можете вместо этого попробовать SynonymFilter. Это позволяет создать файл синонимов с такими правилами, как:

data link => datalink

Таким образом, все экземпляры «канала данных» будут индексироваться или запрашиваться (как настроено) как «канал данных». Это может нормализовать определенные условия поиска, не открывая банку с червями, когда мы не доверяем пользователю, чтобы он вообще знал, где находятся пробелы.

SynonymFilters могут быть полезны и для других целей. Например, если вы отфильтруете пунктуацию из поискового индекса, «C++» и «C» могут оказаться эквивалентными Solr. С правилом синонимов:

C++ => cplusplus

это заканчивается совершенно отлично. (Конечно, ни один реальный человек никогда не будет искать cplusplus, но если вы примените такое же преобразование к "C++" в своих проиндексированных документах и в запросах ваших пользователей, то они никогда не узнают, что "cplusplus " — это значение, которое действительно соответствует совпадению.)

person frances    schedule 03.03.2015
comment
Ваш поиск data-link действительно ищет термин datalink -- на самом деле, он не находит datalink. Попробуй сам. Я думаю, проблема в том, что этот многофразовый запрос, который он генерирует, "parsedquery": "MultiPhraseQuery(text:\"(data datalink) link\")" не тот, который он должен генерировать. Когда я ищу канал данных, он попадает только в канал данных, а не в канал данных. - person John Thompson; 05.03.2015
comment
Да, я мог бы быть более откровенным. Поиск содержит ссылку на данные. Возвращает ли он на самом деле результат, содержащий этот термин, — это другой вопрос, который может зависеть от других факторов в конфигурации вашего запроса. Возможно, это звучит как уловка, но это правда. И вы действительно не предоставили достаточно информации о конфигурации вашего запроса, чтобы я мог знать наверняка. Этот запрос должен работать, если вы ослабите критерии соответствия, но я бы не стал его рекомендовать. Вы можете сделать поиск по схеме ИЛИ с помощью q.op=OR (стандартный анализатор запросов) или mm=1 (dismax/edismax). - person frances; 06.03.2015
comment
В общем, применять в анализаторе запросов как concatenateWords, так и generateWordParts, вероятно, плохая идея. Гораздо более распространено использовать оба вместе в анализаторе индекса, если оба необходимы, хотя я обнаружил, что это может быть опасно и при поиске по фразе. Для чистого и гибкого поиска фраз по терминам, которые могут быть разделены дефисом, а могут и не быть, лучшей моделью, которую я нашел, является описанная выше модель, в которой используется copyField. - person frances; 06.03.2015
comment
Вы также можете просмотреть этот вопрос stackoverflow.com/questions/23492868. - person frances; 06.03.2015
comment
Можете ли вы объяснить решение copyField более подробно? Как сделать запрос к обоим полям одновременно, чтобы один анализатор запросов генерировал части слов, а другой объединял текст? - person John Thompson; 06.03.2015
comment
Чтобы создать два разных поля с разными анализаторами, вам нужны два разных блока fieldType. Тот, который у вас есть, называется text_en_splitting, поэтому другой может быть text_en_combining. Вы создаете два поля, используя разные fieldType для каждого, а затем запись <copyField source="field1" dest="field2"/> в schema.xml гарантирует, что данные, которые вы вставляете в field1, попадут в оба. - person frances; 06.03.2015
comment
Чтобы искать их вместе, это зависит от того, какой анализатор запросов вы используете, хотя проще всего использовать dismax или расширенный dismax. С помощью этих синтаксических анализаторов вы можете указать аргумент qf, в котором перечислены поля, которые вы хотите найти, например. qf=field1 field2, а затем ваш основной запрос не указывает поле, например. q="data-link". Синтаксический анализатор разбивает запрос и применяет его к каждому полю в qf. - person frances; 06.03.2015
comment
Если вы предпочитаете использовать стандартный анализатор запросов, вам нужно предоставить подробную разбивку самостоятельно, например: `q=field1:data-link ИЛИ field2:data-link. - person frances; 06.03.2015
comment
И мне нужно только установить stored="true" для field1? Я делаю подсветку кликов, поэтому хочу убедиться, что это работает для обоих полей. - person John Thompson; 06.03.2015
comment
О, но когда вы сказали, что гораздо чаще использовать оба вместе в анализаторе индексов, если оба необходимы, хотя я обнаружил, что это может быть опасно и при поиске по фразе. -- Вы имеете в виду, что если я не хочу искажать поиск фраз, два отдельных поля, которые я использую, должны использовать stored="true" по отдельности? Это удвоило бы размер моего индекса, не так ли? Кстати, все это начинает звучать как реальный недостаток Solr — я не могу поверить, что у этой поисковой системы нет лучшего способа поиска фраз, которые могут появляться или не появляться с составными словами в тексте. - person John Thompson; 06.03.2015
comment
Я полагаю, что если вы делаете подсветку хитов, вам потребуется сохранить оба поля. Вы можете проиндексировать эти два поля и сохранить третье отображаемое поле. Вам придется использовать регулярное выражение, чтобы применить собственную подсветку, что может усложнить работу с такими вещами, как выведение... вероятно, оно того не стоит. В конце концов, это выбор, который вы должны сделать. Для самого большого индекса, которым я сейчас управляю (по размеру хранилища, а не по количеству записей), у меня есть все текстовые поля, проиндексированные четыре раза в разных полях. - person frances; 06.03.2015
comment
Другой способ — выбрать один метод индексации, максимально приближенный к желаемому поведению. Вы можете индексировать как с generateWordParts, так и с concatenateWords, но искать только с одним или другим. Проблема StackOverflow, о которой я упоминал выше, связана с тем, как это может запутать поиск. Идея заключалась в том, что трудно сказать, является ли слово вторым или третьим словом во фразе, если предыдущий термин может состоять из одного или двух слов. Это недостаток, который влияет только на поиск, который включает как поиск по фразе, так и записи с терминами через дефис, поэтому он может быть не слишком частым. - person frances; 06.03.2015

Чтобы разрешить совпадение data link и datalink, вы должны добавить ShingleFilter в свой индексатор и цепочку запросов:

https://cwiki.apache.org/confluence/display/solr/Filter+Descriptions#FilterDescriptions-ShingleFilter

Вы можете комбинировать это с WordDelimiterFilter.

Чтобы уменьшить количество сгенерированных, но, вероятно, бесполезных токенов, вы можете добавить LengthFilter к min=2 и некоторому разумному максимальному значению. Например, WDF мог выдать из "iPhone6s": "i", "Phone", "6", "s" (и многое другое в зависимости от конфига).

Кроме того, RemoveDuplicateTokensFilter может быть добавлен последним в этой цепочке:

https://cwiki.apache.org/confluence/display/solr/Filter+Descriptions#FilterDescriptions-RemoveDuplicatesTokenFilter

person Risadinha    schedule 12.07.2016