La recherche Full Text avec Solr

Configurer un moteur de recherche performant à l'aide d'Apache Lucene/Solr et Apache Tomcat
Solr


précédentsommairesuivant

VIII. Un schéma d'exemple : index de livres au format poche

VIII-A. Cahier des charges

Notre index permettra de rechercher des livres en anglais ou en français d'après les données "texte" suivantes : titre, nom de série, synopsis, nom d'auteur.

Les autres valeurs dont nous disposons permettront de définir des filtres pour préciser la recherche ou bien d'afficher des informations élémentaires (image, lien).

Notre application proposera les situations suivantes :
  • affichage des détails d'un livre ;
  • auto complétion ;
  • recherche avec traitements spécifiques à l'Anglais, surlignement des résultats et proposition de corrections ;
  • recherche avec traitements spécifiques au Français, surlignement des résultats et proposition de corrections.

Information légale :
Pour préparer cet exemple, je me suis appuyé sur les écrits d'Ilona Andrews. Leurs travaux sont protégés par le droit d'auteur (loi américaine). J'ai obtenu l'autorisation d'utiliser les extraits présentés ici. Si vous souhaitez les utiliser dans vos propres publications, vous devrez faire de même.

VIII-B. solrconfig.xml (composants)

Fichier solrconfig.xml de départ :
Sélectionnez
<?xml version="1.0" encoding="UTF-8" ?>
<config>
    <abortOnConfigurationError>${config.abortOnConfigurationError}</abortOnConfigurationError>
    <dataDir>${config.dataDir}</dataDir>

    <requestHandler name="standard" class="solr.SearchHandler" default="true"/>
</config>

D'après notre cahier des charges, nous allons devoir dresser la liste des composants non standards que nous allons utiliser, et plus particulièrement ceux qui ont un impact sur notre Schéma.

affichage des détails d'un livre :
  • solr.SearchHandler et solr.MoreLikeThisHandler peuvent convenir, et ils sont chargés par défaut ;
  • aucun composant supplémentaire n'est requis.
auto complétion lors de la recherche :
  • les composants les mieux adaptés pour cela sont solr.TermsComponent et Suggester (une extension de solr.SpellCheckComponent), or ils ne sont pas chargés par défaut ;
  • malheureusement, les implémentations actuelles de Suggester utilisent un dictionnaire en mémoire (non conservé sur disque) qu'il faut penser à reconstruire après chaque reload du noyau ;
  • notre choix se portera sur le composant le plus simple à utiliser : solr.TermsComponent.
recherche avec propositions de corrections :
  • le composant solr.SpellCheckComponent est requis ;
  • les champs concernés doivent subir très peu de traitements lors de d'analyse.
recherche avec surlignement des résultats :
  • le composant solr.HighlightComponent est requis ;
  • les champs concernés dans schema.xml ont besoin des valeurs "stored", "termVectors", "termPositions" et "termOffsets" définies à "true".
Ajout des composants requis par notre application :
Sélectionnez
    <searchComponent name="terms" class="solr.TermsComponent"/>

    <searchComponent name="spellCheck" class="solr.SpellCheckComponent">
        <lst name="spellchecker">
            <str name="name">spellcheckEn</str>
            <str name="field">lwAllEn</str>
            <str name="queryAnalyzerFieldType">lowerText</str>
            <str name="spellcheckIndexDir">./spellcheckEn</str>
            <str name="buildOnOptimize">${config.dictionaries.buildOnOptimize}</str>
        </lst>
        <lst name="spellchecker">
            <str name="name">spellcheckFr</str>
            <str name="field">lwAllFr</str>
            <str name="queryAnalyzerFieldType">lowerText</str>
            <str name="spellcheckIndexDir">./spellcheckFr</str>
            <str name="buildOnOptimize">${config.dictionaries.buildOnOptimize}</str>
        </lst>
    </searchComponent>
Personnalisation facultative de solr.HighlightComponent :
Sélectionnez
    <searchComponent name="highlight" class="solr.HighlightComponent">
        <highlighting>
            <formatter name="html" class="solr.highlight.HtmlFormatter" default="true">
                <lst name="defaults">
                    <str name="hl.simple.pre"><![CDATA[[hl]]]></str>
                    <str name="hl.simple.post"><![CDATA[[/hl]]]></str>
                </lst>
            </formatter>
            <fragmentsBuilder name="colored" default="true" class="solr.highlight.ScoreOrderFragmentsBuilder">
                <lst name="defaults">
                    <str name="hl.tag.pre"><![CDATA[
                        [hl=yellow],[hl=lawgreen],[hl=aquamarine]
                        ,[hl=magenta],[hl=palegreen],[hl=coral]
                        ,[hl=wheat],[hl=khaki],[hl=lime],[hl=deepskyblue]
                    ]]></str>
                    <str name="hl.tag.post"><![CDATA[[/hl]]]></str>
                </lst>
            </fragmentsBuilder>
        </highlighting>
    </searchComponent>

Le composant spellCheck a une dépendance sur certains éléments de schema.xml : les champs "lwAllEn" et "lwAllFr", ainsi que le type "lowerText" qui leur est appliqué. Afin que les fonctionnalités de correction orthographique soient efficaces, ce type de données doit subir peu de traitements au moment de l'analyse. Idéalement, il faudrait nous contenter d'un tokenizer permettant d'identifier les mots, et de filtres pour supprimer la ponctuation et pour normaliser le texte. La réduction (stemming) et les autres manipulations complexes du texte ne sont pas souhaitables ici.

Dans le fichier solrconfig.xml, les nœuds <searchComponent> sont positionnés à l'intérieur du nœud <config>.

VIII-C. schema.xml

Les situations gérées par notre application supposent certains types et certains attributs.

Nous allons créer les types suivants :
  • rawText : prévu pour l'affichage ;
  • lowerText : pour le bon fonctionnement la correction orthographique ;
  • grammedText : prévu pour l'auto complétion ;
  • stemmedEnglishText : prévu pour la recherche en langue anglaise ;
  • stemmedFrenchText : prévu pour la recherche en langue française.

Dictionnaire des données et variantes de chaque champ :

champ type (variantes) stored indexed required multiValued
isbn13 string true   true  
createdAt tdate true true true  
copyrightYear tint true true true  
nbPages tint true true    
nbInSeries tint true true    
urlEn string true     true
urlFr string true     true
coverImageEn string true     true
coverImageFr string true     true
titleEn rawText (grammedText, stemmedEnglishText) true true    
titleFr rawText (grammedText, stemmedFrenchText) true true    
seriesEn rawText (grammedText, stemmedEnglishText) true true    
seriesFr rawText (grammedText, stemmedFrenchText) true true    
synopsisEn rawText (grammedText, stemmedEnglishText) true true   true
synopsisFr rawText (grammedText, stemmedFrenchText) true true   true
authorNames rawText (stemmedEnglishText, stemmedFrenchText) true true true true
originalCoverArtBy rawText true true   true
originalCoverDesignBy rawText true true   true
originalInteriorTextDesignBy rawText true true   true
editors rawText true true true true
allEn rawText (grammedText, stemmedEnglishText)       true
allFr rawText (grammedText, stemmedFrenchText)       true

Fichier final : conf/schema.xml

Une fois le fichier schema.xml en place, vous pouvez recharger votre noyau afin de vérifier que tout s'est bien passé.

http://localhost:8080/solr/admin/cores?core=livres&action=reload

Un "status" de zéro est ok :
Sélectionnez
<?xml version="1.0" encoding="UTF-8"?>
<response>
    <lst name="responseHeader">
        <int name="status">0</int>
        <int name="QTime">490</int>
    </lst>
</response>

VIII-D. Envoi des données au noyau

Pour remplir notre noyau avec des données, nous allons préparer un gabarit du document XML final. Il nous restera alors à y copier/coller les données.

Structure XML pour remplir notre index :
Sélectionnez
<add>
    <doc>
        <field name="isbn13"></field>
        <field name="copyrightYear"></field>
        <field name="nbPages"></field>
        <field name="nbInSeries"></field>
        <field name="urlEn"></field>
        <field name="urlEn"></field>
        <field name="urlFr"></field>
        <field name="urlFr"></field>
        <field name="coverImageEn"></field>
        <field name="coverImageFr"></field>
        <field name="titleEn"></field>
        <field name="titleFr"></field>
        <field name="authorNames"></field>
        <field name="authorNames">/field>
        <field name="seriesEn"></field>
        <field name="seriesFr"></field>
        <field name="editors"></field>
        <field name="synopsisEn"></field>
        <field name="synopsisEn"></field>
        <field name="synopsisFr"></field>
        <field name="originalCoverArtBy"></field>
        <field name="originalCoverDesignBy"></field>
        <field name="originalInteriorTextDesignBy"></field>
    </doc>
</add>

Fichier final : livres.xml

Une fois le fichier XML complet, vous pouvez l'envoyer à Solr par le moyen que vous avez choisi (cURL, PHP, etc.). Pour cela, référez-vous au chapitre V. Maintenance des données d'un index. Après envoi de votre document, attendez quelques secondes que Solr termine ses traitements (sauf si waitSearcher était activé, en ce cas vous n'avez pas besoin d'attendre) puis vous pourrez commencer à interroger le noyau.

Si Solr répond par un code HTTP 400, c'est probablement parce que votre document XML ne correspond pas au schéma de données. La raison exacte de l'erreur est précisée dans la balise <h1> de la réponse HTTP, rédigée au format HTML.

VIII-E. Interrogation du noyau

Voici une requête avec un mot volontairement incomplet, permettant de démontrer les capacités de racinisation de notre application. Le résultat n'a pas été identifié par une recherche partielle sur le mot "dominates", quoique cela aurait pu être le cas si nous avions utilisé l'un des champs gr* (grammed) pour la recherche, mais bien parce que le lemme (stem) de ce mot est "domin".

highlighting
Image 13 : Démonstration de recherche avec highlighting + spellcheck

Nous allons préparer deux types de requêtes : la recherche de documents à partir d'une chaîne saisie par l'internaute, puis l'affichage d'un document à partir de son ID.

L'exercice consiste à déterminer les champs affectés par la query string, les facettes à renvoyer, quels champs utiliser pour le highlighting, etc.

Pour le prototypage de nos quatre requestHandlers spécifiques (recherche + affichage, chacun dans deux langues), je vous propose la configuration suivante du requestHandler par défaut :

solrconfig.xml
Sélectionnez
...
<requestHandler name="standard" class="solr.SearchHandler" default="true">
    <lst name="defaults">
        <bool name="indent">on</bool>
        <str name="echoParams">explicit</str>
        <str name="wt">xml</str><!-- Vous pouvez le modifier à json, php... -->
    </lst>
</requestHandler>
...

Pour des raisons pratiques d'affichage de cet article, je vais découper les requêtes HTTP en plusieurs lignes (un paramètre par ligne). Bien entendu, pour les exécuter, vous devez les coller en une seule ligne dans votre navigateur :

Voyons maintenant une recherche minimaliste que nous allons améliorer peu à peu (req-1.0) :

Requête :
Sélectionnez
http://localhost:8080/solr/livres/select?
fl=score%20isbn13%20copyrightYear%20nbPages%20nbInSeries%20authorNames%20titleEn%20seriesEn
&sort=score%20desc
&q=stAllEn:magic
Résultat :
TéléchargerCacherSélectionnez

Nous allons nous attacher à obtenir un maximum d'informations dans le résultat de notre requête.

VIII-E-1. Conseils pour bien suivre le déroulement de cet exemple

Le principe que j'utilise pour profiler un requestHandler Solr est de commencer par une recherche puis d'alterner les paramètres et leurs valeurs jusqu'à trouver une combinaison qui fonctionne. La recherche de départ devrait être générique afin de renvoyer un maximum de résultats, mais vous pouvez aussi le faire avec des cas particuliers pour observer le comportement de votre noyau dans toutes les situations.

Lors du développement de cet exemple, je vais utiliser "magic" comme chaîne de recherche.

Je vous recommande fortement de faire vous-même des variantes de chacune des requêtes que je propose à la suite, de les numéroter et d'en conserver le résultat. Vous n'avez pas besoin de conserver les requêtes elles-mêmes, car Solr en rappelle les paramètres et leurs valeurs lorsqu'il vous envoie la réponse.

VIII-E-2. Recherche à partir d'une chaîne saisie par l'utilisateur

VIII-E-2-a. Découpage de la chaîne saisie : solr.SearchHandler

Nous avons déjà configuré le découpage du texte dans schema.xml, mais il s'agit ici de choisir la manière dont Solr découpe le paramètre q. En effet, suivant le plugin choisi, Solr met à disposition des paramètres différents.

Solr dispose de deux méthodes principales pour effectuer une recherche : Lucene et DisMax. Vous pouvez changer de l'une à l'autre par l'intermédiaire du paramètre defType, qui indique à Solr quel plugin il doit utiliser pour décomposer le paramètre q.

Exemples :
  • http://localhost:8080/solr/livres/select?q=... équivalent à http://localhost:8080/solr/livres/select?defType=lucene&q=...
  • http://localhost:8080/solr/livres/select?defType=edismax&q=...

Ces valeurs sont configurables dans solrconfig.xml :

solrconfig.xml : LuceneQParserPlugin
Sélectionnez
...
<requestHandler name="/searchEn" class="solr.SearchHandler">
    <lst name="defaults">
        <str name="defType">lucene</str>
        <!-- defType "lucene" est la valeur par défaut,
            elle est donc facultative --!>
...
solrconfig.xml : ExtendedDismaxQParserPlugin
Sélectionnez
...
<requestHandler name="/searchEn" class="solr.SearchHandler">
    <lst name="defaults">
        <str name="defType">edismax</str>
...

Les différents types de plugin pour décomposer le paramètre q sont disponibles dans l'API : Solr QParserPlugin

Avec LuceneQParserPlugin, le principe de base est de chercher dans un unique champ par défaut ou bien de spécifier chacun des champs souhaités directement dans la requête (ce qui peut également être fait par le biais de filtres, comme nous l'avons vu précédemment). Vous devrez configurer le paramètre df (default field) pour qu'il cible un champ fortement analysé, par exemple avec de la racinisation ou de la phonétique (selon le type de donnés mises à l'index). À moins d'en avoir un besoin spécifique, évitez les n-grams pour la recherche car ils renvoient trop de faux positifs.

Dans la mesure où df ne référence qu'un seul champ, la chaîne saisie par l'internaute est comparée uniquement à ce champ dans l'index. C'est pour cette raison que ce paramètre est habituellement un champ "catch-all", c'est-à-dire la cible de plusieurs règles copyField dans votre schema.xml. Pour changer la liste des champs recherchés, il faut modifier les règles copyField dans votre schema.xml, puis réindexer la totalité de vos documents.

schema.xml
Sélectionnez
...
<copyField source="titleEn" dest="stAllEn"/>
<copyField source="seriesEn" dest="stAllEn"/>
<copyField source="synopsisEn" dest="stAllEn"/>
<copyField source="authorNames" dest="stAllEn"/>
...
URL :
Sélectionnez
http://localhost:8080/solr/livres/select?df=stAllEn&q=...
Équivalent dans solrconfig.xml :
Sélectionnez
...
<requestHandler name="/searchEn" class="solr.SearchHandler">
    <lst name="defaults">
        <str name="defType">lucene</str><!-- facultatif -->
        <str name="df">stAllEn</str>
...

Puisque LuceneQParserPlugin a besoin de champs spécifiques pour fonctionner convenablement, cela engendre à la fois une augmentation du temps d'indexation et une augmentation de la consommation d'espace disque, sauf si vous avez besoin de ces mêmes champs supplémentaires (avec exactement le même type et les mêmes paramètres) pour une autre fonctionnalité. Toutefois, je n'ai pas encore rencontré de situation qui justifie le partage de champs de cette manière.

Exemple "req-2.0" :

Requête :
Sélectionnez
http://localhost:8080/solr/livres/select?
fl=score%20isbn13%20copyrightYear%20nbPages%20nbInSeries%20authorNames%20titleEn%20seriesEn
&sort=score%20desc
&df=stAllEn
&q=magic
Résultat :
TéléchargerCacherSélectionnez

En revanche, avec ExtendedDismaxQParserPlugin, le principe est de laisser votre configuration appliquer une logique pour déterminer l'influence des termes recherchés sur les champs du noyau par rapport à leur fréquence d'apparition, et vice versa. Vous aurez besoin de configurer q.alt (alternate query) et qf (query fields). Ce dernier paramètre remplace avantageusement df car, plutôt qu'un unique champ, il permet de définir une liste de champs auxquels la chaîne recherchée doit être appliquée ainsi que de donner un poids spécifique à chacun de ces champs. Pour changer la liste des champs recherchés, modifiez qf dans votre solrconfig.xml (il n'est pas utile de réindexer les documents).

URL :
Sélectionnez
http://localhost:8080/solr/livres/select?defType=edismax&qf=stTitleEn^5.0 stSeriesEn^4.0 stSynopsisEn stAuthorNamesEn^2.0&q.alt=*:*&q=...
Équivalent dans solrconfig.xml :
Sélectionnez
...
<requestHandler name="/searchEn" class="solr.SearchHandler">
    <lst name="defaults">
        <str name="defType">edismax</str>
        <str name="qf">stTitleEn^5.0 stSeriesEn^4.0 stSynopsisEn stAuthorNamesEn^2.0</str>
        <str name="q.alt">*:*</str>
...

Exemple "req-2.1" :

Requête :
Sélectionnez
http://localhost:8080/solr/livres/select?
fl=score%20isbn13%20copyrightYear%20nbPages%20nbInSeries%20authorNames%20titleEn%20seriesEn
&sort=score%20desc
&defType=edismax
&qf=stTitleEn^5.0%20stSeriesEn^4.0%20stSynopsisEn%20stAuthorNamesEn^2.0
&q.alt=*:*
&q=magic
Résultat :
TéléchargerCacherSélectionnez

Vous pouvez observer la différence de score :

Résultats de req-2.0
score isbn13 seriesEn nbInSeries
0.12109267 978-0441014897 Kate Daniels 1
0.11724747 978-0441015832 Kate Daniels 2
0.11487859 978-0441017027 Kate Daniels 3
0.108308576 978-0441017805 The Edge 1
0.076585725 978-0441019458 The Edge 2
0.06769286 978-0441018529 Kate Daniels 4
0.06769286 978-0441020423 Kate Daniels 5
Résultats de req-2.1
score isbn13 seriesEn nbInSeries
0.35326093 978-0441014897 Kate Daniels 1
0.35326093 978-0441015832 Kate Daniels 2
0.35326093 978-0441017027 Kate Daniels 3
0.35326093 978-0441018529 Kate Daniels 4
0.35326093 978-0441020423 Kate Daniels 5
0.014130437 978-0441017805 The Edge 1
0.009991728 978-0441019458 The Edge 2

Vous l'aurez compris, les différentes versions de DisMax sont prévues pour des requêtes utilisateur contenant des mots dans le désordre et pouvant apparaître dans n'importe quelle partie de vos documents. Si vos documents sont composés uniquement d'un champ texte, comme c'est le cas si vous indexez des fichiers avec Carrot, alors vous pouvez vous contenter de LuceneQParserPlugin. Cependant, si vous développez une application de recherche dans des documents avec plusieurs champs comme je vous en fais la démonstration ici, vous préférerez sans doute ExtendedDismaxQParserPlugin.

Je vous recommande de préparer quelques requêtes req-2.2, req-2.3 (et ainsi de suite, au moins 3 ou 4 de votre cru) afin de les exécuter à nouveau dans le chapitre suivant avec les nouveaux paramètres. Vos variantes pourraient par exemple modifier le poids des champs dans le paramètre qf.

Mes démonstrations dans cet article sont basées sur ExtendedDismaxQParserPlugin car c'est le plugin utilisé dans la version actuelle de la démo distribuée avec la version actuelle de Solr. Toutefois, la page de documentation de ce plugin précise que son API est expérimentale et qu'elle peut changer de manière incompatible dans le futur. Il est donc possible que les paramètres changent, disparaissent ou deviennent insuffisants dans une prochaine version. En cas de doute, remplacez-le par DisMaxQParserPlugin (defType=dismax).

VIII-E-2-b. Explication de la présence des résultats : solr.HighlightComponent

Ce composant permet d'expliquer à vos internautes pourquoi vous leur affichez les résultats que vous affichez. Il met en valeur les segments de termes que Solr a utilisés pour déterminer quels documents sont utiles.

Chronologiquement, ce composant est exécuté après que les paramètres rows et offset aient pris effet. Le highlighting est calculé uniquement pour le jeu de résultats affiché, pas pour l'ensemble théorique non paginé.

Les paramètres suivants sont utiles :
  • highlight (bool) : active ou désactive le composant pour cette requête ;
  • hl (bool) : active ou désactive l'affichage du highlighting dans la réponse ;
  • hl.fl (str) : liste des champs à renvoyer dans la réponse ; si le paramètre qf est également défini, il est préférable de donner des listes de champs similaires pour l'un et pour l'autre ;
  • hl.snippets (int) : nombre maximum de résultats de highlighting à renvoyer pour chaque champ ; attention, ce paramètre a la valeur "1" par défaut ;
  • hl.simple.pre (str) : chaîne servant de préfixe aux segments de highlighting ; par défaut "<em>" ;
  • hl.simple.post (str) : chaîne servant de suffixe aux segments de highlighting ; par défaut "</em>".

Le paramètre hl.fl prend pour valeur une liste de champs. Souvenez-vous que c'est à travers lui que Solr vous explique pourquoi le document a été choisi dans les résultats de recherche : les champs de ce paramètre doivent impérativement être les mêmes (ou un équivalent par l'intermédiaire de copyField) que ceux analysés par le QParserPlugin pour sortir les documents de l'index. Si vous ne lui donnez pas la bonne liste de champs, ou si leur type est différent du type des champs analysés par le QParserPlugin, alors le Highlighting sera incomplet ou incohérent.

Si hl.fl n'est pas spécifié dans la requête, alors Solr utilise les paramètres df ou qf s'ils sont donnés, ou sinon les champs de LuceneQParser.

Si hl.fl n'est pas précisé dans la requête, alors vous laissez à Solr le soin de choisir l'ordre des champs dans les résultats de highlighting. En revanche, si vous donnez le paramètre, l'ordre des champs dans votre liste sera respecté par Solr au moment d'afficher les résultats. Vous pouvez observer cette différence de comportement avec les résultats de "req-3.1" et "req-3.1b" (voir plus loin).

Si vous optez pour un champ "catch-all" comme valeur du paramètre hl.fl, alors vos résultats seront peu précis. Il est préférable de prendre le temps d'identifier la liste des champs individuels qui correspondent à ce champ catch-all, car cela donne des indications plus précises dans le résultat de la requête.

Avec un champ "catch-all" pour valeur de hl.fl (ici, puisque hl.fl est vide, Solr prend la valeur de df) :

Requête :
Sélectionnez
http://localhost:8080/solr/livres/select?
fl=score%20isbn13%20copyrightYear%20nbPages%20nbInSeries%20authorNames%20titleEn%20seriesEn
&sort=score%20desc
&df=stAllEn
&highlight=on
&hl=true
&hl.simple.pre=[hl]
&hl.simple.post=[/hl]
&hl.snippets=100
&q=magic
La réponse nous dit simplement que le terme apparaît dans le document :
Sélectionnez
...
<lst name="978-0441014897">
  <arr name="stAllEn">
    <str>[hl]Magic[/hl] Bites</str>
    <str>Atlanta would be a nice place to live, if it weren't for
[hl]magic[/hl]... One moment [hl]magic[/hl] dominates, and</str>
    <str> your house from monsters. Here skyscrapers topple under onslaught
of [hl]magic[/hl]; werebears and werehyenas</str>
    <str> likes her sword a little too much and has a hard time controlling
her mouth. The [hl]magic[/hl] in her blood</str>
  </arr>
</lst>
...

Avec des champs individuels pour valeur de hl.fl :

Requête :
Sélectionnez
http://localhost:8080/solr/livres/select?
fl=score%20isbn13%20copyrightYear%20nbPages%20nbInSeries%20authorNames%20titleEn%20seriesEn
&sort=score%20desc
&df=stAllEn
&highlight=on
&hl=true
&hl.simple.pre=[hl]
&hl.simple.post=[/hl]
&hl.fl=stTitleEn,stSeriesEn,stSynopsisEn,stAuthorNamesEn
&hl.snippets=100
&q=magic
La réponse nous dit dans quels champs le terme apparaît :
Sélectionnez
...
<lst name="978-0441014897">
  <arr name="stTitleEn">
    <str>[hl]Magic[/hl] Bites</str>
  </arr>
  <arr name="stSynopsisEn">
    <str>Atlanta would be a nice place to live, if it weren't for
[hl]magic[/hl]... One moment [hl]magic[/hl] dominates, and</str>
    <str> your house from monsters. Here skyscrapers topple under onslaught
of [hl]magic[/hl]; werebears and werehyenas</str>
    <str> likes her sword a little too much and has a hard time controlling
her mouth. The [hl]magic[/hl] in her blood</str>
  </arr>
</lst>
...

Dans la mesure où je préfère avoir les détails, pour la suite de l'article, je ferai en sorte que Solr n'utilise jamais un champ "catch-all" comme paramètre hl.fl. Il s'agit d'une préférence personnelle, vous devrez étudier cela pour chaque application.

Voici maintenant des exemples plus complets.

Exemple "req-3.0" basé sur "req-2.0" (donnons à hl.fl soit la même valeur que df, soit la version lemmatisée de son découpage par l'intermédiaire de copyField) :

Requête :
Sélectionnez
http://localhost:8080/solr/livres/select?
fl=score%20isbn13%20copyrightYear%20nbPages%20nbInSeries%20authorNames%20titleEn%20seriesEn
&sort=score%20desc
&df=stAllEn
&highlight=on
&hl=true
&hl.simple.pre=[hl]
&hl.simple.post=[/hl]
&hl.fl=stTitleEn,stSeriesEn,stSynopsisEn,stAuthorNamesEn
&hl.snippets=100
&q=magic
Résultat :
TéléchargerCacherSélectionnez

Exemple "req-3.1" basé sur "req-2.1" (donnons à hl.fl soit la même liste de champs que qf, soit un champ équivalent par l'intermédiaire de copyField) :

Requête :
Sélectionnez
http://localhost:8080/solr/livres/select?
fl=score%20isbn13%20copyrightYear%20nbPages%20nbInSeries%20authorNames%20titleEn%20seriesEn
&sort=score%20desc
&defType=edismax
&qf=stTitleEn^5.0%20stSeriesEn^4.0%20stSynopsisEn%20stAuthorNamesEn^2.0
&q.alt=*:*
&highlight=on
&hl=true
&hl.simple.pre=[hl]
&hl.simple.post=[/hl]
&hl.fl=stTitleEn,stSeriesEn,stSynopsisEn,stAuthorNamesEn
&hl.snippets=100
&q=magic
Résultat :
TéléchargerCacherSélectionnez

Exemple "req-3.1b" basé sur "req-3.1" (laissons Solr deviner la valeur de hl.fl d'après la valeur de qf) :

Requête :
Sélectionnez
http://localhost:8080/solr/livres/select?
fl=score%20isbn13%20copyrightYear%20nbPages%20nbInSeries%20authorNames%20titleEn%20seriesEn
&sort=score%20desc
&defType=edismax
&qf=stTitleEn^5.0%20stSeriesEn^4.0%20stSynopsisEn%20stAuthorNamesEn^2.0
&q.alt=*:*
&highlight=on
&hl=true
&hl.simple.pre=[hl]
&hl.simple.post=[/hl]
&hl.snippets=100
&q=magic
Réponse :
TéléchargerCacherSélectionnez

Dans le chapitre précédent, nous avons mis en valeur la différence de scoring entre req-2.0 et req-2.1. Elle est expliquée ici par la réponse du composant Highlighting en req-3-0 et req-3.1 : le mot recherché, "magic", n'apparaît pas dans le titre des livres "The Edge". Il apparaît seulement dans leur synopsis, ce qui est suffisant pour req-2.0 et req-3.0 (les requêtes avec defType=lucene) puisque les champs stTitleEn et stSynopsisEn sont copiés dans le champ stAllEn utilisé pour ces requêtes. Toutefois, dans req-2.1 et req-3.1 (les requêtes avec defType=edismax), le poids du champ stTitleEn est de 5, contre 1 pour le champ stSynopsisEn.

Solr a bel et bien obéi à notre configuration :
  • dans req-2.0 et req-3.0 (defType=lucene) : les résultats sont triés uniquement d'après la présence des mots recherchés dans l'ensemble du document, leur importance étant simplement calculée par la position et la fréquence des mots recherchés ;
  • dans req-2.1 et req-3.1 (defType=edismax) : le score des documents est modifié selon le champ dans lequel les mots recherchés sont présents.

Reprenons les tableaux du chapitre précédent en ajoutant les informations du composant Highlighting :

Résultats de req-3.0
score isbn13 seriesEn nbInSeries hl
0.12109267 978-0441014897 Kate Daniels 1 stTitleEn * 1, stSynopsisEn * 4
0.11724747 978-0441015832 Kate Daniels 2 stTitleEn * 1, stSynopsisEn * 2
0.11487859 978-0441017027 Kate Daniels 3 stTitleEn * 1, stSynopsisEn * 1
0.108308576 978-0441017805 The Edge 1 stSynopsisEn * 4
0.076585725 978-0441019458 The Edge 2 stSynopsisEn * 2
0.06769286 978-0441018529 Kate Daniels 4 stTitleEn * 1
0.06769286 978-0441020423 Kate Daniels 5 stTitleEn * 1
Résultats de req-3.1
score isbn13 seriesEn nbInSeries hl
0.35326093 978-0441014897 Kate Daniels 1 stTitleEn * 1, stSynopsisEn * 4
0.35326093 978-0441015832 Kate Daniels 2 stTitleEn * 1, stSynopsisEn * 2
0.35326093 978-0441017027 Kate Daniels 3 stTitleEn * 1, stSynopsisEn * 1
0.35326093 978-0441018529 Kate Daniels 4 stTitleEn * 1
0.35326093 978-0441020423 Kate Daniels 5 stTitleEn * 1
0.014130437 978-0441017805 The Edge 1 stSynopsisEn * 4
0.009991728 978-0441019458 The Edge 2 stSynopsisEn * 2

Les explications fournies par le composant Highlighting vous permettent d'afficher à vos clients la raison des résultats ou des extraits de document. Elles vous permettent également d'affiner les autres paramètres de votre application. Par exemple, dans les exemples req-2.1 et req-3.1 (defType=edismax), le résultat de highlighting vous donne des indices pour configurer le poids de chaque champ dans le paramètre qf.

À mesure que la quantité de documents indexés par votre noyau augmente, le scoring des résultats devient de plus en plus stratégique pour votre application. Il est à votre charge de déterminer les requêtes utilisateur les plus fréquentes et les combinaisons de paramètres donnant les résultats les plus pertinents.

Il est ici temps d'appliquer les nouveaux paramètres de req-3.1 aux requêtes req-2.2 et suivantes, à savoir les requêtes que vous avez sans aucun doute préparées au chapitre précédent. Vous pourrez alors mieux observer les différences dans la réponse.

VIII-E-2-c. Explication du score des résultats : solr.DebugComponent

Dans les chapitres précédents, nous avons vu que nous pouvons manipuler l'importance d'un champ par rapport à un autre. Cependant, le score des documents ne correspond pas toujours à ce que l'on pourrait attendre. Solr propose un outil pour étudier les calculs qui l'ont amené à attribuer le score des documents : DebugComponent.

Une question que nous pourrions nous poser est "pourquoi la plupart des documents ont-ils le même score dans les réponses à req-2.1 et req-3.1 ?"

En effet, dans les résultats de ces requêtes, les documents de la série "Kate Daniels" ont tous un score de 0.35326093, tandis que les documents de la série "The Edge" ont des scores individuels.

Exemple "req-4.0" basé sur "req-2.0" :

Requête :
Sélectionnez
http://localhost:8080/solr/livres/select?
fl=score%20isbn13%20copyrightYear%20nbPages%20nbInSeries%20authorNames%20titleEn%20seriesEn
&sort=score%20desc
&df=stAllEn
&debugQuery=on
&debug.explain.structured=true
&q=magic
Réponse :
TéléchargerCacherSélectionnez

Exemple "req-4.1" basé sur "req-2.1" :

Requête :
Sélectionnez
http://localhost:8080/solr/livres/select?
fl=score%20isbn13%20copyrightYear%20nbPages%20nbInSeries%20authorNames%20titleEn%20seriesEn
&sort=score%20desc
&defType=edismax
&qf=stTitleEn^5.0%20stSeriesEn^4.0%20stSynopsisEn%20stAuthorNamesEn^2.0
&q.alt=*:*
&debugQuery=on
&debug.explain.structured=true
&q=magic
Réponse :
TéléchargerCacherSélectionnez

Dans les deux résultats ci-dessus, vous noterez la présence d'une ligne <str name="QParser">...</str> indiquant le type de plugin utilisé : lucene, edismax, etc. Cela peut notamment être utile pour vérifier si Solr a pris en compte le paramètre defType.

Afin d'y voir plus clair, voici une version condensée de ces résultats :

Résultats de req-4.0
score isbn13 seriesEn nbInSeries hl debug
0.12109267 978-0441014897 Kate Daniels 1 stTitleEn*1
stSynopsisEn*4
 
Sélectionnez
0.12109267 = (MATCH) fieldWeight(stAllEn:magic in 0), product of:
  2.236068 = tf(termFreq(stAllEn:magic)=5)
  0.8664686 = idf(docFreq=7, maxDocs=7)
  0.0625 = fieldNorm(field=stAllEn, doc=0)
0.11724747 978-0441015832 Kate Daniels 2 stTitleEn*1
stSynopsisEn*2
 
Sélectionnez
0.11724747 = (MATCH) fieldWeight(stAllEn:magic in 1), product of:
  1.7320508 = tf(termFreq(stAllEn:magic)=3)
  0.8664686 = idf(docFreq=7, maxDocs=7)
  0.078125 = fieldNorm(field=stAllEn, doc=1)
0.11487859 978-0441017027 Kate Daniels 3 stTitleEn*1
stSynopsisEn*1
 
Sélectionnez
0.11487859 = (MATCH) fieldWeight(stAllEn:magic in 2), product of:
  1.4142135 = tf(termFreq(stAllEn:magic)=2)
  0.8664686 = idf(docFreq=7, maxDocs=7)
  0.09375 = fieldNorm(field=stAllEn, doc=2)
0.108308576 978-0441017805 The Edge 1 stSynopsisEn*4
 
Sélectionnez
0.108308576 = (MATCH) fieldWeight(stAllEn:magic in 5), product of:
  2.0 = tf(termFreq(stAllEn:magic)=4)
  0.8664686 = idf(docFreq=7, maxDocs=7)
  0.0625 = fieldNorm(field=stAllEn, doc=5)
0.076585725 978-0441019458 The Edge 2 stSynopsisEn*2
 
Sélectionnez
0.076585725 = (MATCH) fieldWeight(stAllEn:magic in 6), product of:
  1.4142135 = tf(termFreq(stAllEn:magic)=2)
  0.8664686 = idf(docFreq=7, maxDocs=7)
  0.0625 = fieldNorm(field=stAllEn, doc=6)
0.06769286 978-0441018529 Kate Daniels 4 stTitleEn*1
 
Sélectionnez
0.06769286 = (MATCH) fieldWeight(stAllEn:magic in 3), product of:
  1.0 = tf(termFreq(stAllEn:magic)=1)
  0.8664686 = idf(docFreq=7, maxDocs=7)
  0.078125 = fieldNorm(field=stAllEn, doc=3)
0.06769286 978-0441020423 Kate Daniels 5 stSynopsisEn*1
 
Sélectionnez
0.06769286 = (MATCH) fieldWeight(stAllEn:magic in 4), product of:
  1.0 = tf(termFreq(stAllEn:magic)=1)
  0.8664686 = idf(docFreq=7, maxDocs=7)
  0.078125 = fieldNorm(field=stAllEn, doc=4)
Résultats de req-4.1
score isbn13 seriesEn nbInSeries hl debug
0.35326093 978-0441014897 Kate Daniels 1 stTitleEn*1
stSynopsisEn*4
 
Sélectionnez
0.35326093 = (MATCH) max of:
  0.014130437 = (MATCH) weight(stSynopsisEn:magic in 0), product of:
    0.097945176 = queryWeight(stSynopsisEn:magic), product of:
      1.1541507 = idf(docFreq=5, maxDocs=7)
      0.08486342 = queryNorm
    0.14426884 = (MATCH) fieldWeight(stSynopsisEn:magic in 0), product of:
      2.0 = tf(termFreq(stSynopsisEn:magic)=4)
      1.1541507 = idf(docFreq=5, maxDocs=7)
      0.0625 = fieldNorm(field=stSynopsisEn, doc=0)
  0.35326093 = (MATCH) weight(stTitleEn:magic^5.0 in 0), product of:
    0.4897259 = queryWeight(stTitleEn:magic^5.0), product of:
      5.0 = boost
      1.1541507 = idf(docFreq=5, maxDocs=7)
      0.08486342 = queryNorm
    0.72134423 = (MATCH) fieldWeight(stTitleEn:magic in 0), product of:
      1.0 = tf(termFreq(stTitleEn:magic)=1)
      1.1541507 = idf(docFreq=5, maxDocs=7)
      0.625 = fieldNorm(field=stTitleEn, doc=0)
0.35326093 978-0441015832 Kate Daniels 2 stTitleEn*1
stSynopsisEn*2
 
Sélectionnez
0.35326093 = (MATCH) max of:
  0.012489661 = (MATCH) weight(stSynopsisEn:magic in 1), product of:
    0.097945176 = queryWeight(stSynopsisEn:magic), product of:
      1.1541507 = idf(docFreq=5, maxDocs=7)
      0.08486342 = queryNorm
    0.12751685 = (MATCH) fieldWeight(stSynopsisEn:magic in 1), product of:
      1.4142135 = tf(termFreq(stSynopsisEn:magic)=2)
      1.1541507 = idf(docFreq=5, maxDocs=7)
      0.078125 = fieldNorm(field=stSynopsisEn, doc=1)
  0.35326093 = (MATCH) weight(stTitleEn:magic^5.0 in 1), product of:
    0.4897259 = queryWeight(stTitleEn:magic^5.0), product of:
      5.0 = boost
      1.1541507 = idf(docFreq=5, maxDocs=7)
      0.08486342 = queryNorm
    0.72134423 = (MATCH) fieldWeight(stTitleEn:magic in 1), product of:
      1.0 = tf(termFreq(stTitleEn:magic)=1)
      1.1541507 = idf(docFreq=5, maxDocs=7)
      0.625 = fieldNorm(field=stTitleEn, doc=1)
0.35326093 978-0441017027 Kate Daniels 3 stTitleEn*1
stSynopsisEn*1
 
Sélectionnez
0.35326093 = (MATCH) max of:
  0.010597828 = (MATCH) weight(stSynopsisEn:magic in 2), product of:
    0.097945176 = queryWeight(stSynopsisEn:magic), product of:
      1.1541507 = idf(docFreq=5, maxDocs=7)
      0.08486342 = queryNorm
    0.10820163 = (MATCH) fieldWeight(stSynopsisEn:magic in 2), product of:
      1.0 = tf(termFreq(stSynopsisEn:magic)=1)
      1.1541507 = idf(docFreq=5, maxDocs=7)
      0.09375 = fieldNorm(field=stSynopsisEn, doc=2)
  0.35326093 = (MATCH) weight(stTitleEn:magic^5.0 in 2), product of:
    0.4897259 = queryWeight(stTitleEn:magic^5.0), product of:
      5.0 = boost
      1.1541507 = idf(docFreq=5, maxDocs=7)
      0.08486342 = queryNorm
    0.72134423 = (MATCH) fieldWeight(stTitleEn:magic in 2), product of:
      1.0 = tf(termFreq(stTitleEn:magic)=1)
      1.1541507 = idf(docFreq=5, maxDocs=7)
      0.625 = fieldNorm(field=stTitleEn, doc=2)
0.35326093 978-0441018529 Kate Daniels 4 stTitleEn*1
 
Sélectionnez
0.35326093 = (MATCH) max of:
  0.35326093 = (MATCH) weight(stTitleEn:magic^5.0 in 3), product of:
    0.4897259 = queryWeight(stTitleEn:magic^5.0), product of:
      5.0 = boost
      1.1541507 = idf(docFreq=5, maxDocs=7)
      0.08486342 = queryNorm
    0.72134423 = (MATCH) fieldWeight(stTitleEn:magic in 3), product of:
      1.0 = tf(termFreq(stTitleEn:magic)=1)
      1.1541507 = idf(docFreq=5, maxDocs=7)
      0.625 = fieldNorm(field=stTitleEn, doc=3)
0.35326093 978-0441020423 Kate Daniels 5 stTitleEn*1
 
Sélectionnez
0.35326093 = (MATCH) max of:
  0.35326093 = (MATCH) weight(stTitleEn:magic^5.0 in 4), product of:
    0.4897259 = queryWeight(stTitleEn:magic^5.0), product of:
      5.0 = boost
      1.1541507 = idf(docFreq=5, maxDocs=7)
      0.08486342 = queryNorm
    0.72134423 = (MATCH) fieldWeight(stTitleEn:magic in 4), product of:
      1.0 = tf(termFreq(stTitleEn:magic)=1)
      1.1541507 = idf(docFreq=5, maxDocs=7)
      0.625 = fieldNorm(field=stTitleEn, doc=4)
0.014130437 978-0441017805 The Edge 1 stSynopsisEn*4
 
Sélectionnez
0.014130437 = (MATCH) max of:
  0.014130437 = (MATCH) weight(stSynopsisEn:magic in 5), product of:
    0.097945176 = queryWeight(stSynopsisEn:magic), product of:
      1.1541507 = idf(docFreq=5, maxDocs=7)
      0.08486342 = queryNorm
    0.14426884 = (MATCH) fieldWeight(stSynopsisEn:magic in 5), product of:
      2.0 = tf(termFreq(stSynopsisEn:magic)=4)
      1.1541507 = idf(docFreq=5, maxDocs=7)
      0.0625 = fieldNorm(field=stSynopsisEn, doc=5)
0.009991728 978-0441019458 The Edge 2 stSynopsisEn*2
 
Sélectionnez
0.009991728 = (MATCH) max of:
  0.009991728 = (MATCH) weight(stSynopsisEn:magic in 6), product of:
    0.097945176 = queryWeight(stSynopsisEn:magic), product of:
      1.1541507 = idf(docFreq=5, maxDocs=7)
      0.08486342 = queryNorm
    0.102013476 = (MATCH) fieldWeight(stSynopsisEn:magic in 6), product of:
      1.4142135 = tf(termFreq(stSynopsisEn:magic)=2)
      1.1541507 = idf(docFreq=5, maxDocs=7)
      0.0625 = fieldNorm(field=stSynopsisEn, doc=6)

Pour req-2.0 (defType=lucene), le calcul du score est simplement le produit de quelques fonctions.

Pour req-2.1 (defType=edismax), le calcul du score est un peu plus complexe car plusieurs champs sont concernés. Le score final est le score maximum de l'ensemble des champs.

Dans notre exemple, les titres de la série "Kate Daniels" contiennent systématiquement le mot recherché en première position. Par ailleurs, alors que le score de synopsisEn varie d'un livre à l'autre, il est toujours plus faible que le score de titleEn à cause de la position du terme recherché et du boost appliqué au titre. Tous ces facteurs expliquent pourquoi le score de ces documents est toujours le même : 0.35326093. En revanche, les titres de la série "The Edge" ne contiennent pas le mot recherché : il figure uniquement dans leur synopsis, selon différentes fréquences et à différentes positions, c'est pourquoi le score global de chaque document est différent.

Cet exemple illustre la différence d'impact entre la position des termes, la fréquence des termes et le boost des champs, sur le score des documents dans les résultats de recherche.

Dans notre situation, req-2.1 donne des résultats mieux adaptés que req-2.0. Dans la suite de l'article, nous allons donc utiliser req-2.1 comme base des requêtes.

VIII-E-2-d. Filtres automatiques sur les résultats : solr.FacetComponent

VIII-E-2-d-i. Fonctionnement général

L'une des grandes fonctionnalités de Solr est de renvoyer des "facettes" sur le jeu de résultats. Ce sont des filtres applicables au jeu de résultats pour permettre au client de lancer des requêtes plus précises.

Chronologiquement, les facettes sont déterminées après le jeu de résultats, mais avant que les paramètres start et rows ne limitent l'affichage. Ainsi, les facettes n'influencent ni le scoring ni la liste de résultats, et elles ne sont pas influencées par la pagination des résultats.

Les conditions techniques pour obtenir les facettes sur un champ sont les suivantes :
  • pour un champ texte, il doit avoir un fieldType très peu analysé, par exemple une version brute ("rawText" dans notre schéma d'exemple) ou normalisée (mise en minuscules etc.) ;
  • pour un champ texte, éviter les fieldTypes ayant des analyses lourdes (racinisation, n-grams etc.) ;
  • pour un champ numérique ou date, il est préférable d'utiliser l'un des types "trie" dont le nom commence par "t" : tint, tfloat, tdate, etc. ;
  • le fieldType du champ doit être indexed, mais pas obligatoirement stored.

Concernant 2º point ci dessus, il est possible d'obtenir les facettes sur un champ lourdement analysé mais l'intérêt en serait limité. En effet, Solr donnerait un très grand nombre de faux positifs (toutes les variantes de chaque terme), or ce sont des termes non destinés à des êtres humains et donc peu adaptés à une UI de recherche.

Solr propose quatre types de facettes :
  • Requêtes (facet_queries) : des requêtes arbitraires selon la syntaxe habituelle de Lucene ou Solr ;
  • Champs (facet_fields) : les valeurs distinctes du champ et leur nombre d'occurrences (en SQL, cela se rapproche d'un COUNT(1) dans une requête comprenant une clause GROUP BY sur ce champ) ;
  • Dates (facet_dates) : cette fonctionnalité est dépréciée, au profit de facet_ranges ci dessous ;
  • Intervalles (facet_ranges) : toutes les valeurs existant entre une borne minimale et une borne maximale, avec un intervalle défini.
Les paramètres généraux de FacetComponent sont :
  • facet (bool) : activer ou désactiver le composant pour la requête en cours.

La plupart des paramètres de facet_field et de facet_range ont une valeur globale et une valeur par champ. Vous pouvez par exemple définir un tri par défaut des facettes (facet.sort=count), tout en définissant un tri spécifique pour la facette sur le champ copyrightYear (f.copyrightYear.facet.sort=index). Nous verrons des exemples de cela par la suite.

VIII-E-2-d-ii. Les requêtes-facettes : facet_query
Les paramètres de facet_query sont :
  • facet.query (str) : la requête Solr à exécuter : ce paramètre doit être envoyé une fois pour chaque requête-facette.

Voici un bref exemple :

Requête :
Sélectionnez
http://localhost:8080/solr/livres/select?
fl=score%20isbn13%20copyrightYear%20nbPages%20nbInSeries%20authorNames%20titleEn%20seriesEn
&sort=score%20desc
&defType=edismax
&qf=stTitleEn^5.0%20stSeriesEn^4.0%20stSynopsisEn%20stAuthorNamesEn^2.0
&q.alt=*:*
&rows=0
&facet=on
&facet.query=sword
&facet.query=mercenary
&q=magic
Réponse :
Sélectionnez
<?xml version="1.0" encoding="UTF-8"?>
<response>
<lst name="responseHeader">
  <int name="status">0</int>
  <int name="QTime">0</int>
  <lst name="params">
    <str name="facet">on</str>
    <str name="sort">score desc</str>
    <str name="qf">stTitleEn^5.0 stSeriesEn^4.0 stSynopsisEn stAuthorNamesEn^2.0</str>
    <str name="q.alt">*:*</str>
    <str name="defType">edismax</str>
    <str name="rows">0</str>
    <str name="fl">score isbn13 copyrightYear nbPages nbInSeries authorNames titleEn seriesEn</str>
    <arr name="facet.query">
      <str>sword</str>
      <str>mercenary</str>
    </arr>
    <str name="q">magic</str>
  </lst>
</lst>
<result name="response" numFound="7" start="0" maxScore="0.35326093"/>
<lst name="facet_counts">
  <lst name="facet_queries">
    <int name="sword">1</int>
    <int name="mercenary">4</int>
  </lst>
  <lst name="facet_fields"/>
  <lst name="facet_dates"/>
  <lst name="facet_ranges"/>
</lst>
</response>

Si nous exécutons les deux requêtes suggérées ci-dessus, voici le résultat :

Requête "magic+sword" :
Sélectionnez
http://localhost:8080/solr/livres/select?
fl=score%20isbn13%20copyrightYear%20nbPages%20nbInSeries%20authorNames%20titleEn%20seriesEn
&sort=score%20desc
&defType=edismax
&qf=stTitleEn^5.0%20stSeriesEn^4.0%20stSynopsisEn%20stAuthorNamesEn^2.0
&q.alt=*:*
&rows=0
&omitHeader=true
&facet=on
&facet.query=sword
&facet.query=mercenary
&q=magic%20sword
Réponse "magic+sword" :
Sélectionnez
<?xml version="1.0" encoding="UTF-8"?>
<response>
<result name="response" numFound="1" start="0" maxScore="0.23749547"/>
<lst name="facet_counts">
  <lst name="facet_queries">
    <int name="sword">1</int>
    <int name="mercenary">1</int>
  </lst>
  <lst name="facet_fields"/>
  <lst name="facet_dates"/>
  <lst name="facet_ranges"/>
</lst>
</response>
Requête "magic+mercenary" :
Sélectionnez
http://localhost:8080/solr/livres/select?
fl=score%20isbn13%20copyrightYear%20nbPages%20nbInSeries%20authorNames%20titleEn%20seriesEn
&sort=score%20desc
&defType=edismax
&qf=stTitleEn^5.0%20stSeriesEn^4.0%20stSynopsisEn%20stAuthorNamesEn^2.0
&q.alt=*:*
&rows=0
&omitHeader=true
&facet=on
&facet.query=sword
&facet.query=mercenary
&q=magic%20mercenary
Réponse "magic+mercenary" :
Sélectionnez
<?xml version="1.0" encoding="UTF-8"?>
<response>
<result name="response" numFound="4" start="0" maxScore="0.22955763"/>
<lst name="facet_counts">
  <lst name="facet_queries">
    <int name="sword">1</int>
    <int name="mercenary">4</int>
  </lst>
  <lst name="facet_fields"/>
  <lst name="facet_dates"/>
  <lst name="facet_ranges"/>
</lst>
</response>

Comme nous pouvons le voir ici, nous pouvons demander à Solr de nous donner les facettes sur n'importe quelle requête. Dans le cas de "magic+sword", l'index ne renvoie qu'un seul résultat, et il contient les termes "sword" et "mercenary". Dans le cas de "magic+mercenary", l'index renvoie quatre résultats, parmi lesquels le terme "sword" apparaît dans un seul document alors que "mercenary" apparaît dans les quatre documents.

Si vous souhaitez savoir dans quel(s) champ(s) les termes suggérés apparaissent, il vous faut exécuter les requêtes suggérés en activant les paramètres de Highlighting.

VIII-E-2-d-iii. Les champs facettes : facet_field
Les paramètres de facet_field sont :
  • facet.field (str) : le nom du champ : ce paramètre doit être envoyé une fois pour chaque nom de champ (il ne contient pas une liste de noms de champs) ;
  • facet.prefix (str) : limite les valeurs des facettes à celles correspondant à ce préfixe ;
  • facet.sort (str) : le tri des facettes : "index" ou "count".
  • facet.mincount (int) : le nombre minimal d'occurrences du terme pour qu'il apparaisse dans la liste des termes ;
  • facet.missing (bool) : valeur virtuelle comptant les documents ne comprenant pas ce champ.
  • etc.
Requête :
Sélectionnez
http://localhost:8080/solr/livres/select?
fl=score%20isbn13%20copyrightYear%20nbPages%20nbInSeries%20authorNames%20titleEn%20seriesEn
&sort=score%20desc
&defType=edismax
&qf=stTitleEn^5.0%20stSeriesEn^4.0%20stSynopsisEn%20stAuthorNamesEn^2.0
&q.alt=*:*
&rows=0
&facet=on
&facet.mincount=1
&facet.sort=count
&facet.field=copyrightYear
&facet.field=authorNames
&facet.field=seriesEn
&facet.field=editors
&facet.field=originalCoverArtBy
&facet.field=originalCoverDesignBy
&facet.field=originalInteriorTextDesignBy
&f.copyrightYear.facet.sort=index
&q=magic
Réponse :
Sélectionnez
<?xml version="1.0" encoding="UTF-8"?>
<response>
<lst name="responseHeader">
  <int name="status">0</int>
  <int name="QTime">0</int>
  <lst name="params">
    <str name="facet">on</str>
    <str name="sort">score desc</str>
    <str name="facet.mincount">1</str>
    <str name="qf">stTitleEn^5.0 stSeriesEn^4.0 stSynopsisEn stAuthorNamesEn^2.0</str>
    <str name="q.alt">*:*</str>
    <str name="defType">edismax</str>
    <str name="rows">0</str>
    <str name="f.copyrightYear.facet.sort">index</str>
    <str name="fl">score isbn13 copyrightYear nbPages nbInSeries authorNames titleEn seriesEn</str>
    <str name="facet.sort">count</str>
    <str name="q">magic</str>
    <arr name="facet.field">
      <str>copyrightYear</str>
      <str>authorNames</str>
      <str>seriesEn</str>
      <str>editors</str>
      <str>originalCoverArtBy</str>
      <str>originalCoverDesignBy</str>
      <str>originalInteriorTextDesignBy</str>
    </arr>
  </lst>
</lst>
<result name="response" numFound="7" start="0" maxScore="0.35326093"/>
<lst name="facet_counts">
  <lst name="facet_queries"/>
  <lst name="facet_fields">
    <lst name="copyrightYear">
      <int name="2007">1</int>
      <int name="2008">1</int>
      <int name="2009">2</int>
      <int name="2010">2</int>
      <int name="2011">1</int>
    </lst>
    <lst name="authorNames">
      <int name="Gordon, Andrew">7</int>
      <int name="Gordon, Ilona">7</int>
      <int name="Ilona Andrews">7</int>
    </lst>
    <lst name="seriesEn">
      <int name="Kate Daniels">5</int>
      <int name="The Edge">2</int>
    </lst>
    <lst name="editors">
      <int name="ACE Fantasy">7</int>
    </lst>
    <lst name="originalCoverArtBy">
      <int name="Ward, Chad Michael">5</int>
      <int name="Vebell, Victoria">2</int>
    </lst>
    <lst name="originalCoverDesignBy">
      <int name="Fiore DeFex, Annette">5</int>
      <int name="Fiore, Annette">2</int>
    </lst>
    <lst name="originalInteriorTextDesignBy">
      <int name="Rosario (del), Kristin">7</int>
    </lst>
  </lst>
  <lst name="facet_dates"/>
  <lst name="facet_ranges"/>
</lst>
</response>
facet_field suggère d'ajouter des paramètres "fq" avec la syntaxe de valeur chaîne ou numérique :
  • 1 résultat pour : http://localhost:8080/solr/livres/select?q=magic&fq=copyrightYear:2007
  • 2 résultats pour : http://localhost:8080/solr/livres/select?q=magic&fq=copyrightYear:2010
  • 5 résultats pour : http://localhost:8080/solr/livres/select?q=magic&fq=originalCoverArtBy:"Ward,%20Chad%20Michael"
  • etc.
VIII-E-2-d-iv. Les facettes sur un type date : facet_date

C'est une fonctionnalité DEPRECATED. Il est préférable d'utiliser facet_range à la place.

VIII-E-2-d-v. Les facettes par intervalles : facet_range
Les paramètres de facet_range sont :
  • facet.range (str) : le nom du champ à utiliser pour renvoyer une facette d'intervalles : ce paramètre doit être envoyé une fois pour chaque nom de champ (il ne contient pas une liste de noms de champs) ;
  • facet.range.start (int/float/date) : la borne minimale des facettes à renvoyer ;
  • facet.range.end (int/float/date) : la borne maximale des facettes à renvoyer ;
  • facet.range.gap (int/float/date) : l'intervalle des facettes à renvoyer ;
  • facet.mincount (int) : le nombre minimal d'occurrences du terme pour qu'il apparaisse dans la liste des termes ;
  • facet.missing (bool) : valeur virtuelle comptant les documents ne comprenant pas ce champ.
  • etc.
Requête :
Sélectionnez
http://localhost:8080/solr/livres/select?
fl=score%20isbn13%20copyrightYear%20nbPages%20nbInSeries%20authorNames%20titleEn%20seriesEn
&sort=score%20desc
&defType=edismax
&qf=stTitleEn^5.0%20stSeriesEn^4.0%20stSynopsisEn%20stAuthorNamesEn^2.0
&q.alt=*:*
&rows=0
&facet=on
&facet.mincount=1
&facet.range=createdAt
&f.createdAt.facet.range.start=2011-01-00T00:00:00.000Z
&f.createdAt.facet.range.end=2012-12-00T00:00:00.000Z
&f.createdAt.facet.range.gap=%2B1DAY
&facet.range=nbPages
&f.nbPages.facet.range.start=0
&f.nbPages.facet.range.end=3000
&f.nbPages.facet.range.gap=50
&q=magic
Réponse :
Sélectionnez
<?xml version="1.0" encoding="UTF-8"?>
<response>
<lst name="responseHeader">
  <int name="status">0</int>
  <int name="QTime">321</int>
  <lst name="params">
    <str name="facet">on</str>
    <str name="sort">score desc</str>
    <str name="facet.mincount">1</str>
    <str name="f.nbPages.facet.range.end">3000</str>
    <str name="f.createdAt.facet.range.end">2012-12-00T00:00:00.000Z</str>
    <str name="qf">stTitleEn^5.0 stSeriesEn^4.0 stSynopsisEn stAuthorNamesEn^2.0</str>
    <arr name="facet.range">
      <str>createdAt</str>
      <str>nbPages</str>
    </arr>
    <str name="q.alt">*:*</str>
    <str name="f.nbPages.facet.range.gap">50</str>
    <str name="defType">edismax</str>
    <str name="rows">0</str>
    <str name="f.createdAt.facet.range.start">2011-01-00T00:00:00.000Z</str>
    <str name="fl">score isbn13 copyrightYear nbPages nbInSeries authorNames titleEn seriesEn</str>
    <str name="q">magic</str>
    <str name="f.createdAt.facet.range.gap">+1DAY</str>
    <str name="f.nbPages.facet.range.start">0</str>
  </lst>
</lst>
<result name="response" numFound="7" start="0" maxScore="0.35326093"/>
<lst name="facet_counts">
  <lst name="facet_queries"/>
  <lst name="facet_fields"/>
  <lst name="facet_dates"/>
  <lst name="facet_ranges">
    <lst name="createdAt">
      <lst name="counts">
        <int name="2011-06-04T00:00:00Z">2</int>
        <int name="2011-06-05T00:00:00Z">3</int>
        <int name="2011-06-25T00:00:00Z">2</int>
      </lst>
      <str name="gap">+1DAY</str>
      <date name="start">2010-12-31T00:00:00Z</date>
      <date name="end">2012-11-30T00:00:00Z</date>
    </lst>
    <lst name="nbPages">
      <lst name="counts">
        <int name="250">2</int>
        <int name="300">3</int>
        <int name="350">1</int>
        <int name="450">1</int>
      </lst>
      <int name="gap">50</int>
      <int name="start">0</int>
      <int name="end">3000</int>
    </lst>
  </lst>
</lst>
</response>
facet_range suggère d'ajouter des paramètres "fq" avec la syntaxe d'intervalles :
  • 2 résultats pour : http://localhost:8080/solr/livres/select?q=magic&fq=createdAt:[2011-06-04T00:00:00Z%20TO%202011-06-04T23:59:59Z]
  • 3 résultats pour : http://localhost:8080/solr/livres/select?q=magic&fq=createdAt:[2011-06-05T00:00:00Z%20TO%202011-06-05T23:59:59Z]
  • 3 résultats pour : http://localhost:8080/solr/livres/select?q=magic&fq=nbPages:[300%20TO%20349]

Les paramètres facet.range.start, facet.range.end et facet.range.gap sont tellement liés à chaque champ d'application qu'il est rare de les configurer au niveau global, mais plutôt individuellement pour chaque champ. En effet, quel intérêt de configurer un intervalle global "facet.range.gap=%2B1DAY" si les facet_range ne sont pas toutes de type tdate et si cet intervalle n'est pas cohérent pour toutes ?

VIII-E-2-d-vi. Exemple complet

Exemple "req-5.1" basé sur "req-2.1" :

Requête :
Sélectionnez
http://localhost:8080/solr/livres/select?
fl=score%20isbn13%20copyrightYear%20nbPages%20nbInSeries%20authorNames%20titleEn%20seriesEn
&sort=score%20desc
&defType=edismax
&qf=stTitleEn^5.0%20stSeriesEn^4.0%20stSynopsisEn%20stAuthorNamesEn^2.0
&q.alt=*:*
&rows=0
&facet=on
&facet.query=sword
&facet.query=mercenary
&facet.mincount=1
&facet.sort=count
&facet.field=copyrightYear
&facet.field=authorNames
&facet.field=seriesEn
&facet.field=editors
&facet.field=originalCoverArtBy
&facet.field=originalCoverDesignBy
&facet.field=originalInteriorTextDesignBy
&f.copyrightYear.facet.sort=index
&facet.range=createdAt
&f.createdAt.facet.range.start=2011-01-00T00:00:00.000Z
&f.createdAt.facet.range.end=2012-12-00T00:00:00.000Z
&f.createdAt.facet.range.gap=%2B1DAY
&facet.range=nbPages
&f.nbPages.facet.range.start=0
&f.nbPages.facet.range.end=3000
&f.nbPages.facet.range.gap=50
&q=magic

Dans l'URL ci dessus, nous voulons obtenir deux facettes "query", sept facettes "field" et deux facettes "range". Les facettes "field" sont triées par nombre d'occurrence des termes, sauf la facette sur le champ "copyrightYear" qui est triée par ordre d'indexation (donc "ordinal croissant"). Si des termes indexés dans le noyau n'apparaissent pas dans le jeu de résultats, ils ne sont pas renvoyés par Solr (facet.mincount >= 1).

Réponse :
TéléchargerCacherSélectionnez

VIII-E-2-e. Statistiques sur le jeu de résultats : solr.StatsComponent

Le composant de statistiques de Solr permet d'obtenir certaines métriques sur les champs numériques du noyau : min, max, sum, count, missing, sumOfSquares, mean et stddev. Toutes ces mesures ne sont pas utiles pour tous les cas d'application, à vous de voir si cela vous est utile et dans quelle proportion.

Ces mesures peuvent être récupérées soit sur un champ (stats.field) soit sur une facette (stats.facet).

Pour le moment, stats.field ne fonctionne que sur des types numériques. Essayer d'obtenir stats.field sur un type différent donne une erreur http-500.
Par ailleurs, stats.facet sur un type date ne donne pas toujours des résultats satisfaisants car il n'y a pas de notion d'intervalle.

Exemple "req-6.1" basé sur "req-2.1" :

Requête :
Sélectionnez
http://localhost:8080/solr/livres/select?
fl=score%20isbn13%20copyrightYear%20nbPages%20nbInSeries%20authorNames%20titleEn%20seriesEn
&sort=score%20desc
&defType=edismax
&qf=stTitleEn^5.0%20stSeriesEn^4.0%20stSynopsisEn%20stAuthorNamesEn^2.0
&q.alt=*:*
&rows=0
&stats=on
&stats.field=copyrightYear
&stats.field=nbPages
&q=magic
Réponse :
Sélectionnez
<?xml version="1.0" encoding="UTF-8"?>
<response>
<lst name="responseHeader">
  <int name="status">0</int>
  <int name="QTime">10</int>
  <lst name="params">
    <str name="sort">score desc</str>
    <str name="stats">on</str>
    <str name="qf">stTitleEn^5.0 stSeriesEn^4.0 stSynopsisEn stAuthorNamesEn^2.0</str>
    <str name="q.alt">*:*</str>
    <str name="defType">edismax</str>
    <str name="rows">0</str>
    <str name="fl">score isbn13 copyrightYear nbPages nbInSeries authorNames titleEn seriesEn</str>
    <str name="q">magic</str>
    <arr name="stats.field">
      <str>copyrightYear</str>
      <str>nbPages</str>
    </arr>
  </lst>
</lst>
<result name="response" numFound="7" start="0" maxScore="0.35326093"/>
<lst name="stats">
  <lst name="stats_fields">
    <lst name="copyrightYear">
      <double name="min">2007.0</double>
      <double name="max">2011.0</double>
      <double name="sum">14064.0</double>
      <long name="count">7</long>
      <long name="missing">0</long>
      <double name="sumOfSquares">2.8256596E7</double>
      <double name="mean">2009.142857142857</double>
      <double name="stddev">1.3451854182690985</double>
    </lst>
    <lst name="nbPages">
      <double name="min">260.0</double>
      <double name="max">462.0</double>
      <double name="sum">2292.0</double>
      <long name="count">7</long>
      <long name="missing">0</long>
      <double name="sumOfSquares">779840.0</double>
      <double name="mean">327.42857142857144</double>
      <double name="stddev">69.96870048542453</double>
    </lst>
  </lst>
</lst>
</response>

Le résultat ci dessus nous apprend que, pour cette recherche, les années de publication des livres sont comprises entre 2007 et 2011. La moyenne de publication des livres est début 2009. Leur nombre de pages est compris entre 260 et 462, pour un total de 2292 pages, et en moyenne 327 pages par livre.

Exemple "req-6.1b" basé sur "req-6.1" (ajout d'une facette sur le champ seriesEn) :

Requête :
Sélectionnez
http://localhost:8080/solr/livres/select?
fl=score%20isbn13%20copyrightYear%20nbPages%20nbInSeries%20authorNames%20titleEn%20seriesEn
&sort=score%20desc
&defType=edismax
&qf=stTitleEn^5.0%20stSeriesEn^4.0%20stSynopsisEn%20stAuthorNamesEn^2.0
&q.alt=*:*
&rows=0
&omitHeader=true
&stats=on
&stats.field=copyrightYear
&stats.field=nbPages
&stats.facet=seriesEn
&q=magic
Réponse :
Sélectionnez
<?xml version="1.0" encoding="UTF-8"?>
<response>
<result name="response" numFound="7" start="0" maxScore="0.35326093"/>
<lst name="stats">
  <lst name="stats_fields">
    <lst name="copyrightYear">
      <double name="min">2007.0</double>
      <double name="max">2011.0</double>
      <double name="sum">14064.0</double>
      <long name="count">7</long>
      <long name="missing">0</long>
      <double name="sumOfSquares">2.8256596E7</double>
      <double name="mean">2009.142857142857</double>
      <double name="stddev">1.3451854182690985</double>
      <lst name="facets">
        <lst name="seriesEn">
          <lst name="Kate Daniels">
            <double name="min">2007.0</double>
            <double name="max">2011.0</double>
            <double name="sum">10045.0</double>
            <long name="count">5</long>
            <long name="missing">0</long>
            <double name="sumOfSquares">2.0180415E7</double>
            <double name="mean">2009.0</double>
            <double name="stddev">1.5811388300841898</double>
          </lst>
          <lst name="The Edge">
            <double name="min">2009.0</double>
            <double name="max">2010.0</double>
            <double name="sum">4019.0</double>
            <long name="count">2</long>
            <long name="missing">0</long>
            <double name="sumOfSquares">8076181.0</double>
            <double name="mean">2009.5</double>
            <double name="stddev">0.7071067811865476</double>
          </lst>
        </lst>
      </lst>
    </lst>
    <lst name="nbPages">
      <double name="min">260.0</double>
      <double name="max">462.0</double>
      <double name="sum">2292.0</double>
      <long name="count">7</long>
      <long name="missing">0</long>
      <double name="sumOfSquares">779840.0</double>
      <double name="mean">327.42857142857144</double>
      <double name="stddev">69.96870048542453</double>
      <lst name="facets">
        <lst name="seriesEn">
          <lst name="Kate Daniels">
            <double name="min">260.0</double>
            <double name="max">366.0</double>
            <double name="sum">1504.0</double>
            <long name="count">5</long>
            <long name="missing">0</long>
            <double name="sumOfSquares">460120.0</double>
            <double name="mean">300.8</double>
            <double name="stddev">43.92265930018355</double>
          </lst>
          <lst name="The Edge">
            <double name="min">326.0</double>
            <double name="max">462.0</double>
            <double name="sum">788.0</double>
            <long name="count">2</long>
            <long name="missing">0</long>
            <double name="sumOfSquares">319720.0</double>
            <double name="mean">394.0</double>
            <double name="stddev">96.16652224137046</double>
          </lst>
        </lst>
      </lst>
    </lst>
  </lst>
</lst>
</response>

Le résultat ci dessus précise le résultat précédent, en nous donnant le détail : nous avons les mêmes données chiffrées pour chacune des séries de l'index.

VIII-E-3. Similitude de documents : MoreLikeThis

L'affichage d'un document suppose de lancer une requête Solr avec un filtre sur son ID et d'attendre un seul résultat. Dans ce contexte, les composants de groupes (solr.FacetComponent, solr.HighlightComponent, solr.StatsComponent) ont une utilité très réduite voire nulle. En revanche, il peut être intéressant de proposer aux clients des résultats élargis : c'est ce que permettent solr.MoreLikeThisComponent et solr.MoreLikeThisHandler.

Solr dispose à la fois d'un MoreLikeThisComponent et d'un MoreLikeThisHandler. Le premier est prévu pour être activé sur solr.SearchHandler tandis que le second remplace solr.SearchHandler dans la configuration d'un requestHandler. Puisque l'affichage d'un document n'utilise aucune fonctionnalité de SearchHandler, nous pouvons nous permettre de le remplacer par MoreLikeThisHandler :

conf/solrconfig.xml
Sélectionnez
...
<requestHandler name="/displayTest" class="solr.MoreLikeThisHandler">
  <arr name="components">
    <str>query</str>
  </arr>
  <lst name="defaults">
    <bool name="indent">on</bool>
    <str name="echoParams">explicit</str>
    <str name="wt">xml</str>
  </lst>
</requestHandler>
...

MLT obéit au paramètre qf s'il est utilisé avec MoreLikeThisHandler, mais pas avec MoreLikeThisComponent.

Paramètres disponibles uniquement en cas d'utilisation de MoreLikeThisComponent dans un StandardRequestHandler :
  • mlt (bool) : activer ou désactiver MLT pour cette requête ;
  • mlt.count (int) : le nombre de résultats similaires à renvoyer dans la réponse.
Paramètres disponibles uniquement en cas d'utilisation de MoreLikeThisHandler :
  • mlt.match.include (bool) : renvoyer ou non le document source dans la réponse ;
  • mlt.match.offset (int) : sur quel résultat du paramètre "q" agir (par défaut le premier) ;
  • mlt.interestingTerms (str) : "list", "details" ou "none" ; donne quelques explications sur les termes utilisés pour sortir les résultats similaires ; nécessite "mlt.boost=true".
Paramètres communs de MoreLikeThisComponent et MoreLikeThisHandler :
  • mlt.fl (str) : liste des champs à utiliser pour effectuer la comparaison ; ils devraient avoir les attributs termstermVectors, termPositions et termOffsets ;
  • mlt.mintf (float) : Term Frequency - fréquence minimum des termes dans le document source ;
  • mlt.mindf (float) : Document Frequency - nombre minimum de documents dans lesquels un terme doit apparaître ;
  • mlt.minwl (int) : Word Length - longueur minimale des termes ;
  • mlt.boost (bool) : utile pour mlt.interestingTerms ;
  • mlt.qf (str) : même contenu que mlt.fl avec la syntaxe de DisMaxQParserPlugin pour booster les champs ;
  • etc.

Une fois que vous avez collé le code du nouveau requestHandler dans votre fichier solrconfig.xml et après avoir relancé le noyau, chargez l'URL suivante :

Requête "req-7.1" :
Sélectionnez
http://localhost:8080/solr/livres/displayTest?
fl=isbn13%20createdAt%20copyrightYear%20nbPages%20nbInSeries%20authorNames%20titleEn%20seriesEn%20synopsisEn%20urlEn%20coverImageEn
&mlt.fl=stSynopsisEn
&mlt.count=5
&mlt.interestingTerms=details
&mlt.mintf=2
&mlt.minwl=3
&mlt.boost=true
&q=isbn13:978-0441014897
Réponse (lire les termes listés dans "interestingTerms") :
TéléchargerSélectionnez
<?xml version="1.0" encoding="UTF-8"?>
<response>
<lst name="responseHeader">
  <int name="status">0</int>
  <int name="QTime">40</int>
  <lst name="params">
    <str name="mlt.minwl">3</str>
    <str name="mlt.boost">true</str>
    <str name="mlt.fl">stSynopsisEn</str>
    <str name="mlt.mintf">2</str>
    <str name="mlt.interestingTerms">details</str>
    <str name="mlt.count">5</str>
    <str name="fl">isbn13 createdAt copyrightYear nbPages nbInSeries authorNames
titleEn seriesEn synopsisEn urlEn coverImageEn</str>
    <str name="q">isbn13:978-0441014897</str>
  </lst>
</lst>
<result name="match" numFound="1" start="0">
  [NDLR] Code trop long pour être inclus intégralement,
    voir fichier joint pour le détail
</result>
<result name="response" numFound="6" start="0">
  [NDLR] Code trop long pour être inclus intégralement,
    voir fichier joint pour le détail
</result>
<lst name="interestingTerms">
  <float name="stSynopsisEn:and">1.0</float>
  <float name="stSynopsisEn:her">0.89764106</float>
  <float name="stSynopsisEn:the">0.7777778</float>
  <float name="stSynopsisEn:magic">0.59200746</float>
  <float name="stSynopsisEn:kate">0.59200746</float>
  <float name="stSynopsisEn:but">0.3847033</float>
  <float name="stSynopsisEn:daniel">0.29600373</float>
  <float name="stSynopsisEn:she">0.25646886</float>
</lst>
</response>

Un constat que nous pouvons faire ici est que l'intégralité de notre index est mise dans les résultats de MLT. Ce n'est pas un comportement souhaitable. Nous aimerions mieux avoir des suggestions un peu mieux ciblées. Pour cela, nous allons devoir modifier quelques paramètres de notre noyau.

La première étape est d'identifier les termes qui nous posent problème. Pour cela, manipulez les paramètres mlt.mintf, mlt.mintf et mlt.minwl jusqu'à obtenir un jeu de résultats satisfaisant : ne pas ignorer trop de mots importants, tout en ayant un jeu de résultats convenable. Au bout de quelques essais, vous devriez vous rendre compte que, dans le cas qui nous occupe, deux termes nous empêchent d'avancer : "kate" et "daniel", qui sont le prénom et le nom de l'héroïne de l'une des deux séries de l'index. Ces deux termes figurent dans tous les synopsis de la série, et par conséquent ils causent un conflit avec les recommandations de MoreLikeThis. Nous allons devoir empêcher ces mots de figurer dans l'index, tout du moins pour le champ synopsis.

Ajoutez ceci au fichier stopwords_gen.txt, puis rechargez le noyau et relancez l'indexation de tous les documents :

 
Sélectionnez
kate
daniels

Au bout de quelques secondes, lancez de nouveau la requête précédente :

Requête "req-7.1" :
Sélectionnez
http://localhost:8080/solr/livres/displayTest?
fl=isbn13%20createdAt%20copyrightYear%20nbPages%20nbInSeries%20authorNames%20titleEn%20seriesEn%20synopsisEn%20urlEn%20coverImageEn
&mlt.fl=stSynopsisEn
&mlt.count=5
&mlt.interestingTerms=details
&mlt.mintf=2
&mlt.minwl=3
&mlt.boost=true
&q=isbn13:978-0441014897
Réponse (lire les termes listés dans "interestingTerms") :
TéléchargerSélectionnez
<?xml version="1.0" encoding="UTF-8"?>
<response>
<lst name="responseHeader">
  <int name="status">0</int>
  <int name="QTime">10</int>
  <lst name="params">
    <str name="mlt.minwl">3</str>
    <str name="mlt.boost">true</str>
    <str name="mlt.fl">stSynopsisEn</str>
    <str name="mlt.mintf">2</str>
    <str name="mlt.interestingTerms">details</str>
    <str name="mlt.count">5</str>
    <str name="fl">isbn13 createdAt copyrightYear nbPages nbInSeries authorNames
titleEn seriesEn synopsisEn urlEn coverImageEn</str>
    <str name="q">isbn13:978-0441014897</str>
  </lst>
</lst>
<result name="match" numFound="1" start="0">
  [NDLR] Code trop long pour être inclus intégralement,
    voir fichier joint pour le détail
</result>
<result name="response" numFound="6" start="0">
  [NDLR] Code trop long pour être inclus intégralement,
    voir fichier joint pour le détail
</result>
<lst name="interestingTerms">
  <float name="stSynopsisEn:and">1.0</float>
  <float name="stSynopsisEn:her">0.89764106</float>
  <float name="stSynopsisEn:the">0.7777778</float>
  <float name="stSynopsisEn:magic">0.59200746</float>
  <float name="stSynopsisEn:but">0.3847033</float>
  <float name="stSynopsisEn:she">0.25646886</float>
</lst>
</response>

Nous pouvons voir que les termes "kate" et "daniel", qui figuraient dans presque tous les documents de notre noyau, sont maintenant ignorés par notre application.

En l'occurence, les termes restants sont surtout des déterminants. Il nous faut donc inclure ces termes au fichier stopwords (réduction du bruit), puis augmenter ou réduire les paramètres du composant afin d'inclure des mots ayant une fréquence plus faible mais qui sont plus intéressants pour notre application.

VIII-F. solrconfig.xml (request handlers)

Une fois le schéma finalisé et après avoir déterminé avec assurance les grands types de requêtes que nous allons lancer sur notre schéma, nous pouvons les formaliser dans solrconfig.xml, sous forme de request handlers.

Fichier final :
TéléchargerCacherSélectionnez

précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2010 Guillaume Rossolini. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.