Lucene - My very own search engine



Was'n das jetzt schon wieder?

Herzlich Willkommen zu einer neuen Folge meiner "Was'n das jetzt schon wieder ?" Reihe. Wieder einmal beruflich angestoßen - die Auseinandersetzung mit Apache Lucene ( auch wenn es hier im Konkreten um die einfache Variante im Zend Framework geht). Heute grübeln wir kurz über News-Aggregation und Suche, Low-Level-Zauberei mit großer Wirkung auf Unbedarfte.

Zur Ausgangslage

Gegeben sei ein System (vielleicht sowas wie ein Wordpress), mittels dessen ich eigenen Artikel und Bilder oder auch Inhalte aus fremden Quellen veröffentlichen möchte. Besonders bei Bilder, die man sonst vielleicht mühsam in einer Bilddatenbank sucht, ins eigene System hochläft etc oder auch bei fremden Artikeln eine oft doofe Sache. Viel Copy & Paste etc.
Oder auch: Ich möchte diverse Quellen Anzapfen, die Informationen enthalten die mich meistens interssieren und darin bequem stöbern. Nun - so etwas wollen wir heute realisieren.

Vorbereitungen

Da wir das Rad nicht neu erfinden wollen, "schnell" mal das Zend Framework installiert. Nun basteln wir uns 3 kleine Files:

   init.php      Das soll der Platz für die üblichen Initialisierungsschritte und Hilfsfunktionen sein.

   import.php    Hier werden die Quellen angezapft und in die Lucene Search Engine reingestopft.

Ein sehr, sehr einfaches Beispiel, das ein zu indizierendes Dokument in den Index speichert:


// Neuen Index in /tmp öffnen
$Index = Zend_Search_Lucene::create( "/tmp" );
// Dokument erzeugen
$Document = new Zend_Search_Lucene_Document();
// Unser eigene Mime-Type, den wir vielleicht später brauchen
$Document->addField(Zend_Search_Lucene_Field::text(
	'content-type',
	'text/html'));
// Der Titel
$Document->addField(Zend_Search_Lucene_Field::text(
	'title',
	'Der Title des Dokumentes, der Webseite oder des Bildes'));
// Der Text an sich. Wird nicht gespeichert, daher könne wir ihn später
// bei der Suche nicht anzeigen.
$Document->addField(Zend_Search_Lucene_Field::unStored(
	'text',
	'Zu indizierenden Text, worum es überhaupt geht. Wird aber nicht gespeichert.'));
// Dokument indizieren
$Index->addDocument( $Document );

   search.php    Searching like a Googler - Damit wollen wir suchen.

Das folgende Beispiel sucht nach dem was in $searchPhrase angeben wurde und gibt dann die Treffer aus. Diese Treffer sind automatisch nach "Score" - also der Relevanz - sortiert.


// Bestehenden Index in /tmp öffnen
$Index = Zend_Search_Lucene::open( "/tmp" );
// Früher habe ich selbst gesucht, heute lasse ich suchen
$hits = $Index->find( $searchPhrase );
foreach ($hits as $hit) {
	$Document = $hit->getDocument();
	echo 'Score: ' . $hit->score .PHP_EOL;
	echo 'Type : ' . $Document->getFieldValue('content-type') .PHP_EOL;
	echo 'Title: ' . $Document->getFieldValue('title') .PHP_EOL;
}

Ja, natürlich kann man das auch » hier runterladen «.

Live ausprobieren

Index erstellen

Nach einem Klick auf den Knopf wird der aktuelle Index komplett gelöscht (bloß so) und mit Daten aus diversen Feeds von KURIER.at, derStandard.at, FUTUREZONE.at, OE24, Goolem und Zend gefüttern. Zur Draufgabe wird die Google-Bildersuche nach "iPhone 5" und "Calvin and Hobbes" befragt. Die Bilder (oder genauer: Die Daten) kommen ebenfalls in den Index.


Frage das Orakel

Lucene bietet eine sehr mächtige Abfragesprache. + für "muss", - für "Ausschluß", Fuzzy-Suche, Suche in bestimmten Feldern der Dokumente etc. Einfach mal ausprobieren. Es gibt übrigens auch das Tageshoroskop ;)

Deutsche Umlaute funzen leider nicht - das muss ich noch fixen. Sorry.

Das Folgende ist so der Versuch einer typischen "Live search". Es werden übrigens nicht mehr als die besten 30 Ergebnisse angezeigt, weil sich das azuf meinem Monitor schön ausgeht.




Conclusio

Das Prinzip ist so einfach wie bezaubernd, die Möglichkeiten wunderbar. Für diese einfache Präsentation wurde die Lucene Implementierung des Zend Frameworks 1.12 verwendet, die zwar kompatible zur Java-Implementierung 2.3 ist, aber längst nicht dessen Performance erreicht.
Des weiteren bietet Apache mit Apache Solr einen Enterprise-fähigen Search-Engine-Server mit simpelstem REST-Interface an. Eine wirklich sehr bestechende Sache. Dieses wird wohl auch einer der Gründe sein, warum Lucene in ZF 2.0 nicht mehr enthalten ist - Solr ist einfach viel g'schmeidiger.

Dieses Beispiel hier habe ich in 2 Stunden ( und dann noch 2 Stunden für das Beispiel, ein bisschen den Code herichten und an den RegEx-es für die Bildersuche feilen) oder so schnell zusammengeschustert, sollte aber bereits die Möglichkeiten aufzeigen. Eine Art Zauberei - aber doch nur mit Wasser gekocht. Es gbit da noch ein paar anderen Ideen, aber die möchte ich hier (noch nicht) zum Besten geben ;) Die größten Geheimnisse liegen sicher in den Fragen:

  * Wie sehen meinen Lucene Dokumente aus, welche Daten stopfe ich hinein?
  * Wie halte ich meinen Index aktuell?
  * Wie genau vorgegeben oder frei für den User suche ich?

Weiters gibt es auch noch Stoppwort-Listen und die Möglichkeit, sich eigene Text-Analyzer zu schreiben. Eine Aktualisierung indizierter Dokumente gibt es übrigens nicht. Dazu muss zuerst ein Dokument gesucht und gelsöcht werden und dann neu angelegt. Macht man diesen Vorgang öfters, empfiehlt es sich, den Index von Zeit zu Zeit mittels $Index->optimize() zu reorganisieren - eine unter Umständen sehr langwierige Operation.

Abfragen können über den erwarteten Standardweg oder eigene, spezialisierte Query-Objekte erledigt werden. Wer schon mal kilometerlange und ineffiziente SQL-Statements mit LILKE und MATCH zu debuggen versucht hat (und sich wunderte warum die DB dafür ewig braucht) wird das sicher mögen.

Zuletzt sei noch angemerkt: Die Sache gar nicht so Low-Level wie ich eingangs etwas blasphemisch gesagt habe. Da steckt ganz schön viel Gehirnschmalz der Apache-Jungs dahinter - Respekt!



Wie immer Danke für Ihre Aufmerksamkeit - Good Night & Good Luck!