Skip to topic | Skip to bottom
Home
Zope.SuchenImZoper1.19 - 04 Sep 2006 - 13:22 - FrankBurkhardt [Zum Ende]

Start of topic | Direkt zum Menü

Volltextsuche in Zope3

Auf dieser Seite soll beschrieben werden, wie man im Zope3-Webauftritt eine Suchmaschine mit Volltextsuche realisiert. Dabei wird sowohl die Theorie dahinter als auch die praktische Durchführung erklärt. Es sind auch komplexere Methoden denk- und machbar, ContentObjects unter Zope3 zu suchen, der Zope3-Neuling ist aber meist primär an einer Volltextsuche interessiert.

Das Prinzip

Natürlich wird nicht in Echtzeit gesucht - zumindest nicht über den Content aller Objekte der ZopeInstance. Das Prinzip ist folgendes:
  • Werden ContentObjects hinzugefügt, so werden diese mit einer ZodB -weit eindeutigen ID versehen. Die Komponente, die das erledigt (Das IntId -UtilitY) muss umbedingt vor allen zu durchsuchenden Objekten eingefügt werden. Alle vor diesem UtilitY angelegten Objekte werden niemals gefunden. Genauere Information und eine Möglichkeit, wie solche Objekte doch gefunden werden können, gibt's unter CookBook#MissingIds .
  • Ein weiteres UtilitY - der CataLog - wird mit einem oder mehreren Indizes bestückt. Der CataLog sorgt dafür, dass diese Indizes mitbekommen, wenn ContentObjects erstellt, modifiziert bzw. entfernt werden. Das passiert mittels EvenT.
  • Man definiert per InterFace genau, welche Art von ContentObjects man finden will. Das könnte z.B. zope.index.text.interfaces.ISearchableText sein - ein Zope-Standard- InterFace für die Extraktion von einfachem Text.
  • Eine Suchanfrage des Benutzers wird durch das Aufrufen einer VieW mit Parametern realisiert.
  • Die Klasse hinter der VieW greift auf den CataLog zu und bekommt eine Liste mit ContentObjects zurück, die die Suchkriterien erfüllen.
  • Die Liste wird mit einem PageTemplate so aufbereitet, dass der Anwender seine Suchergebnisse als schickes HTML sieht. Man sollte dabei gleich dafür sorgen, dass der Anwender nur Ergebnisse angezeigt bekommt, die er später auch sehen darf.

Umsetzung

  • Bevor man Daten in den Webauftritt aufnimmt, muss man im SitE-Manager ein IntId -UtilitY hinzufügen und RegisTrieren?. Dieses kümmert sich daraum, dass alle ContentObjects eine einheitliche ID bekommen - die Infrastruktur hinter der Suchmaschine besteht darauf.
  • Man benötigt einen einheitliches Such- InterFace für ContentObjects, die in der ZopeInstance zum Einsatz kommen. Dieses InterFace muss entweder von den entsprechenden Objekten direkt oder per Adapter bereitgestellt werden.
  • Ebenfalls im SitE -Manager muss ein CataLog -UtilitY hinzugefügt werden. Davon kann man auch mehrere haben. Dieses muss registriert? werden. Für die folgenden Beispiele wird davon ausgegangen, dass es unter dem Namen googlelike mit dem Zugriffsrecht zope.Public registriert wird.
  • Der hinzugefügte CataLog ist ein ConTainer für Indizes.
  • In den CataLog fügt man einen TextIndex ein, der auf das einheitliche Suchinterface (z.B. zope.index.text.interfaces.ISearchableText ) eingeschossen wird, im Beispiel das Feld getContent ausliesst und das Feld aus callable betrachtet.
  • Man benötigt eine VieW (man kann diese z.B. an den RootFolder anhängen), in der eine Suchanfrage ausgewertet und die Suchergebnisse angezeigt werden.

Eine Suchanfrage

So sieht eine normale Suchanfrage aus, wobei query der Suchstring ist - also üblicherweise das, was der Benutzer in den Browser hackt:

from zope.app.catalog.interfaces import ICatalog
from zope.app import zapi
catalog = zapi.getUtility(ICatalog, 'googlelike')
results=catalog.searchResults(query)

Folgende query -Syntax wird erkannt:

  • meinwort sucht nach dem Wort mainwort - zeigt ausschliesslich Objekte an, wo exakt dieses Wort gefunden wurde - das kann auch der normale TextIndex.
  • meinwort meinzweiteswort oder auch meinwort and meinzweiteswort sucht nach Objekten, die beide Worte enthalten.
  • "wort1 wort2" sucht genau nach dieser Wortkonstellation (wort2 direkt hinter wort1)
  • wort1 -wort2 oder auch wort1 not wort2 liefert Objekte, die wort1 , jedoch nicht wort2 enthalten.
  • anfang*ende sucht nach Wörtern, die mit anfang beginnen und mit ende aufhören. Wildcards wir * (beliebig viele Zeichen) oder ? (genau ein Zeichen) können sowohl am Anfang, als auch in der Mitte oder am Ende eines Wortes stehen und entsprechen Globber-Ausdrücken wie von der Bourne-Shell gewohnt.

Es existiert ein erweiterter Index namens TextIndexNG3, der noch mächtigere Suchanfragen ermöglicht.

Bausteine der Suchmaschine

Das einheitliche Suchinterface

Damit eine Suche Sinn macht, müssen Objekte ein einheitliches InterFace bieten, an dem die Suchmaschine den Inhalt der Objekte im Volltext abgreifen kann. Zope3 bringt schon ein solches InterFace mit:

zope.index.text.interfaces.ISearchableText

Dieses InterFace stellt nur eine Methode zur Verfügung:

class ISearchableText(Interface):
   def getSearchableText():
[...]

Komplexere Such- InterFaces sind denk- und machbar. MpgSite bringt z.B. eines mit, dass grossen Wert auf Mehrsprachigkeit legt (siehe MpgSiteSearch#InterFace).

Alle ContentObjects, die man so durchsuchen will, müssen dieses InterFace irgendwie bereitstellen. Dafür gibt's zwei Möglichkeiten. Hier ein noch nicht durchsuchbares ContentObject:

app.py

[...]
class MyObject(Persistent,Contained):
   def __init__(self,data=''):
      self.data=data

configure.zcml

   <class class=".app.MyObject>
      <implements interface=".interfaces.IMyObject" />
      [...]
   </class>

ContentObject aufmotzen

Man kann ein eigenes ContentObject einfach um die Funktionalität des Suchinterfaces erweitern. So hat man alles in einer kompakten Klasse.

app.py

[...]
class MyObject(Persistent,Contained):
   def __init__(self,data=''):
      self.data=data
   def getSearchableText(self):
      return (self.data,)

configure.zcml

   <class class=".app.MyObject>
      <implements interface=".interfaces.IMyObject" />
      <implements interface="zope.index.text.interfaces.ISearchableText" />
      [...]
   </class>

Nachteile:

  • Sollte irgendjemand eine Idee haben, den Text aus MyObject -Objekten auf eine bessere/schnellere/coolere Weise in durchsuchbaren Text umwandeln zu können, hat er schlechte Karten - es gibt keine Möglichkeit die Suchinterface-Funktionalität jetzt auszutauschen, ohne am Code des ContentObject selbst Änderungen vorzunehmen.

AdapTer schreiben

Das ist die empfohlene Methode um ein ContentObject bis zur Suchfähigkeit aufzurüsten. Der Suchindex versucht immer, ein zu indizierendes Objekt zum Suchinterface zu adaptieren, bevor er es als nicht-indizierbar einstuft. man schreibe und registriere also einfach einen entsprechenden AdapTer:

adapters.py

from zope.index.text.interfaces import ISearchableText
from zope.component import adapts
from zope.interface import implements

class MyObjectSearchable(object):
   def __init__(self,myobject):
      self.context=myobject

   def getSearchableText(self):
      return (self.context.data,)

configure.zcml

   <class class=".app.MyObject>
      <implements interface=".interfaces.IMyObject" />
      [...]
   </class>
   <adapter
      factory=".adapters.MyObjectSearchable"
      provides="zope.index.text.interfaces.ISearchableText"
      for=".interfaces.IMyObject"
   />

Indizes

Indizes sind die eigentlich Arbeitstiere der Suchmaschine. Zope3 bringt bereits 2 Indextypen mit:

  • Feldindex: Damit kann man einfache Datentypen in Feldern indizieren und danach je nach Feldwert Listen mit matchenden Objekten erzeugen.
    Anwendungsbeispiel: Ein Index geburtstage wird benutzt, um alle PIM-Personen-Objekte von Leuten zu finden, die zwischen 1970 und 1980 geboren wurden.
  • Textindex: Dieser Index nimmt ein Feld entgegen, interpretiert dieses ausschliesslich als Text, spaltet diesen in Wörter auf und merkt sich diese. In einer anschliessenden Suche kann der Index aus den Wörtern der Suchanfrage ermitteln, in welchen Dokumenten diese vorkommen und liefert eine Liste dieser Dokumente zurück.

Für eine reine Volltextsuche benötigen wir davon genau einen TextIndex oder auch einen Ting3-Index, der einige zusätzliche Goodies und Flexibilität bietet.

Index-Komponenten speichern den eigentlich Suchindex. Sie iterieren nicht über Objekte, sondern werden vom CataLog mit solchen versorgt.

Catalog

Siehe CataLog.

Die Such- VieW

Eine solche VieW braucht man, weil der Benutzer ja eine Liste der Suchergebnisse zu Gesicht bekommen will, was nur geht, wenn er eine Anfrage an den Zope-Server stellt, die per Definition immer mit einer VieW beantwortet wird. Im Beispiel hängen wir eine VieW an den RootFolder, die den Namen suchmaschine.html trägt und den Parameter query auswertet. Man sucht also wie folgt:

http://zopeserver/suchmaschine.html?query=suchanfrage

Ein PageTemplate, das ein Eingabefeld auswertet und per HTTP-Get (HTTP-Put geht auch) an diese VieW weiterleitet ist schnell selbst geschrieben.

configure.zcml

[...]
   <browser:page
      for="zope.app.folder.interfaces.IRootFolder"
      name="suchmaschine.html"
      layer="mpgsite.skin.mpg"
      class=".views.SearchEngineView"
      permission="zope.View"
      template="searchresults.pt"
      />
[...]

views.py

from zope.app.catalog.interfaces import ICatalog
from zope.app import zapi

class SearchEngineView(BrowserView):
   def __call__(self):
      catalog = zapi.getUtility(ICatalog, 'googlelike')
      query=self.request['query']
      self.searchresults=catalog.searchResults(fulltext=query)
      return self.index()

searchresults.pt

<metal:block metal:use-macro="context/@@standard_macros/view">
<div metal:fill-slot="body">
   <h2>Search results</h2>
   <div tal:repeat="obj view/searchresults">
      <a tal:attributes="href obj/@@absolute_url" tal:content="obj/@@absolute_url" />
   </div>
</div>
</metal:block>

Die Suchmaschine ist sehr rudimentär, sollte aber funktionieren. Folgendes wird man auf jeden Fall noch einbauen wollen:

  • Filterung nach Zugriffsrechten - schliesslich soll nicht jeder alle Seiten sehen dürfen.
  • Batch-Anzeige. Niemand will 16000 Treffer auf einer Seite begutachten - und kein Serverbetreiber will die dafür nötige Rechenzeit zur Verfügung stellen.
  • Anzeige von Titel und kurzem Textausschnitt der Seite. Mit Zope-Bordmitteln kann man noch weitere Dinge anzeigen - z.B. die Zeit der letzten Modifikation oder den Autor des Objektes (Stichwort DublinCore).
  • Sprachangepasste Suche. Das Paket MpgSite kann so etwas (siehe MpgSiteSearch).
  • Ergebnisliste=Suchmaske. Oft will der Benutzer seine Suchen noch etwas modifzieren (z.B. bei einem Tippfehler oder wenn zusätzliche Optionen einfliessen sollen). Dann sollte die Suchergebnisseite gleich noch ein Textfeld enthalten, um das Such- VieW erneut auszulösen. Eine kurze Änderung am PageTemplate reicht dafür.
  • Diese Suche nimmt kein Ranking vor, jedoch ruhen einige Hoffnungen auf TextIndexNG3 , das in Zukunft ein Ranking nach Wortdichte bieten soll.
  • Treten Fehler auf wird eine nichtssagende Seite anstelle der Ergebnisliste angezeigt. Das kann man abfangen und z.B. darauf hinweisen, dass der Benutzer eine bestimmte Syntax einhalten muss, um den Query-Parser nicht zu verärgern.
    Hinweis: TextIndexNG3 bietet mehrere Query-Parser. Zwei davon sind so einfach gestrickt, dass sie sich niemals beschweren. Das mindert jedoch auch die Macht der Suchfunktion.

Links


[Zurück zum Start]

Aktuelle Wiki-Seite: Zope > SuchenImZope

[Zurück zum Start]

Alle Inhalte dieses Web dürfen frei kopiert und verändert werden.