0. Введение.
Делаем полнотекстовый поиск на русском языке в Django с помощью батарейки djang-haystack и поискового движка Solr.
Весь код ниже для django 1.7 и Ubuntu 14.04
Документация django-haystack - http://django-haystack.readthedocs.org/
Документация Solr - https://wiki.apache.org/solr/
1. Установка haystack и Solr
установка django-haystack
pip install django-haystack
Установка Solr
Ставим питоновский пакет для работы с Solr
pip install pysolr django-haystack
В stettings.py Django добавляем haystack в установленные приложения:
INSTALLED_APPS = [
'haystack',
]
В stettings.py Django добавляем настройки для Solr
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.solr_backend.SolrEngine',
'URL': 'http://127.0.0.1:8983/solr'
},
}
Ставим java
apt-get install openjdk-7-jre-headless jetty
Cтавим сам Solr
curl -LO https://archive.apache.org/dist/lucene/solr/4.10.4/solr-4.10.4.tgz
tar xvzf solr-4.10.4.tgz
копируем папку solr-4.10.4 в /opt/solr/
Запуск Solr
Варианты запуска:
-
напрямую из папки /opt/solr/example командой
java -jar start.jar
-
с помощью супервизора
[program:solr] command=java -Djetty.host=127.0.0.1 -Djetty.port=8983 -jar start.jar directory=/opt/solr/example/ stderr_logfile=/var/log/solr.error.log stdout_logfile=/var/log/solr.log autorestart=true
-
путем доабвления в автозапуск
описано тут - http://avihost.ru/ustanovka-apache-solr-na-debianubuntu.html
2. Настройка haystack
Для примера берем простую модель данных:
class Note(models.Model):
user = models.ForeignKey(User)
pub_date = models.DateTimeField()
title = models.CharField(max_length=200)
body = models.TextField()
В папке с приложением создаем файл search_indexes.py:
import datetime
from haystack import indexes
from myapp.models import Note
class NoteIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
author = indexes.CharField(model_attr='user')
pub_date = indexes.DateTimeField(model_attr='pub_date')
def get_model(self):
return Note
Выбрав для поля text настройку use_template=True, необходимо создать шаблон данных. В папке с шаблонами созадем файл search/indexes/myapp/note_text.txt с содержанием:
{{ object.title }}
{{ object.user.get_full_name }}
{{ object.body }}
3. Настраиваем Solr для работы с нашими данными.
Настройка Solr состоит из двух частей (файлов): - в файле schema.xml описывается настройка полей с даными (как данные разбиваются, как обрабатываются и т.д.); - в файле solrconfig.xml - делаются настройки самого Solr (в нашем случае там настраиваем
Строим базовую схему для Solr
python manage.py build_solr_schema > schema.xml
Полезно: http://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters - основные настройки влияющие на поиск.
Скачиваем файл http://download.services.openoffice.org/files/contrib/dictionaries/ru_RU.zip оттуда берем два файла ru_RU.dic, ru_RU.aff - перекодируем в utf-8, в файле ru_RU.aff в начале меняем указание на кодировку.
Редактируем полученный schema.xml для хорошего поиска по русски:
-
Заменяем поле
<field name="text"...
на:<field name="text" type="spell_ru" indexed="true" stored="true" multiValued="false" />
-
Добавляем:
<fieldType name="spell_ru" class="solr.TextField" positionIncrementGap="100" omitNorms="true"> <analyzer> <tokenizer class="solr.StandardTokenizerFactory"/> <filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.HunspellStemFilterFactory" dictionary="ru_RU.dic" affix="ru_RU.aff" ignoreCase="true" /> </analyzer> </fieldType>
-
Закидываем полученный schema.xml в папку /opt/solr/example/solr/collection1/conf/
- Перезагружаем Solr
-
Пересоздаем поисковый индекс
python manage.py rebuild_index
-
При обновлении данных необходимо перестраивать или обновлять поисковый индекс:
python manage.py update_index #обновить индекс python manage.py rebuild_index #перестроить индекс полностью (при изменении schema.xml)
Добавляем в Solr функцию исправления ошибок (speling)
-
Изменяем search_index.py
class NoteIndex(indexes.SearchIndex, indexes.Indexable): ... suggestions = indexes.FacetCharField() def prepare(self, obj): prepared_data = super(NoteIndex, self).prepare(obj) prepared_data['suggestions'] = prepared_data['text'] return prepared_data
-
В schema.xml добавялем
<field name="suggestions" type="spell_ru" indexed="true" stored="true" multiValued="false" />
-
В solrconfig.xml
-
находим
<requestHandler name="/select"
и исправляем его на:<requestHandler name="/select" class="solr.SearchHandler"> <lst name="defaults"> <str name="echoParams">explicit</str> <int name="rows">10</int> <str name="df">text</str> <str name="spellcheck.dictionary">default</str> <str name="spellcheck.dictionary">wordbreak</str> <str name="spellcheck">on</str> <str name="spellcheck.extendedResults">false</str> <str name="spellcheck.onlyMorePopular">true</str> <str name="spellcheck.count">5</str> <str name="spellcheck.collate">true</str> </lst> <arr name="last-components"> <str>spellcheck</str> </arr> </requestHandler>
-
Находим
<searchComponent name="spellcheck"
и исправляем на:<searchComponent name="spellcheck" class="solr.SpellCheckComponent"> <str name="queryAnalyzerFieldType">textSpell</str> <lst name="spellchecker"> <str name="name">default</str> <str name="field">suggestions</str> <str name="classname">solr.DirectSolrSpellChecker</str> <str name="distanceMeasure">internal</str> <float name="accuracy">0.5</float> <int name="maxEdits">2</int> <int name="minPrefix">1</int> <int name="maxInspections">5</int> <int name="minQueryLength">4</int> <float name="maxQueryFrequency">0.01</float> </lst> <lst name="spellchecker"> <str name="name">wordbreak</str> <str name="classname">solr.WordBreakSolrSpellChecker</str> <str name="field">suggestions</str> <str name="combineWords">true</str> <str name="breakWords">true</str> <int name="maxChanges">10</int> </lst> </searchComponent>
-
-
settings.py добавляем к настройкам haystack:
HAYSTACK_CONNECTIONS = { 'default': { 'INCLUDE_SPELLING': True, }, }
Добавление в поиск функции транслитерации (russkiy - русский):
-
В solrconfig.xml в
<fieldType name="spell_ru" ... <analyzer>
добавляем:<filter class="solr.ICUTransformFilterFactory" id="Any-Cyrillic; NFD; [^\p{Alnum}] Remove" />
-
В solrconfig.xml к другим поля lib добавляем:
<lib dir="${solr.install.dir:../../..}/contrib/analysis-extras/lib" regex=".*\.jar"/> <lib dir="${solr.install.dir:../../..}/contrib/analysis-extras/lucene-libs" regex=".*\.jar"/>
4. Работа с поиском во вью и шаблонах:
views.py приложения
В переменную q получаем поисковый запрос из формы с сайта
-
Базовый поиск:
results = SearchQuerySet().filter(content=q)
-
Исправление ошибок:
sqs = SearchQuerySet().auto_query(q) q_spell = sqs.spelling_suggestion() #получаем исправленное слово, например (малако - молоко) results = SearchQuerySet().filter(content=q_spell) #ищем по исправленному слову
В шаблоне (пример из документации):
{% extends 'base.html' %}
{% block content %}
<h2>Search</h2>
<form method="get" action=".">
<table>
{{ form.as_table }}
<tr>
<td> </td>
<td>
<input type="submit" value="Search">
</td>
</tr>
</table>
{% if query %}
<h3>Results</h3>
{% for result in page.object_list %}
<p>
<a href="{{ result.object.get_absolute_url }}">{{ result.object.title }}</a>
</p>
{% empty %}
<p>No results found.</p>
{% endfor %}
{% if page.has_previous or page.has_next %}
<div>
{% if page.has_previous %}<a href="?q={{ query }}&page={{ page.previous_page_number }}">{% endif %}« Previous{% if page.has_previous %}</a>{% endif %}
|
{% if page.has_next %}<a href="?q={{ query }}&page={{ page.next_page_number }}">{% endif %}Next »{% if page.has_next %}</a>{% endif %}
</div>
{% endif %}
{% else %}
{# Show some example queries to run, maybe query syntax, something else? #}
{% endif %}
</form>
{% endblock %}
Для вопросов и предложений о сотрудничестве пищите на ilyutoev@gmail.com.