acts_as_solr mit Rich Document Indexing
Ein herzliches Dankeschön an die Central Virginia Ruby Enthusiasts‘ Group, die mich eingeladen hat, einen Vortrag über Solr+Ruby zu halten.…
Ein herzliches Dankeschön an die Central Virginia Ruby Enthusiasts‘ Group, die mich eingeladen hat, einen Vortrag über Solr+Ruby zu halten. Das war für mich ein guter Grund, mich wieder intensiv mit solr-ruby und acts_as_solr zu beschäftigen.
Lassen Sie uns ein Rails-Projekt von Grund auf neu beginnen, um zu zeigen, wie einfach es ist, mit acts_as_solr loszulegen. Das Beispiel ist in der Tat ein ziemlich realistischer Bedarf. Wir werden Lebensläufe indizieren, die in Standard-Rich-Document-Formaten wie PDF, Word, HTML oder einfachem Text vorliegen können.
rails resume
cd resume
script/generate scaffold resume first_name:string last_name:string file_name:string
rake db:migrate
Dank der Magie von Rails haben wir jetzt eine funktionierende Anwendung, die Standard-CRUD-Operationen auf einer Tabelle mit Lebensläufen in einer relationalen Datenbank ermöglicht. (wird hier nicht weiter erläutert, aber starten Sie script/server und navigieren Sie zu den üblichen http://localhost:3000/resumes. Wir werden uns näher an das Metall halten und script/console für direkte ActiveRecord- und Solr-API-Basteleien verwenden)
Als nächstes fügen wir das Plugin acts_as_solr zu unserer Anwendung hinzu:
script/plugin install git://github.com/mattmatt/acts_as_solr.git
Eine Anmerkung zur acts_as_solr-Codebasis: Alles begann mit einem unschuldigen Hack, den ich auf der Solr-User-Liste gepostet habe. Er wurde von Thiago aufgegriffen [Anm. d. Red.: 18.3.09: Thiago Jackiw wurde besonders erwähnt] und er machte daraus ein ernstzunehmendes, universell einsetzbares ActiveRecord-Modellierungs-Plugin , das bei RubyForge gehostet wird und jetzt als zahlreiche Git-Repository-Forks existiert. Die derzeit am besten gepflegte Version ist der Zweig von Mathias Meyer.
Und wir starten Solr:
rake solr:start
Wir fügen nun Solr in den Lebenszyklus des Lebenslaufmodells ein, so dass ein Lebenslauf, wenn er in der Datenbank hinzugefügt oder aktualisiert wird, auch in Solr indiziert und aus Solr gelöscht wird, wenn er aus der Datenbank entfernt wird. Es könnte wirklich nicht einfacher sein:
class Resume < ActiveRecord::Base
acts_as_solr
end
Das Einbinden von acts_as_solr bietet nicht nur die Lifecycle-Hooks, um die Datenbank und Solr synchron zu halten, sondern auch zusätzliche Finder-Methoden. Hier ist ein Beispiel für die Verwendung von Resume#find_by_solr:
$ script/console
>> Resume.create(:first_name=>;'Joe', :last_name=>'Programmer')
>> Resume.find_by_solr("program*")
=> 0, :total=>1, :docs=>[#]}>
Die Abfrage „program*“ findet alle indizierten Wörter, die mit „program“ beginnen. Beachten Sie, dass das Ergebnis von find_by_solr eine ActsAsSolr::SearchResults Instanz ist. Dieser Wrapper liefert die Dokumente, die normalerweise von ActiveRecord-Suchmethoden zurückgegeben werden, zusätzlich zu anderen Solr-Informationen, einschließlich der query_time und der Gesamtzahl der gefundenen Dokumente. Die Reihenfolge des Dokumenten-Arrays ist standardmäßig absteigend (ein Maß für die Relevanz einer Abfrage).
So weit so gut – wir haben eine Rails-Anwendung mit einem ActiveRecord-Modell, das mit acts_as_solr verknüpft ist. Jetzt kommt der kniffligere Teil der Indizierung des Lebenslauftextes.
Solr Cell
Eine Bibliothek zur Extraktion von Inhalten (auch bekannt als Solr Cell) wurde in Solr 1.4 hinzugefügt. Zum Zeitpunkt der Erstellung dieses Artikels bettet acts_as_solr jedoch Solr 1.3 ein. Wir müssen also ein wenig hacken, um eine neuere Version von Solr mit den Solr Cell-Abhängigkeiten und der Konfiguration einzubinden. Es ist wahrscheinlich, dass acts_as_solr in Zukunft mit integriertem Solr Cell ausgeliefert wird.
Stoppen Sie zunächst Solr:
rake solr:stop
Holen Sie sich einen nächtlichen Build von Solr von http://people.apache.org/builds/lucene/solr/nightly/. Entpacken Sie die Distribution und kopieren Sie das Verzeichnis lib, das das Solr Cell Plugin und die Abhängigkeiten enthält, und ersetzen Sie auch solr.war (die gesamte Solr-Webanwendung).
cp -R apache-solr-nightly/example/solr/lib resume/vendor/plugins/acts_as_solr/solr/solr/
cp apache-solr-nightly/example/webapps/solr.war resume/vendor/plugins/acts_as_solr/solr/webapps/solr.war
Und nun fügen Sie den Solr Cell Request Handler zu vendor/plugins/acts_as_solr/solr/solr/conf/solrconfig.xml hinzu (fügen Sie ihn irgendwo als Geschwister zu den anderen definierten Request Handlern hinzu):
Und aktivieren Sie das Remote-Streaming, indem Sie enableRemoteStreaming=“true“ auf dem Element requestParsers einstellen.
Bei der Aktivierung von Remote-Streaming wird eine strenge Warnung ausgegeben: „Stellen Sie sicher, dass Ihr System über eine Authentifizierung verfügt, bevor Sie Remote-Streaming aktivieren! Wir raten Ihnen, Solr mit einer Firewall so zu schützen, dass nur der Anwendungsserver oder in diesem Beispiel einfach localhost selbst Anfragen an Solr stellen kann. Wenn Sie das Remote-Streaming aktivieren, können einige Request-Handler, sofern konfiguriert, Inhalte von einer URL oder einem lokalen Dateipfad abrufen. Das ist nicht unbedingt etwas Schlechtes, aber die Einschränkung, wer oder wo Anfragen an Solr gestellt werden können, ist eine kluge Überlegung für den Produktionseinsatz. Selbst wenn das Remote-Streaming deaktiviert ist, ist der allgemeine Zugriff auf /update möglich und Dokumente können problemlos hinzugefügt oder gelöscht werden. Betrachten Sie dies also als ein Problem bei der Produktionsbereitstellung, das Sie in Ihrer Netzwerkarchitektur berücksichtigen sollten.
Dies gibt uns nun die Möglichkeit, umfangreiche Dokumentinhalte mit einfachen Anfragen an Solr zu indizieren. Dank der Flexibilität von Solr beim Streaming von Inhalten kann Solr den Inhalt einer Datei von einem lokalen Dateipfad, einer aus der Ferne zugänglichen URL oder durch die Datei selbst, die in der Anfrage gepostet wird, abrufen. In dieser Übung werden wir Solr einen lokalen Dateipfad senden, was voraussetzt, dass die Solr- und Ruby ActiveRecord-Anwendungsebene denselben Pfad sehen können. Hier sehen Sie ein Beispiel für eine einfache Anfrage, die für die Indizierung einer PDF-Datei erforderlich ist:
curl "http://localhost:8982/solr/update/extract?stream.file=/path/to/ErikHatcherResume.pd&ext.idx.attr=false&ext.def.fl=text_t&ext.ignore.und.fl=true&ext.map.title=title_t&ext.literal.id=1&wt=ruby"
Das sind ein paar hässliche Parameter, aber zum Glück werden sie auf der Solr Cell Wiki-Seite im Detail beschrieben. Die Solr-Anfrage in Prosa – das lokale /path/to/ErikHatcherResume.pdf wird an Solr gesendet, Solr liest den Inhalt dieser Datei, der Text wird in das text_t-Feld extrahiert, undefinierte Felder werden ignoriert, extrahierte allgemeine Attribute werden ignoriert, aber das title-Feld wird auf das title_t-Feld abgebildet und das id-Feld wird buchstäblich auf den Wert von 1 abgebildet. Das allgemeine Schema acts_as_solr verfügt über eine bequeme *_t-Feldzuordnung, um sowohl den Textinhalt als auch die Metadatenattribute nach Bedarf einzubringen. Alle *_t-Felder werden intern in ein einziges durchsuchbares „Text“-Feld kopiert.
Die solr-ruby Bibliothek bietet zum Zeitpunkt der Erstellung dieses Artikels keine integrierte Unterstützung für Solr Cell-artige Anfragen, obwohl sie die Verwendung benutzerdefinierter Anfragetypen problemlos ermöglicht. Hier ist unsere solr_cell_request.rb:
class SolrCellRequest < Solr::Request::Select
def initialize(doc,file_name)
params = {
'ext.idx.attr' => false, # don't index any attributes, unless explicitly mapped
'ext.def.fl' => 'text_t', # all text extracted goes to text_t (since it is a stored field, for highlighting)
'ext.ignore.und.fl' => true, # ignore all undefined fields
'ext.map.title' => 'title_t',
'ext.resource.name' => file_name, # TIKA-154 workaround
'stream.file' => file_name,
}
doc.each do |f|
params["ext.literal.#{f.name}"] = f.value
if f.boost
params["ext.boost.#{f.name}"] = f.boost
end
end
super(nil,params)
end
def handler
'update/extract'
end
end
class SolrCellResponse < Solr::Response::Ruby
end
Während der Entwicklung von SolrCellRequest ist mir aufgefallen, dass reine Textdateien (überraschenderweise!) nicht indiziert wurden. Ich fragte danach und erhielt schnell eine Erklärung. Das Problem wird behoben, wenn eine neuere Version von Tika, einschließlich TIKA-154, in Solr integriert wird. In der Zwischenzeit kann das Problem durch die Einstellung ext.resource.name gelöst werden.
Das an den SolrCellRequest-Konstruktor übergebene Dokument ist ein Solr::Document. Wir sind vorgeprescht, weil wir wissen, dass wir eine acts_as_solr-Methode, bei der der ActiveRecord als Solr::Document verfügbar ist, einfach überschreiben können. [Beachten Sie, dass Solr-Ruby eine parallele *Response-Klasse zur *Request verlangt. Deshalb ist der Dummy SolrCellResponse notwendig].
script/console ist immer noch unser Freund. Versuchen wir es mit der reinen solr-ruby API:
$ script/console
Loading development environment (Rails 2.2.2)
>> solr = Solr::Connection.new("http://localhost:8982/solr")
>> req = SolrCellRequest.new(Solr::Document.new(:id=>1), '/path/to/ErikHatcherResume.pdf')
>> solr.send(req)
>> solr.commit
Checkpoint – wir haben jetzt Ruby in die Lage versetzt, Rich Files über eine sehr einfache API in Solr zu indizieren. Was bleibt noch übrig? Wir müssen diese Indizierung in den ActiveRecord-Lebenszyklus von acts_as_solr einbinden. Es gibt eine nette und einfache Methode, die Sie auf der Basis des acts_as_solr-Modells überschreiben können, um zu ändern, wie die Indizierungsanfrage an Solr funktioniert. Sie sieht wie folgt aus (in der commons_methods.rb von acts_as_solr):
def solr_add(add_xml) # note, it is actually a Solr::Document passed in, not XML
ActsAsSolr::Post.execute(Solr::Request::AddDocument.new(add_xml))
end
Wir werden das in unserem Modell Lebenslauf außer Kraft setzen:
class Resume < ActiveRecord::Base
acts_as_solr
def solr_add(doc)
# puts doc.to_xml.to_s # handy view of the Solr doc acts_as_solr builds
if file_name
ActsAsSolr::Post.execute(SolrCellRequest.new(doc, file_name))
else
ActsAsSolr::Post.execute(Solr::Request::AddDocument.new(doc))
end
end
end
Und nun das große Finale, der Code, den wir Rubyisten so gerne sehen, diese eine elegante Codezeile:
>> Resume.create(:first_name => "Erik", :last_name=>"Hatcher", :file_name=>"/path/to/ErikHatcherResume.pdf")
Und ein kurzer Test, der zeigt, dass es funktioniert („java“ steht in meinem Lebenslauf):
>> Resume.find_by_solr("java")
=> 1, :docs=>[#]}>
Siehe auch den Artikel Inhaltsextraktion mit Tika von Sami Siren.
Wir freuen uns über Ihre Kommentare und Ihr Feedback zu diesem Eintrag. Insbesondere bin ich daran interessiert, von den Ruby-Anwendern da draußen zu erfahren, welche Herausforderungen Sie bei der Verwendung von Solr hatten und wie wir Ihnen helfen können, Fehler zu beheben oder Sie weiterzubilden.