备注:之前的英文博客我不翻墙也是能进去的,今天不翻墙就根本看不到英文博客,简直心累。
这部分内容是关于搜索的。
很多网站会直接使用google,bing来进行搜索,这些对于大部分是静态网页的网站,例如论坛来说是很方便的。但是我们这个微博应用程序,是由那些不长的博文组成的,是动态的。例如我们想搜索含有“dog”的微博,除非有用户搜索,否则大型搜索引擎是不会返回搜索结果的索引。(我的理解就是除非有人搜索这个词出来了这个词的搜索结果的页面,否则大型搜索引擎是不会返回搜索结果页面的。)
一、全文搜索引擎的简介
不幸的是,在关系数据库中的全文搜索支持没有很好的规范。每个数据库都以自己的方式实现全文搜索,并且 SQLAlchemy 没有实现全文搜索。
我们让数据库处理常规数据,我们将创建一个专门的数据库,专注服务于文本搜索。
教程:现在有一些开源的全文搜索引擎。在我的知识范围内唯一一个用 Python 编写的 Flask 扩展是 Whoosh。 一个纯 Python 的搜索引擎的好处就是在 Python 解释器可用的任何地方能够安装和运行。缺点也是很显然的,性能可能比不上 C 或者 C++ 编写的。我的观点是最理想的解决方式就是开发一个连接不同搜索引擎的 Flask 扩展,以某种方式来处理搜索,就像 Flask-SQLAlchemy 一样。但是目前在 Flask 中暂时没有这类型的扩展。Django 开发者提供了一个很好的扩展,用来支持不同的全文搜索引擎,叫做 django-haystack。也许不久就会有人写一个类似的 Flask 扩展。
flask/bin/pip install Flask-WhooshAlchemy
于是我直接
这样了
二、配置
配置 Flask-WhooshAlchemy 也是相当简单。我们只需要告诉扩展全文搜索数据库的名称(文件 config.py):
WHOOSH_BASE = os.path.join(basedir, 'search.db')
三、模型修改
因为把 Flask-WhooshAlchemy 整合进 Flask-SQLAlchemy,我们需要在模型的类中指明哪些数据需要建立搜索索引(文件 app/models.py):
from app importappimportsysif sys.version_info >= (3, 0):
enable_search=Falseelse:
enable_search=Trueimportflask.ext.whooshalchemy as whooshalchemyclassPost(db.Model):__searchable__ = ['body']
id= db.Column(db.Integer, primary_key=True)
body= db.Column(db.String(140))
timestamp=db.Column(db.DateTime)
user_id= db.Column(db.Integer, db.ForeignKey('user.id'))def __repr__(self):return '' %(self.body)ifenable_search:
whooshalchemy.whoosh_index(app, Post)
备注:python3使用这个扩展是有点问题的,上面代码是英文博客中进行了以下修正之后的。
这个模块有一个新的__searchable__字段,它是一个列表,包括了所有可以被当做搜索索引的数据库字段。在我们的项目里我们只需要所有文章帖子的body字段。
在这个模块中,我们也必须通过调用whoosh_index这个方法来初始化全文索引。
这不是一个能影响我们关系型数据库的改变,所以我们没必要换新的数据库。
因为之前存储在数据库的 blog 是没有建立索引的。为了保持数据库和全文搜索引擎的同步,我们需要删除之前撰写的 blog:
flask/bin/python
>>> from app.models import Post
>>> from app import db
>>> for post in Post.query.all():
... db.session.delete(post)
>>> db.session.commit()
四、全文搜索
在搜索之前,我们需要在数据库中添加一些blog,可以在网站上添加,也可以直接使用python命令行。
>>> from app.models import User, Post
>>> from app import db
>>> import datetime
>>> u = User.query.get(1)
>>> p = Post(body='my first post', timestamp=datetime.datetime.utcnow(), author=u)
>>> db.session.add(p)
>>> p = Post(body='my second post', timestamp=datetime.datetime.utcnow(), author=u)
>>> db.session.add(p)
>>> p = Post(body='my third and last post', timestamp=datetime.datetime.utcnow(), author=u)
>>> db.session.add(p)
>>> db.session.commit()
Flask-WhooshAlchemy这个扩展能连接Flask-SQLAlchemy然后自动提交。我们不需要维护全文索引,因为它已经帮我们做了这件事。
现在我们在全文索引中有一些 blog,我们可以这样搜索:
>>> Post.query.whoosh_search('post').all()
[, , ]
>>> Post.query.whoosh_search('second').all()
[]
>>> Post.query.whoosh_search('second OR last').all()
[, ]
在上面例子中可以看到,查询并不限制于单个词。实际上,Whoosh 支持一个更加强大的 搜索查询语言。
五、整合全文搜索到应用程序
1. 配置
在配置文件中,我们需要指明搜索结果返回的最大数量(文件 config.py):
MAX_SEARCH_RESULTS = 50
2.搜索表单
在导航栏中添加一个搜索表单,这样网站所有页面都有搜索功能。
首先,我们添加一个搜索表单类(文件 app/forms.py):
classSearchForm(Form):
search= StringField('search', validators=[DataRequired()])
接着我们必须创建一个搜索表单对象并且使得它对所有模版中可用,因为我们将搜索表单放在导航栏中,导航栏是所有页面共有的。最容易的方式就是在 before_request 函数中创建这个表单对象,接着把它放在全局变量 g 中(文件 app/views.py):
from forms importSearchForm
@app.before_requestdefbefore_request():
g.user=current_userifg.user.is_authenticated():
g.user.last_seen=datetime.utcnow()
db.session.add(g.user)
db.session.commit()
g.search_form= SearchForm()
接着添加表单到模板中(文件 app/templates/base.html):
|Your Profile|{{ g.search_form.hidden_tag() }}{{ g.search_form.search(size=20) }}|Logout{% endif %}
注意,只有当用户登录后,我们才会显示搜索表单。before_request 函数仅仅当用户登录才会创建一个表单对象,因为我们的程序不会对非认证用户显示任何内容。
3.搜索视图函数
上面的模版中,我们在 action 字段中设置发送搜索请求到 search 视图函数。search 视图函数如下(文件 app/views.py):
@app.route('/search', methods=['POST'])
@login_requireddefsearch():if notg.search_form.validate_on_submit():return redirect(url_for('index'))return redirect(url_for('search_results', query=g.search_form.search.data))
它只是从查询表单这能够获取查询的内容,并把它作为参数重定向另外一页。搜索工作不在这里直接做的原因还是担心用户无意中触发了刷新,这样会导致表单数据被重复提交。
4.搜索结果页
一旦一个查询字段被接受,form POST handler就会通过页面重定向把它发送到search_result。(文件 app/views.py):
from config importMAX_SEARCH_RESULTS
@app.route('/search_results/')
@login_requireddefsearch_results(query):
results=Post.query.whoosh_search(query, MAX_SEARCH_RESULTS).all()return render_template('search_results.html',
query=query,
results=results)
然后搜索结果显示方法会发送这个查询到Whoosh,参数是最大的搜索结果数目,因为我们不想呈现一个很大数目的结果页面,所以我们只显示前50条数据。
最后一部分就是搜索结果的模版(文件 app/templates/search_results.html):
{% extends "base.html" %}
{% block content %}
Search results for "{{query}}":
{% for post in results %}{% include 'post.html' %}
{% endfor %}
{% endblock %}
效果: