Căutarea full-text în șine folosind elasticsearch

În acest articol vă voi arăta cum să implementați căutarea full-text utilizând Ruby on Rails și Elasticsearch. Toată lumea este folosită în zilele noastre pentru a introduce un termen de căutare și a obține sugestii și rezultate cu termenul de căutare evidențiat. Dacă spui greșit ceea ce încerci să cauți, având auto-corect este, de asemenea, o caracteristică frumoasă, așa cum se poate vedea pe site-uri precum Google sau Facebook. 

Implementarea tuturor acestor funcții utilizând doar o bază de date relațională precum MySQL sau Postgres nu este simplă. Din acest motiv, folosim Elasticsearch, despre care vă puteți gândi ca o bază de date construită și optimizată special pentru căutare. Este open source și este construit pe partea de sus a Apache Lucene. 

Una dintre cele mai frumoase caracteristici ale Elasticsearch este aceea care expune funcționalitatea sa folosind REST API, astfel încât există biblioteci care înfășoară acea funcționalitate pentru majoritatea limbajelor de programare.

Introducerea elasticului

Anterior, am menționat că Elasticsearch este ca o bază de date pentru căutare. Ar fi util dacă sunteți familiarizat cu o anumită terminologie din jurul lui.

  • Camp: Un câmp este ca o pereche cheie-valoare. Valoarea poate fi o valoare simplă (șir, număr întreg, dată) sau o structură imbricată ca o matrice sau un obiect. Un câmp este similar cu o coloană dintr-un tabel dintr-o bază de date relațională.
  • Document: Un document este o listă de câmpuri. Este un document JSON care este stocat în Elasticsearch. Este ca un rând într-un tabel într-o bază de date relațională. Fiecare document este stocat într-un index și are un tip și un id unic.  
  • Tip: Un tip este ca un tabel într-o bază de date relațională. Fiecare tip are o listă de câmpuri care pot fi specificate pentru documentele de tipul respectiv.
  • Index: Un index este echivalentul unei baze de date relaționale. Acesta conține definiția pentru mai multe tipuri și stochează mai multe documente.

Un lucru de observat aici este că în Elasticsearch, atunci când scrieți un document într-un index, câmpurile documentului sunt analizate, cuvânt cu cuvânt, pentru a face căutarea ușor și rapid. Elasticsearch sprijină, de asemenea, localizarea geografică, astfel încât să puteți căuta documente care se află la o anumită distanță de o anumită locație. Exact așa efectuează Foursquare căutarea.

Aș dori să menționez că Elasticsearch a fost construit cu o mare scalabilitate în minte, deci este foarte ușor să construim un cluster cu mai multe servere și să avem disponibilitate ridicată chiar dacă unele servere coboară. Nu voi acoperi specificul modului de planificare și implementare a diferitelor tipuri de clustere în acest articol.

Instalarea elasticului

Dacă utilizați Linux, este posibil să instalați Elasticsearch din unul dintre depozit. Este disponibil în APT și YUM.

Dacă utilizați Mac, îl puteți instala utilizând Homebrew: prepara elassearch. După instalarea elastică, veți vedea lista de dosare relevante din terminalul dvs.:

Pentru a verifica dacă instalarea funcționează, tastați elasticsearch în terminalul dvs. pentru al porni. Atunci fugi curl localhost: 9200 în terminalul dvs. și ar trebui să vedeți ceva de genul:

Instalați HQ-ul elastic

Elastic HQ este un plugin de monitorizare pe care îl putem folosi pentru a gestiona Elasticsearch din browser, similar cu phpMyAdmin pentru MySQL. Pentru a le instala, trebuie doar să rulați în terminal:

/usr/local/Cellar/elasticsearch/2.2.0_1/libexec/bin/plugin -install royrusso / elasticsearch-HQ

Odată instalat, navigați la http: // localhost: 9200 / _plugin / hq în browserul dvs.:

Click pe Conectați și veți vedea un ecran care arată starea clusterului:

În acest moment, după cum s-ar putea aștepta, nu se creează încă indici sau documente, dar avem instanța noastră locală de Elasticsearch instalată și rulată.

Crearea unei aplicații Rails

Voi crea o aplicație foarte simplă Rails, în care puteți adăuga articole în baza de date, astfel încât să putem efectua o căutare completă pe ele folosind Elasticsearch. Începeți prin crearea unei noi aplicații Rails:

șine noi elastice-șine

Apoi vom genera o nouă resursă de articole cu schele:

șine genera schelă Titlul articolului: text șir: text

Acum trebuie să adăugăm o nouă rută rădăcină, astfel încât să vedem în mod implicit lista articolelor. Editați | × config / routes.rb:

Rails.application.routes.draw se bazează pe: resursele "articole # index": articolele se termină 

Creați baza de date executând comanda rake db: migrați. Dacă începeți rails server, deschideți browserul dvs., navigați la localhost: 3000 și adăugați câteva articole în baza de date sau pur și simplu descărcați fișierul db / seeds.rb cu date fictive pe care le-am creat, astfel încât să nu trebuie să vă petreceți prea mult timp completarea formularelor.

Adăugarea căutării

Acum, când avem aplicația noastră Rails cu articole din baza de date, suntem gata să adăugăm funcția de căutare. Vom începe prin adăugarea referinței la ambele pietre Elasticsearch:

bijuterie elastica-model "bijuterie elastica"

Pe multe site-uri web, este foarte comun să aveți o casetă text pentru căutare în meniul de sus din toate paginile. Din acest motiv, voi crea o formă parțială app / opinii / căutare / _form.html.erb.După cum puteți vedea, trimit formularul utilizând GET, deci este ușor să copiați și să inserați adresa URL pentru o anumită căutare.

<%= form_for :term, url: search_path, method: :get do |form| %> 

<%= text_field_tag :term, params[:term] %> <%= submit_tag "Search", name: nil %>

<% end %>

Adăugați o referință la formular pe aspectul principal al site-ului web. Editați | × app / opinii / machete / application.html.erb.

 <%= render 'search/form' %> <%= yield %> 

De asemenea, avem nevoie de un controler pentru a efectua căutarea reală și pentru a afișa rezultatele, astfel că generăm rularea comenzii rails g Căutare nouă de controler.

clasa SearchController < ApplicationController def search if params[:term].nil? @articles = [] else @articles = Article.search params[:term] end end end 

După cum puteți vedea, numesc metoda căutare pe modelul articolului. Nu am definit-o încă, așa că dacă încercăm să efectuăm o căutare în acest moment, avem o eroare. De asemenea, nu am adăugat o ruta pentru SearchController pe config / routes.rb fișier, deci hai să facem acest lucru:

Rails.application.routes.draw se referă la: resursele "articole # index": articolele primesc "căutare", la: "search # search" end

Dacă ne uităm la documentația pentru bijuterie 'elasticsearch-șine',  trebuie să includem două module pe modelele pe care vrem să le indexăm în Elasticsearch, în cazul nostru Article.rb.

solicita clasa "elasticsearch / model" < ActiveRecord::Base include Elasticsearch::Model include Elasticsearch::Model::Callbacks end

Primul model introduce metoda de căutare pe care o folosim în controlerul nostru anterior, printre altele. Cel de-al doilea modul se integrează cu callback-urile ActiveRecord pentru a indexa fiecare instanță a unui articol pe care îl salvăm în baza de date și, de asemenea, actualizează indexul dacă modificăm sau ștergem articolul din baza de date. Deci, este transparent pentru noi.

Dacă ați importat datele mai devreme în baza de date, acele articole nu sunt încă în indexul Elasticsearch; numai cele noi sunt indexate automat. Din acest motiv, trebuie să le indexăm manual și este ușor să începem consola rails. Atunci trebuie doar să fugim irb (principală)> Articol.import.

Acum suntem gata să încercăm funcția de căutare. Dacă introduc rubrica și fac clic pe căutare, aici sunt rezultatele:

Căutare subliniată

Pe multe site-uri web, puteți vedea pe pagina cu rezultatele căutării modul în care termenul pe care l-ați căutat este evidențiat. Acest lucru este foarte ușor de făcut folosind Elasticsearch.

Editați | × app / modele / article.rb și modificați metoda de căutare prestabilită:

Definiți: pre_tags: ['interogare', 'căutare', 'căutare', ''], post_tags: [''], câmpuri: title: , text: )

În mod implicit, funcția căutare metoda este definită de modelele "elasticsearch" de bijuterie, iar obiectul proxy __elasticsearch__ este oferit pentru a accesa clasa de învelitoare pentru API-ul Elasticsearch. Așadar, putem modifica interogarea implicită folosind opțiunile JSON standard furnizate de documentație. 

Acum, metoda de căutare va împacheta rezultatele care corespund interogării cu etichetele HTML specificate. Din acest motiv, trebuie să actualizăm și pagina cu rezultatele căutării, astfel încât să putem face etichete HTML în siguranță. Pentru a face acest lucru, editați app / opinii / căutare / search.html.erb.

rezultatele cautarii

<% if @articles %>
    <% @articles.each do |article| %>
  • <%= link_to article.try(:highlight).try(:title) ? article.highlight.title[0].html_safe : article.title, controller: "articles", action: "show", id: article._id %>

    <% if article.try(:highlight).try(:text) %> <% article.highlight.text.each do |snippet| %>

    <%= snippet.html_safe %>...

    <% end %> <% end %>
  • <% end %>
<% else %>

Căutarea dvs. nu a corespuns niciunui document.

<% end %>

Adăugați un stil CSS la app / active / stylesheets / search.scss, pentru eticheta marcată:

.search_results em background-color: galben; font-style: normal; font-weight: bold; 

Încercați să căutați "rubin" din nou:

După cum puteți vedea, este ușor să evidențiați termenul de căutare, dar nu ideal, deoarece trebuie să trimitem o interogare JSON specificată în documentația Elasticsearch și nu avem nici un fel de abstractizare.

Searchkick Gem

Bijutectura Searchkick este furnizată de Instacart, și este o abstractizare în partea superioară a bijuteriilor oficiale Elasticsearch. Voi reface funcția de evidențiere, așa că începem prin adăugarea gem "searchkick" la gemfile. Prima clasă pe care trebuie să o schimbăm este modelul Article.rb:

Articol de clasă < ActiveRecord::Base searchkick end

După cum puteți vedea, este mult mai simplu. Trebuie să reanalizăm articolele din nou și să executăm comanda rake searchkick: reindex CLASS = Articol. Pentru a evidenția termenul de căutare, trebuie să transmitem un parametru suplimentar metodei de căutare de la noi search_controller.rb.

clasa SearchController < ApplicationController def search if params[:term].nil? @articles = [] else term = params[:term] @articles = Article.search term, fields: [:text], highlight: true end end end

Ultimul fișier pe care trebuie să îl modificăm este vizualizari / căutare / search.html.erb deoarece rezultatele sunt returnate într-un format diferit prin searchkick acum:

Cauta rezultate pentru: <%= params[:term] %>

<% if @articles %>
    <% @articles.with_details.each do |article, details| %>
  • <%= link_to article.title, controller: "articles", action: "show", id: article.id %>

    <%= details[:highlight][:text].html_safe %>...

  • <% end %>
<% else %>

Căutarea dvs. nu a corespuns niciunui document.

<% end %>

Acum este timpul să rulați din nou aplicația și să testați funcționalitatea de căutare:

Observați că am introdus ca termen de căutare "dato". Am făcut acest lucru cu scopul de a vă arăta că în mod implicit searchkickeste creat pentru a analiza textul indexat și a fi mai permisiv cu greșelile.

sugestiile automate

Autosuggest sau typeahead prezice ceea ce un utilizator va tasta, făcând experiența de căutare mai rapidă și mai ușoară. Rețineți că, dacă nu aveți mii de înregistrări, ar fi mai bine să filtrați pe partea clientului.

Să începem prin adăugarea pluginului typeahead, care este disponibil prin intermediul gem "bootstrap-typeahead-șine", și adăugați-l la Gemfile. Apoi, trebuie să adăugăm câteva JavaScript app / active / application.js / javascript astfel încât atunci când începeți să tastați în caseta de căutare, apar câteva sugestii.

// = necesita jquery // = cere jquery_ujs // = cere turbolinks // = necesita bootstrap-typeahead-rails // = require_tree. var hot = funcția () var motor = nou Bloodhound (datumTokenizer: funcția (d) consolă.log (d) la distanță: url: '... / search / typeahead /% QUERY'); var promise = motor.initializare (); promite .done (functie () console.log ('succes');)) .fail (function () console.log ('error')); (nume, articol, afișaj: titlu, sursă: engine.ttAdapter ()); $ (Document) .ready (gata); $ (document) .on ('pagina: încărcare', gata);

Câteva comentarii despre fragmentul anterior. În ultimele două rânduri, pentru că nu am dezactivat turbolink-urile, aceasta este modalitatea de a conecta codul pe care vreau să îl rulez la încărcarea paginii. În prima parte a scenariului, puteți vedea că folosesc Bloodhound. Acesta este motorul de sugestie typeahead.js și, de asemenea, configurez punctul final JSON pentru a face cererile AJAX pentru a obține sugestiile. După asta, sună inițializa () pe motor și am configurat tiphead în câmpul text de căutare folosind id-ul "term".

Acum, trebuie să facem implementarea backend-ului pentru sugestii, să începem prin adăugarea rutei, să editați app / config / routes.rb.

Rails.application.routes.draw se referă la: "articole # index" resurse: articole "căutare", la: "căutare # căutare" obține "căutare / typeahead /: term '=>

Apoi, voi adăuga implementarea app / controlere / search_controller.rb.

Definiți tipul de afisare a fișierului json: Article.search (params [: term], fields: ["title"], limit: 10, load: false, greșit: below: 5. title: article.title, value: article.id sfârșitul final

Această metodă returnează rezultatele căutării pentru termenul introdus utilizând JSON. Căut doar după titlu, dar aș putea specifica și corpul articolului. De asemenea, limitez numărul de rezultate de căutare la maxim 10.

Acum suntem gata să încercăm implementarea tipului de tip:

Concluzie

După cum puteți vedea, folosirea Elasticsearch cu Rails face căutarea datelor noastre foarte ușor și foarte rapid. Aici v-am arătat cum să utilizați pietrele de joasă nivel oferite de Elasticsearch, precum și bijuteria Searchkick, care este o abstracție care ascunde unele detalii despre modul în care lucrează Elasticsearch. 

În funcție de nevoile dvs. specifice, s-ar putea să fiți fericit să utilizați Searchkick și să vă implementați rapid și ușor căutarea dvs. de text întreg. Pe de altă parte, dacă aveți alte interogări complexe, inclusiv filtre sau grupuri, este posibil să trebuiască să aflați mai multe despre detaliile limbii de interogare pe Elasticsearch și să ajungeți să utilizați modelele elastice de pietre de nivel inferior și modelele elasticsearch- șine'.

Cod