1.Elasticsearch相关概念
Elasticsearch中有几个基本概念,如节点、索引、文档等,下面分别说明一下。理解这些概念对熟悉Elasticsearch有帮助
-
节点和集群
Elasticsearch本质上是一个分布式数据库,允许多台服务器协同工作,每台服务器均可以运行多个Elasticsearch实例。
单个Elasticsearch实例称为一个节点(Node),一组节点构成一个集群(Cluster)。 -
索引
索引就是index,Elasticsearch会索引所有字段,经过处理后写入一个反向索引。查找数据时候,直接查找该索引。所以,Elasticsearch数据管理的顶层单位就叫做索引,其实相当于MySql、MongDB等中数据库的概念,值得注意的是,每个索引(及数据库)的名字必须小写。 -
文档
索引里的单条记录称为文档(document),许多条文档构成一个索引。
对同一个索引里面的文档,不要求有相同的结果(scheme),但是结构最好保持一致,因为有利于效率。 -
类型
文档可以分组,例如weather这个文档,即可以按城市分组(北京和上海),也可以按气候分组(晴天和雨天)。这种分组叫做类型(type),他是虚拟的逻辑分组,用来过滤文档,类似mysql中的数据表,mongodb中的集合。
不同类型的文档因该具有相似的结构,举例来说,id字段不能再这个组中是字符串,在另一个组中却变成了数值。这点与关系型数据的表是不同的。因该把性质完全相同的数据存成两个索引,而不是把类型相同的数据存在一个索引 -
字段
每个文档都类似一个json结构,包含允许多字段,每个字段都有其对应的值,多个字段组成了一个文档,其实可以类比为mysql数据表中的字段,
在Elasticsearch中,文档归属于一种类型(type),而这些类型存在于索引中,我们可以画一个简单的对比图,与传统数据库的关系。
Relational DB --> Databases --> Tables --> Rows – >Columns
ElasticSearch --> indices -->types --> documents – > fields
2.准备工作
首先要确保Es已经安装好,安装方式可参考:
https://cuiqingcai.com/31085.html
来自崔大神的博客
安装好之后确认它可在9200端口上正常运行。
在本地9200端口看到:
{
"name" : "DESKTOP-3BGU5UH",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "rh-hJvleSkysw4eED99kUw",
"version" : {
"number" : "7.17.0",
"build_flavor" : "default",
"build_type" : "zip",
"build_hash" : "bee86328705acaa9a6daede7140defd4d9ec56bd",
"build_date" : "2022-01-28T08:36:04.875279988Z",
"build_snapshot" : false,
"lucene_version" : "8.11.1",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
python操作es的库是
pip3 install elasticsearch
3.创建索引
from elasticsearch import Elasticsearch
es = Elasticsearch()
result = es.indices.create(index="news", ignore=400)
print(result)
这里我们首先创建一个Elasticsearch对象,并且没有设置任何参数,默认情况下他会链接本地9200端口的es服务,我们也可以设定特定的连接信息,如:
es = Elasticsearch(["https://[username:password@]hostname:post"], verify_certs=True) # 是否验证SSL证书
运行结果如下:
{'acknowledged': True, 'shards_acknowledged': True, 'index': 'news'}
可以看到,返回结果是JSON类型,其中acknowledge字段表示创建操作执行成功。
如果在执行一边就会出现:
{'error': {'root_cause': [{'type': 'resource_already_exists_exception', 'reason': 'index [news/TNEjlIt8SmyuI0veOvQCXg] already exists', 'index_uuid': 'TNEjlIt8SmyuI0veOvQCXg', 'index': 'news'}], 'type': 'resource_already_exists_exception', 'reason': 'index [news/TNEjlIt8SmyuI0veOvQCXg] already exists', 'index_uuid': 'TNEjlIt8SmyuI0veOvQCXg', 'index': 'news'}, 'status': 400}
创建失败,其中status状态码为400,表示错误原因是索引已存在。
注意在这里的代码中,我们使用ignore参数为400,说明如果返回结果是400的话就忽略这个错误不报错
如果不加就会出现:
File "D:\python3网络爬虫\venv\lib\site-packages\elasticsearch\transport.py", line 466, in perform_request
raise e
File "D:\python3网络爬虫\venv\lib\site-packages\elasticsearch\transport.py", line 434, in perform_request
timeout=timeout,
File "D:\python3网络爬虫\venv\lib\site-packages\elasticsearch\connection\http_urllib3.py", line 291, in perform_request
self._raise_error(response.status, raw_data)
File "D:\python3网络爬虫\venv\lib\site-packages\elasticsearch\connection\base.py", line 329, in _raise_error
status_code, error_message, additional_info
elasticsearch.exceptions.RequestError: RequestError(400, 'resource_already_exists_exception', 'index [news/TNEjlIt8SmyuI0veOvQCXg] already exists')
所以我们擅用ignore参数,把一些意外情况排除,程序不会中断。
4.删除索引
from elasticsearch import Elasticsearch
es = Elasticsearch(verify_certs=True) # 是否验证SSL证书
result = es.indices.delete(index="news", ignore=[400, 404])
print(result)
ignore的作用和上面一致
返回结果为:
{'acknowledged': True}
如果再次删除:
{'error': {'root_cause': [{'type': 'index_not_found_exception', 'reason': 'no such index [news]', 'resource.type': 'index_or_alias', 'resource.id': 'news', 'index_uuid': '_na_', 'index': 'news'}], 'type': 'index_not_found_exception', 'reason': 'no such index [news]', 'resource.type': 'index_or_alias', 'resource.id': 'news', 'index_uuid': '_na_', 'index': 'news'}, 'status': 404}
5.插入数据
Elasticsearch就像MongoDB一样,再插入数据的时候直接插入结构华字典数据,插入数据可调用create方法。例如我们这里插入一段新闻。
from elasticsearch import Elasticsearch
es = Elasticsearch(verify_certs=True) # 是否验证SSL证书
es.indices.create(index="news", ignore=[400, 404])
data = {
"title": "乘风破浪不负韶华,奋斗青春圆梦高考",
"url": "https://view.inews.qq.com/a/EDU2021041600732200"
}
result = es.create(index="news", id=1, body=data)
print(result)
传入3个参数,index代表索引名称、id是数据的唯一标识、body文档内容结果如下:
{'_index': 'news', '_type': '_doc', '_id': '1', '_version': 1, 'result': 'created', '_shards': {'total': 2, 'successful': 1, 'failed': 0}, '_seq_no': 0, '_primary_term': 1}
也可以用index方法来插入数据,create需要指定id字段来唯一标识数据,而index可以不指定id,那么它会自动产生一个id
result = es.index(index="news", body=data)
create方法内部其实调用了index方法,是对index方法的封装
6.更新数据
更新数据也非常简单,我们同样需要指定数据的id和内容,调用update即可,代码如下:
from elasticsearch import Elasticsearch
es = Elasticsearch()
data = {
'title': '乘风破浪不负韶华,奋斗青春圆梦高考',
'url': 'http://view.inews.qq.com/a/EDU2021041600732200',
'date': '2021-07-05'
}
result = es.update(index='news', body=data, id=1)
print(result)
这里这样修改会遇到问题可能和崔大神出书版本有关系:
elasticsearch.exceptions.RequestError: RequestError(400, 'x_content_parse_exception', '[1:2] [UpdateRequest] unknown field [title]')
例如:
from elasticsearch import Elasticsearch
es = Elasticsearch()
data = {
"doc":{
'title': '乘风破浪不负韶华,奋斗青春圆梦高考',
'url': 'http://view.inews.qq.com/a/EDU2021041600732200',
'date': '2021-07-09'
}
}
result = es.update(index='news', body=data, id=1)
print(result)
结果:
{'_index': 'news', '_type': '_doc', '_id': '1', '_version': 2, 'result': 'updated', '_shards': {'total': 2, 'successful': 1, 'failed': 0}, '_seq_no': 8, '_primary_term': 1}
7.删除数据
from elasticsearch import Elasticsearch
es = Elasticsearch()
result = es.delete(index='news', id=1)
print(result)
运行结果如下:
{'_index': 'news', '_type': '_doc', '_id': '1', '_version': 3, 'result': 'deleted', '_shards': {'total': 2, 'successful': 1, 'failed': 0}, '_seq_no': 11, '_primary_term': 1}
删除成功 _version:3 版本发生改变第一次为创建第二次为更新,第三次为删除
8.查询数据
对于上面的操作都很基础和简单,es真正强大的地方在于它的检索功能
对于中文来说,我们需要安装一个分词插件,使用的是elasticsearch-analysis-ik。我们用es的另一个命令行工具elasticsearch-plugin来安装这个插件,这里安装的版本是7.13.2要和es版本对应起来,命令是:
elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.13.2/elasticsearch-analysis-ik-7.13.2.zip
安装好之后重启es就好。
首先我们重新建一个索引并指定分词的字段,相应代码如下:
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', body=mapping)
print(result)
这里我们先将之前的索引删除,然后创建新的索引,接着更新他的mapping信息。mapping信息中指定分词的字段,包含字段的类型type、分词器analyzer和搜索器search_analyzer。指定搜索分词器search_analyzer为ik_max_word表示使用我们刚才安装的中文分词插件,如果不指定则会使用默认的英文分词器。
接下来插入几条数据。
from elasticsearch import Elasticsearch
es = Elasticsearch()
datas = [
{
'title': '高考结局大不同',
'url': 'https://k.sina.com.cn/article_7571064628_1c3454734001011lz9.html',
},
{
'title': '进入职业大洗牌时代,“吃香”职业还吃香吗?',
'url': 'https://new.qq.com/omn/20210828/20210828A025LK00.html',
},
{
'title': '乘风破浪不负韶华,奋斗青春圆梦高考',
'url': 'http://view.inews.qq.com/a/EDU2021041600732200',
},
{
'title': '他,活出了我们理想的样子',
'url': 'https://new.qq.com/omn/20210821/20210821A020ID00.html',
}
]
for data in datas:
es.index(index='news', body=data)
这里指定了4条数据他们都带有title和url字段,然后通过index方法传入将他们插入es中索引名称为news
接下来我们更具关键词查询一下相关内容:
result = es.search(index="news")
print(result)
结果如下:
{'took': 1, 'timed_out': False, '_shards': {'total': 1, 'successful': 1, 'skipped': 0, 'failed': 0}, 'hits': {'total': {'value': 4, 'relation': 'eq'}, 'max_score': 1.0, 'hits': [{'_index': 'news', '_type': '_doc', '_id': 'PsLjuH4BJ6CKg0kw5yYC', '_score': 1.0, '_source': {'title': '高考结局大不同', 'url': 'https://k.sina.com.cn/article_7571064628_1c3454734001011lz9.html'}}, {'_index': 'news', '_type': '_doc', '_id': 'P8LjuH4BJ6CKg0kw5yZ8', '_score': 1.0, '_source': {'title': '进入职业大洗牌时代,“吃香”职业还吃香吗?', 'url': 'https://new.qq.com/omn/20210828/20210828A025LK00.html'}}, {'_index': 'news', '_type': '_doc', '_id': 'QMLjuH4BJ6CKg0kw5yaE', '_score': 1.0, '_source': {'title': '乘风破浪不负韶华,奋斗青春圆梦高考', 'url': 'http://view.inews.qq.com/a/EDU2021041600732200'}}, {'_index': 'news', '_type': '_doc', '_id': 'QcLjuH4BJ6CKg0kw5yaL', '_score': 1.0, '_source': {'title': '他,活出了我们理想的样子', 'url': 'https://new.qq.com/omn/20210821/20210821A020ID00.html'}}]}}
可以看到,这里查询出了插入的4条数据。他们出现在hits字段里面,其中total字段标明了查询的结果条目数,max_score代表了最大匹配分数。
另外,我们还可以进行全文检索,这才是体现es搜索引擎特性的地方:
from elasticsearch import Elasticsearch
import json
dsl = {
'query': {
'match': {
'title': '高考 圆梦'
}
}
}
es = Elasticsearch()
result = es.search(index='news', body=dsl)
print(result)
这里我们使用es支持的dsl语句来进行查询,使用match指定全文搜索,检索的字段是title,内容是高考圆梦,搜索内容如下:
{'took': 1, 'timed_out': False, '_shards': {'total': 1, 'successful': 1, 'skipped': 0, 'failed': 0}, 'hits': {'total': {'value': 2, 'relation': 'eq'}, 'max_score': 1.7796917, 'hits': [{'_index': 'news', '_type': '_doc', '_id': 'PMLiuH4BJ6CKg0kwGSbH', '_score': 1.7796917, '_source': {'title': '乘风破浪不负韶华,奋斗青春圆梦高考', 'url': 'http://view.inews.qq.com/a/EDU2021041600732200'}}, {'_index': 'news', '_type': '_doc', '_id': 'OsLiuH4BJ6CKg0kwGSYl', '_score': 0.81085134, '_source': {'title': '高考结局大不同', 'url': 'https://k.sina.com.cn/article_7571064628_1c3454734001011lz9.html'}}]}}
还可以用json形式:
from elasticsearch import Elasticsearch
import json
es = Elasticsearch()
dsl = {
'query': {
'match': {
'title': '高考 圆梦'
}
}
}
result = es.search(index='news', body=dsl)
print(json.dumps(result, indent=2, ensure_ascii=False))
结果如下更加清晰了:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 1.7796917,
"hits": [
{
"_index": "news",
"_type": "_doc",
"_id": "TMLluH4BJ6CKg0kw9SZ3",
"_score": 1.7796917,
"_source": {
"title": "乘风破浪不负韶华,奋斗青春圆梦高考",
"url": "http://view.inews.qq.com/a/EDU2021041600732200"
}
},
{
"_index": "news",
"_type": "_doc",
"_id": "SsLluH4BJ6CKg0kw9Cbi",
"_score": 0.81085134,
"_source": {
"title": "高考结局大不同",
"url": "https://k.sina.com.cn/article_7571064628_1c3454734001011lz9.html"
}
}
]
}
}