文章轉自 :https://github.com/WapeYang/The-Flask-Mega-Tutorial/blob/master/textsearch.rst
感謝原作者的付出
轉載時間爲:2014-05-06
全文搜索
回顧
在前面的章節(:ref:`pagination`),我們已經加強了數據庫查詢,因此能夠在頁面上獲取各種查詢。
今天,我們會繼續探討數據庫的話題,只是領域不同。所有存儲內容的應用程序必須提供搜索能力。
許多其它類型的網站可能使用了谷歌、必應等索引所有的內容並且提供查詢結果。這個對於大多數靜態頁面的網站,像論壇,是很好用。我們應用程序 microblog 的基本單元是用戶短小的 blog,不是整個頁面。我們希望搜索結果是動態的。例如,我們想要在所有的 blog 中搜索關鍵詞 “dog”。這是顯而易見的,除非是有人搜索這個詞,不然大型的搜索引擎不可能索引搜索結果。因此我們除了使用自己的搜索是別無選擇的。
全文搜索引擎的簡介
不幸的是,在關係數據庫中的全文搜索支持沒有得到很好的規範。每個數據庫都以自己的方式實現全文搜索,並且 SQLAlchemy 沒有實現全文搜索。
我們目前使用了 SQLite 作爲數據庫,因此我們可以繞過 SQLAlchemy,使用 SQLite 提供的特性來創建全文文本索引。但是這並不是一個好主意,因爲如果我們要更換數據庫的時候,我們需要重寫全文搜索的代碼。
因此,相反我們讓數據庫處理常規數據,我們將創建一個專門的數據庫,專注服務於文本搜索。
現在有一些開源的全文搜索引擎。在我的知識範圍內唯一一個用 Python 編寫的 Flask 擴展是 Whoosh。一個純 Python 的搜索引擎的好處就是在 Python 解釋器可用的任何地方能夠安裝和運行。缺點也是很顯然的,性能可能比不上 C 或者 C++ 編寫的。我的觀點是最理想的解決方式就是開發一個連接不同搜索引擎的 Flask 擴展,以某種方式來處理搜索,就像 Flask-SQLAlchemy 一樣。但是目前在 Flask 中暫時沒有這類型的擴展。Django 開發者提供了一個很好的擴展,用來支持不同的全文搜索引擎,叫做 django-haystack。也許不久就會有人寫一個類似的 Flask 擴展。
如果你暫時沒有在虛擬環境上安裝 Flask-WhooshAlchemy,請安裝它。Windows 用戶應該運行這個:
flask\Scripts\pip install Flask-WhooshAlchemy
其它用戶必須運行這個:
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 import app import flask.ext.whooshalchemy as whooshalchemy class Post(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 '<Post %r>' % (self.body) whooshalchemy.whoosh_index(app, Post)
模型有一個新的 __searchable__ 字段,這裏麪包含數據庫中的所有能被搜索並且建立索引的字段。在我們的例子中,我們只要索引 blog 的 body 字段。
通過調用 whoosh_index 函數,我們爲這個模型初始化了全文搜索索引。
因爲這個改變並不影響到關係數據庫的格式,因此不需要錄製新的遷移腳本。
因爲之前存儲在數據庫的 blog 是沒有建立索引的。爲了保持數據庫和全文搜索引擎的同步,我們需要刪除之前撰寫的 blog:
>>> from app.models import Post >>> from app import db >>> for post in Post.query.all(): ... db.session.delete(post) >>> db.session.commit()
搜索
現在我們準備開始搜索。首先讓我們在數據庫中添加些 blog。有兩種方式去添加。我們可以運行應用程序,通過瀏覽器像普通用戶一樣添加 blog。另外一種就是在 Python 提示符下。
在 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()
現在我們在全文索引中有一些 blog,我們可以這樣搜索:
>>> Post.query.whoosh_search('post').all() [<Post u'my second post'>, <Post u'my first post'>, <Post u'my third and last post'>] >>> Post.query.whoosh_search('second').all() [<Post u'my second post'>] >>> Post.query.whoosh_search('second OR last').all() [<Post u'my second post'>, <Post u'my third and last post'>]
在上面例子中你可以看到,查詢並不限制於單個詞。實際上,Whoosh 支持一個更加強大的 搜索查詢語言。
整合全文搜索到應用程序
爲了使得搜索功能在我們的應用程序中可用,我們需要添加些修改。
配置
在配置文件中,我們需要指明搜索結果返回的最大數量(文件 config.py):
MAX_SEARCH_RESULTS = 50
搜索表單
我們準備在導航欄中添加一個搜索表單。把表單放在導航欄中是有好處的,因爲應用程序所有頁都有搜索表單。
首先,我們添加一個搜索表單類(文件 app/forms.py):
class SearchForm(Form): search = TextField('search', validators = [Required()])
接着我們必須創建一個搜索表單對象並且使得它對所有模版中可用,因爲我們將搜索表單放在導航欄中,導航欄是所有頁面共有的。最容易的方式就是在 before_request 函數中創建這個表單對象,接着把它放在全局變量 g 中(文件 app/views.py):
from forms import SearchForm @app.before_request def before_request(): g.user = current_user if g.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):
<div>Microblog: <a href="{{ url_for('index') }}">Home</a> {% if g.user.is_authenticated() %} | <a href="{{ url_for('user', nickname = g.user.nickname) }}">Your Profile</a> | <form style="display: inline;" action="{{url_for('search')}}" method="post" name="search">{{g.search_form.hidden_tag()}}{{g.search_form.search(size=20)}}<input type="submit" value="Search"></form> | <a href="{{ url_for('logout') }}">Logout</a> {% endif %} </div>
注意,只有當用戶登錄後,我們纔會顯示搜索表單。before_request 函數僅僅當用戶登錄纔會創建一個表單對象,因爲我們的程序不會對非認證用戶顯示任何內容。
搜索視圖函數
上面的模版中,我們在 action 字段中設置發送搜索請求到 search 視圖函數。search 視圖函數如下(文件 app/views.py):
@app.route('/search', methods = ['POST']) @login_required def search(): if not g.search_form.validate_on_submit(): return redirect(url_for('index')) return redirect(url_for('search_results', query = g.search_form.search.data))
這個函數實際做的事情不多,它只是從查詢表單這能夠獲取查詢的內容,並把它作爲參數重定向另外一頁。搜索工作不在這裏直接做的原因還是擔心用戶無意中觸發了刷新,這樣會導致表單數據被重複提交。
搜索結果頁
一旦查詢的關鍵字被接收到,search_results 函數就會開始工作(文件 app/views.py):
from config import MAX_SEARCH_RESULTS @app.route('/search_results/<query>') @login_required def search_results(query): results = Post.query.whoosh_search(query, MAX_SEARCH_RESULTS).all() return render_template('search_results.html', query = query, results = results)
搜索結果視圖函數把查詢傳遞給 Whoosh,並且把最大的結果數也作爲參數傳遞給 Whoosh。
最後一部分就是搜索結果的模版(文件 app/templates/search_results.html):
<!-- extend base layout --> {% extends "base.html" %} {% block content %} <h1>Search results for "{{query}}":</h1> {% for post in results %} {% include 'post.html' %} {% endfor %} {% endblock %}
結束語
如果你想要節省時間的話,你可以下載 microblog-0.10.zip。
我希望能在下一章繼續見到各位!