Indexer : La boucle SPHINX

Avant de commencer :
Pour comprendre l’intérêt du plugin, nous invitions à lire l’article suivant Indexer : Introduction. La configuration et l’installation du serveur Sphinx sont expliquées dans l’article Indexer : Installation et Configuration ; l’usage de la recherche dans Indexer : Rechercher. Le paramétrage de l’indexation dans Indexer : Indexation.

Comment construire ses boucles SPHINX

Sur la page des résultats de recherche, on distinguera en général :
— la boucle principale, qui affiche les résultats de la recherche
— le rappel des éléments de la requête
— le nombre de résultats et les outils de tri
— les suggestions
— la pagination
— les facettes.

Le plugin Indexer offre des briques pour construire tous ces éléments en fonction des spécificités de votre système d’indexation.

À noter : Ce plugin est exploité sur de grosses bases de contenus, parfois réparties sur plusieurs sites SPIP, ce qui explique certains choix de structuration des données. Par conséquent, les résultats d’une recherche peuvent ne pas figurer dans la base de données du site que l’on est en train de consulter. Ceci implique que tous les éléments nécessaires à leur affichage (par exemple : titre, date, mots-clés, auteur, résumé, URL du contenu et URL du logo) doivent être stockés dans l’index — on n’ira pas faire de boucles SPIP à l’intérieur des boucles SPHINX.

La boucle principale

La boucle principale est celle qui va afficher les résultats de la recherche, sous forme de blocs avec titre et lien, et parfois un extrait où les mots de la requête sont surlignés (balise #SNIPPET.

Cette boucle doit prévoir deux grandes familles de critères :

1. quels sont les éléments de l’URL que l’on va transformer en filtres pour limiter le contenu (par date par auteur). Ce sont des critères de la forme
{filter #ENV*{auteur}, 'IN(properties.authors, @valeurs)', 'LENGTH(properties.authors) = 0'}

Cette syntaxe un peu compliquée se lit comme suit : « si l’URL contient une précision &auteur=Toto, ajouter un critère de recherche Sphinx demandant à ce que la propriété indexée properties.authors contienne la chaîne "Toto" ». La partie qui vient après la virgule correspond au cas particulier : « si l’URL contient une précision &auteur=-, limiter la recherche aux articles ayant properties.authors vide ».

Ces critères sont détaillés ci-dessous, dans la partie « filtres ».

2. Quelles facettes faut-il demander à Sphinx de nous renvoyer.

Ces critères ne servent pas à filtrer les résultats, mais permettent de demander à Sphinx de calculer les facettes qui permettront d’affiner par la suite la recherche. En règle générale, chaque filtre défini au point 1 sera accompagné par une facette équivalente. Mais ce n’est pas une obligation technique, et il est possible de prévoir un jeu limité de facettes, et des critères de filtrage qui seraient activés autrement que par des facettes (par exemple des cases à cocher programmées de manière classique).

Ces critères sont de la forme {facet auteur, properties.authors ORDER BY COUNT(*) DESC} ; éventuellement {facet auteur, properties.authors ORDER BY COUNT(*) DESC LIMIT 50} pour modifier le nombre maximal de facettes (par défaut, 20), ce qui peut être utile par exemple si les facettes représentent les années de publication.

On prendra l’habitude d’écrire d’abord la liste des filtres, puis la listes de facettes.

Le rappel des éléments de la requête

En général en tête de la page de recherche, on présente un formulaire de recherche qui rappelle au moins les termes de la requête qui vient d’être effectuée. Ce formulaire peut être incomplet, mais il est très important qu’il rappelle tous les éléments qui ont permis de construire la requête.

Ainsi par exemple si l’utilisatrice a saisi une requête sur les mots (full-texte) « Cheval » et la facette « Année : 2015 », il faut que ces deux éléments soient rappelés dans ce bloc. On se basera sur les paramètres de l’URL pour savoir quoi afficher. Chacun des paramètres qui peuvent constituer un filtre doit y figurer.

Par exemple, pour la facette « par année », on écrira au minimum :
[<p>Année: (#ENV{annee})</p>]

Mais ce rappel est aussi un endroit idéal pour ajouter l’interaction « supprimer la facette d’année », ce qui fait qu’on indiquera plutôt :
[<p><a href="[(#SELF|parametre_url{annee,''})]">x</a> Année: (#ENV{annee})</p>]

Le code se complique un peu, mais l’interface devient plus utilisable. Si on a beaucoup de filtres possibles, c’est un peu répétitif, et on peut passer par une boucle DATA pour lister les différents paramètres, tous traités de la même manière (cf. l’exemple dans content/sphinx.html).

Pour le full texte, par convention (hors cas particuliers), l’input texte libre correspondant est toujours présent, soit sous la forme du #FORMULAIRE_RECHERCHE{#SELF}, soit directement sous la forme :
<input type=search name=recherche value="[(#ENV{recherche}|attribut_html)]" placeholder="Saisir une requête…">

Ces éléments de rappel doivent impérativement se trouver en dehors de la boucle de recherche, de manière à apparaître dans tous les cas, que la recherche ait donné ou non des résultats.

Le nombre de résultats et les outils de tri

Dans la partie optionnelle avant la boucle principale, on va gérer l’affichage du nombre de résultats :
<h2>#GRAND_TOTAL documents trouvés (#TOTAL_BOUCLE affichés)</h2>

On peut aussi proposer ici [<p>(#SPHINX_MESSAGE)</p>] qui contiendra d’éventuelles suggestions d’orthographe.

On peut afficher le nombre de résultats trouvés, en sachant que, comme SPHINX coupe systématiquement à 1000 résultats, il faut éventuellement prévoir le cas où ce nombre est atteint.

[(#SPHINX_META|table_valeur{total}|=={1000}|?{'+ de '}) ][(#SPHINX_META|table_valeur{total}) ]résultats

Au même endroit on peut ajouter des infos de débogage, mais il n’est pas utile de les laisser en production : code technique de la requête Sphinx, et temps mis à répondre.

[<div><tt>(#SPHINX_QUERY|htmlspecialchars)</tt></div>]
requête effectuée en [(#SPHINX_META|table_valeur{time}|mult{1000})] ms

C’est ici aussi qu’on peut ajouter des boucles secondaires qui iront par exemple chercher les auteurs ou les mots-clés correspondant à le requête, de manière à suggérer un filtrage pertinent. Cette recherche peut être faite soit dans l’index sphinx, si vous avez indexés ces objets, soit avec une boucle de recherche SPIP normale (les tables auteurs et mots-clés étant en général plus petites que les tables articles, on n’a pas le même souci de performance). C’est ce que fait Le Monde diplomatique en proposant « Auteurs trouvés : le sous-commandant Marcos » lorsque l’on recherche le mot « Marcos ».

Les outils de tri en général sont par date et par pertinence ; il est bien sûr possible d’ajouter des tris sur d’autres critères en fonction des besoins (prix, notes, places disponibles…).

En termes de code, pas de surprise : il s’agit d’un critère SPIP classique dans la boucle SPHINX :
{par #GET{tri}}{inverse #GET{sens_tri}}

Pour l’interface, il faut élucider ce que l’utilisatrice a demandé ou pas, ce qu’on propose par défaut, etc.

<aside class="tri">
	[(#ENV{order}|match{date}|?{
		[(#SET{tri,date})]
		[(#SET{sens_tri,-1})]
		résultats triés par date
	})]
	<div>
	[(#GET{tri}|?{
		<a href="[(#SELF|parametre_url{order,''})]">trier par pertinence</a>
	,
		<a href="[(#SELF|parametre_url{order,date})]">trier par date</a>
	})]
	</div>
</aside>

La pagination

C’est du SPIP parfaitement standard : un critère {pagination 10} et une balise [<div class="pagination">(#PAGINATION)</div>] dans la partie optionnelle après de la boucle.

Les facettes

C’est l’apport le plus remarquable du moteur SPHINX : à chaque requête, il permet d’associer des facettes, qui représentent les dimensions secondaires associées à cette requête précise.

Ainsi par exemple si on demande des facettes d’auteurs, d’année et de mots-clés, SPHINX renverra un tableau de trois séries de facettes. La série des facettes d’auteurs indiquera jusqu’à 20 auteurs liés aux mots saisis dans la recherche, et pour chacun de ces auteurs, le nombre d’articles qui correspondent à cette saisie et à cet auteur.

Le tri des auteurs ou mots-clés est fait en général en fonction du nombre de résultats (mais on peut préférer par ordre alphabétique : cela se décide dans l’écriture du critère qui crée la facette). Le tri des dates est en général par ordre alphabétique (trier par nombre de résultats donne un affichage confus).

Les facettes permettent à la fois de 1) montrer quels sont les contextes dans lesquelles un mot ou une expression « full texte » apparaissent dans l’ensemble de la base (par exemple : quels sont les auteurs qui parlent le plus souvent de SPIP ?) ; et 2) dans le même temps, de créer des suggestions d’affinage de la recherche, par simple clic sur les mots en question, qui se transformeront en filtres.

Le code des facettes figure dans le squelette listes/sphinx_facettes.html et est inclus dans la partie optionnelle après de la boucle SPHINX par l’appel suivant :
[(#INCLURE{fond=liste/sphinx_facettes,facets=#SPHINX_FACETS,env})]

Des filtres spécifiques à des cas courants de comparaisons sont pré-programmés, ce qui évite de devoir connaître et écrire soi-même le code des tests.

Filtre pour un champ n’ayant qu’une valeur (mono-valué)

{filtermono test, champ, valeur(s)[, comparaison]}

Le test en premier permet de passer n’importe quelle valeur fixe ou dynamique (avec filtre ou pas) qui permettra de dire si on va ajouter le filtre ou pas. Le cas courant est de mettre la valeur d’un paramètre de l’URL, et s’il est présent et rempli, on ajoute le filtre.

La comparaison est optionnelle, et vaut « = » par défaut.

Le champ de valeur peut être une liste de plusieurs valeurs, et dans ce cas le test sera un « OU » sur chacune des comparaisons !

Exemples

// Les documents publiés par défaut, sinon ceux du statut demandé
{filtermono #ENV{statut,publie}, properties.statut, #ENV{statut,publie}}
// Les documents de 2014 ou 2013
{filtermono #ENV{annee}, year(date), #LISTE{2014,2013}}
// Les documents ayant au moins #ENV{favs} partages
{filtermono #ENV{favs}, length(properties.share), #ENV{favs}, '>='}

Filtre pour un champ JSON ayant plusieurs valeurs (multi-valué)

{filtermultijson test, champ, valeur(s)}

Mêmes principes que pour le critère précédent sauf que le critère cherche si les valeurs font partie du tableau « champ » (qui doit donc être une liste de plusieurs valeurs).

Si on donne plusieurs valeurs, le critère fera un « ET » entre les tests. Si l’une de ces valeurs est elle-même un tableau, le critère fera un « OU » (avec la commande Sphinx IN).

-  Si les valeurs sont #LISTE{mot1, mot2, mot3} : ça cherchera les documents qui ont mot1 ET mot2 ET mot3.
-  Si les valeurs sont #LISTE{mot1, #LISTE{mot2, mot3}} : ça cherchera les documents qui ont mot1 ET (mot2 OU mot3).

Exemples

// Un auteur précis parmi ceux du document
{filtermultijson #ENV{auteur}, properties.authors, #ENV{auteur}}
// Les documents ayant tous les tags demandés, par ex si tags[]=truc&tags[]=bidule
{filtermultijson #ENV{tags}, properties.tags, #ENV{tags}}

Filtre de distance

{filterdistance test, point1, point2, distance[, comparaison[, nom du champ]]}

Ce critère sélectionne uniquement les réponses qui font que la distance entre le point 1 et le point 2 correspond à la comparaison demandée avec la distance passée en paramètre.

La comparaison est optionnelle est vaut « <= » par défaut.
Le nom de la distance calculée est optionnelle et vaudra par défaut « distance_0 » pour la première distance demandée, puis « distance_1 », etc.

Ce paramètre permet de maîtriser le nom afin de pouvoir plus facilement
demander un tri par le nom voulu, et récupérer la valeur avec une balise.

Exemple

// Tous les documents qui sont à moins de 5km de Bordeaux, avec comme nom #DISTANCE
#SET{bordeaux, #ARRAY{lat, 44.83717, lon, -0.57403}}
#SET{point_document, #ARRAY{lat, properties.geo.lat, lon, properties.geo.lon}}
{filterdistance , testok, #GET{bordeaux}, #GET{point_document}, 5000, '<=', distance}

Tri des résultats

Comme dans les boucles SPIP habituelles, on peut utiliser le critère {par champ} pour trier les résultats, y compris avec les propriétés JSON {par properties.objet}.

Options pour les requêtes

{option nom, valeur}

Exemple

// Modifier la pondération des champs lors de la recherche libre
{option field_weights, "(title=10, content=5)"}

// Augmenter la limite du nombre de résultats (1000 par défaut)
{option max_matches, 999999}

Un exemple de boucle

<BOUCLE_recherche_sphinx(SPHINX)
	{index #ENV{source,spip}}
	{recherche #ENV*{recherche}}

	{filter #ENV{annee},  'YEAR(date) = @valeur' }
	{filter #ENV{tag},    'IN(properties.tags, @valeurs)',    'LENGTH(properties.tags) = 0'}
	{filter #ENV{auteur}, 'IN(properties.authors, @valeurs)', 'LENGTH(properties.authors) = 0'}

	{par #GET{tri}}{inverse #GET{sens_tri}}

	{facet auteur, properties.authors ORDER BY COUNT(*) DESC}
	{facet tag,    properties.tags ORDER BY COUNT(*) DESC}
	{facet annee, YEAR(date) ORDER BY date DESC}

	{pagination 10}
>

	[(#SET{properties,#PROPERTIES|json_decode{1}})]
	<li class='item'>
		<article class='entry article hentry'>
			<strong class='h3-like entry-title'>[(#SCORE|mult{100}|div{#GET{max}}|intval)%] <a href="#URI">#TITLE</a></strong>
			<p class="publication">
				[<time pubdate="pubdate" datetime="[(#DATE|date_iso)]">(#DATE|affdate_jourcourt)</time>][<span class="authors"><span class="sep">, </span> <:par_auteur:> (#GET{properties}|table_valeur{authors}|implode{', '})</span>]
			</p>
			[<div class="introduction entry-content">(#SNIPPET|sinon{#SUMMARY})</div>]
		</article>
	</li>

</BOUCLE_recherche_sphinx>
		</ul>
	</div>

	[<div class="pagination">(#PAGINATION)</div>]

	[(#INCLURE{fond=liste/sphinx_facettes,facets=#SPHINX_FACETS,env})]
	[(#INCLURE{fond=liste/sphinx_metas,meta=#SPHINX_META,env})]

</B_recherche_sphinx>
	<h2>Pas de résultat pour :</h2>
	<div><tt>#SPHINX_QUERY</tt></div>
	[<p>(#SPHINX_MESSAGE)</p>]

	[(#INCLURE{fond=liste/sphinx_metas,meta=#SPHINX_META,env})]

<//B_recherche_sphinx>

 Les criteres de boucle

  • index
  • recherche
  • filter
  • facet
  • pagination

Les balises

  • #SPHINX_META
  • #SPHINX_MESSAGE
  • #SPHINX_QUERY
  • #SPHINX_FACETS
  • #SNIPPET
  • #SUMMARY
  • #PROPERTIES
  • #DATE

Discussion

Aucune discussion

Ajouter un commentaire

Avant de faire part d’un problème sur un plugin X, merci de lire ce qui suit :

  • Désactiver tous les plugins que vous ne voulez pas tester afin de vous assurer que le bug vient bien du plugin X. Cela vous évitera d’écrire sur le forum d’une contribution qui n’est finalement pas en cause.
  • Cherchez et notez les numéros de version de tout ce qui est en place au moment du test :
    • version de SPIP, en bas de la partie privée
    • version du plugin testé et des éventuels plugins nécessités
    • version de PHP (exec=info en partie privée)
    • version de MySQL / SQLite
  • Si votre problème concerne la partie publique de votre site, donnez une URL où le bug est visible, pour que les gens puissent voir par eux-mêmes.
  • En cas de page blanche, merci d’activer l’affichage des erreurs, et d’indiquer ensuite l’erreur qui apparaît.

Merci d’avance pour les personnes qui vous aideront !

Par ailleurs, n’oubliez pas que les contributeurs et contributrices ont une vie en dehors de SPIP.

Qui êtes-vous ?
[Se connecter]

Pour afficher votre trombine avec votre message, enregistrez-la d’abord sur gravatar.com (gratuit et indolore) et n’oubliez pas d’indiquer votre adresse e-mail ici.

Ajoutez votre commentaire ici

Ce champ accepte les raccourcis SPIP {{gras}} {italique} -*liste [texte->url] <quote> <code> et le code HTML <q> <del> <ins>. Pour créer des paragraphes, laissez simplement des lignes vides.

Ajouter un document

Suivre les commentaires : RSS 2.0 | Atom