jeudi 22 octobre 2009

Recherche par facets avec Bobo-Browse

Bonjour,

Donc la situation est la suivante. Chez TELUS nous avons un produit de catalogue électronique qui utilise Lucène comme moteur de recherche. Mon boss me demande si on peut faire du filtrage par catégorie, c'est à dire, afficher, pour un résultat de recherche, le nombre de produits pour chaque catégorie du site, ce qui permet de faire un rafinage de la recherche de type "drill-down". Donc je cherche un peu et je tombe sur Solr naturellement, mais après lecture sommaire, je trouve ca plutôt complexe et étant donné que nous avons déjà du code qui roule en production et que je ne désire pas me lancer dans une refonte majeur de l'indexation, je continue de chercher. Finalement je tombe sur Bobo Browse, hébergé sur Google Code. Même si le nom m'arrête un peu, je continue de lire un peu et ca a effectivement l'air de correspondre à ce que je veux.

Donc je fais le tour du site, il y a un peu de documentation, et je fini par tomber sur un exemple avec des données sur les autos. Donc je me lance, je checkout le projet pour télécharger les exemples sur mon poste, car ils ne semblent pas venir avec le jar de la distribution. Je lance le build ant avec un run-cardemo et l'application se lance, mais bang, ca plante au chargement. Je suis sous windows et il semble y avoir un problème avec les backslash (\) du path. J'édite la propriété "bobo.root" du build ant pour hardcoder un path avec des slash (/) et je remplace location par value pour éviter que ant remplace mon slash par un backslash et la ca fonctionne. La démo semble rapide, même avec beaucoup de données et ca semble faire exactement ce que je veux que ca fasse, donc je creuse encore.
<property name="bobo.root" value="c:/java/bobo" />
Prochaine étape, comprendre la démo et extraire le code dont j'ai besoin.

On comprend rapidement qu'il faut fournir à bobo-browse une liste de facet, chaque facet possédant un type, soit SINGLE (value simple), MULTI (valeurs multiple), PATH (valeurs hiérarchique) ou RANGE (interval). On peut ensuite lancer un recherche, ou fixer les valeurs de certains facet, par exemple pour simuler une sélection de l'utilisateur. Si on n'a qu'une facet, fixer une sélection ne sert évidemment à rien (sauf pour les facets de type PATH), mais quand on a plusieurs facets, fixer une valeur permet de faire un drill-down et de préciser les valeurs des autres facets. Aussi, pour les facets de type PATH, fixer une valeur permet de compter les résultats pour les valeurs des sous éléments de la hiérarchie.

Donc maintenant, voici un exemple. J'ai d'abord un index de basé déjà construit qui contient des documents avec 3 champs, soit "text", "author" et "tags". Le champ "tags" contient 2 valeurs alors que les 2 autres, une seul valeur. Il existe 4 valeurs de tags utilisé et 4 auteurs et j'ai 500 documents dans l'index.

Premièrement il faut construire notre reader et nos facets, author et tags
IndexReader reader = IndexReader.open(directory);
Collection handlers = new ArrayList();
handlers.add(new SimpleFacetHandler("author", "author"));
handlers.add(new MultiValueFacetHandler("tag", "tags"));
BoboIndexReader boboReader = BoboIndexReader.getInstance(reader, handlers);
Ensuite, nous devons créer notre requête bobo-browse

//Objet de requete
BrowseRequest br = new BrowseRequest();
br.setCount(50);
br.setOffset(0);

//Query Lucene
QueryParser parser =
new QueryParser("text", new StandardAnalyzer());
Query q = parser.parse("Lucene");
br.setQuery(q);

//Facets utilisé pour cette requête
FacetSpec authorSpec = new FacetSpec();
authorSpec.setOrderBy(FacetSortSpec.OrderHitsDesc);
br.setFacetSpec("author", authorSpec);
FacetSpec tagSpec = new FacetSpec();
tagSpec.setOrderBy(FacetSortSpec.OrderHitsDesc);
br.setFacetSpec("tag", tagSpec);

//Execution de la requete
Browsable browser = new BoboBrowser(boboReader);
BrowseResult result = browser.browse(br);

//Parcours des résultats
int totalHits = result.getNumHits();
System.out.println("Total: " + totalHits);

BrowseHit[] hits = result.getHits();
System.out.println("Hits : " + hits.length);

System.out.println("Facet tag:");
Map facetMap = result.getFacetMap();
FacetAccessible tagFacets = facetMap.get("tag");
List facets = tagFacets.getFacets();
for (Iterator it = facets.iterator(); it.hasNext();) {
BrowseFacet browseFacet = (BrowseFacet) it.next();
System.out.println(browseFacet.getValue()
+ " : " + browseFacet.getHitCount());
}

System.out.println("Facet author:");
FacetAccessible authorFacets = facetMap.get("author");
facets = authorFacets.getFacets();
for (Iterator it = facets.iterator(); it.hasNext();) {
BrowseFacet browseFacet = (BrowseFacet) it.next();
System.out.println(browseFacet.getValue()
+ " : " + browseFacet.getHitCount());
}


Voici la sortie du programme
Total: 500
Hits : 50
Facet category:
information : 288
opinion : 264
idea : 236
faq : 212
Facet author:
Lemieux : 156
Durand : 132
Gagnon : 108
Larouche : 104

Si on fait la somme des valeurs des auteurs, on obtient 500, chaque document ayant un auteur et la somme des tags donne 1000, chaque document ayant 2 tags.

Bonne chance avec vos expérimentation bobo-browse.

Aucun commentaire: