Fündig werden

Perlfect Search ist eine leistungsfähige Suchmaschine für die eigene Website und dabei gerade mal 20 KByte groß. Mit dem geschickten Einsatz von Perl-Modulen kann man noch fehlende Eigenschaften zum Durchsuchen dynamischer Websites ergänzen.

In Pocket speichern vorlesen Druckansicht 8 Kommentare lesen
Lesezeit: 8 Min.
Von
  • Daniel Naber

Wie jede leistungsfähige Suchmaschine arbeitet Perlfect Search beim Suchvorgang nicht direkt mit den Dokumenten, sondern mit einem Index. Dieser muss vorhanden sein und ist nach jeder Änderung eines Dokuments zu aktualisieren.

Das mag etwas unpraktisch erscheinen, ermöglicht es aber, eine übliche Suchanfrage in deutlich unter einer Sekunde zu bearbeiten, selbst bei einigen tausend Dokumenten im Index. Der Vorgang der Indizierung selber kann allerdings bei einer großen Anzahl von Dateien viel Zeit kosten. Als Alternative bieten sich ht://dig und kostenlose Suchservices im Netz an, die der Kasten ‘Andere Produkte’ kurz vorstellt.

Mehr Infos

Andere Produkte

Wer einen größeren Funktionsumfang braucht und dafür höhere Komplexität in Kauf nimmt, sollte sich ht://dig anschauen. Diese Suchmaschine, die ebenfalls der GPL unterliegt, kann etliche zehntausend Seiten indizieren, sogar über mehrere Server verteilt. Wie der Name andeutet, lädt sie die Seiten dazu mit HTTP. Bei der Suche stehen die üblichen Operatoren und diverse unscharfe Suchmethoden wie Soundex zur Verfügung. Viele Optionen ermöglichen es, Indizierung und Suche genau den eigenen Vorstellungen anzupassen. Nur eine tiefgreifendere Anpassung der Ergebnisseiten ist nicht so einfach - schließlich handelt es sich um ein ausgewachsenes C++-Programm. Ist einem das alles zu viel, kann man einen externen Suchservice über die eigenen Seiten schicken. Mit der Installation und Konfiguration der Software kommt man dann gar nicht mehr in Kontakt. Eine Sammlung ebenfalls kostenloser Anbieter dazu findet sich unter www.searchtools.com/tools/tools-remote.html.

Wer sich für Perlfect Search entscheidet, kann folgende Vorteile nutzen:

  • einfache Installation;
  • sinnvolles Ranking, also nach Relevanz sortierte Treffer;
  • ‘+’ und ‘-’-Operatoren wie bei Altavista;
  • die Ergebnisseite ist leicht anzupassen;
  • Open Source (GPL).

Für die zurzeit aktuelle Version 3.09 sind unter http://mini.gt.owl.de/~dnaber/perlfect/ einige Patches mit Erweiterungen zu finden, zum Beispiel für das Indizieren von PDF-Dateien. Zuerst zur Installation: Obwohl das README das Installationsskript anpreist, ist die Einrichtung selbst ohne Skript so einfach, dass man sie am besten gleich von Hand ausführt. Im Verzeichnis cgi-bin entpackt man das Archiv. Nun sind in der Datei conf.pl die ersten fünf Optionen anzupassen, alle anderen Werte dienen der Feineinstellung und können zunächst unverändert bleiben. Bei der manuellen Installation muss man im Installationsverzeichnis ein Verzeichnis data anlegen, das die Index-Dateien aufnimmt. Alle Perl-Dateien außer search.pl sollten Rechte bekommen, die eine Ausführung als CGI nicht gestatten. Alternativ kann man sie in ein vom Webserver nicht benutztes Verzeichnis kopieren. Der Aufruf ./indexer.pl generiert den Index. Jetzt bleibt nur noch, die Datei search_ form.html, eventuell mit einem angepassten action-Wert in das Wurzelverzeichnis des HTTP-Daemons zu kopieren, und man kann mit dem Testen der Suchmaschine beginnen.

In der aktuellen Version fehlt vor allem eine Fähigkeit, die für professionelle Zwecke unerlässlich ist. Perlfect Search indiziert die HTML-Dateien nur direkt von der Festplatte. Fast jede moderne Website besteht aber zumindest zum Teil aus dynamisch generierten Seiten. Die Lösung besteht darin, die Dateien zum Indizieren über HTTP zu laden - und genau um diese Erweiterung geht es hier.

Über HTTP bekommt die Suchmaschine genau das zu sehen, was ein Anwender sähe, unabhängig davon, ob die Seite statisches HTML ist oder zum Beispiel die Ausgabe von CGI-Skripten oder Java Server Pages. Indiziert wird nur das, was auf den Webseiten verlinkt ist, Dateileichen im Filesystem lassen sich also mit der Suche nicht mehr finden.

Mit dieser Erweiterung arbeitet Perlfect Search wie die großen Suchmaschinen als ‘Spider’. Aber selbstverständlich sollte man damit keine fremden Websites indizieren. Einen korrekten Spider zu schreiben, ist ein enormer Aufwand, Fehler in der Implementierung können zu einer massiven Verschwendung von Bandbreite führen. Was es alles zu beachten gilt, steht unter www.searchtools.com/info/robots/robot-checklist.html. Perlfect Search mit Erweiterung dient explizit nur für die eigenen Server.

Mehr Infos

Listing 1: Website rekursiv durchsuchen

Auszug aus indexer_web.pl: Die Funktion crawl() durchsucht rekursiv alle URLs und speichert sie samt MD5-Signatur im Hash %list. check_url() überspringt alle URLs, die nicht im Index erscheinen sollen.

## Fetch $url and all URLs that this document links to. 
## Remember visited documents and their checksums in %list
sub crawl {
my $url = shift;
# fetch URL via http, if not yet visited:
foreach $visited_url (values %list) {
if( $url eq $visited_url ) {
print "Ignoring '$url': already visited\n"
if ( $debug );
return;
}
}
my ($url, $content) = get_url($url);
# Calculate checksum of content:
$md5->reset();
$md5->add($content);
my $digest = $md5->hexdigest();
# URL with the same content already visited?:
if( $list{$digest} ) {
print "Ignoring '$url': content identical ".
"to '$list{$digest}'\n" if ( $debug );
$list{'clone_'.$cloned_documents} = $url;
$cloned_documents++;
return;
}
# return if content could not be fetched,
# but before remember digest and URL:
$list{$digest} = $url;
return if( ! $url );
# Perlfect Search expects filenames, so make one:
my $filename = filename_from_url($url);
# call the Perlfect Search index functions:
my $doc_id = record_file($filename);
index_file($url, $doc_id, $content);
# 'parse' HTML for new URLs (Meta-Redirects and Anchors):
while( $content =~ m/
content\s*=\s*["'][0-9]+;\s*URL\s*=\s*(.*?)['"]
|
href\s*=\s*["'](.*?)['"]
/gisx ) {
my $new_url = $+;
my $next_url = next_url($url, $new_url, $digest);
if ( $next_url ) {
crawl($next_url);
}
}

}

...
## Check if URL is accepted, return 1 if yes, 0 otherwise
sub check_accept_url {
my $url = shift;
my $reject;
# ignore "empty" links (shouldn't happen):
if( ! $url || $url eq '' ) {
$reject = "empty/undefined URL";
}
# ignore foreign servers and non-http protocols:
if( $url =~ m/:\/\// && $url !~ m/^$LIMIT_URL/i ) {
$reject = "foreign server or non-http protocol";
}
# ignore file links:
if( $url =~ m/^file:/i ) {
$reject = "file URL";
}
# ignore javascript: links:
if( $url =~ m/^javascript:/i ) {
$reject = "javascript link";
}
# ignore mailto: links:
if( $url =~ m/^mailto:/i ) {
$reject = "mailto link";
}
# ignore document internal links:
if( $url =~ m/^#/i ) {
$reject = "local link";
}
# user's configuration:
foreach my $regexp (@no_index) {
if( $url =~ m/$regexp/ ) {
$reject = "matches '$regexp' from \$NO_INDEX_FILE";
}
}
if( $reject ) {
print "Ignoring '$url': $reject\n" if( $debug );
return 0;
}
return 1;
}

Die Erweiterung besteht aus einer neuen Datei indexer_web.pl (siehe Listing), neuen Optionen in conf.pl und kleinen Änderungen in indexer.pl, um auf die neuen Funktionen zuzugreifen. Die Abhängigkeiten zwischen den Dateien sind in Abb. 1 zu sehen.

So fügt sich die Erweiterung in das System ein. Ein Pfeil bedeutet ‘nutzt Variablen oder Funktionen von ...’ (Abb. 1).

In indexer.pl sorgt ein require indexer_web.pl für das Laden der Erweiterung. Von der Shell aus dient also weiterhin indexer.pl zur Erstellung des Index. In indexer.pl besetzt der Aufruf von init() zunächst einige Variablen. Dann wird die crawl()-Funktion gestartet, die rekursiv Seiten lädt und den Links darin folgt. Da es hier um Perl geht, ist klar: zum Erkennen der Links dienen reguläre Ausdrücke, die vor allem nach ‘href’ suchen. Wer Frames benutzt, aber keinen noframes-Bereich hat, ist hier schon am Ende. Javascript-Links ignoriert das Skript ebenso wie fragwürdige HTML-Syntax, etwa fehlende Anführungszeichen um URLs. Mit diesen Einschränkungen befindet sich Perlfect Search aber in guter Gesellschaft: Lässt sich die eigene Website nicht damit indizieren, dürften auch alle anderen Suchmaschinen daran scheitern.

Das Laden der Seiten geschieht in der Funktion get_url(), die dafür auf das Modul LWP::UserAgent zurückgreift. Man erzeugt dazu ein HTTP::Request-Objekt für eine bestimmte URL und übergibt dies an die request()-Funktion. Über das zurückgegebene HTTP::Response-Objekt sind dann Header und Inhalt der Seite zugänglich. Vorher ist noch zu prüfen, ob der Content-Type der Seite ein sinnvolles Indizieren überhaupt zulässt. Der andere Weg wäre, Binärdateien aufgrund des Dateinamens herauszufiltern. Bei diesem Verfahren kommen aber schnell Dutzende von Dateiendungen zusammen, und wenn man eine vergisst, indiziert man möglicherweise megabyteweise Binärmüll.

Ist der Inhalt der Datei sinnvoll, erzeugt das Skript darüber einen MD5-Hash, um keine Dateien doppelt zu indizieren. Der häufigste Fall ist, dass http://server/ und http://server/index. html dieselbe Datei liefern, der Server aber eine Anfrage auf http://server/ eben nicht mit einem Redirect beantwortet (den die Funktion get_url() durchaus beachten würde). Die MD5-Werte landen als Schlüssel des Hashes in %list, seine Werte sind die URLs. Inhaltlich gleiche Dateien hätten also gleiche Keys, was bei einem Hash unmöglich ist. Deshalb bekommen die Dubletten Keys mit einer laufenden Nummer. So bleiben ihre URLs erhalten, und man kann sie bei wiederholtem Auftreten direkt ignorieren, ohne die Prüfsumme berechnen zu müssen.

In indexer.pl sollte sich durch diese Erweiterung möglichst wenig ändern. Deshalb baut filename_from_url() aus der URL einen Dateinamen, den man an record_file() aus indexer.pl übergeben kann. Die eigentliche Indizierung findet also nach wie vor in indexer.pl statt und zwar durch die Funktion index_file(). Dann durchsucht das Skript die Datei nach Links, mit denen sich crawl() schließlich selber aufruft, sofern sie auf denselben Server zeigen. Ob das der Fall ist, stellt check_accept_url() fest. Es filtert dabei zugleich mailto:- und javascript:-Links heraus - sowie alle in conf/no_index.txt angegebenen Muster.

Berkeley DB Hashes in Perlfect Search
DB Key Value
docs_db Dokumenten-ID Dateiname
desc_db Dokumenten-ID Beschreibung aus META-Tag oder generiert aus dem Anfang vom BODY
titles_db Dokumenten-ID TITLE-Tag
terms_db Term ID für inv_index_db und temporäre Hashes
inv_index_db ID aus terms_db Liste mit Paaren (Dokumenten-ID, Relevanz des Dokuments bezüglich dieses Terms)
df_db Term-ID Anzahl, in wie vielen verschiedenen Dokumenten dieser Term auftaucht (temporärer Hash)
tf_db Term-ID Liste mit Paaren (Dokumenten-ID, Anzahl der Vorkommen des Terms in diesem Dokument) (temporärer Hash)

Dank dem URI-Modul ist die Behandlung relativer Links ein Kinderspiel. next_url() generiert damit aus einer URL eine neue absolute URL. Dabei beachtet es den Spezialfall, dass Links mittels mehrerer ‘../’ zu weit nach oben verzweigen können, auf Dateien, die gar nicht mehr im Wurzelverzeichnis liegen. Solche Verweise werden als Links auf die oberste Ebene des Servers interpretiert - ein Zugeständnis an defekte Seiten, das die gängigen Browser genauso zulassen.

Sollte die Indizierung stecken bleiben und nicht alle Dateien finden, hilft in vielen Fällen die Debugging-Ausgabe, die sich mit $debug = 1 aktivieren lässt. In schwierigeren Fällen empfiehlt es sich, die Seiten auf korrekte HTML-Syntax zu prüfen und die gesamte Website manuell mit ausgeschaltetem Java/Javascript zu kontrollieren. Gibt es dabei Probleme, ist es um die Indizierbarkeit schlecht bestellt - nicht nur für Perlfect Search.

Wer jetzt auf den Geschmack gekommen ist und selber Anpassungen vornehmen möchte, findet in der Tabelle unten eine Liste der erzeugten permanenten und der wichtigsten temporären Datenstrukturen. Alle Daten speichert das Skript im Berkeley-DB-Format, mit AnyDBM_File benutzt es dazu ein auf dem Rechner vorhandenes Modul. Bei Schwierigkeiten installiert man möglichst DB_File, damit läuft es auf jeden Fall. Treten trotzdem mysteriöse Fehler auf (inklusive ‘Division by zero’), hilft es oft, alle Dateien im Verzeichnis data zu löschen und die Indizierung noch einmal von vorne zu beginnen. Solche Fehler entstehen nicht im normalen Betrieb, wohl aber, wenn man eine laufende Indizierung mittels Ctrl-C abbricht und unvollständige Daten zurückbleiben.

Hier ging es nur um die technische Seite einer Suchmaschine. Die Suchfunktion bietet sich auch an, um die Benutzbarkeit der eigenen Website deutlich zu verbessern. Eine kleine Linksammlung, die dazu Hinweise gibt, findet man unter http://usableweb.com/topics/000744-0-0.html.

Daniel Naber
studiert naturwissenschaftliche Informatik an der Uni Bielefeld und arbeitet bei fractales multimedia als Web-Developer.

Mitarbeit am Artikel: Giorgos Zervas (ck)