1. 什么是 Elasticsearch
Elasticsearch 是一个全文搜索引擎,可以快速地储存、搜索和分析海量数据。
它是一个开源的搜索引擎,建立在一个全文搜索引擎库 Apache Lucene™ 基础之上。但它又不仅仅是 Lucene,它可以被下面这样准确的形容:
- 一个分布式的实时文档存储,每个字段可以被索引与搜索
- 一个分布式实时分析搜索引擎
- 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据
2. Elasticsearch的相关概念
在 Elasticsearch 中有几个基本的概念,如节点、索引、文档等等。下面简单介绍一下。
Node 和 Cluster
Elasticsearch 本质上是一个分布式数据库,允许多台服务器协同工作,每台服务器可以运行多个 Elasticsearch 实例。
单个 Elasticsearch 实例称为一个节点(Node)。一组节点构成一个集群(Cluster)。
Index
Elasticsearch 数据管理的顶层单位就叫做 Index(索引),其实就相当于 MySQL、MongoDB 等里面的数据库的概念。注意每个 Index (即数据库)的名字必须是小写。
Document
Index 里面单条的记录称为 Document(文档)。许多条 Document 构成了一个 Index。同一个 Index 里面的Document,不要求有相同的结构(scheme),但是最好保持相同,这样有利于提高搜索效率。
Type
Type是虚拟的逻辑分组,用来过滤 Document,类似 MySQL 中的数据。不同的 Type 应该有相似的结构(Schema)。根据规划,Elastic 6.x 版只允许每个 Index 包含一个 Type,7.x 版将会彻底移除 Type。
Fields
每个 Document 都类似一个 JSON 结构,它包含了许多字段,每个字段都有其对应的值,多个字段组成了一个 Document,其实就可以类比 MySQL 数据表中的字段。
3.Python 对接 Elasticsearch
Elasticsearch 实际上提供了一系列 Restful API 来进行存取和查询操作,我们可以使用 curl 等命令来进行操作,但毕竟命令行模式没那么方便,所以这里我们就直接介绍利用 Python 来对接 Elasticsearch 的相关方法。
首先是python 中的es库安装
pip3 install elasticsearch
同时对于中文来说,我们需要安装一个分词插件,这里使用的是 elasticsearch-analysis-ik,GitHub 链接为:https://github.com/medcl/elasticsearch-analysis-ik
这里也可以用另一个命令行工具elasticsearch-plugin install来安装,注意版本号要和elasticsearch的版本号对应
elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.2.4/elasticsearch-analysis-ik-6.2.4.zip
接着介绍python操作es的方法,官方文档是:https://elasticsearch-py.readthedocs.io/,所有的用法都可以在里面查到,文章后面的内容也是基于官方文档来的。
(1)创建索引
这里我们创建一个名为 news 的索引:
from elasticsearch import Elasticsearch
es = Elasticsearch()
result = es.indices.create(index='news', ignore=400)
print(result)
注意,这里的 ignore 参数为 400,表示当索引若已经存在,就忽略这个报错,不抛异常。
如果执行成功,返回
{'acknowledged': True, 'shards_acknowledged': True, 'index': 'news'}
(2)删除索引
删除索引类似,可调用delete()方法
from elasticsearch import Elasticsearch
es = Elasticsearch()
result = es.indices.delete(index='news', ignore=[400, 404])
print(result)
注意这里的ignore参数加了404,表示若索引本来不存在,则忽略这个报错不抛异常。
(3)插入数据
1.es可以插入结构化字典数据,可以调用create()方法
from elasticsearch import Elasticsearch
es = Elasticsearch()
es.indices.create(index='news', ignore=400)
data = {'title': '美国留给伊拉克的是个烂摊子吗', 'url': 'http://view.news.qq.com/zt2011/usa_iraq/index.htm'}
result = es.create(index='news', doc_type='politics', id=1, body=data)
print(result)
我们传入了四个参数,index 参数代表了索引名称,doc_type 代表了文档类型,body 则代表了文档具体内容,id 则是数据的唯一标识 ID。
运行结果为:
{'_index': 'news', '_type': 'politics', '_id': '1', '_version': 1, 'result': 'created', '_shards': {'total': 2, 'successful': 1, 'failed': 0}, '_seq_no': 0, '_primary_term': 1}
2.可以使用 index() 方法来插入数据,而可以不指定id,自动生成id。
es.index(index='news', doc_type='politics', body=data)
(4) 更新数据
1.es更新数据用update方法,制定具体的id和data即可。
from elasticsearch import Elasticsearch
es = Elasticsearch()
data = {
'title': '美国留给伊拉克的是个烂摊子吗',
'url': 'http://view.news.qq.com/zt2011/usa_iraq/index.htm',
'date': '2011-12-16'
}
result = es.update(index='news', doc_type='politics', body=data, id=1)
print(result)
2.也可以使用index()方法
es.index(index='news', doc_type='politics', body=data, id=1)
index方法若数据存在则更新,若数据不存在则插入。
返回结果
{'_index': 'news', '_type': 'politics', '_id': '1', '_version': 2, 'result': 'updated', '_shards': {'total': 2, 'successful': 1, 'failed': 0}, '_seq_no': 1, '_primary_term': 1}
注意到_version这个字段,代表数据的版本,第一次插入是1,第二次更新为2。
(5)删除数据
如果想删除一条数据可以调用 delete() 方法,指定需要删除的数据 id 即可。
from elasticsearch import Elasticsearch
es = Elasticsearch()
result = es.delete(index='news', doc_type='politics', id=1)
print(result)
返回
{'_index': 'news', '_type': 'politics', '_id': '1', '_version': 3, 'result': 'deleted', '_shards': {'total': 2, 'successful': 1, 'failed': 0}, '_seq_no': 2, '_primary_term': 1}
可以看到返回结果_version又变成了3。
(6) 查询数据
普通的增删改大部分的数据库都可以做到,Elasticsearch 更特殊的地方在于其异常强大的检索功能。
对于检索中文,需要指定分词器,并且需要在在新建索引时,在mapping中指定需要分词的字段。
from elasticsearch import Elasticsearch
es = Elasticsearch()
mapping = {
'properties': {
'title': {
'type': 'text',
'analyzer': 'ik_max_word',
'search_analyzer': 'ik_max_word'
}
}
}
es.indices.delete(index='news', ignore=[400, 404])
es.indices.create(index='news', ignore=400)
result = es.indices.put_mapping(index='news', doc_type='politics', body=mapping)
print(result)
先删掉索引,新建索引,并更新mapping信息。mapping 信息中指定了分词的字段,指定了字段的类型 type 为 text,分词器 analyzer 和 搜索分词器 search_analyzer 为 ik_max_word。若不指定分词器则会默认使用英文分词器。
为了实验检索功能,我们先插入一批数据。
datas = [
{
'title': '美国留给伊拉克的是个烂摊子吗',
'url': 'http://view.news.qq.com/zt2011/usa_iraq/index.htm',
'date': '2011-12-16'
},
{
'title': '公安部:各地校车将享最高路权',
'url': 'http://www.chinanews.com/gn/2011/12-16/3536077.shtml',
'date': '2011-12-16'
},
{
'title': '中韩渔警冲突调查:韩警平均每天扣1艘中国渔船',
'url': 'https://news.qq.com/a/20111216/001044.htm',
'date': '2011-12-17'
},
{
'title': '中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首',
'url': 'http://news.ifeng.com/world/detail_2011_12/16/11372558_0.shtml',
'date': '2011-12-18'
}
]
for data in datas:
es.index(index='news', doc_type='politics', body=data)
现根据关键字查询信息
1.查询index和type下的所有数据,使用search()方法,指定索引和类型。
result = es.search(index='news', doc_type='politics')
print(result)
可以看到,刚刚插入的4条数据返回,返回结果会出现在 hits 字段里面,然后其中有 total 字段标明了查询的结果条目数,还有 max_score 代表了最大匹配分数。
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 4,
"max_score": 1.0,
"hits": [
{
"_index": "news",
"_type": "politics",
"_id": "c05G9mQBD9BuE5fdHOUT",
"_score": 1.0,
"_source": {
"title": "美国留给伊拉克的是个烂摊子吗",
"url": "http://view.news.qq.com/zt2011/usa_iraq/index.htm",
"date": "2011-12-16"
}
},
{
"_index": "news",
"_type": "politics",
"_id": "dk5G9mQBD9BuE5fdHOUm",
"_score": 1.0,
"_source": {
"title": "中国驻洛杉矶领事馆遭亚裔男子枪击,嫌犯已自首",
"url": "http://news.ifeng.com/world/detail_2011_12/16/11372558_0.shtml",
"date": "2011-12-18"
}
},
{
"_index": "news",
"_type": "politics",
"_id": "dU5G9mQBD9BuE5fdHOUj",
"_score": 1.0,
"_source": {
"title": "中韩渔警冲突调查:韩警平均每天扣1艘中国渔船",
"url": "https://news.qq.com/a/20111216/001044.htm",
"date": "2011-12-17"
}
},
{
"_index": "news",
"_type": "politics",
"_id": "dE5G9mQBD9BuE5fdHOUf",
"_score": 1.0,
"_source": {
"title": "公安部:各地校车将享最高路权",
"url": "http://www.chinanews.com/gn/2011/12-16/3536077.shtml",
"date": "2011-12-16"
}
}
]
}
}
2.全文检索
Elasticsearch 支持的 DSL 语句来进行查询,使用 match 指定全文检索,检索的字段是 title,内容是“中国领事馆”。
dsl = {
'query': {
'match': {
'title': '中国 领事馆'
}
}
}
es = Elasticsearch()
result = es.search(index='news', doc_type='politics', body=dsl)
print(json.dumps(result, indent=2, ensure_ascii=False))
返回结果如下:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 2.546152,
"hits": [
{
"_index": "news",
"_type": "politics",
"_id": "dk5G9mQBD9BuE5fdHOUm",
"_score": 2.546152,
"_source": {
"title": "中国驻洛杉矶领事馆遭亚裔男子枪击,嫌犯已自首",
"url": "http://news.ifeng.com/world/detail_2011_12/16/11372558_0.shtml",
"date": "2011-12-18"
}
},
{
"_index": "news",
"_type": "politics",
"_id": "dU5G9mQBD9BuE5fdHOUj",
"_score": 0.2876821,
"_source": {
"title": "中韩渔警冲突调查:韩警平均每天扣1艘中国渔船",
"url": "https://news.qq.com/a/20111216/001044.htm",
"date": "2011-12-17"
}
}
]
}
}
可以看到,_score字段为检索的匹配分数,检索时会对对应的字段全文检索,结果还会按照检索关键词的相关性进行排序,这就是一个基本的搜索引擎雏形。
另外 Elasticsearch 还支持非常多的查询方式,详情可以参考官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/6.3/query-dsl.html