本文将讲解在django中使用whoosh搜索引擎+haystack搜索框架+jieba中文分词进行关键字搜索
1.搜索引擎whoosh
当我们在一个搜索框中输入关键字进行搜索时,例如搜索“编程”这两个字,如果直接用sql语句操作数据库的话,那么将是下面:
select id,name,author from book where name like "%曹操%" or author like "%曹操%",直接使用like语句搜索name或者author包含“曹操”的信息,但是如果你输入的是“三国曹操”,那么有很多数据就搜索不出来了,并且like操作速度非常慢,直接使用like就是作死。这个时候我们就需要搜索引擎了。
搜索引擎会帮助我们做哪些事情呢
1.建立数据的索引表
搜索引擎会去数据库查询数据,把所有数据进行解析,建立一种对应的关系,例如“曹操”对应id为1,2,3的数据。其中,关键字和哪条记录的关联叫做索引的结构或者数据
2.进行分词操作
将数据库中的数据进行分词操作,不仅仅让它去搜索的时候建立分词,而是一开始在对所有数据进行解析的时候已经建立了分词
搜索引擎不用我们去实现,有相对应的搜索引擎去帮助我们去做,whoose搜索引擎,直接用Python语言编写的
2.搜索框架haystack
有了搜索引擎,但是我们还需要搜索框架,因为在项目中使用这个搜索引擎需要去跟搜索引擎对接,如何去调用whoosh,怎么让它建立索引,如何去使用它的查询结果等,这些工作我们可以省略去操作,原因在于有一个搜索框架叫haystack,haystack 搭建了用户和搜索引擎之间的沟通问题,我们不用关心搜索引擎如何去使用,因为haystack统一封装了接口,我们能够直接去调用,haystack能够对接whoosh,Elasticsearcdeng 等搜索框架,在使用的时候,调用haystack的方法就可以了,想当于我们在搜索框输入关键字,直接通过网络方式传给haystack所对应的的接口,它会帮助我们去查询,然后把结果返回给我们,并且haystack对于django支持比较好,直接有一个对应的django-haystack,直接安装。
使用haystack,首先需要在app中注册haystack,并且配置对应的搜索框架
HAYSTACK_CONNECTIONS = {
'default': {
#使用whoosh引擎
ENGINE': 'haystack.backends.whoosh_cn_backend.WhooshEngine',【默认不支持cn,所以先去掉,有了jieba再加上】
#索引文件路径 配置好搜索引擎,肯定会对数据库中的数据建立索引,索引数据放的位置,文件自动生成
'PATH': os.path.join(BASE_DIR, 'whoosh_index'),
}
}
#当添加、修改、删除数据时,自动生成最新索引【注意是通过访问django修改时,直接在数据库中修改不会,因为这是django中配置的】
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
配置结束,接下来,我们要让haystack给那个表建立相对应的索引结构,需要进行额外配置,去你想建立索引的数据模型类所在的同级目录,即对应的工程找到应用,建立一个文件search_indexes.py,名字固定,因为haystack在建立索引的时候,会从应用所在的,文件夹中去找这个文件,所以名字必须统一起来,这个文件里面应该写什么呢,在这个文件中具体以类的形式进行沟通,所以应该定义一书籍索引的类,通常你对哪个模型建立索引,你就起模型名字+Index当做类名,里面的信息都是从haystack中继承过来,第一个参数表示建立索引,第二个参数表示是可以建立索引的,并且然后需要加一个text文本字段,设置指定个模板信息使用模板的方式去告诉对模型类中的哪个字段建立索引,这个文本字段用来去找模板文件哪些字段是用来建立索引的,然后再加两个方法,这两个方法就决定了haystack在帮助我们建立索引的时候用哪张表,然后从哪张表的数据建立索引。一个方法指名哪个模型类一个方法指名哪些字段建立索引。
然后在template下面建立 search/indexes/应用名称 前两个目录都是固定的,第三层是应用的名称,一一对应上,然后建立book_text.txt,名字是刚刚指定的类名的小写加_text.txt ,这里面就是对应哪个字段建立索引。例如:
{{object.name}}
{{object.author}}
object固定的,使用命令生成索引文件,python manage.py rebuild_index,然后会提示你建立了多少条索引,然后会在项目中生成一个whoosh_index目录,里面的文件就是whoosh搜索引擎建立的内容了,这个文件夹里内容就当于告诉whoosh,以后去搜索数据的时候,去哪查,它不是直接去数据库中,而是从索引中,索引文件比数据库快的多。
使用搜索,在项目配置文件中配置,固定import haystack.urls,url(r'^search/', include(haystack.urls)),在form表单中,支持get方式,字段name叫q。
以上只是完成了搜索功能,但是搜索结果如何返回给我们呢,默认使用模板的方式,模板的名字必须是固定的,叫search.html必须放在template/search下面,默认提供三个模板变量
query 表示搜索的关键字
page 表示搜索出来的当前页对象
paginator 分页对象
3.中文分词工具jieba
因为whoosh默认只支持英文分词,不支持中文分词,因为英文的单词很容易分词,但是中文一句话不容易分,jieba是中国人写的,支持中文分词,只用配置文件即可
找到whoosh的安装路径 ,首先workon到自己的项目虚拟环境中,cd .virtualenvs/django_py3/lib/python3.5/site-packages/haystack/backends,这里是whoosh文件的后台,cd backends
新建 ChineseAnalyzer.py
import jieba from whoosh.analysis import Tokenizer, Token class ChineseTokenizer(Tokenizer): def __call__(self, value, positions=False, chars=False, keeporiginal=False, removestops=True, start_pos=0, start_char=0, mode='', **kwargs): t = Token(positions, chars, removestops=removestops, mode=mode, **kwargs) seglist = jieba.cut(value, cut_all=True) for w in seglist: t.original = t.text = w t.boost = 1.0 if positions: t.pos = start_pos + value.find(w) if chars: t.startchar = start_char + value.find(w) t.endchar = start_char + value.find(w) + len(w) yield t def ChineseAnalyzer(): return ChineseTokenizer()
cp whoosh_backend.py whoosh_cn_backend.py
然后vi whoosh_cn_backend.py
首先from .ChineseAnalyzer import ChineseAnalyze
然后/analyzer 修改设置analyzer=ChineseAnalyzer(),
注意:修改settings.py中加上_cn
4.结合使用流程
就是使用whoosh这个搜索引擎,对数据库的数据进行建立索引,分词,但是我们直接区操作whoosh不方便,就引入了haystack方便与whoosh进行沟通,并且whoosh不支持中文分词,所以引入了jieba
5.代码实现
项目urls.py
from django.conf.urls import url,include from django.contrib import admin import haystack.urls urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^', include("test_search.urls")), url(r'^search/', include(haystack.urls)), ]
test_search应用下建立search_indexes.py
# coding=utf-8 from haystack import indexes from .models import Book class BookIndex(indexes.SearchIndex, indexes.Indexable): """建立索引时被使用的类""" text = indexes.CharField(document=True, use_template=True) def get_model(self): return Book def index_queryset(self, using=None): return self.get_model().objects.all()
template下面建立search/indexes/search_index/book_text.txt
# 指明建立索引的字段 {{ object.name }} {{ object.author }}
search/search.html 用来接受搜索返回的结果
搜索的关键字是:{{ query }} <br> 有几条数据:{{ page|length }}<br> 搜索到的结果有: <ul> {% for result in page %} <li> 书名:{{ result.object.name }}<br> 作者:{{ result.object.author }}<br> </li> {% empty %} 没有查询到数据 {% endfor %} </ul>
models.py
from django.db import models # Create your models here. class Book(models.Model): name=models.CharField(max_length=30) author=models.CharField(max_length=30) class Meta: db_table="book"
settings.py
""" Django settings for test13 project. Generated by 'django-admin startproject' using Django 1.11.7. For more information on this file, see https://docs.djangoproject.com/en/1.11/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/1.11/ref/settings/ """ import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '1pg)2+&$76&it8(2$xm1hrs@yzqzkm2-k+il595^9*pa(yliv)' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'haystack', 'test_search', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'test13.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')] , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'test13.wsgi.application' # Database # https://docs.djangoproject.com/en/1.11/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'HOST': '127.0.0.1', 'PORT': 3306, 'USER': 'root', 'PASSWORD': 'mysql', 'NAME': 'test13' } } # Password validation # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/1.11/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.11/howto/static-files/ STATIC_URL = '/static/' # 配置haystack HAYSTACK_CONNECTIONS = { 'default': { # 使用whoosh引擎 'ENGINE': 'haystack.backends.whoosh_cn_backend.WhooshEngine', # 【默认不支持cn,所以去掉】 # 索引文件路径 配置好搜索引擎,肯定会对数据库中的数据建立索引,索引数据放的位置,文件自动生成 'PATH': os.path.join(BASE_DIR, 'whoosh_index'), } } # 当添加、修改、删除数据时,自动生成最新索引 HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
6.测试
数据库中数据
mysql> select * from book; +----+-----------------+--------------------+ | id | name | author | +----+-----------------+--------------------+ | 11 | 三国曹操传 | 王冲 | | 12 | 曹植传 | 曹操的孙子写 | | 13 | 曹操传奇 | 刘某某 | | 14 | 周瑜传 | 陆子敬 | +----+-----------------+--------------------+ 4 rows in set (0.00 sec)
结果