瞭解學習 Elasticsearch 及其與 Python 實現全文搜尋
Elasticsearch簡介
ElasticSearch是一個基於Lucene的搜尋伺服器.它提供了一個分散式多使用者能力的全文搜尋引擎,基於RESTful web介面。Elasticsearch是用Java開發的,並作為Apache許可條款下的開放原始碼釋出,是當前流行的企業級搜尋引擎。設計用於雲端計算中,能夠達到實時搜尋,穩定,可靠,快速,安裝使用方便。
我們建立一個網站或應用程式,並要新增搜尋功能,但是想要完成搜尋工作的建立是非常困難的。我們希望搜尋解決方案要執行速度快,我們希望能有一個零配置和一個完全免費的搜尋模式,我們希望能夠簡單地使用JSON通過HTTP來索引資料,我們希望我們的搜尋伺服器始終可用,我們希望能夠從一臺開始並擴充套件到數百臺,我們要實時搜尋,我們要簡單的多租戶,我們希望建立一個雲的解決方案。因此我們利用Elasticsearch來解決所有這些問題及可能出現的更多其它問題。
想查資料就免不了搜尋,搜尋就離不開搜尋引擎,百度、谷歌都是一個非常龐大複雜的搜尋引擎,他們幾乎索引了網際網路上開放的所有網頁和資料。然而對於我們自己的業務資料來說,肯定就沒必要用這麼複雜的技術了,如果我們想實現自己的搜尋引擎,方便儲存和檢索,Elasticsearch 就是不二選擇,它是一個全文搜尋引擎,可以快速地儲存、搜尋和分析海量資料。
安裝
方法一:到Elasticsearch 的官方網站下載 Elasticsearch,https://www.elastic.co/downloads/elasticsearch,同時官網也附有安裝說明。首先把安裝包下載下來並解壓,然後執行 bin/elasticsearch
bin\elasticsearch.bat
(Windows) 即可啟動 Elasticsearch 了。
方法二:Mac 下個人推薦使用 Homebrew 安裝(在沒有安裝Java8的情況下先安裝Java8):
brew install elasticsearch
使用
1.首先要先啟動elasticsearch,直接在終端輸入elasticsearch。
2.Elasticsearch 預設會在 9200 埠上執行,我們開啟瀏覽器訪問http://localhost:9200/ 就可以看到類似下面的內容
{
"name" : "0HdVomu",
"cluster_name" : "elasticsearch_jing",
"cluster_uuid" : "W0S1FiOPRLesVCTBkLVHqA",
"version" : {
"number" : "6.4.2",
"build_flavor" : "oss",
"build_type" : "tar",
"build_hash" : "04711c2",
"build_date" : "2018-09-26T13:34:09.098244Z",
"build_snapshot" : false,
"lucene_version" : "7.4.0",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}
如果看到上面內容,說明 Elasticsearch 安裝並啟動成功了, Elasticsearch 版本是 6.4.2 版本,版本很重要,以後安裝一些外掛都要做到版本對應才可以。
Elasticsearch 相關概念
在 Elasticsearch 中有幾個基本的概念,如節點、索引、文件等等,下面來分別說明一下,理解了這些概念對熟悉 Elasticsearch 是非常有幫助的。
Node 和 Cluster
Elasticsearch 本質上是一個分散式資料庫,允許多臺伺服器協同工作,每臺伺服器可以執行多個 Elasticsearch 例項。
單個 Elasticsearch 例項稱為一個節點(Node)。一組節點構成一個叢集(Cluster)。
Index
Elasticsearch 會索引所有欄位,經過處理後寫入一個反向索引(Inverted Index)。查詢資料的時候,直接查詢該索引。
所以,Elasticsearch 資料管理的頂層單位就叫做 Index(索引),其實就相當於 MySQL、MongoDB 等裡面的資料庫的概念。另外值得注意的是,每個 Index (即資料庫)的名字必須是小寫。
Document
Index 裡面單條的記錄稱為 Document(文件)。許多條 Document 構成了一個 Index。
Document 使用 JSON 格式表示,下面是一個例子。
同一個 Index 裡面的 Document,不要求有相同的結構(scheme),但是最好保持相同,這樣有利於提高搜尋效率。
Type
Document 可以分組,比如 weather 這個 Index 裡面,可以按城市分組(北京和上海),也可以按氣候分組(晴天和雨天)。這種分組就叫做 Type,它是虛擬的邏輯分組,用來過濾 Document,類似 MySQL 中的資料表,MongoDB 中的 Collection。
不同的 Type 應該有相似的結構(Schema),舉例來說,id 欄位不能在這個組是字串,在另一個組是數值。這是與關係型資料庫的表的一個區別。性質完全不同的資料(比如 products 和 logs)應該存成兩個 Index,而不是一個 Index 裡面的兩個 Type(雖然可以做到)。
根據規劃,Elastic 6.x 版只允許每個 Index 包含一個 Type,7.x 版將會徹底移除 Type。
Fields
即欄位,每個 Document 都類似一個 JSON 結構,它包含了許多欄位,每個欄位都有其對應的值,多個欄位組成了一個 Document,其實就可以類比 MySQL 資料表中的欄位。
在 Elasticsearch 中,文件歸屬於一種型別(Type),而這些型別存在於索引(Index)中,我們可以畫一些簡單的對比圖來類比傳統關係型資料庫:
-
Relational DB -> Databases -> Tables -> Rows -> Columns
-
Elasticsearch -> Indexs -> Types -> Documents -> Fields
以上就是 Elasticsearch 裡面的一些基本概念,通過和關係性資料庫的對比更加有助於理解。
Python 對接 Elasticsearch
Elasticsearch 實際上提供了一系列 Restful API 來進行存取和查詢操作,我們可以使用 curl 等命令來進行操作,但畢竟命令列模式沒那麼方便,所以這裡我們就直接介紹利用 Python 來對接 Elasticsearch 的相關方法。
Python 中對接 Elasticsearch 使用的就是一個同名的庫,安裝方式非常簡單:
pip3 install elasticsearch
建立 Index
我們先來看下怎樣建立一個索引(Index),這裡我們建立一個名為 news 的索引:
from elasticsearch import Elasticsearch
es = Elasticsearch()
result = es.indices.create(index='news', ignore=400)
print(result)
如果建立成功,會返回如下結果:
{'acknowledged': True, 'shards_acknowledged': True, 'index': 'news'}
返回結果是 JSON 格式,其中的 acknowledged 欄位表示建立操作執行成功。
但這時如果我們再把程式碼執行一次的話,就會返回如下結果:
{'error': {'root_cause': [{'type': 'resource_already_exists_exception', 'reason': 'index [news/QM6yz2W8QE-bflKhc5oThw] already exists', 'index_uuid': 'QM6yz2W8QE-bflKhc5oThw', 'index': 'news'}], 'type': 'resource_already_exists_exception', 'reason': 'index [news/QM6yz2W8QE-bflKhc5oThw] already exists', 'index_uuid': 'QM6yz2W8QE-bflKhc5oThw', 'index': 'news'}, 'status': 400}
它提示建立失敗,status 狀態碼是 400,錯誤原因是 Index 已經存在了。
注意這裡我們的程式碼裡面使用了 ignore 引數為 400,這說明如果返回結果是 400 的話,就忽略這個錯誤不會報錯,程式不會執行丟擲異常。
假如我們不加 ignore 這個引數的話:
es = Elasticsearch()
result = es.indices.create(index='news')
print(result)
再次執行就會報錯了:
raise HTTP_EXCEPTIONS.get(status_code, TransportError)(status_code, error_message, additional_info)
elasticsearch.exceptions.RequestError: TransportError(400, 'resource_already_exists_exception', 'index [news/QM6yz2W8QE-bflKhc5oThw] already exists')
-
這樣程式的執行就會出現問題,所以說,我們需要善用 ignore 引數,把一些意外情況排除,這樣可以保證程式的正常執行而不會中斷。
刪除 Index
刪除 Index 也是類似的,程式碼如下:
from elasticsearch import Elasticsearch
es = Elasticsearch()
result = es.indices.delete(index='news', ignore=[400, 404])
print(result)
這裡也是使用了 ignore 引數,來忽略 Index 不存在而刪除失敗導致程式中斷的問題。
如果刪除成功,會輸出如下結果:
{'acknowledged': True}
如果 Index 已經被刪除,再執行刪除則會輸出如下結果:
{'error': {'root_cause': [{'type': 'index_not_found_exception', 'reason': 'no such index', 'resource.type': 'index_or_alias', 'resource.id': 'news', 'index_uuid': '_na_', 'index': 'news'}], 'type': 'index_not_found_exception', 'reason': 'no such index', 'resource.type': 'index_or_alias', 'resource.id': 'news', 'index_uuid': '_na_', 'index': 'news'}, 'status': 404}
這個結果表明當前 Index 不存在,刪除失敗,返回的結果同樣是 JSON,狀態碼是 400,但是由於我們添加了 ignore 引數,忽略了 400 狀態碼,因此程式正常執行輸出 JSON 結果,而不是丟擲異常。
插入資料
Elasticsearch 就像 MongoDB 一樣,在插入資料的時候可以直接插入結構化字典資料,插入資料可以呼叫 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)
這裡我們首先聲明瞭一條新聞資料,包括標題和連結,然後通過呼叫 create() 方法插入了這條資料,在呼叫 create() 方法時,我們傳入了四個引數,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}
結果中 result 欄位為 created,代表該資料插入成功。
另外其實我們也可以使用 index() 方法來插入資料,但與 create() 不同的是,create() 方法需要我們指定 id 欄位來唯一標識該條資料,而 index() 方法則不需要,如果不指定 id,會自動生成一個 id,呼叫 index() 方法的寫法如下:
es.index(index='news', doc_type='politics', body=data)
create() 方法內部其實也是呼叫了 index() 方法,是對 index() 方法的封裝。
更新資料
更新資料也非常簡單,我們同樣需要指定資料的 id 和內容,呼叫 update() 方法即可,程式碼如下:
-
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)
這裡我們為資料增加了一個日期欄位,然後呼叫了 update() 方法,結果如下:
{'_index': 'news', '_type': 'politics', '_id': '1', '_version': 2, 'result': 'updated', '_shards': {'total': 2, 'successful': 1, 'failed': 0}, '_seq_no': 1, '_primary_term': 1}
可以看到返回結果中,result 欄位為 updated,即表示更新成功,另外我們還注意到有一個欄位 _version,這代表更新後的版本號數,2 代表這是第二個版本,因為之前已經插入過一次資料,所以第一次插入的資料是版本 1,可以參見上例的執行結果,這次更新之後版本號就變成了 2,以後每更新一次,版本號都會加 1。
另外更新操作其實利用 index() 方法同樣可以做到,寫法如下:
es.index(index='news', doc_type='politics', body=data, id=1)
可以看到,index() 方法可以代替我們完成兩個操作,如果資料不存在,那就執行插入操作,如果已經存在,那就執行更新操作,非常方便。
刪除資料
如果想刪除一條資料可以呼叫 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}
可以看到執行結果中 result 欄位為 deleted,代表刪除成功,_version 變成了 3,又增加了 1。
查詢資料
上面的幾個操作都是非常簡單的操作,普通的資料庫如 MongoDB 都是可以完成的,看起來並沒有什麼了不起的,Elasticsearch 更特殊的地方在於其異常強大的檢索功能。
對於中文來說,我們需要安裝一個分詞外掛,這裡使用的是 elasticsearch-analysis-ik,GitHub 連結為:https://github.com/medcl/elasticsearch-analysis-ik,這裡我們使用 Elasticsearch 的另一個命令列工具 elasticsearch-plugin 來安裝,這裡安裝的版本是 6.2.4,請確保和 Elasticsearch 的版本對應起來,命令如下:
elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.2.4/elasticsearch-analysis-ik-6.2.4.zip
這裡的版本號請替換成你的 Elasticsearch 的版本號。
安裝之後重新啟動 Elasticsearch 就可以了,它會自動載入安裝好的外掛。
首先我們新建一個索引並指定需要分詞的欄位,程式碼如下:
-
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)
這裡我們指定了四條資料,都帶有 title、url、date 欄位,然後通過 index() 方法將其插入 Elasticsearch 中,索引名稱為 news,型別為 politics。
接下來我們根據關鍵詞查詢一下相關內容:
result = es.search(index='news', doc_type='politics')
print(result)
可以看到查詢出了所有插入的四條資料:
{
"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"
}
}
]
}
}
可以看到返回結果會出現在 hits 欄位裡面,然後其中有 total 欄位標明瞭查詢的結果條目數,還有 max_score 代表了最大匹配分數。
另外我們還可以進行全文檢索,這才是體現 Elasticsearch 搜尋引擎特性的地方:
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))
這裡我們使用 Elasticsearch 支援的 DSL 語句來進行查詢,使用 match 指定全文檢索,檢索的欄位是 title,內容是“中國領事館”,搜尋結果如下:
{
"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"
}
}
]
}
}
這裡我們看到匹配的結果有兩條,第一條的分數為 2.54,第二條的分數為 0.28,這是因為第一條匹配的資料中含有“中國”和“領事館”兩個詞,第二條匹配的資料中不包含“領事館”,但是包含了“中國”這個詞,所以也被檢索出來了,但是分數比較低。
因此可以看出,檢索時會對對應的欄位全文檢索,結果還會按照檢索關鍵詞的相關性進行排序,這就是一個基本的搜尋引擎雛形。
另外 Elasticsearch 還支援非常多的查詢方式,詳情可以參考官方文件:https://www.elastic.co/guide/en/elasticsearch/reference/6.3/query-dsl.html
以上便是對 Elasticsearch 的基本介紹以及 Python 操作 Elasticsearch 的基本用法,但這僅僅是 Elasticsearch 的基本功能,它還有更多強大的功能等待著我們的探索,後面會繼續更新,敬請期待。
--------------------- 作者:HelloWorld搬運工 來源:CSDN 原文:https://blog.csdn.net/wufaliang003/article/details/81368365?utm_source=copy 版權宣告:本文為博主原創文章,轉載請附上博文連結!