days05-DSL查询文档以及对搜索结果进行处理

一、 DSL查询文档

        1. DSL查询分类

DSL Query的分类

Elasticsearch提供了基于JSON的DSL(Domain Specific  Language)来定义查询。常见的查询类型包括:

  • 查询所有:查询出所有数据,一般测试用。例如:match_all
  • 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:
    • match_query
    • multi_match_query
  • 精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如:
    • ids
    • range
    • term
  • 地理(geo)查询:根据经纬度查询。例如:
    • geo_distance
    • geo_bounding_box
  • 复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:
    • bool
    • function_score

        利用下面的代码可以对索引库数据进行查询所有的指令,当运行成功以后我们会发现,数据并不是 所有的,是因为对内存和对服务器有压力,只是查出来一部分,当后面学到了分页查询就可以查后面的数据了!!

#在Dev-Tools中
#查询所有
GET /hotel/_search
{
  "query": {
    "match_all": {
    }
  }
}

        2. 全文检索查询

全文检索查询,会对用户输入内容分词,常用于搜索框搜索:

 match查询:全文检索查询的一种,会对用户输入内容分词,然后去倒排索引库检索,语法:

# match查询
GET /hotel/_search
{
  "query": {
    "match": {
      "all": "如家外滩"
    }
  }
}

multi_match:与match查询类似,只不过允许同时查询多个字段,语法:

# multi_match查询
GET /hotel/_search
{
  "query": {
    "multi_match": {
      "query": "如家外滩",
      "fields": ["brand","name"]
    }
  }
}

总结: 

match和multi_match的区别是什么?

  • match:根据一个字段查询
  • multi_match:根据多个字段查询,参与查询字段越多,查询性能越差

        3. 精准查询

精确查询一般是查找keyword、数值、日期、boolean等类型字段。是一个不可分割的整体,所以不会对搜索条件分词。常见的有:

  • term:根据词条精确值查询
  • range:根据值的范围查询

# 精确查询————term
GET /hotel/_search
{
  "query": {
    "term": {
      "city": {
        "value": "上海"
      }
    }
  }
}

# 精确查询————range
GET /hotel/_search
{
  "query": {
   "range": {
     "price": {
       "gte": 100, #大于等于,gt:大于
       "lte": 1000 # 小于等于  lt 小于
     }
   }
  }
}

        4. 地理坐标查询

根据经纬度查询。常见的使用场景包括:

  • 携程:搜索我附近的酒店

  • 滴滴:搜索我附近的出租车

  • 微信:搜索我附近的人

                                

根据经纬度查询。例如:

  • geo_bounding_box:查询geo_point值落在某个矩形范围的所有文档

# geo_bounding_box 查询
GET /hotel/_search
{
  "query": {
    "geo_bounding_box":{
      "location":{
  # 以左上角和右下角为长宽 形成一个矩形
        "top_left":{
          "lat":31.1,
          "lon":121.5
        },
        "bottom_right":{
          "lat":30.9,
          "lon":121.7
        }
      }
    }
  }
}

  •  geo_distance:查询到指定中心点小于某个距离值的所有文档

# geo_distance 查询
GET /hotel/_search
{
  "query": {
    "geo_distance":{
      "distance":"10km",
      "location":"31.21,121.5"
    }
  }
}

        5. 组合查询

复合(compound)查询:复合查询可以将其它简单查询组合起来,实现更复杂的搜索逻辑,例如:

  • fuction score:算分函数查询,可以控制文档相关性算分,控制文档排名。比如说我们在搜索外滩+如家的时候 会发现这两者都包含的会显示在靠前的位置,得分会比较高,而只包含二者中的一者时候会靠后,得分比较低,这就是相关性。例如百度竞价。允许有广告的排在第一上。

 相关性算分

当我们利用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列。

 总结:

elasticsearch中的相关性打分算法是什么?

  • TF-IDF:在elasticsearch5.0之前,会随着词频增加而越来越大
  • BM25:在elasticsearch5.0之后,会随着词频增加而增大,但增长曲线会趋于水平

Function Score Query

        可以修改文档的相关性算分(query score),根据新得到的算分排序。一般来说查出来的都会按照相关性由高向下排,但是比如说人家花了钱了,虽然相关性不高但是也需要给人家的信息提前。比如百度搜索的时候,一般来说首发都是广告...

 案例:给“如家”这个品牌的酒店排名靠前一些???

GET /hotel/_search
{
  "query": {
    "function_score": {
      "query": {"match": {  # 正常搜索方式
        "all": "外滩"
      }},
      "functions": [  
        {
          "filter": {
            "term": {
              "brand": "如家" # 过滤条件:
                        #在正常搜索出的结果对如家酒店+10分 并且与原始分数相乘
            }
          },
          "weight": 10  # 算分函数
        }
      ],
      "boost_mode": "multiply"  #加权方式  function score与query score如何运算
    }
  }
}

总结:

function score query定义的三要素是什么?

  • 过滤条件:哪些文档要加分

  • 算分函数:如何计算function  score

  • 加权方式:function score 与 query score如何运算

复合查询 Boolean Query

  • must:必须匹配每个子查询,类似“与”
  • should:选择性匹配子查询,类似“或”
  • must_not:必须不匹配,不参与算分,类似“非”
  • filter:必须匹配,不参与算分

 不参与算分:只会输出是或者否

比如说下面的图片,当我们在搜索时,比如在点击城市为”上海“,价格在”100-300元的基础上,再搜索栏里面搜索“如家”的时候,前两者就可以用filter 不参与算分,这样可以减少服务器的计算压力。

 案例:利用bool查询实现功能

需求:搜索名字包含“如家”,价格不高于400,在坐标31.21,121.5周围10km范围内的酒店。

GET /hotel/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "如家"
          }
        }
      ],
      "must_not": [
        {
          "range": {
            "price": {
              "gt": 400
            }
          }
        }
      ],
      "filter": [
        {
         "geo_distance": {
           "distance": "10km", 
           "location": {
             "lat": 31.21,
             "lon": 121.5
           }
         } 
        }
      ]
    }
  }
}

二、  搜索结果处理

        1. 排序

elasticsearch支持对搜索结果排序,默认是根据相关度算分(_score)来排序。可以排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等。

案例:对酒店数据按照用户评价降序排序,评价相同的按照价格升序排序

# sort 排序

GET /hotel/_search
{
  "query": {
    "match_all": {}
  }
  , "sort": [
    {
        "score": "desc"
     
    },
    {
      #当分数相同时,看价格那个便宜
      "price": "asc"
    }
  ]
}

 案例:实现对酒店数据按照到你的位置坐标的距离升序排序

 获取经纬度的方式:https://lbs.amap.com/demo/jsapi-v2/example/map/click-to-get-lnglat/


# 找到114.50856,38.083942(自己所在位置) 周围的酒店,距离升序


GET /hotel/_search
{
  "query": {
    "match_all": {}
  }
  , "sort": [
    {
        "_geo_distance": {
          "location": {
            "lat": 38.083942,
            "lon": 114.50856
          },
          "order": "asc" , 
          "unit": "km"
        }
    }
  ]
}

当运行完结果就会发现score=null,也就是说当我们在排序的时候就不参与打分了,因为无意义了,这样也会提高效率,减小计算压力!! 

        2. 分页

elasticsearch 默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了。

elasticsearch中通过修改from、size参数来控制要返回的分页结果:

当单点分页的时候会发现很正常可以理解这样的分页逻辑,但是当面对集群时呢?

ES是分布式的,所以会面临深度分页问题。例如按price排序后,获取from = 990,size =10的数据:

  1. 首先在每个数据分片上都排序并查询前1000条文档。
  2. 然后将所有节点的结果聚合,在内存中重新排序选出前1000条文档
  3. 最后从这1000条中,选取从990开始的10条文档

如果搜索页数过深,或者结果集(from + size)越大,对内存和CPU的消耗也越高。因此ES设定结果集查询的上限是10000

那如果我们查询的数据结果集大于10000该怎么办呢?

深度分页解决方案

 针对深度分页,ES提供了两种解决方案:

  • search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
  • scroll:原理将排序数据形成快照,保存在内存。官方已经不推荐使用。

总结:

 from + size:

  • 优点:支持随机翻页

  • 缺点:深度分页问题,默认查询上限(from + size)是10000

  • 场景:百度、京东、谷歌、淘宝这样的随机翻页搜索

after search:

  • 优点:没有查询上限(单次查询的size不超过10000)

  • 缺点:只能向后逐页查询,不支持随机翻页

  • 场景:没有随机翻页需求的搜索,例如手机向下滚动翻页

scroll:

  • 优点:没有查询上限(单次查询的size不超过10000)

  • 缺点:会有额外内存消耗,并且搜索结果是非实时的

  • 场景:海量数据的获取和迁移。

从ES7.1开始不推荐,建议用 after search方案。

        3. 高亮

高亮:就是在搜索结果中把搜索关键字突出显示。

原理是这样的:

  • 将搜索结果中的关键字用标签标记出来
  • 在页面中给标签添加css样式

# 当 match与高亮中的字段保持一致时
GET /hotel/_search
{
  
  "query": {
    "match": {
      "name": "如家"
    }
  },
  "highlight": {
    "fields": {
      "name":{
        
    }
    }
  }
}
# 高亮显示查询,默认情况下,Es搜索字段必须与高亮字段一致
# 当match查找与高亮字段不一致时,我们的all中包含name字段
GET /hotel/_search
{
  
  "query": {
    "match": {
      "all": "如家"
    }
  },
  "highlight": {
    "fields": {
      "name":{
        "require_field_match": "false"  # 默认为true
    }
    }
  }
}

总结:搜索结果处理整体语法: 

 三、RestClient查询文档

        1. 快速入门

 测试:我们新建一个测试类用来实现今天所学内容,名为 HotelSearchTest:

private RestHighLevelClient client;

    @Test
    void testMatchAll() throws IOException {
        //1. 准备request
        SearchRequest request = new SearchRequest("hotel");
        //2. 准备DSL
        request.source().query(QueryBuilders.matchAllQuery());
        //3. 发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //4. 解析结果
        SearchHits searchHits = response.getHits();
        //4,1 查询总条数
        long total = searchHits.getTotalHits().value;
        //4.2 查询的结果数组
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit:hits){
            // 4.3 得到source
            String source = hit.getSourceAsString();
            // 4.4 打印结果
            System.out.println(source);
        }

    }

    @BeforeEach
    void setUp(){
        this.client=new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://192.168.229.101:9200")
        ));
    }
        @AfterEach
        void tearDown() throws IOException {
            this.client.close();

    }

 RestAPI中其中构建DSL是通过HighLevelRestClient中的resource()来实现的,其中包含了查询、排序、分页、高亮等所有功能:

总结:

查询的基本步骤是:

  1. 创建SearchRequest对象

  2. 准备Request.source(),也就是DSL。

    1. QueryBuilders来构建查询条件

    2. 传入Request.source() 的 query() 方法

  3. 发送请求,得到结果

  4. 解析结果(参考JSON结果,从外到内,逐层解析)

        2. match查询

只需要在上面的MacthAll代码中将其中一行的代码替换为以下代码即可,其余代码不变:

//2. 准备DSL
        request.source().query(QueryBuilders.matchQuery("name","如家"));

        3. 精确查询

会发现我们有些步骤只需要复制粘贴就行无需改动,所以利用抽取的方法将相同部分定义成一个方法,每个查询方法中只需要调用即可:抽取快捷键:ctrl+alt+m

同样的道理,只有第二步代码不同:

# 精确查询——term
//2. 准备DSL
        request.source().query(QueryBuilders.termQuery("city","上海"));
# 精确查询——range
//2. 准备DSL
  request.source().query(QueryBuilders.rangeQuery("price").gte(100).lte(300));

        4. 复合查询

    /*复合查询*/
    @Test
    void testBool() throws IOException {
        //1. 准备request
        SearchRequest request = new SearchRequest("hotel");
        //2. 准备DSL
        //2.1 准备BoolQuery
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //2.2 添加term
        boolQuery.must(QueryBuilders.termQuery("city","北京"));
        //2.3 添加range
        boolQuery.filter(QueryBuilders.rangeQuery("price").gte(200).lte(1000));
        request.source().query(boolQuery);
        //3. 发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        handleResponse(response);

    }

 通过这几个查询,可以总结出,我们在构建查询条件时,只要记住一个类:QueryBuilders 

        5. 排序、分页、高亮 

                5.1 排序、分页

        搜索结果的排序和分页是与query同级的参数,对应的API代码如下:

  /*排序和分页*/
    @Test
    void testPageAndSort() throws IOException {
        // 页码,每页大小
        int page=2,size=5;
        //1. 准备request
        SearchRequest request = new SearchRequest("hotel");
        //2. 准备DSL
        //2.1 准备query
        request.source().query(QueryBuilders.matchAllQuery());
        //2.2 排序sort
        request.source().sort("price", SortOrder.DESC);
        //2.3 分页
        request.source().from((page-1)*size).size(size);
        //3. 发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        handleResponse(response);
    }

                 5.2 高亮

高亮API包括请求DSL构建和结果解析两部分。我们先看看请求的DSL构建:

 

 

    @Test
    void testHighLight() throws IOException {

        //1. 准备request
        SearchRequest request = new SearchRequest("hotel");
        //2. 准备DSL
        //2.1 准备query
        request.source().query(QueryBuilders.matchQuery("all","如家"));
        //2.2 高亮
        request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
        //3. 发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        //4. 解析
        handleResponse(response);
    }



//在抽取的共同代码中也就是解析结果的代码中加入高亮解析代码
 // 获取高亮结果
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if(!CollectionUtils.isEmpty(highlightFields)) {
                // 根据字段名获取高亮结果
                HighlightField highlightField = highlightFields.get("name");
                //获取高亮值
                if (highlightField!=null) {
                    String name = highlightField.getFragments()[0].string();
                    // 覆盖高亮结果
                    doc.setName(name);
                }

            }

 总结:

  • 所有搜索DSL的构建,记住一个API:SearchRequest的source()方法。

  • 高亮结果解析式参考json结果,逐层解析

 

        本期关于查询文档以及搜索结果处理的操作先写到这哈!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要根据高频词对文档进行分类,可以使用以下步骤: 1. 收集待分类文档,并将它们存储在一个列表或数据框中。 2. 对每个文档进行文本预处理,包括分词、去停用词、词干化等操作,以便能够提取出单词。 3. 统计每个文档中单词的频率,并按照频率从高到低排序,选取前N个频率最高的单词作为该文档的特征词。 4. 统计所有文档中出现的单词频率,并按照频率从高到低排序,选取前M个频率最高的单词作为所有文档的特征词。 5. 将每个文档表示为一个向量,向量中的每个元素对应一个特征词的出现次数。 6. 使用分类算法(如朴素贝叶斯、支持向量机等)对向量进行分类。 下面是一个简单的示例代码,可以用于对文档进行分类: ```python import pandas as pd from sklearn.feature_extraction.text import CountVectorizer from sklearn.naive_bayes import MultinomialNB # 收集待分类文档 docs = [ "This is a document about python programming.", "I love to program in python.", "Python programming is very popular these days.", "Java is another popular programming language.", "I prefer python over java for programming tasks." ] # 定义停用词 stop_words = ['is', 'a', 'about', 'to', 'in', 'very', 'these', 'days', 'another', 'for', 'tasks'] # 对每个文档进行文本预处理 vectorizer = CountVectorizer(stop_words=stop_words) X = vectorizer.fit_transform(docs) # 统计每个文档中单词的频率,并按照频率从高到低排序 words_freq = pd.DataFrame(X.toarray(), columns=vectorizer.get_feature_names()) words_freq = words_freq.sum().sort_values(ascending=False) # 选取前N个频率最高的单词作为该文档的特征词 N = 3 top_words = words_freq[:N].index.tolist() # 将每个文档表示为一个向量 X = X.toarray() X = [[doc[i] for i in range(len(doc)) if vectorizer.get_feature_names()[i] in top_words] for doc in X] # 使用分类算法进行分类 y = ['Python', 'Python', 'Python', 'Java', 'Python'] clf = MultinomialNB() clf.fit(X, y) # 测试分类器 test_doc = "I want to learn python programming." test_doc = vectorizer.transform([test_doc]) test_doc = [test_doc[0, i] for i in range(test_doc.shape[1]) if vectorizer.get_feature_names()[i] in top_words] pred = clf.predict([test_doc]) print(pred) ``` 在上面的示例代码中,我们使用朴素贝叶斯分类器对文档进行分类。首先,我们使用CountVectorizer对文档进行处理,将文档表示为向量。然后,我们统计每个文档中单词的频率,并选取前N个频率最高的单词作为该文档的特征词。接着,我们将每个文档表示为一个向量,向量中的每个元素对应一个特征词的出现次数。最后,我们使用朴素贝叶斯分类器对向量进行分类,得到文档所属的类别。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值