这是Flask Mega-Tutorial系列的第十六部分,我将在其中为Microblog添加全文搜索功能。
本章的目标是为Microblog实现搜索功能,以便用户可以使用自然语言查找有趣的用户动态内容。许多不同类型的网站,都可以使用Google,Bing等搜索引擎来索引所有内容,并通过其搜索API提供搜索结果。 这这方法适用于静态页面较多的的大部分网站,比如论坛。 但在我的应用中,基本的内容单元是一条用户动态,它是整个网页的很小一部分。 我想要的搜索结果的类型是针对这些单独的用户动态而不是整个页面。 例如,如果我搜索单词“dog”,我想查看任何用户发表的包含该单词的动态。 很明显,显示所有包含“dog”(或任何其他可能的搜索字词)的用户动态的页面并不存在,大型搜索引擎也就无法索引到它。所以,我别无选择,只能自己实现搜索功能。另外如果你也刚学python不久,平时问题多,可以来小编的Python交流.裙 :一久武其而而流一思(数字的谐音)转换下可以找到了,里面有最新Python教程项目可拿,不懂的问题多跟里面的人交流,都会解决哦!
本章的GitHub链接为:Browse, Zip, Diff.
全文搜索引擎简介
对于全文搜索的支持不像关系数据库那样是标准化的。 有几种开源的全文搜索引擎:Elasticsearch,Apache Solr,Whoosh,Xapian,Sphinx等等,如果这还不够,常用的数据库也可以像我上面列举的那些专用搜索引擎一样提供搜索服务。 SQLite,MySQL和PostgreSQL都提供了对搜索文本的支持,以及MongoDB和CouchDB等NoSQL数据库当然也提供这样的功能。
如果你想知道哪些应用程序可以在Flask应用中运行,那么答案就是所有! 这是Flask的强项之一,它在完成工作的同时不会自作主张。 那么到底选择哪一个呢?
在专用搜索引擎列表中,Elasticsearch非常流行,部分原因是它在ELK栈中是用于索引日志的“E”,另两个是Logstash和Kibana。 使用某个关系数据库的搜索能力也是一个不错的选择,但考虑到SQLAlchemy不支持这种功能,我将不得不使用原始SQL语句来处理搜索,否则就需要一个包, 它提供一个文本搜索的高级接口,并与SQLAlchemy共存。
基于上述分析,我将使用Elasticsearch,但我将以一种非常容易切换到另一个搜索引擎的方式来实现所有文本索引和搜索功能。 你可以用其他搜索引擎的替代替换我的实现,只需在单个模块中重写一些函数即可。
安装Elasticsearch
有几种方法可以安装Elasticsearch,包括一键安装程序,带有需要自行安装的二进制程序的zip包,甚至是Docker镜像。 该文档有一个安装页面,其中包含所有这些安装选项的详细信息。 如果你使用Linux,你可能会有一个可用于你的发行版的软件包。 如果你使用的是Mac并安装了Homebrew,那么你可以简单地运行brew install elasticsearch
。
在计算机上安装Elasticsearch后,你可以在浏览器的地址栏中输入http://localhost:9200
来验证它是否正在运行,预期的返回结果是JSON格式的服务基本信息。
由于我使用Python来管理Elasticsearch,因此我会使用其对应的Python客户端库:
1 (venv) $ pip install elasticsearch
当然不要忘记更新requirements.txt文件:
1 (venv) $ pip freeze > requirements.txt
Elasticsearch入门
我将在Python shell中为你展示使用Elasticsearch的基础知识。 这将帮助你熟悉这项服务,以便了解稍后将讨论的实现部分。
要建立与Elasticsearch的连接,需要创建一个Elasticsearch
类的实例,并将连接URL作为参数传递:
1 >>> from elasticsearch import Elasticsearch 2 >>> es = Elasticsearch('http://localhost:9200')
Elasticsearch中的数据需要被写入索引中。 与关系数据库不同,数据只是一个JSON对象。 以下示例将一个包含text
字段的对象写入名为my_index
的索引:
1 >>> es.index(index='my_index', doc_type='my_index', id=1, body={'text': 'this is a test'})
如果需要,索引可以存储不同类型的文档,在本处,可以根据不同的格式将doc_type
参数设置为不同的值。 我要将所有文档存储为相同的格式,因此我将文档类型设置为索引名称。
对于存储的每个文档,Elasticsearch使用了一个唯一的ID来索引含有数据的JSON对象。
让我们在这个索引上存储第二个文档:
>>> es.index(index='my_index', doc_type='my_index', id=2, body={'text': 'a second test'})
现在,该索引中有两个文档,我可以发布自由格式的搜索。 在本例中,我要搜索this test
:
1 >>> es.search(index='my_index', doc_type='my_index', 2 ... body={'query': {'match': {'text': 'this test'}}})
来自es.search()
调用的响应是一个包含搜索结果的Python字典:
1 { 2 'took': 1, 3 'timed_out': False, 4 '_shards': {'total': 5, 'successful': 5, 'skipped': 0, 'failed': 0}, 5 'hits': { 6 'total': 2, 7 'max_score': 0.5753642, 8 'hits': [ 9 { 10 '_index': 'my_index', 11 '_type': 'my_index', 12 '_id': '1', 13 '_score': 0.5753642, 14 '_source': {'text': 'this is a test'} 15 }, 16 { 17 '_index': 'my_index', 18 '_type': 'my_index', 19 '_id': '2', 20 '_score': 0.25316024, 21 '_source': {'text': 'a second test'} 22 } 23 ] 24 } 25 }
在结果中你可以看到搜索返回了两个文档,每个文档都有一个分配的分数。 分数最高的文档包含我搜索的两个单词,而另一个文档只包含一个单词。 你可以看到,即使是最好的结果的分数也不是很高,因为这些单词与文本不是完全一致的。
现在,如果我搜索单词second
,结果如下:
1 >>> es.search(index='my_index', doc_type='my_index', 2 ... body={'query': {'mat