Elasticsearch هو محرك بحث مزود بـ json Rest API، باستخدام Lucene ومكتوب بلغة Java. يتوفر وصف لجميع مزايا هذا المحرك على
يتم استخدام محركات مماثلة لعمليات البحث المعقدة في قاعدة بيانات المستندات. على سبيل المثال، البحث مع الأخذ بعين الاعتبار مورفولوجية اللغة أو البحث عن طريق الإحداثيات الجغرافية.
سأتحدث في هذه المقالة عن أساسيات ES باستخدام مثال فهرسة منشورات المدونة. سأوضح لك كيفية تصفية المستندات وفرزها والبحث فيها.
لكي لا أعتمد على نظام التشغيل، سأقدم جميع الطلبات إلى ES باستخدام CURL. هناك أيضًا مكون إضافي لـ Google Chrome يسمى
يحتوي النص على روابط للوثائق والمصادر الأخرى. وفي النهاية توجد روابط للوصول السريع إلى الوثائق. يمكن العثور على تعريفات للمصطلحات غير المألوفة في
تثبيت
للقيام بذلك، نحتاج أولا إلى جافا. المطورين
توزيع ES متاح في bin/elasticsearch
. أيضا متاح
بعد التثبيت والتشغيل، دعونا نتحقق من الوظيفة:
# для удобства запомним адрес в переменную
#export ES_URL=$(docker-machine ip dev):9200
export ES_URL=localhost:9200
curl -X GET $ES_URL
سوف نتلقى شيئا مثل هذا:
{
"name" : "Heimdall",
"cluster_name" : "elasticsearch",
"version" : {
"number" : "2.2.1",
"build_hash" : "d045fc29d1932bce18b2e65ab8b297fbf6cd41a1",
"build_timestamp" : "2016-03-09T09:38:54Z",
"build_snapshot" : false,
"lucene_version" : "5.4.1"
},
"tagline" : "You Know, for Search"
}
الفهرسة
دعونا نضيف منشورًا إلى ES:
# Добавим документ c id 1 типа post в индекс blog.
# ?pretty указывает, что вывод должен быть человеко-читаемым.
curl -XPUT "$ES_URL/blog/post/1?pretty" -d'
{
"title": "Веселые котята",
"content": "<p>Смешная история про котят<p>",
"tags": [
"котята",
"смешная история"
],
"published_at": "2014-09-12T20:44:42+00:00"
}'
استجابة الخادم:
{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"created" : false
}
تم إنشاء ES تلقائيًا
# Получим mapping всех типов индекса blog
curl -XGET "$ES_URL/blog/_mapping?pretty"
في استجابة الخادم قمت بإضافة قيم حقول الوثيقة المفهرسة في التعليقات:
{
"blog" : {
"mappings" : {
"post" : {
"properties" : {
/* "content": "<p>Смешная история про котят<p>", */
"content" : {
"type" : "string"
},
/* "published_at": "2014-09-12T20:44:42+00:00" */
"published_at" : {
"type" : "date",
"format" : "strict_date_optional_time||epoch_millis"
},
/* "tags": ["котята", "смешная история"] */
"tags" : {
"type" : "string"
},
/* "title": "Веселые котята" */
"title" : {
"type" : "string"
}
}
}
}
}
}
تجدر الإشارة إلى أن ES لا يفرق بين قيمة واحدة ومجموعة من القيم. على سبيل المثال، يحتوي حقل العنوان ببساطة على عنوان، ويحتوي حقل العلامات على مصفوفة من السلاسل، على الرغم من تمثيلها بنفس الطريقة في التعيين.
سنتحدث أكثر عن رسم الخرائط لاحقًا.
طلبات
استرجاع مستند عن طريق معرفه:
# извлечем документ с id 1 типа post из индекса blog
curl -XGET "$ES_URL/blog/post/1?pretty"
{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"title" : "Веселые котята",
"content" : "<p>Смешная история про котят<p>",
"tags" : [ "котята", "смешная история" ],
"published_at" : "2014-09-12T20:44:42+00:00"
}
}
ظهرت مفاتيح جديدة في الرد: _version
и _source
. بشكل عام، جميع المفاتيح تبدأ بـ _
تصنف على أنها رسمية.
مفتاح _version
يظهر نسخة الوثيقة. إنه ضروري لكي تعمل آلية القفل المتفائلة. على سبيل المثال، نريد تغيير مستند له الإصدار 1. ونرسل المستند الذي تم تغييره ونشير إلى أن هذا تحرير لمستند بالإصدار 1. إذا قام شخص آخر أيضًا بتحرير مستند بالإصدار 1 وأرسل التغييرات قبلنا، إذن لن يقبل ES تغييراتنا، لأنه يقوم بتخزين المستند بالإصدار 2.
مفتاح _source
يحتوي على الوثيقة التي قمنا بفهرستها. لا يستخدم ES هذه القيمة لعمليات البحث لأن يتم استخدام الفهارس للبحث. لتوفير المساحة، يقوم ES بتخزين مستند مصدر مضغوط. إذا كنا بحاجة إلى المعرف فقط، وليس المستند المصدر بأكمله، فيمكننا تعطيل تخزين المصدر.
إذا لم نكن بحاجة إلى معلومات إضافية، يمكننا الحصول فقط على محتويات _source:
curl -XGET "$ES_URL/blog/post/1/_source?pretty"
{
"title" : "Веселые котята",
"content" : "<p>Смешная история про котят<p>",
"tags" : [ "котята", "смешная история" ],
"published_at" : "2014-09-12T20:44:42+00:00"
}
يمكنك أيضًا تحديد حقول معينة فقط:
# извлечем только поле title
curl -XGET "$ES_URL/blog/post/1?_source=title&pretty"
{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"title" : "Веселые котята"
}
}
لنقم بفهرسة عدد قليل من المشاركات الإضافية وتشغيل استعلامات أكثر تعقيدًا.
curl -XPUT "$ES_URL/blog/post/2" -d'
{
"title": "Веселые щенки",
"content": "<p>Смешная история про щенков<p>",
"tags": [
"щенки",
"смешная история"
],
"published_at": "2014-08-12T20:44:42+00:00"
}'
curl -XPUT "$ES_URL/blog/post/3" -d'
{
"title": "Как у меня появился котенок",
"content": "<p>Душераздирающая история про бедного котенка с улицы<p>",
"tags": [
"котята"
],
"published_at": "2014-07-21T20:44:42+00:00"
}'
Сортировка
# найдем последний пост по дате публикации и извлечем поля title и published_at
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"size": 1,
"_source": ["title", "published_at"],
"sort": [{"published_at": "desc"}]
}'
{
"took" : 8,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
},
"hits" : {
"total" : 3,
"max_score" : null,
"hits" : [ {
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_score" : null,
"_source" : {
"title" : "Веселые котята",
"published_at" : "2014-09-12T20:44:42+00:00"
},
"sort" : [ 1410554682000 ]
} ]
}
}
لقد اخترنا المشاركة الأخيرة. size
يحد من عدد الوثائق التي سيتم إصدارها. total
يظهر إجمالي عدد المستندات المطابقة للطلب. sort
في الإخراج يحتوي على مجموعة من الأعداد الصحيحة التي يتم من خلالها إجراء الفرز. أولئك. تم تحويل التاريخ إلى عدد صحيح. يمكن العثور على مزيد من المعلومات حول الفرز في
المرشحات والاستعلامات
ES منذ الإصدار 2 لا يميز بين عوامل التصفية والاستعلامات، بدلا من ذلك
يختلف سياق الاستعلام عن سياق عامل التصفية حيث يقوم الاستعلام بإنشاء _score ولا يتم تخزينه مؤقتًا. سأريكم ما هو _score لاحقًا.
تصفية حسب التاريخ
نحن نستخدم الطلب
# получим посты, опубликованные 1ого сентября или позже
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"filter": {
"range": {
"published_at": { "gte": "2014-09-01" }
}
}
}'
التصفية حسب العلامات
نحن نستخدم
# найдем все документы, в поле tags которых есть элемент 'котята'
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"_source": [
"title",
"tags"
],
"filter": {
"term": {
"tags": "котята"
}
}
}'
{
"took" : 9,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
},
"hits" : {
"total" : 2,
"max_score" : 1.0,
"hits" : [ {
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"title" : "Веселые котята",
"tags" : [ "котята", "смешная история" ]
}
}, {
"_index" : "blog",
"_type" : "post",
"_id" : "3",
"_score" : 1.0,
"_source" : {
"title" : "Как у меня появился котенок",
"tags" : [ "котята" ]
}
} ]
}
}
البحث عن النص الكامل
تحتوي ثلاث من وثائقنا على ما يلي في حقل المحتوى:
<p>Смешная история про котят<p>
<p>Смешная история про щенков<p>
<p>Душераздирающая история про бедного котенка с улицы<p>
نحن نستخدم
# source: false означает, что не нужно извлекать _source найденных документов
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"_source": false,
"query": {
"match": {
"content": "история"
}
}
}'
{
"took" : 13,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
},
"hits" : {
"total" : 3,
"max_score" : 0.11506981,
"hits" : [ {
"_index" : "blog",
"_type" : "post",
"_id" : "2",
"_score" : 0.11506981
}, {
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_score" : 0.11506981
}, {
"_index" : "blog",
"_type" : "post",
"_id" : "3",
"_score" : 0.095891505
} ]
}
}
لكن إذا بحثنا عن “القصص” في حقل المحتوى فلن نجد شيئاً، لأن يحتوي الفهرس على الكلمات الأصلية فقط، وليس على أصولها. من أجل إجراء بحث عالي الجودة، تحتاج إلى تكوين المحلل.
حقل _score
عروض
محللات
يتكون المحللون من واحد
ES لديها عدة
لنستخدم
# используем анализатор standard
# обязательно нужно перекодировать не ASCII символы
curl -XGET "$ES_URL/_analyze?pretty&analyzer=standard&text=%D0%92%D0%B5%D1%81%D0%B5%D0%BB%D1%8B%D0%B5%20%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D0%B8%20%D0%BF%D1%80%D0%BE%20%D0%BA%D0%BE%D1%82%D1%8F%D1%82"
{
"tokens" : [ {
"token" : "веселые",
"start_offset" : 0,
"end_offset" : 7,
"type" : "<ALPHANUM>",
"position" : 0
}, {
"token" : "истории",
"start_offset" : 8,
"end_offset" : 15,
"type" : "<ALPHANUM>",
"position" : 1
}, {
"token" : "про",
"start_offset" : 16,
"end_offset" : 19,
"type" : "<ALPHANUM>",
"position" : 2
}, {
"token" : "котят",
"start_offset" : 20,
"end_offset" : 25,
"type" : "<ALPHANUM>",
"position" : 3
} ]
}
# используем анализатор russian
curl -XGET "$ES_URL/_analyze?pretty&analyzer=russian&text=%D0%92%D0%B5%D1%81%D0%B5%D0%BB%D1%8B%D0%B5%20%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D0%B8%20%D0%BF%D1%80%D0%BE%20%D0%BA%D0%BE%D1%82%D1%8F%D1%82"
{
"tokens" : [ {
"token" : "весел",
"start_offset" : 0,
"end_offset" : 7,
"type" : "<ALPHANUM>",
"position" : 0
}, {
"token" : "истор",
"start_offset" : 8,
"end_offset" : 15,
"type" : "<ALPHANUM>",
"position" : 1
}, {
"token" : "кот",
"start_offset" : 20,
"end_offset" : 25,
"type" : "<ALPHANUM>",
"position" : 3
} ]
}
قام المحلل القياسي بتقسيم السلسلة إلى مسافات وتحويل كل شيء إلى أحرف صغيرة، وقام المحلل الروسي بإزالة الكلمات غير المهمة وتحويلها إلى أحرف صغيرة وترك أصل الكلمات.
دعونا نرى أي Tokenizer وTokenFilters وCharFilters يستخدمه المحلل الروسي:
{
"filter": {
"russian_stop": {
"type": "stop",
"stopwords": "_russian_"
},
"russian_keywords": {
"type": "keyword_marker",
"keywords": []
},
"russian_stemmer": {
"type": "stemmer",
"language": "russian"
}
},
"analyzer": {
"russian": {
"tokenizer": "standard",
/* TokenFilters */
"filter": [
"lowercase",
"russian_stop",
"russian_keywords",
"russian_stemmer"
]
/* CharFilters отсутствуют */
}
}
}
دعونا نصف محللنا استنادًا إلى اللغة الروسية، والذي سيقطع علامات html. دعنا نسميها افتراضية، لأنها سيتم استخدام محلل بهذا الاسم بشكل افتراضي.
{
"filter": {
"ru_stop": {
"type": "stop",
"stopwords": "_russian_"
},
"ru_stemmer": {
"type": "stemmer",
"language": "russian"
}
},
"analyzer": {
"default": {
/* добавляем удаление html тегов */
"char_filter": ["html_strip"],
"tokenizer": "standard",
"filter": [
"lowercase",
"ru_stop",
"ru_stemmer"
]
}
}
}
أولاً، ستتم إزالة جميع علامات HTML من السلسلة المصدر، ثم سيقوم معيار الرمز المميز بتقسيمها إلى رموز مميزة، وستنتقل الرموز المميزة الناتجة إلى أحرف صغيرة، وستتم إزالة الكلمات غير المهمة، وستظل الرموز المميزة المتبقية هي أصل الكلمة.
إنشاء فهرس
أعلاه وصفنا المحلل الافتراضي. سيتم تطبيقه على جميع حقول السلسلة. يحتوي منشورنا على مجموعة من العلامات، لذلك ستتم معالجة العلامات أيضًا بواسطة المحلل. لأن نحن نبحث عن المشاركات حسب المطابقة التامة للعلامة، ثم نحتاج إلى تعطيل التحليل لحقل العلامات.
لنقم بإنشاء مدونة فهرس 2 باستخدام محلل ورسم خرائط، حيث يتم تعطيل تحليل حقل العلامات:
curl -XPOST "$ES_URL/blog2" -d'
{
"settings": {
"analysis": {
"filter": {
"ru_stop": {
"type": "stop",
"stopwords": "_russian_"
},
"ru_stemmer": {
"type": "stemmer",
"language": "russian"
}
},
"analyzer": {
"default": {
"char_filter": [
"html_strip"
],
"tokenizer": "standard",
"filter": [
"lowercase",
"ru_stop",
"ru_stemmer"
]
}
}
}
},
"mappings": {
"post": {
"properties": {
"content": {
"type": "string"
},
"published_at": {
"type": "date"
},
"tags": {
"type": "string",
"index": "not_analyzed"
},
"title": {
"type": "string"
}
}
}
}
}'
دعونا نضيف نفس المشاركات الثلاث إلى هذا الفهرس (blog3). سأحذف هذه العملية لأن... إنه مشابه لإضافة المستندات إلى فهرس المدونة.
البحث عن النص الكامل مع دعم التعبير
دعونا نلقي نظرة على نوع آخر من الطلب:
# найдем документы, в которых встречается слово 'истории'
# query -> simple_query_string -> query содержит поисковый запрос
# поле title имеет приоритет 3
# поле tags имеет приоритет 2
# поле content имеет приоритет 1
# приоритет используется при ранжировании результатов
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
"query": {
"simple_query_string": {
"query": "истории",
"fields": [
"title^3",
"tags^2",
"content"
]
}
}
}'
لأن نحن نستخدم محللًا ذو أصول روسية، ثم سيعيد هذا الطلب جميع المستندات، على الرغم من أنها تحتوي فقط على كلمة "تاريخ".
قد يحتوي الطلب على أحرف خاصة، على سبيل المثال:
""fried eggs" +(eggplant | potato) -frittata"
طلب بناء الجملة:
+ signifies AND operation
| signifies OR operation
- negates a single token
" wraps a number of tokens to signify a phrase for searching
* at the end of a term signifies a prefix query
( and ) signify precedence
~N after a word signifies edit distance (fuzziness)
~N after a phrase signifies slop amount
# найдем документы без слова 'щенки'
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
"query": {
"simple_query_string": {
"query": "-щенки",
"fields": [
"title^3",
"tags^2",
"content"
]
}
}
}'
# получим 2 поста про котиков
مراجع
PS
إذا كنت مهتمًا بمقالات ودروس مماثلة، أو لديك أفكار لمقالات جديدة، أو لديك مقترحات للتعاون، فسأكون سعيدًا بتلقي رسالة في رسالة شخصية أو عبر البريد الإلكتروني [البريد الإلكتروني محمي].
المصدر: www.habr.com