PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


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

10 Responses to 'Die eigene Suchmaschine in PHP leicht gemacht: Lucene'

Subscribe to comments with RSS or TrackBack to 'Die eigene Suchmaschine in PHP leicht gemacht: Lucene'.

  1. […] Dieser Eintrag wurde auf Twitter von Michael Kliewe, open source erwähnt. open source sagte: Die eigene Suchmaschine in PHP leicht gemacht: Lucene | PHP …: Lucene ist ein Open-Source-Suchalgorithmus, der als Apa http://url4.eu/pZxC […]

  2. Das du Lucene empfehlst wundert mich ja etwas…
    Schau dir mal Sphinx an 😉

    xn

    26 Nov 09 at 10:30

  3. > Sobald große Mengen an Daten anfallen, wird Mysql
    > langsam.

    Dies trifft leider aber auch auf Zend_Search_Lucene zu. habe damit selber gerade zu kämpfen. Habe eine recht komplexe Suche gebaut und die geht bereits bei 10.000 Datensätzen in die Knie. Bei anderen Indizes kann diese Zahl auch höher liegen.

    Diese Einschränkung trifft natürlich nicht auf Lucene selber zu. Das ist sehr schnell.

    Gruss,
    Ralf

    Ralf Eggert

    26 Nov 09 at 10:35

  4. @xn: Ich habe Lucene vorgestellt und zumindestens Lucene vor Mysql gestellt. Sphinx habe ich mir noch nicht angeschaut, wie ich geschrieben habe, in dem Benchmark hat es ja besser abgeschnitten. Kommt Zeit kommt Artikel 😉

    @Ralf: Erstmal schön, dich hier zu sehen 😉
    Liegt das Problem also an der ZF-Umsetzung, sprich an den Zend_Search_Lucene Klassen? Wäre ja mal interessant, mit einem anderen Client auf den Index zuzugreifen und dann die Zeit zu messen.
    Hast du dir schon Sphinx (das ZF-Proposal steckt ja leider noch in den Kinderschuhen, aber es gibt ja PHP-Funktionen) angeschaut, oder Zend_Search_Xapian? Warum hast du Lucene genommen?

    In naher Zukunft werde ich Lucene auch beruflich brauchen und mal mit größeren Datenmengen füttern, mal sehen was dann passiert.

    Michael Kliewe

    26 Nov 09 at 11:14

  5. Naja, es liegt halt daran, dass Zend_Search_Lucene eben komplett in PHP entwickelt wurde. Das ist naturgemäß langsamer als das „originale“ Lucene.

    Mein konkretes Problem liegt daran, dass bei einer Suche immer alle Datensätze angefasst werden müssen, selbst wenn man nur 20 auf einer Seite ausgeben möchte. Es werden also erst 10.000 Datensätze gelesen, Objekte erstellt und dann kann erst sortiert werden. Diese 10.000 Datensätze bekommt man dann zurück und holt sich nur die 20, die man auf der Seite ausgeben möchte. Die anderen 9.980 Datensätze braucht man dann nicht mehr. Dieser Overhead zieht solch eine Suche ganz schön in die Länge. Aber auch das Indizieren wird langsamer je mehr Datensätze vorhanden sind. Ob diese Art der Implementierung auch anders geht, weiss ich nicht. Derzeit ist Zend_Search_Lucene leider so aufgebaut.

    Sphinx und Xapian kenne ich noch nicht, habe ich also auch noch nicht ausprobiert. Zend_Search_Lucene habe ich genommen, weil es bis dato nichts anderes im ZF gab und ich mir mehr Performance erhofft hatte.

    Ralf Eggert

    26 Nov 09 at 11:50

  6. Man muss sich bei ZSL halt bewusst sein, dass es nicht skaliert. Für kleinere Projekte ist es aber eine schnell umzusetzende Möglichkeit eine Suche zu implementieren.

    Wir haben gerade ein Projekt mit Sphinx umgesetzt. Sehr schöne Sache. Besonders wenn man sich sein MySQL mit der SphinxSE (http://www.sphinxsearch.com/docs/current.html#sphinxse) kompiliert. Da brauchts dann auch keinen Client mehr sondern nur noch reines SQL (inklusive der Möglichkeit Tabellen zu joinen). Der Nachteil ist natürlich, dass eine Implementierung sehr viel auswändiger ist als bei ZSL.

    Jan

    26 Nov 09 at 16:37

  7. Na, wenn wir schon Alternativen in den Ring werfen .. dann gleich richtig 🙂 http://lucene.apache.org/solr/ – kann ich sehr empfehlen .. klar, braucht zwar ne Java Grundlage, aber für ne ordentliche Suche sollte das nicht stören. Hat alles dabei was man sich träumen lässt .. Suche in Range, Highlighter, Debugger, Facet-Search usw usw

    Steffkes

    27 Nov 09 at 10:32

  8. […] Die eigene Suchmaschine in PHP leicht gemacht: Lucene Ich habe ja bereits über Lucene als Suchmaschine für WordPress berichtet. Nun hat Michael Kliewe auf PHP Gangsta ein interessantes Tutorial zur Nutzung von Lucene in PHP veröffentlicht. Alles ganz einfach wenn man das Zend Framework verwendet, was das WordPress Plugin übrigens auch tut! 🙂 […]

  9. […] […]

  10. […] lot of clones for different programming languages. In this post I will introduce a solution I found here (unfortunately in German […]

Leave a Reply

You can add images to your comment by clicking here.