Hľadanie na stránkach

Zápisník experimentátora

Hierarchy: Vývoj stránky

Na stránkach môžete hľadať podľa zadaného výrazu. Fulltextové vyhľadávanie nájde všetky stránky, ktoré daný výraz obsahujú. Hľadanie je urobené pomocou Search API pre Google App Engine.

Ako je to urobené

Pretože s vyhľadávaním sa môže stretnúť aj niekto iný, ktorý pracuje s Google App Engine, tu je zjednodušený popis mojej implementácie v Pythone.

Indexovanie

Stránky mám uložené v NDB databáze. Konkrétnu stránku reprezentuje trieda Page. V položkách title, body a url sú uložené podstatné údaje pre indexovanie. Vo funkcii put_index ich preto používam na vytvorenie poľa fields. Následne pomocou poľa vytvorím objekt search.Document a doplním mu ešte ďalšie parametre. V takejto podobe sú už údaje indexovateľné a možno ich indexovať funkciou search.Index. Po indexovaní si poznačím dátum, kedy som danú stránku indexoval. Tu je kód mierne chybný, ale nie je to nič hrozné. Keby indexovanie zlyhalo, mal by som napriek tomu poznačené, že som indexoval. Vo výslednom kóde stránky to bude opravené.

from google.appengine.ext import ndb
from google.appengine.api import search

class Page(ndb.Model):
    """Stranka"""
    title = ndb.StringProperty(required=True)
    body = ndb.TextProperty()
    published_when = ndb.DateTimeProperty(auto_now_add=True)
    modified_when = ndb.DateTimeProperty()
    tags = ndb.StringProperty(repeated=True)
    id = ndb.IntegerProperty()
    draft = ndb.BooleanProperty(required=True, default=False)
    url = ndb.StringProperty()
    shortcut = ndb.StringProperty()
    download = ndb.StringProperty()
    hierarchia = ndb.StringProperty()
    indexed_when = ndb.DateTimeProperty()

    def put_index(self):
        fields = [search.TextField(name='title', value=self.title),
                  search.HtmlField(name='body', value=self.body),
                  search.AtomField(name='url', value='/page/' + self.url),
                  ]

        doc = search.Document(doc_id='page_' + str(self.id) + '_sk', fields=fields, language='sk')

        try:
            add_result = search.Index('arduinoslovakia_index').put(doc)
            logging.info(add_result)
        except search.Error:
            logging.exception("Error adding document: " + id)

        self.indexed_when = datetime.datetime.now()
        self.put()
        return add_result

Hľadanie

Na vykresľovanie obsahu stránok používam šablónovací systém Jinja2. Stránka sa skladá z dvoch častí. V prvej je jednoriadkový formulár a v druhej samotné vykreslenie výsledkov. Zo šablóny sa aj laik dovtípi, že premenné sa do šablóny doplňujú cez syntax {{xxx}}.

Vykresľovanie nájdených stránok pomocou premenných je podriadené tomu, čo dodá vyhľadávací systém. Prvý riadok skonštruuje nadpis stránky s hypertextom a druhý riadok skonštruuje útržok s nájdeným textom. Zatiaľ sa mi nepodarilo objaviť, ako ten útržok zobraziť aj s diakritikou.

<div class="col-sm-8">
  <form role="form" method="post" enctype="multipart/form-data" action="{{searchurl}}">
    <div class="form-group">
    <label for="sel_search">Hľadaný výraz:</label>
      <input class="form-control" name="sel_search" value="{{ search }}">
    </div>
    <button type="submit" class="btn btn-default">Hľadať</button>
  </form>

  {% if show_results==1 %}
  <h3>Výsledky</h3>
  <div class="list-group">
  {% for item in results %}
    <div class="list-group-item">
      <h4><a href="{{item.fields[1].value}}">{{item.fields[0].value}}</a></h4>
      <p class="list-group-item-text">{% autoescape false %}{{item.expressions[0].value}}{% endautoescape %}</p>
    </div>
  {% endfor %}
  </div>
  {% endif %}
</div>

Samotné hľadanie je v Search API relatívne jednoduché. Vo funkcii get iba zobrazujem prázdny hľadací formulár a výsledky hľadania zobrazujem iba vtedy, keď sú z formulára odoslané do funkcie post. Podstatné je správne naplniť premenné query_string a query_options. Výsledok hľadania dodá funkcia search.Query. Výsledok sa už len uloží do premennej template_values a Jinja2 sa postará o vytvorenie stránky.

import os
import jinja2
import webapp2
import menu
import copy
from google.appengine.api import search
import logging

JINJA_ENVIRONMENT = jinja2.Environment(
    loader=jinja2.FileSystemLoader(os.path.dirname(__file__)+'/templates'),
    extensions=['jinja2.ext.autoescape'],
    autoescape=True)


class SearchHandler(webapp2.RequestHandler):

    def get(self):
        template_values = {
            'caption': u'Hľadanie',
            'description': u'Hľadanie podľa vloženého výrazu',
            'path': self.request.path,
            'menu': copy.deepcopy(menu.globalmenu),
            'showadd': False,
            'search': u'',
            'searchurl': u'/search'
        }

        template = JINJA_ENVIRONMENT.get_template('search.html')
        self.response.write(template.render(template_values))

    def post(self):
        template_values = {
            'caption': u'Hľadanie',
            'description': u'Hľadanie podľa vloženého výrazu',
            'path': self.request.path,
            'menu': copy.deepcopy(menu.globalmenu),
            'showadd': False,
            'search': self.request.get('sel_search'),
            'searchurl': u'/search'
        }

        index = search.Index('arduinoslovakia_index')
        query_string = self.request.get('sel_search')
        query_options = search.QueryOptions(
            limit = 25,
            returned_fields = ['title', 'url'],
            snippeted_fields = ['body']
            )
        logging.info('query_string: ' + query_string)
        query = search.Query(query_string=query_string, options=query_options)
        results = index.search(query)
        logging.info(results)

        template_values['results'] = results
        template_values['show_results'] = 1

        template = JINJA_ENVIRONMENT.get_template('search.html')
        self.response.write(template.render(template_values))


app = webapp2.WSGIApplication([
    ('/search', SearchHandler),
], debug=True)

15.03.2017


Menu