PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


Archive for the ‘Volltextsuche’ tag

Die eigene Suchmaschine in PHP leicht gemacht: Lucene

with 10 comments

Nicht nur Google hat ausgereifte Suchalgorithmen, jeder Programmierer kann sich auch seine eigene Volltextsuche auf die Webseite bauen. Das können zum Beispiel alle Unterseiten sein, die durchsucht werden sollen, aber auch Dateien, Emails, Dokumente und Texte jeglicher Art.

Ich werde am Ende auch kurz aufzeigen, warum der Mysql-Volltextindex kein guter bzw. schneller Index ist, und warum Lucene und andere Suchengines ihre Daseinsberechtigung haben.

In diesem Artikel soll es also um Lucene gehen. Lucene ist ein Open-Source-Suchalgorithmus, der als Apache-Projekt weiterentwickelt wird und auf den viele weitere Produkte aufbauen (das bekannteste ist wohl Solr). Der Grundaufbau einer solchen „Suchmaschine“ besteht aus 2 Teilen: Dem Indexer und der Suche.

Der Indexer ist zum Befüllen des Datenbestandes (des Indexes) zuständig. Ihm übergibt man also alle Texte und Dokumente, und sagt ihm dabei, welche Felder und Daten davon wichtig sind, und eventuell noch wie wichtig die einzelnen Dokumente sind. Lucene ist zum Beispiel auch in der Lage, HTML-Dateien zu parsen und daraus title, meta-tags, header usw zu extrahieren. Man spart also Arbeit, und kann die Suche später auf die entsprechenden Bereiche beschränken. Der Index wird dann im Dateisystem abgelegt.

Die Suche spuckt dann die Ergebnisse aus, wenn man sie mit mehr oder minder komplexen Suchaufgaben befeuert. Dabei sind nicht nur einfache Stichwortsuchen möglich, sondern auch „ungefähre Treffer“, man erhält einen Relevanzwert(Score) und noch einige weitere Informationen.

Wenn wir nun in PHP einen solchen Index aufbauen wollen, nutzen wir am besten die Zend_Search_Lucene Klassen dafür. Hier ein einfaches Beispiel, wie man den Index füllt:

<?php

include_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();

$index = Zend_Search_Lucene::create('/tmp/index');

$document = new Zend_Search_Lucene_Document();
$document->addField(Zend_Search_Lucene_Field::Text('title', 'Titel 1 des Dokuments'));
$document->addField(Zend_Search_Lucene_Field::Text('content', 'Hier ist ein toller Text'));
$index->addDocument($document);

$document = new Zend_Search_Lucene_Document();
$document->addField(Zend_Search_Lucene_Field::Text('title', 'Das hier ist der zweite Titel'));
$document->addField(Zend_Search_Lucene_Field::Text('content', 'Und hier steht der Inhalt eines Buches'));
$index->addDocument($document);

Wir definieren also ein Verzeichnis, in dem der Index abgelegt werden soll. Dann erstellen wir ein Dokument, zu dem wir dann ein Feld hinzufügen, in diesem Fall ein Textfeld. Dieses wird gesplittet und jedes Wort kann als Suchwort genutzt werden. Text-Felder werden zum Index hinzugefügt und komplett gespeichert, um sie bei den Ergebnissen auszugeben. Es gibt auch noch weitere Feldtypen, die zum beispiel nur indiziert aber nicht gespeichert werden, oder nur gespeichert und nicht indiziert. Hier gibt es eine Übersicht der Feldtypen.
Zum Schluss fügen wir das Dokument noch zum Index hinzu. Um die Suche nachher etwas interessanter zu machen, fügen wir noch ein weiteres Dokument hinzu. Das Ergebnis sieht dann so aus:

index

Reingucken brauchen wir da nicht, denn der Inhalt ist relativ kryptisch. Wir wollen ja auch nicht direkt auf diese Dateien zugreifen, sondern mittels der Suche. Das geht wie folgt:

<?php

include_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();

$index = Zend_Search_Lucene::open('/tmp/index');

$queries = array('Buch', 'toller', 'ist', 'title:ist');

foreach ($queries as $query) {
	$results = $index->find(
	  		Zend_Search_Lucene_Search_QueryParser::parse($query)
	);
	echo "Suche: " . $query . "\n";
	echo count($results) . " Ergebnisse \n\n";
	foreach ($results as $result) {
		echo 'Inhalt: ' . $result->content . "\n";
		echo 'Score: ' . $result->score . "\n";
		echo  "\n";
	}
}

Die Abfragen können normale Sucheworte sein, man kann nur in bestimmten Feldern suchen, boolsche Operatoren (AND/OR) nutzen als auch noch viel komplexere Abfragen starten.

Die Ausgabe sieht wie folgt aus:

luceneresult

Es ist also wirklich kein Hexenwerk, mit knapp 30 Zeilen haben wir sowohl den Index gefüllt als auch einige Suchabfragen gestartet und die Ergebnisse ausgegeben.
Mit Lucene kann man noch sehr viel mehr machen, alles hier aufzuzählen würde den Rahmen sprengen. Einfach mal im Zend Framework Manual gucken, dann bekommt man einen Eindruck, was alles möglich ist.

Achso, ich erwähnte ja noch, dass ein Mysql-Volltextindex nicht so sinnvoll ist. Sobald große Mengen an Daten anfallen, wird Mysql langsam. Hier gibt es ein wunderbares PDF-Dokument mit Benchmarks.

Interessant sieht auch Sphinx aus, habe mich allerdings noch nicht damit beschäftigt.

Lucene ist also besonders interessant bei Daten, die nicht bereits in der Datenbank vorhanden sind. Als Beispiele wären da Dokumente, Twitter-Nachrichten, Emails oder statische HTML-Dateien genannt. Bevor man das also in seine Datenbank pumpt, nur um eine langsame Volltextsuche zu erhalten, sollte man lieber Lucene benutzen.

Written by Michael Kliewe

November 26th, 2009 at 9:48 am