MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

ElasticSearch格式化搜索结果API的定制化

2024-08-174.5k 阅读

ElasticSearch 格式化搜索结果 API 基础

ElasticSearch 作为一款强大的分布式搜索引擎,其提供了丰富的 API 来满足不同场景下对搜索结果的处理需求。格式化搜索结果 API 允许用户以特定的格式获取搜索结果,使得数据展示和进一步处理更加方便。

在 ElasticSearch 中,基本的搜索请求通过 _search 端点发起。例如,一个简单的全索引搜索请求如下:

GET /your_index_name/_search
{
    "query": {
        "match_all": {}
    }
}

上述请求会返回 your_index_name 索引中的所有文档。然而,默认返回的结果格式可能并不满足所有应用场景的需求。ElasticSearch 提供了多种方式来定制搜索结果的格式。

选择特定字段返回

有时候,我们并不需要返回文档中的所有字段,而只需要部分关键信息。可以通过 _source 参数来指定要返回的字段。例如,假设文档包含 titlecontentauthor 字段,我们只希望返回 titleauthor

GET /your_index_name/_search
{
    "query": {
        "match_all": {}
    },
    "_source": ["title", "author"]
}

如果只想排除某些字段,而不是明确指定包含的字段,可以使用 _source.excludes

GET /your_index_name/_search
{
    "query": {
        "match_all": {}
    },
    "_source": {
        "excludes": ["content"]
    }
}

定制高亮显示

高亮显示是格式化搜索结果中非常实用的功能,它可以帮助用户快速定位搜索关键词在文档中的位置。要实现高亮显示,需要在搜索请求中添加 highlight 部分。例如,搜索 title 字段中包含 “elasticsearch” 的文档,并对匹配到的关键词进行高亮:

GET /your_index_name/_search
{
    "query": {
        "match": {
            "title": "elasticsearch"
        }
    },
    "highlight": {
        "fields": {
            "title": {}
        }
    }
}

上述请求中,highlight.fields 定义了要高亮显示的字段。默认情况下,ElasticSearch 使用 <em> 标签包裹高亮的关键词。可以通过 pre_tagspost_tags 来自定义高亮标签。例如:

GET /your_index_name/_search
{
    "query": {
        "match": {
            "title": "elasticsearch"
        }
    },
    "highlight": {
        "fields": {
            "title": {}
        },
        "pre_tags": ["<strong class='highlight'>"],
        "post_tags": ["</strong>"]
    }
}

使用脚本定制结果

ElasticSearch 支持使用脚本对搜索结果进行定制化处理。这在需要对文档字段进行复杂计算或转换时非常有用。例如,假设文档中有 pricediscount 字段,我们希望计算出打折后的价格并在结果中返回。首先,需要确保 ElasticSearch 启用了脚本功能(在 elasticsearch.yml 中配置 script.allowed_types: [painless])。然后,可以使用 Painless 脚本进行计算:

GET /your_index_name/_search
{
    "query": {
        "match_all": {}
    },
    "script_fields": {
        "discounted_price": {
            "script": {
                "source": "doc['price'].value * (1 - doc['discount'].value)",
                "lang": "painless"
            }
        }
    }
}

在上述示例中,script_fields 定义了一个新的字段 discounted_price,通过 Painless 脚本计算得出。

聚合结果格式化

聚合是 ElasticSearch 强大的数据分析功能之一。在获取聚合结果时,也可以对其进行格式化。例如,对文档按 category 字段进行分组,并统计每个分组中的文档数量:

GET /your_index_name/_search
{
    "size": 0,
    "aggs": {
        "category_count": {
            "terms": {
                "field": "category"
            }
        }
    }
}

上述请求中,size: 0 表示不返回文档本身,只返回聚合结果。category_count 是自定义的聚合名称,terms 聚合根据 category 字段进行分组。

可以进一步对聚合结果进行格式化,比如按照文档数量降序排列:

GET /your_index_name/_search
{
    "size": 0,
    "aggs": {
        "category_count": {
            "terms": {
                "field": "category",
                "order": {
                    "_count": "desc"
                }
            }
        }
    }
}

深度定制结果结构

有时候,默认的搜索结果结构无法满足复杂的业务需求,需要对整个结果结构进行深度定制。这可以通过编写自定义插件或使用 ElasticSearch 的管道聚合功能来实现。

自定义插件实现深度定制

编写自定义插件可以让我们完全控制搜索结果的生成过程。以下是一个简单的自定义插件示例,用于修改搜索结果的根结构。首先,创建一个插件项目,结构如下:

my_custom_plugin/
├── src/
│   └── main/
│       ├── java/
│       │   └── com/
│       │       └── example/
│       │           └── myplugin/
│       │               ├── MyCustomSearchResultFormatterPlugin.java
│       │               └── MyCustomSearchResultFormatter.java
│       └── resources/
│           └── elasticsearch-plugin.properties
├── build.gradle

MyCustomSearchResultFormatterPlugin.java 中:

package com.example.myplugin;

import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.plugins.SearchPlugin;
import org.elasticsearch.search.SearchModule;

import java.util.Collection;
import java.util.Collections;

public class MyCustomSearchResultFormatterPlugin extends Plugin implements SearchPlugin {

    @Override
    public Collection<SearchModule.SearchResultFormatter> getSearchResultFormatters() {
        return Collections.singletonList(new MyCustomSearchResultFormatter());
    }
}

MyCustomSearchResultFormatter.java 中:

package com.example.myplugin;

import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.SearchModule;
import org.elasticsearch.search.builder.SearchSourceBuilder;

import java.io.IOException;

public class MyCustomSearchResultFormatter implements SearchModule.SearchResultFormatter {

    @Override
    public String formatterName() {
        return "my_custom_formatter";
    }

    @Override
    public boolean canFormat(SearchSourceBuilder searchSourceBuilder) {
        return true;
    }

    @Override
    public void format(SearchHits searchHits, XContentBuilder builder) throws IOException {
        builder.startObject();
        builder.field("custom_root_field", "This is a custom root field");
        builder.startArray("hits");
        for (SearchHit hit : searchHits.getHits()) {
            builder.startObject();
            builder.field("_id", hit.getId());
            builder.field("_source", hit.getSourceAsMap());
            builder.endObject();
        }
        builder.endArray();
        builder.endObject();
    }
}

elasticsearch-plugin.properties 中:

plugin.name=my_custom_plugin
plugin.description=A custom plugin for formatting search results
plugin.version=1.0.0

然后,使用 Gradle 构建插件并将其安装到 ElasticSearch 中。之后,就可以在搜索请求中使用 format=my_custom_formatter 来获取定制化的结果格式。

管道聚合实现深度定制

管道聚合允许我们基于其他聚合的结果进行进一步的计算和处理,从而实现对结果结构的深度定制。例如,假设有一个按日期统计文档数量的聚合,我们希望计算每天的文档数量占总文档数量的百分比。

GET /your_index_name/_search
{
    "size": 0,
    "aggs": {
        "daily_count": {
            "date_histogram": {
                "field": "timestamp",
                "interval": "day"
            }
        },
        "total_count": {
            "value_count": {
                "field": "_id"
            }
        },
        "percentage_per_day": {
            "bucket_script": {
                "buckets_path": {
                    "daily": "daily_count._count",
                    "total": "total_count.value"
                },
                "script": {
                    "source": "daily / total * 100",
                    "lang": "painless"
                }
            }
        }
    }
}

在上述示例中,date_histogram 聚合按日期统计文档数量,value_count 聚合计算总文档数量,bucket_script 管道聚合根据前两个聚合的结果计算每天的文档数量占比。

多索引搜索结果格式化

当需要在多个索引中进行搜索并对结果进行格式化时,ElasticSearch 同样提供了相应的支持。可以在 _search 端点中指定多个索引名称。例如,在 index1index2 中搜索:

GET /index1,index2/_search
{
    "query": {
        "match_all": {}
    }
}

对于多索引搜索结果的格式化,其原理和单个索引类似。可以同样使用 _sourcehighlight 等参数。例如,在多索引搜索中只返回 title 字段并进行高亮显示:

GET /index1,index2/_search
{
    "query": {
        "match": {
            "title": "search_term"
        }
    },
    "_source": ["title"],
    "highlight": {
        "fields": {
            "title": {}
        }
    }
}

处理嵌套文档的格式化

如果文档中包含嵌套结构,格式化搜索结果时需要特别注意。假设文档结构如下:

{
    "title": "Sample Document",
    "comments": [
        {
            "author": "Author1",
            "text": "This is a comment"
        },
        {
            "author": "Author2",
            "text": "Another comment"
        }
    ]
}

在搜索时,如果希望对嵌套的 comments 字段进行特定的格式化,比如只返回评论作者。可以使用 nested 查询和 _source 的嵌套字段指定:

GET /your_index_name/_search
{
    "query": {
        "nested": {
            "path": "comments",
            "query": {
                "match": {
                    "comments.text": "comment"
                }
            }
        }
    },
    "_source": {
        "includes": ["title", "comments.author"]
    }
}

上述请求会返回匹配到的文档,并只包含 title 字段和 comments 中的 author 字段。

性能优化与格式化搜索结果

在进行格式化搜索结果的操作时,性能是一个重要的考虑因素。以下是一些性能优化的建议:

  • 减少返回字段:只返回必要的字段可以显著减少网络传输和处理开销。如前文所述,通过 _source 参数精确指定返回字段。
  • 合理使用高亮:高亮显示会增加一定的性能开销,尤其是在文档较大时。尽量避免对过长的文本字段进行高亮,或者使用更高效的高亮算法(如使用自定义高亮脚本优化匹配逻辑)。
  • 优化脚本:如果使用脚本定制结果,确保脚本的逻辑简洁高效。避免在脚本中进行复杂的循环或不必要的计算。
  • 缓存结果:对于一些不经常变化的搜索请求和格式化结果,可以考虑使用缓存机制,如 ElasticSearch 自带的缓存功能或外部缓存(如 Redis)。

不同版本 ElasticSearch 的差异

不同版本的 ElasticSearch 在格式化搜索结果 API 方面可能存在一些差异。例如,在较新的版本中,可能会对脚本的安全性和性能进行改进,同时对某些 API 的参数名称或行为进行调整。

在 ElasticSearch 7.x 版本中,引入了一些新的聚合功能和对现有聚合的优化,这可能会影响到聚合结果的格式化方式。例如,bucket_sort 聚合在 7.x 版本中有了更灵活的排序选项,在定制聚合结果排序时需要注意版本差异。

另外,在 ElasticSearch 从 6.x 升级到 7.x 时,部分 API 的语法发生了变化。比如,在 6.x 版本中可以使用 type 来指定文档类型,但在 7.x 版本中逐渐弃用了文档类型的概念,这可能会影响到一些搜索请求和结果格式化的操作。

因此,在进行 ElasticSearch 格式化搜索结果 API 的开发和使用时,务必参考对应版本的官方文档,以确保代码的兼容性和正确性。

与其他系统集成时的格式化考虑

当 ElasticSearch 与其他系统(如 Web 应用、数据分析平台等)集成时,搜索结果的格式化需要考虑到目标系统的需求。

与 Web 应用集成

在与 Web 应用集成时,通常需要将搜索结果格式化为适合前端展示的数据结构。例如,将 ElasticSearch 的 JSON 格式结果转换为 JavaScript 对象,以便在前端使用 React、Vue 等框架进行展示。可以在后端使用中间件(如 Node.js 的 Express 框架中的自定义中间件)对搜索结果进行预处理。例如,将高亮显示的 HTML 标签进行转义,以防止 XSS 攻击:

const express = require('express');
const app = express();
const elasticsearch = require('elasticsearch');

const client = new elasticsearch.Client({
    host: 'localhost:9200',
    log: 'trace'
});

app.get('/search', async (req, res) => {
    const response = await client.search({
        index: 'your_index_name',
        body: {
            query: {
                match: {
                    title: req.query.q
                }
            },
            highlight: {
                fields: {
                    title: {}
                }
            }
        }
    });

    const formattedResults = response.hits.hits.map(hit => {
        const source = hit._source;
        const highlight = hit.highlight || {};
        source.title = highlight.title ? highlight.title[0].replace(/</g, '&lt;').replace(/>/g, '&gt;') : source.title;
        return source;
    });

    res.json(formattedResults);
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

与数据分析平台集成

当与数据分析平台(如 Tableau、PowerBI 等)集成时,可能需要将 ElasticSearch 的搜索结果格式化为特定的数据格式,如 CSV、JSON Lines 等。可以使用 ElasticSearch 的 _export API(如果支持)或通过编写脚本将搜索结果转换为目标格式。例如,将搜索结果转换为 CSV 格式:

from elasticsearch import Elasticsearch
import csv

es = Elasticsearch(['localhost:9200'])

response = es.search(
    index='your_index_name',
    body={
        "query": {
            "match_all": {}
        }
    }
)

with open('results.csv', 'w', newline='') as csvfile:
    fieldnames = response['hits']['hits'][0]['_source'].keys()
    writer = csv.DictWriter(csvfile, fieldnames=fieldnames)

    writer.writeheader()
    for hit in response['hits']['hits']:
        writer.writerow(hit['_source'])

通过以上对 ElasticSearch 格式化搜索结果 API 定制化的详细介绍,包括基础操作、深度定制、性能优化、版本差异以及与其他系统集成等方面,希望能帮助开发者更好地利用 ElasticSearch 满足各种复杂的搜索结果格式化需求。在实际应用中,需要根据具体的业务场景和数据特点,灵活选择和组合各种定制化方法,以实现高效、准确且符合需求的搜索结果展示和处理。