Wie baut man am besten eine Suchmaschine für eine einzelne Webseite? In meinem Fall war das Szenario so: Ich habe eine Sammlung statischer HTML-Dokumente, will aber eine Suchfunktion anbieten. Also brauchte ich etwas, was die ganzen HTML-Dokumente indexiert und dann durchsuchen kann. Genau das kann Xapian. Speziell für Webseiten gibt es vom Xapian-Projekt die vorgefertigte Omega-Suchmachine.
Also, Xapian kann alles mögliche durchsuchen, man könnte dem Xapian-Index auch programmatisch Elemente der Datenbank hinzufügen. Und es muss nicht für eine Webseite sein, es kann auch sonstwo eingebunden werden – mancher mag sich an xapian als nervenden Prozess in Ubuntu erinnern. Ich aber wollte nur HTML-Seiten durchsuchbar machen und das online anbieten, und genau dafür ist Omega gedacht. Allerdings: Omega hat ein eigenes Webfrontend, das per CGI eingebunden wird. Das wollte ich nicht nutzen, denn ich habe sowieso schon ein zusätzliches dynamisches Backend laufen, und die Template-Funktion von Omega sah unschön und kompliziert aus.
Xapian und Xapian-Fu installieren
Xapian samt Omega sollte in den Quellen sein. Bei mir mit void:
sudo xbps-install xapian-omega
Dazu empfehle ich für die Sinatra-Integration das Gem xapian-fu. Also in die Gemfile:
gem 'xapian-fu'
und dann installieren mit bundle install
.
Allerdings reichte das nicht. Xapian-fu braucht die Ruby-bindings von xapian, und die waren bei mir nicht in den Quellen enthalten. Also muss in dem Fall noch das Archiv xapian-bindings heruntergeladen und die Ruby-Bindings kompiliert werden:
unp xapian-bindings-* cd xapian-bindings-VERSION ./configure --with-ruby cd ruby make sudo make install
Mit omindex indexieren
Omindex installierte sich in den PATH und ist einfach bedienbar. Meine HTML-Dateien liegen im Ordner public/, Bilder sollten ignoriert werden, dafür kam ich auf diesen Befehl:
omindex --db omega/data/default/ --filter image/*:skip public/
Der Code in Sinatra
Statt jetzt den CGI-Part von Omega zu nutzen übernimmt Sinatra mit Xapian-Fu die Suche:
require 'sinatra' require 'xapian-fu' include XapianFu get '/search' do searchterm = params['searchterm'] db = XapianDb.new(:dir => 'omega/data/default/', :create => false) results = db.search(searchterm).map{|match| {url: match.data.split("\n").detect{|x| x.start_with?('url=')}.to_s.sub('url=', ''), caption: match.data.split("\n").detect{|x| x.start_with?('caption=')}.to_s.sub('caption=', '')} } erb :search, locals: {searchresults: results, searchterm: searchterm} end
Man sieht: Der Code nimmt die Datenbank als Quelle, die vorher omindex angelegt hat.
Die Zuweisung in den results-Hash ist hässlich. Ich fand leider keinen Weg, mit xapian-fu die Suchergebnisse richtig strukturiert auszulesen. Nur match.data
ist zugänglich, womit man etwas anfangen kann. Der Weg über einen XapianDocValueAccessor, mit dem man wohl die Felder gezielt auslesen können sollte, funktionierte bei mir nicht. Eventuell codiert omindex den Suchindex zu speziell.
Die Suchergebnisse gehen dann an ein ERB-Template:
<h1>Search results for <%= h searchterm %></h1> <% if searchresults.size > 0 %> <ol id="searchresults"> <% searchresults.select{|item| ! item[:caption].empty? }.each do |result| %> <li> <a href="<%= result[:url] %>"> <span><%= result[:caption] %></span> </a> </li> <% end %> </ol>
Xapian/Omega ist keine Lösung, wenn die Webseite nur aus statischen HTML-Seiten bestehen darf. Aber es ist wohl eine gute Lösung wenn es einen echten Server gibt. Die Integration in Ruby/Sinatra ist relativ einfach.
Dass die xapian-bindings nicht einfach per gem installierbar sind ist der problematischste Punkt, auch das vorgelagerte indexieren wird nicht in jedes Projekt gut passen. Ohne statischen Webseitengenerator im Zentrum würde ich omega und omindex ignorieren und stattdessen nur mit xapian-fu arbeiten. Dann kämen die Daten eben direkt aus der Datenbank.
Hier aber war das Parsen der HTML-Seiten die perfekte Lösung.