elasticsearch基础入门教程

elasticsearch基础入门教程

Posted by yishuifengxiao on 2019-09-20

一 elasticsearch 的基础知识

1.1 基本概念

  • 索引是 ElasticSearch 存放数据的地方,可以理解为关系型数据库中的一个数据库。事实上,我们的数据被存储和索引在分片(shards)中,索引只是一个把一个或多个分片分组在一起的逻辑空间。(索引的名字必须是全部小写,不能以下划线开头,不能包含逗号
  • 类型用于区分同一个索引下不同的数据类型,相当于关系型数据库中的表。在 Elasticsearch 中,我们使用相同类型(type)的文档表示相同的“事物”,因为他们的数据结构也是相同的。每个类型(type)都有自己的映射(mapping)或者结构定义,就像传统数据库表中的列一样。所有类型下的文档被存储在同一个索引下,但是类型的映射(mapping)会告诉 Elasticsearch 不同的文档如何被索引。
  • 文档是 ElasticSearch 中存储的实体,类比关系型数据库,每个文档相当于数据库表中的一行数据。 在 Elasticsearch 中,文档(document)这个术语有着特殊含义。它特指最顶层结构或者根对象(root object)序列化成的 JSON 数据(以唯一 ID 标识并存储于 Elasticsearch 中)。
  • 文档由字段组成,相当于关系数据库中列的属性,不同的是 ES 的不同文档可以具有不同的字段集合。

1.2 ElasticSearch 的对象模型

  • 索引(Index):相当于数据库,用于定义文档类型的存储;在同一个索引中,同一个字段只能定义一个数据类型;
  • 文档类型(Type):相当于关系表,用于描述文档中的各个字段的定义;不同的文档类型,能够存储不同的字段,服务于不同的查询请求;
  • 文档(Document):相当于关系表的数据行,存储数据的载体,包含一个或多个存有数据的字段;
  • 字段(Field):文档的一个 Key/Value 对;
  • 词(Term):表示文本中的一个单词;
  • 标记(Token):表示在字段中出现的词,由该词的文本、偏移量(开始和结束)以及类型组成;

Elasticsearch 是一种 NoSQL 数据库(非关系型数据库),和常规的关系型数据库(比如:MySQL,Oralce 等)的基本概念,对应关系如下:

  • Elasticsearch:index ==> type ==> doc ==> field
  • MySQL: 数据库 ==> 数据表 ==> 行(记录) ==> 列(字段)

注意

  • 在 5.X 版本中,一个 index 下可以创建多个 type;
  • 在 6.X 版本中,一个 index 下只能存在一个 type;
  • 在 7.X 版本中,直接去除了 type 的概念,就是说 index 不再会有 type。

1.3 字段的数据类型

字段的数据类型由字段的属性 type 指定,ElasticSearch 支持的基础数据类型主要有:

  • 字符串类型:keyword 和 text。(在 5.0 之后更改,原来为 string)。
  • 数值类型:字节(byte)、2 字节(short)、4 字节(integer)、8 字节(long)、float、double;
  • 布尔类型:boolean,值是 true 或 false;
  • 时间/日期类型:date,用于存储日期和时间;
  • 二进制类型:binary;
  • IP 地址类型:ip,以字符串形式存储 IPv4 地址;
  • 特殊数据类型:token_count,用于存储索引的字数信息

type:目前在 6.0 的时候,有 keyword 和 text,区别为:

  • keyword:数据类型用来建立电子邮箱地址、姓名、邮政编码和标签等数据类型,不需要进行分词。可以被用来检索过滤、排序和聚合。keyword 类型字段只能用本身来进行检索。
  • text:Text 数据类型被用来索引长文本,这些文本会被分析,在建立索引前会将这些文本进行分词,转化为词的组合,建立索引。比如你配置了 IK 分词器,那么就会进行分词,搜索的时候会搜索分词来匹配这个 text 文档。但是:text 数据类型不能用来排序和聚合

1.4 字符串类型常用的其他属性

  • analyzer:   该属性定义用于建立索引和搜索的分析器名称,默认值是全局定义的分析器名称,该属性可以引用在配置结点(settings)中自定义的分析器;
  • search_analyzer:   该属性定义的分析器,用于处理发送到特定字段的查询字符串;
  • ignore_above:   该属性指定一个整数值,当字符串字段(analyzed string field)的字节数量大于该数值之后,超过长度的部分字符数据将不能被 analyzer 处理,不能被编入索引;对于 not analyzed string 字段,超过长度的部分字符将被忽略,不会被编入索引。默认值是 0,禁用该属性;
  • position_increment_gap:该属性指定在相同词的位置上增加的 gap,默认值是 100;
  • index_options:   索引选项控制添加到倒排索引(Inverted Index)的信息,这些信息用于搜索(Search)和高亮显示:
    • docs:只索引文档编号(Doc Number)
    • freqs:索引文档编号和词频率(term frequency)
    • positions:索引文档编号,词频率和词位置(序号)
    • offsets:索引文档编号,词频率,词偏移量(开始和结束位置)和词位置(序号)
    • 默认情况下,被分析的字符串(analyzed string)字段使用 positions,其他字段使用 docs;

分析器(analyzer)把 analyzed string 字段的值,转换成标记流(Token stream),例如,字符串"The quick Brown Foxes",可能被分解成的标记(Token)是:quick,brown,fox。这些词(term)是该字段的索引值,这使用对索引文本的查找更有效率。字段的属性 analyzer 用于指定在 index-timesearch-time 时,ElasticSearch 引擎分解字段值的分析器名称。

1.5 字段的公共属性

  • index:该属性控制字段是否编入索引被搜索,该属性共有三个有效值:analyzed、no 和 not_analyzed
    • analyzed:(默认属性)表示该字段被分析,编入索引,产生的 token 能被搜索到;
    • not_analyzed:表示该字段不会被分析,使用原始值编入索引,在索引中作为单个词;
    • no:不编入索引,无法搜索该字段;
    • 其中 analyzed 是分析,分解的意思,默认值是 analyzed,表示将该字段编入索引,以供搜索。
  • store:指定是否将字段的原始值写入索引,默认值是 no,字段值被分析,能够被搜索,但是,字段值不会存储,这意味着,该字段能够被查询,但是不会存储字段的原始值。
  • boost:字段级别的助推,默认值是 1,定义了字段在文档中的重要性/权重;
  • include_in_all:该属性指定当前字段是否包括在_all 字段中,默认值是 ture,所有的字段都会包含_all 字段中;如果 index=no,那么属性 include_in_all 无效,这意味着当前字段无法包含在_all 字段中。
  • copy_to:该属性指定一个字段名称,ElasticSearch 引擎将当前字段的值复制到该属性指定的字段中;
  • doc_values:文档值是存储在硬盘上的索引时(indexing time)数据结构,对于 not_analyzed 字段,默认值是 true,analyzed string 字段不支持文档值;
  • fielddata:字段数据是存储在内存中的查询时(querying time)数据结构,只支持 analyzed string 字段;
  • null_value:该属性指定一个值,当字段的值为 NULL 时,该字段使用 null_value 代替 NULL 值;在 ElasticSearch 中,NULL 值不能被索引和搜索,当一个字段设置为 NULL 值,ElasticSearch 引擎认为该字段没有任何值,使用该属性为 NULL 字段设置一个指定的值,使该字段能够被索引和搜索。

1.6 文档类型的属性

文档属性定义了文档类型的共用属性,适用于文档的所有字段。当然也可以指定字段属性,只适用于某个特定的字段。

  • dynamic_date_formats 属性:该属性定义可以识别的日期格式列表;如果文档中有多个字段都是时间格式,可以通用的进行设置。
  • dynamic 属性:默认为 true,允许动态地向文档类型中加入新的字段。可选值为:true,false,strict。

1.7 文档元数据

文档元数据针对的是“行”(一条记录)而言的,下面是一条示例数据

{
"_index" : "my_index",
"_type" : "doc",
"_id" : "1",
"_version" : 2,
"found" : true,
"_source" : {
"username" : "yishui",
"age" : 14,
"birth" : "2018-12-12",
"married" : false,
"years" : "18",
"tages" : [
"boy",
"fish"
],
"money" : 102.3
}
}

一个文档不只有数据。它还包含了元数据(metadata)——关于文档的信息。三个必须的元数据节点是:

节点 说明 描述
_index 文档存储的地方 索引
_type 文档代表的对象的类 类型
_id 文档的唯一标识 与_index 和_type 组合时,就可以在 Elasticsearch 中唯一标识一个文档。

二 elasticsearch 的简单操作

2.1 索引 API

2.1.1 创建索引

创建命令如下:

put /test_add_index
{
"settings": {
"index": {
"number_of_shards": "2", //分片数
"number_of_replicas": "1" //副本数
}
}
}

参数解释:

  • test_add_index是需要创建的索引的名字
  • 请求体参数为可选参数,可以不传

得到的响应结果如下:

{
"acknowledged": true,
"shards_acknowledged": true,
"index": "test_add_index"
}

上述结果表明索引创建成功

官方文档

创建一个明确的索引

{
"settings": {
"index": {
"number_of_shards": 2,
"number_of_replicas": 1
}
},
"mappings": {
"person": {
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "integer"
},
"mail": {
"type": "keyword"
},
"hobby": {
"type": "text"
}
}
}
}
}
2.1.2 查看索引

命令如下:

get /test_add_index
  • test_add_index为索引名

得到的响应结果如下:

{
"test_add_index": {
"aliases": {},
"mappings": {},
"settings": {
"index": {
"creation_date": "1579004758533",
"number_of_shards": "2",
"number_of_replicas": "1",
"uuid": "Oi0CoVwfQaSklYU2UbnG3w",
"version": {
"created": "6050499"
},
"provided_name": "test_add_index"
}
}
}
}
2.1.3 查看全部索引

命令如下:

get /_cat/indices

得到的响应结果如下:

yellow open test_add_index 7M18YvXpS7qlGEWOwroH5A 5 1 0 0  1.1kb  1.1kb
green open .kibana_1 0HDRmKBdQo6N_r1bfmwGQw 1 0 0 0 263b 263b
yellow open index 2j4K_CSoTN6N7lvoUvEh_g 5 1 4 0 13.3kb 13.3kb
green open customer L7QKGkcvT46TeAjO5jeNQg 1 0 2 0 7.9kb 7.9kb

官方文档

2.1.4 删除索引

创建命令如下:

delete /test_add_index

参数解释:

  • test_add_index是需要删除的索引的名字

得到的响应结果如下:

{
"acknowledged": true
}

表明删除成功

官方文档

2.2 文档 API

2.2.1 创建文档

1. 指定 id 创建文档
命令如下:

put /test_add_index/doc/1
{
"username":"zhangsan",
"nickname":"张三"
}

参数解释:

  • test_add_index 索引的名字
  • doc 文档类型
  • 1 文档的 id,根据要求变化

URI的规则为 PUT /{索引}/{类型}/{id}

数据参数放在请求体中

得到的响应结果如下

{
"_index": "test_add_index",
"_type": "doc",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}

表明创建成功

注意:

  • 在创建文档时,如果索引和类型不存在,es 会自动创建对应的 index 和 type
  • id 不变,内容变化时,为更新操作

2. 不指定 id 创建文档

post /test_add_index/doc/
{
"username":"lisi",
"nickname":"李四"
}

参数解释:

  • test_add_index 索引的名字
  • doc 文档类型

URI的规则为 POST /{索引}/{类型}

数据参数放在请求体中

得到的响应结果:

{
"_index": "test_add_index",
"_type": "doc",
"_id": "2bqYSG0BuzzbTWzJC5xK",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}

官方文档

2.2.2 查询文档

1. 查询指定 id 的文档

get /test_add_index/doc/1

参数解释:

  • test_add_index 索引的名字
  • doc 文档类型
  • 1 文档的 id,根据要求变化

查询到数据时的返回结果如下:

{
"_index": "test_add_index",
"_type": "doc",
"_id": "1",
"_version": 1,
"found": true,
"_source": {
"username": "zhangsan",
"nickname": "张三"
}
}

未查询到数据时的结果

{
"_index": "test_add_index",
"_type": "doc",
"_id": "111",
"found": false
}

官方文档

2. 查询所有的文档

get /test_add_index/doc/_search
{
"query":{
"match_all":{}
}
}

参数解释:

  • test_add_index 索引的名字
  • doc 文档类型
  • _search 固定值,表示查询所有

数据参数放在请求体中

得到的响应结果如下:

{
"took": 0, //查询耗时
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 2, //符合条件的总文档数量
"max_score": 1,
"hits": [ //返回的文档详情数据数组,默认为前10个
{
"_index": "test_add_index", //索引名
"_type": "doc",
"_id": "1", //文档的id
"_score": 1, //文档的得分
"_source": { //文档的详情
"username": "zhangsan",
"nickname": "张三"
}
},
{
"_index": "test_add_index",
"_type": "doc",
"_id": "2bqYSG0BuzzbTWzJC5xK",
"_score": 1,
"_source": {
"username": "lisi",
"nickname": "李四"
}
}
]
}
}

3. 批量创建文档

POST _bulk

{"index":{"_index":"test_index","_type":"doc","_id":"3"}}
{"username":"alfred","age":20}
{"delete":{"_index":"test_index","_type":"doc","_id":"1"}}
{"update":{"_id":"2","_index":"test_index","_type":"doc"}}
{"doc":{"age":"20"}}

请求参数不是一个 json 格式,是四行数据,每一行都是一个 json 数据

其中action_type的可选值为 indexupdatedeletecreate

4. 批量查询文档

GET /_mget
{
"docs": [
{"_index": "test_index","_type": "doc","_id": "1"},
{"_index": "test_index","_type": "doc","_id": "2"}
]
}

得到的响应如下:

{
"docs": [
{
"_index": "test_add_index",
"_type": "doc",
"_id": "1",
"_version": 1,
"found": true,
"_source": {
"username": "zhangsan",
"nickname": "张三"
}
},
{
"_index": "test_index",
"_type": "doc",
"_id": "2",
"error": {
"root_cause": [
{
"type": "index_not_found_exception",
"reason": "no such index",
"resource.type": "index_expression",
"resource.id": "test_index",
"index_uuid": "_na_",
"index": "test_index"
}
],
"type": "index_not_found_exception",
"reason": "no such index",
"resource.type": "index_expression",
"resource.id": "test_index",
"index_uuid": "_na_",
"index": "test_index"
}
}
]
}

2.3 映射 API

类似mysql据库中的表结构定义,主要作用如下:

  • 定义 Index 下的字段名(Field Name )
  • 定义字段的类型,比如数值型、字符串型、布尔型等
  • 定义倒排索引相关的配置,比如是否索引、记录 position 等

mapping官方文档

mapping参数文档

2.3.1 查询mapping
get /test_add_index/_mapping

参数解释:

  • test_add_index 索引名字
  • _mapping 固定值

得到的响应如下

{
"test_add_index": {
"mappings": {
"doc": { //type名称
"properties": {
"nickname": {
"type": "text",//字段类型
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"username": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
}
2.3.2 自定义修改 mapping
put /test_add_index

{
"mappings": {
"doc": {
"properties": {
"nickname": {
"type": "text"
},
"username": {
"type": "text"
}
}
}
}
}

mappings 中的字段类型一旦设定以后不能修改,因为 Lucene 实现的倒排索引生成后不允许修改

如果确实需要修改,可以先删除索引再新建

可以新增字段

通过 dynamic 参数来控制字段的新增

  • true 允许新增,默认值
  • false 不允许自动新增字段,但是文档可以正常写入,但无法对字段做查询等操作
  • strict 文档不能写入,报错
PUT my_index
{
"mappings": {
"doc": {
"dynamic": false, //全局级别
"properties": {
"name": {
"type": "text"
},
"profile": {
"dynamic": true, //单个属性级别
"properties": {}
},
"works": {
"dynamic": "strict",
"properties": {
"name": {
"type": "text"
}
}
}
}
}
}
}

具体的用法参见 官方API

2.3.3 copy_to 参数

将该字段的值复制到目标字段,实现类似_all 的作用

不会出现在_source 中,只能用来搜索

PUT my_index
{
"mappings": {
"doc": {
"properties": {
"nickname": {
"type": "text",
"copy_to": "full_name"
},
"username": {
"type": "text",
"copy_to": "full_name"
},
"full_name": {
"type": "text"
}
}
}
}
}

参数解释

  • my_index 索引名字

执行该命令可以新建一个所以,不能再一个已存在的索引上执行该命令,否则会报错

得到的响应如下

{
"acknowledged": true,
"shards_acknowledged": true,
"index": "test_add_index1"
}
2.3.4 index 参数说明

控制当前字段是否为索引,默认为 true,即记录索引,否则为 false,不记录索引,也不能搜索

PUT my_index
{
"mappings": {
"doc": {
"properties": {
"cookie": {
"type": "text",
"index": false
}
}
}
}
}
2.3.5 index_options参数

index_options用于控制倒排索引记录的内容,有以下四种方式

  • docs:只记录doc id
  • freqs:索引文档编号和词频率(term frequency)
  • positions:索引文档编号,词频率和词位置(序号)(term position)
  • offsets:索引文档编号,词频率(term frequency),词偏移量(开始和结束位置)(character offsets)和词位置(序号)(term position)

text类型字段默认配置为 positions,其他字段使用 docs;

记录的内容越多,占用的空间越大

示例如下:

PUT my_index
{
"mappings": {
"doc": {
"properties": {
"cookie": {
"type": "text",
"index_options": 'offsets'
}
}
}
}
}
2.3.6 数据类型

数据类型介绍参见 数据类型

示例如下:

PUT my_index
{
"mappings": {
"properties": {
"date": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
}
}
2.3.7 dynamic-mapping

先创建一个数据

put /my_index/doc/1
{
"username": "yishui",
"age": 14,
"birth": "2018-12-12",
"married": false,
"years": "18",
"tages": [
"boy",
"fish"
],
"money": 102.3
}

然后查看自动创建的类型

GET /my_index/_mapping
{
"my_index" : {
"mappings" : {
"doc" : {
"properties" : {
"age" : {
"type" : "long"
},
"birth" : {
"type" : "date"
},
"married" : {
"type" : "boolean"
},
"money" : {
"type" : "float"
},
"tages" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"username" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"years" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
}

三 文本分词

3.1 分词 API

3.1.1 直接指定分词器
POST /_analyze
{
"analyzer": "standard", //分词器
"text":"hello world" //测试文本
}

得到的响应结果如下:

{
"tokens": [
{
"token": "hello", //分词结果
"start_offset": 0, //起始偏移
"end_offset": 5, //结束偏移
"type": "<ALPHANUM>",
"position": 0 //分词位置
},
{
"token": "world",
"start_offset": 6,
"end_offset": 11,
"type": "<ALPHANUM>",
"position": 1
}
]
}
3.1.2 指定索引中的字段
POST test_add_index/_analyze
{
"field": "username", //测试字段
"text":"hello world!" //测试文本
}

参数解释:

  • test_add_index 索引的名字
  • _analyze 固定值

数据参数放在请求体中

standard是默认的分词器,在没有指定分词器时,默认使用standard分词器

响应结果如下:

{
"tokens": [
{
"token": "hello",
"start_offset": 0,
"end_offset": 5,
"type": "<ALPHANUM>",
"position": 0
},
{
"token": "world",
"start_offset": 6,
"end_offset": 11,
"type": "<ALPHANUM>",
"position": 1
}
]
}
3.1.3 自定义分词器
POST _analyze
{
"tokenizer": "standard",
"filter": [ "lowercase" ],//自定义analyzer
"text":"Hello World!"
}

参数解释:

  • _analyze 固定值

数据参数放在请求体中

得到的响应结果如下:

{
"tokens": [
{
"token": "hello",
"start_offset": 0,
"end_offset": 5,
"type": "<ALPHANUM>",
"position": 0
},
{
"token": "world",
"start_offset": 6,
"end_offset": 11,
"type": "<ALPHANUM>",
"position": 1
}
]
}

3.2 es 自带的分词器

  • standard
  • simple
  • whitespace
  • stop
  • keyword
  • pattern
  • language
3.2.1 standard 分词器

标准分词器(standard tokenizer)是一个基于语法的分词器,对于大多数欧洲语言来说还是不错的,它同时还处理了 Unicode 文本的分词,但分词默认的最大长度是 255 字节,它也移除了逗号和句号这样的标点符号。

英文的处理能力同于 StopAnalyzer.支持中文采用的方法为单字切分。他会将词汇单元转换成小写形式,并去除停用词和标点符号

POST _analyze
{
"analyzer": "standard",
"text":"The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}

得到的响应结果为

{
"tokens": [
{
"token": "the",
"start_offset": 0,
"end_offset": 3,
"type": "<ALPHANUM>",
"position": 0
},
{
"token": "2",
"start_offset": 4,
"end_offset": 5,
"type": "<NUM>",
"position": 1
},
{
"token": "quick",
"start_offset": 6,
"end_offset": 11,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "brown",
"start_offset": 12,
"end_offset": 17,
"type": "<ALPHANUM>",
"position": 3
},
{
"token": "foxes",
"start_offset": 18,
"end_offset": 23,
"type": "<ALPHANUM>",
"position": 4
},
{
"token": "jumped",
"start_offset": 24,
"end_offset": 30,
"type": "<ALPHANUM>",
"position": 5
},
{
"token": "over",
"start_offset": 31,
"end_offset": 35,
"type": "<ALPHANUM>",
"position": 6
},
{
"token": "the",
"start_offset": 36,
"end_offset": 39,
"type": "<ALPHANUM>",
"position": 7
},
{
"token": "lazy",
"start_offset": 40,
"end_offset": 44,
"type": "<ALPHANUM>",
"position": 8
},
{
"token": "dog's",
"start_offset": 45,
"end_offset": 50,
"type": "<ALPHANUM>",
"position": 9
},
{
"token": "bone",
"start_offset": 51,
"end_offset": 55,
"type": "<ALPHANUM>",
"position": 10
}
]
}
3.2.2 simple 分词器

首先通过非字母字符来分割文本信息,然后将词汇单元同一为小写形式。该分析器会去掉数字类型的字符

POST _analyze
{
"tokenizer": "simple",
"text":"The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}

得到的响应如下:

{
"tokens": [
{
"token": "the",
"start_offset": 0,
"end_offset": 3,
"type": "word",
"position": 0
},
{
"token": "quick",
"start_offset": 6,
"end_offset": 11,
"type": "word",
"position": 1
},
{
"token": "brown",
"start_offset": 12,
"end_offset": 17,
"type": "word",
"position": 2
},
{
"token": "foxes",
"start_offset": 18,
"end_offset": 23,
"type": "word",
"position": 3
},
{
"token": "jumped",
"start_offset": 24,
"end_offset": 30,
"type": "word",
"position": 4
},
{
"token": "over",
"start_offset": 31,
"end_offset": 35,
"type": "word",
"position": 5
},
{
"token": "the",
"start_offset": 36,
"end_offset": 39,
"type": "word",
"position": 6
},
{
"token": "lazy",
"start_offset": 40,
"end_offset": 44,
"type": "word",
"position": 7
},
{
"token": "dog",
"start_offset": 45,
"end_offset": 48,
"type": "word",
"position": 8
},
{
"token": "s",
"start_offset": 49,
"end_offset": 50,
"type": "word",
"position": 9
},
{
"token": "bone",
"start_offset": 51,
"end_offset": 55,
"type": "word",
"position": 10
}
]
}
3.2.3 whitespace 分词器

空白分词器(whitespace tokenizer)通过空白来分隔不同的分词,空白包括空格、制表符、换行等。

空白分词器不会删除任何标点符号

POST _analyze
{
"analyzer": "whitespace",
"text":"The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}

得到的响应结果为

{
"tokens": [
{
"token": "The",
"start_offset": 0,
"end_offset": 3,
"type": "word",
"position": 0
},
{
"token": "2",
"start_offset": 4,
"end_offset": 5,
"type": "word",
"position": 1
},
{
"token": "QUICK",
"start_offset": 6,
"end_offset": 11,
"type": "word",
"position": 2
},
{
"token": "Brown-Foxes",
"start_offset": 12,
"end_offset": 23,
"type": "word",
"position": 3
},
{
"token": "jumped",
"start_offset": 24,
"end_offset": 30,
"type": "word",
"position": 4
},
{
"token": "over",
"start_offset": 31,
"end_offset": 35,
"type": "word",
"position": 5
},
{
"token": "the",
"start_offset": 36,
"end_offset": 39,
"type": "word",
"position": 6
},
{
"token": "lazy",
"start_offset": 40,
"end_offset": 44,
"type": "word",
"position": 7
},
{
"token": "dog's",
"start_offset": 45,
"end_offset": 50,
"type": "word",
"position": 8
},
{
"token": "bone.",
"start_offset": 51,
"end_offset": 56,
"type": "word",
"position": 9
}
]
}
3.2.4 stop 分词器

stop 分词器在 simple 分词器的基础上去掉了语气助词等修饰性词语

POST _analyze
{
"analyzer": "stop",
"text":"The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}

得到的响应结果为:

{
"tokens": [
{
"token": "quick",
"start_offset": 6,
"end_offset": 11,
"type": "word",
"position": 1
},
{
"token": "brown",
"start_offset": 12,
"end_offset": 17,
"type": "word",
"position": 2
},
{
"token": "foxes",
"start_offset": 18,
"end_offset": 23,
"type": "word",
"position": 3
},
{
"token": "jumped",
"start_offset": 24,
"end_offset": 30,
"type": "word",
"position": 4
},
{
"token": "over",
"start_offset": 31,
"end_offset": 35,
"type": "word",
"position": 5
},
{
"token": "lazy",
"start_offset": 40,
"end_offset": 44,
"type": "word",
"position": 7
},
{
"token": "dog",
"start_offset": 45,
"end_offset": 48,
"type": "word",
"position": 8
},
{
"token": "s",
"start_offset": 49,
"end_offset": 50,
"type": "word",
"position": 9
},
{
"token": "bone",
"start_offset": 51,
"end_offset": 55,
"type": "word",
"position": 10
}
]
}
3.2.5 keyword 分词器

关键词分词器是一种简单的分词器,将整个文本作为单个的分词,提供给分词过滤器。

不分词,直接将输入作为一个单词输出

POST _analyze
{
"analyzer": "keyword",
"text":"The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}

得到的响应结果为:

{
"tokens": [
{
"token": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone.",
"start_offset": 0,
"end_offset": 56,
"type": "word",
"position": 0
}
]
}
3.2.6 pattern 分词器

通过正则表达式自定义分隔符

默认是\W+,即非字词的符号作为分隔符

POST _analyze
{
"analyzer": "pattern",
"text":"The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}

得到的响应如下

{
"tokens": [
{
"token": "the",
"start_offset": 0,
"end_offset": 3,
"type": "word",
"position": 0
},
{
"token": "2",
"start_offset": 4,
"end_offset": 5,
"type": "word",
"position": 1
},
{
"token": "quick",
"start_offset": 6,
"end_offset": 11,
"type": "word",
"position": 2
},
{
"token": "brown",
"start_offset": 12,
"end_offset": 17,
"type": "word",
"position": 3
},
{
"token": "foxes",
"start_offset": 18,
"end_offset": 23,
"type": "word",
"position": 4
},
{
"token": "jumped",
"start_offset": 24,
"end_offset": 30,
"type": "word",
"position": 5
},
{
"token": "over",
"start_offset": 31,
"end_offset": 35,
"type": "word",
"position": 6
},
{
"token": "the",
"start_offset": 36,
"end_offset": 39,
"type": "word",
"position": 7
},
{
"token": "lazy",
"start_offset": 40,
"end_offset": 44,
"type": "word",
"position": 8
},
{
"token": "dog",
"start_offset": 45,
"end_offset": 48,
"type": "word",
"position": 9
},
{
"token": "s",
"start_offset": 49,
"end_offset": 50,
"type": "word",
"position": 10
},
{
"token": "bone",
"start_offset": 51,
"end_offset": 55,
"type": "word",
"position": 11
}
]
}

3.3 自定义分词

当自带的分词器无法满足要求时,可以自定义分词

通过自定义 Character Filters 、 Tokenizer 和 Token Filter 来实现。

3.3.1 Character Filters
  1. 在 Tokenizer 之前对原始文本进行处理,比如增加、删除或替换字符
    自带的如下:

    • HTML strip 取出 html 标签和转换 html 实体
    • Mapping 进行字符替换操作
    • Pattern Replace 进行正则匹配替换操作
  2. 会影响后续 Tokenizer 解析的 postion 和 offset 的位置

POST _analyze
{
"tokenizer": "keyword", //keyword类型的可以直接看到结果
"char_filter": ["html_strip"], //指明要使用的char_filter
"text":"<p>aaaaa<span>nnn</span></p>"
}

得到的响应

{
"tokens": [
{
"token": "\naaaaannn\n",
"start_offset": 0,
"end_offset": 28,
"type": "word",
"position": 0
}
]
}
3.3.2 Tokenizer

将原始文本按照一定的规则切割成单词 (term or token)

自带的如下

  • 标准分词器:standard tokenizer
  • 关键词分词器:keyword tokenizer
  • 字母分词器:letter tokenizer
  • 小写分词器:lowercase tokenizer
  • 空白分词器:whitespace tokenizer
  • 模式分词器:pattern tokenizer
  • UAX URL 电子邮件分词器:UAX RUL email tokenizer
  • 路径层次分词器:path hierarchy tokenizer

Token Filter

对 Tokenizer 输出的单词(term)进行增加、修改、删除等操作

自带的如下:

  • lowercase 将所有的 term 都转换为 小写
  • stop 删除所有的 stop word
  • NGram 和 Edge NGram 连词分割
  • Synon 添加近义词的 term
POST _analyze
{
"text": "a Hello,world!",
"tokenizer": "standard",
"filter": [
"stop",
"lowercase",
{
"type": "ngram",
"min_gram": 2,
"max_gram": 4
}
]
}

得到的响应如下

{
"tokens": [
{
"token": "he",
"start_offset": 2,
"end_offset": 7,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "hel",
"start_offset": 2,
"end_offset": 7,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "hell",
"start_offset": 2,
"end_offset": 7,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "el",
"start_offset": 2,
"end_offset": 7,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "ell",
"start_offset": 2,
"end_offset": 7,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "ello",
"start_offset": 2,
"end_offset": 7,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "ll",
"start_offset": 2,
"end_offset": 7,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "llo",
"start_offset": 2,
"end_offset": 7,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "lo",
"start_offset": 2,
"end_offset": 7,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "wo",
"start_offset": 8,
"end_offset": 13,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "wor",
"start_offset": 8,
"end_offset": 13,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "worl",
"start_offset": 8,
"end_offset": 13,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "or",
"start_offset": 8,
"end_offset": 13,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "orl",
"start_offset": 8,
"end_offset": 13,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "orld",
"start_offset": 8,
"end_offset": 13,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "rl",
"start_offset": 8,
"end_offset": 13,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "rld",
"start_offset": 8,
"end_offset": 13,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "ld",
"start_offset": 8,
"end_offset": 13,
"type": "<ALPHANUM>",
"position": 2
}
]
}

3.4 中文分词

先安装好ik分词器,安装步骤参见 centos7中elk安装教程

测试接口如下

POST /_analyze

{
"analyzer": "ik_max_word",
"text":"我是中国人"
}

得到的响应如下

{
"tokens": [
{
"token": "我",
"start_offset": 0,
"end_offset": 1,
"type": "CN_CHAR",
"position": 0
},
{
"token": "是",
"start_offset": 1,
"end_offset": 2,
"type": "CN_CHAR",
"position": 1
},
{
"token": "中国人",
"start_offset": 2,
"end_offset": 5,
"type": "CN_WORD",
"position": 2
},
{
"token": "中国",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 3
},
{
"token": "国人",
"start_offset": 3,
"end_offset": 5,
"type": "CN_WORD",
"position": 4
}
]
}

四 查询 API

实现对 es 中数据的查询,查询端口为_search

get /_search
get /my_index/_search
get /my_index1,my_index2/_search
get /my_*/_search
  • 操作简单,方便通过命令行测试
  • 仅包含部分查询语法

常用参数如下:

  • q : 指定查询的语句,语法为 Query String Syntax
  • df : q 中不指定字段时默认的查询的字段,如果不指定,则会查询所有的字段
  • sort : 排序
  • timeout : 指定超时时间,默认不超时
  • from,size : 用于分页
get /my_index/_search?q=user&df=username&sort=age:asc&from=4&size=10&timeout=10s

查询username字段包含user的文档,结果按照age升序排列,返回第 5 到 14 个文档,超过 10s 没有结束则以超时为结束

此语句等价于

Query String Syntax 语法

term 与 phrase

  • user way 等效于 user or way
  • 'user way’语句查询要求先后顺序

泛查询

  • user 等效于在所有字段去匹配该 term

指定字段

  • name:user

将查询语句通过请求体发送到 es,主要参数如下:

  • query 符合 Query DSL 语法的查询语句
  • from ,size
  • timeout
  • sort
post /my_index/_search
{
"profile": "true",//查看具体的执行过程(可选参数)
"query": {
"term": {
"name": "user"
}
}
}

基于 json 定义的查询语言,主要包含以下两种类型

  • 字段类查询

如 term ,match ,range 等,只针对一个字段进行查询

  • 符合查询

如 bool 查询等,包含一个或多个字段类查询或者符合查询语句

字段类查询主要包含两大类

  • 全文匹配 :针对 text 类型的字段进行全文检索,会对查询语句先进行分词处理,如 match ,match_phrase 等 query 类型
  • 单词匹配 :不会对查询语句做分词处理,直接去匹配字段的倒排索引,如 term,terms,range 等 query 类型
4.2.1 match 搜索

对字段做全文检索,最基本和常用的查询类型。

1)match 查询 keyword 字段

match 会被分词,而 keyword 不会被分词,match 的需要跟 keyword 的完全匹配可以。

2)match 查询 text 字段

match 分词,text 也分词,只要 match 的分词结果和 text 的分词结果有相同的就匹配。

API 如下

get /index/_search
{
"query": {
"match": {//关键词
"content": "华为" //content是要查询的字段名,"华为" 是待查询的语句
}
}
}

参数解释:

  • index 是索引名字,可以根据常用变化

得到的响应如下

{
"took": 31,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1, //匹配的文档总数
"max_score": 0.6047856,
"hits": [ //返回的文档列表
{
"_index": "index",
"_type": "fulltext",
"_id": "4",
"_score": 0.6047856, //文档相关度得分
"_source": {
"content": "华为5G合同第一!中国5G或再迎一个爆发时刻"
}
}
]
}
}
4.2.1.1 修改连接条件
GET test_search_index/_search
{
"explain":true,
"query": {
"match": {
"username": "alfred way"
}
}
}

表示查询 username 字段中包含 alfredway 的文档

通过 operator 修改连接条件

GET test_search_index/_search
{
"query": {
"match": {
"username": {
"query": "alfred way",
"operator": "and" //字段可以为 and 或者 or
}
}
}
}

表示查询 username 字段中包含 alfredway 的文档

4.2.1.2 修改需要匹配的单词数

通过 minimum_should_match 控制 需要匹配的单词数

GET test_search_index/_search
{
"query": {
"match": {
"job": {
"query": "java ruby engineer",
"minimum_should_match": "2"
}
}
}
}

表示需要匹配的单词数,需要匹配到 java ruby engineer 三个条件中的任意两个

4.2.2 match_phrase 搜索

对字段做检索,有顺序要求

1)match_phrase 匹配 keyword 字段。

这个必须跟 keywork 一致才可以。

2)match_phrase 匹配 text 字段。

match_phrase 是分词的,text 也是分词的。match_phrase 的分词结果必须在 text 字段分词中都包含,而且顺序必须相同,而且必须都是连续的。

API 接口如下

GET test_search_index/_search
{
"query": {
"match_phrase": {//关键词
"job": "java python"
}
}
}
4.2.2.1 通过 slop 参数控制单词间间隔
GET test_search_index/_search
{
"query": {
"match_phrase": {
"job": {
"query": "java ruby ",
"slop": "1"
}
}
}
}

表示允许搜索结果中 java ruby 这两个关键词之间再出现一个关键词

4.2.3 Query String Query

1)query_string 查询 key 类型的字段,无法查询。

2)query_string 查询 text 类型的字段。

和 match_phrase 区别的是,不需要连续,顺序还可以调换。

GET test_search_index/_search
{
"query": {
"query_string": {
"fields": [
"username",
"job"
],
"query": "yishui OR (java AND python)"
}
}
}

等价于

{
"query": {
"simple_query_string": {
"query": "(job:yishui|username:yishui)(+(job:java|username:java)+(job:python|username:python))"
}
}
}
4.2.4 Simple Query String Query

类似于 Query String Query,但会忽略错误的查询语法,且仅支持部分查询语法

不能使用 OR`` AND ``NOT等关键词,需要用 +|-等代替

GET test_search_index/_search
{
"query": {
"simple_query_string": {
"query": "java +python",
"fields": [
"job"
]
}
}
}
4.2.5 Term Query

将查询语句作为作为整个单词进行查询,即不对查询语句做分词处理

term 用于精确匹配哪些值,比如数字、日期和布尔值或not_analyzed的字符串(未经分析的文本类型数据)

1)term 查询 keyword 字段。

term 不会分词。而 keyword 字段也不分词。需要完全匹配才可。

2)term 查询 text 字段。

因为 text 字段会分词,而 term 不分词,所以 term 查询的条件必须是 text 字段分词后的某一个。

GET test_search_index/_search
{
"query": {
"term": {//关键词
"username": "yishui"
}
}
}

返回username字段中包含了yishui的数据

GET test_search_index/_search
{
"query": {
"term": {//关键词
"username": "yishui java"
}
}
}

查询 es 倒排索引里包含yishui java的数据,注意yishui java是完整的一体的查询条件

terms与term类似,但是terms允许指定多个匹配条件,如果某个字段定义了多个值,文档需要一起去做匹配

{
"query": {
"terms": {
"username": [
"yishui",
"java"
]
}
}
}

即查询usernameyishuijava的数据。

4.2.6 Range Query

主要针对数据和日期类型

GET test_search_index/_search
{
"query": {
"range": {//关键词
"age": {
"gte": 10,
"lte": 20
}
}
}
}

选择条件的含义为

  • gt: greater than
  • gte : greater than or equal to
  • lt : less than
  • lte : less than or equal to

时间示例

{
"query": {
"range": {
"birth": {
"gte": "1995-12-02"
}
}
}
}

针对日期格式还有另外一种更加友好的方式,如

now - 1d

now是基准日期,也可以是具体日期,如 2018-01-01,使用具体日期要用 ||隔离

- 1d 计算公式,主要有

  • +1h :加一个小时
  • -1d : 减一天
  • /d : 将时间四舍五入到天
{
"query": {
"range": {
"birth": {
"gte": "1995|| - 20y"
}
}
}
}
4.2.7 exists查询

用于查询文档中是否包含某个字段或者没有某个字段,类似mysql中的IS_NULL

{
"query": {
"exists": {
"field": "title"
}
}
}

4.3 复合查询

4.3.1 constant_score_query
GET test_search_index/_search
{
"query": {
"constant_score": {//关键词
"filter": { //只能有一个
"match": {
"username": "yishui"
}
}
}
}
}

此查询命令会将所有的查询结果的_score都设置为1

4.3.2 Bool Query

布尔查询由一个或布尔子局组成,主要包含以下四个

条件 说明
filter 只过滤符合条件的文档,不计算相关性得分
must 文档必须符合 must 中所有的条件,会影响相关性得分
must_not 文档必须不符合 must 中所有的条件
should 文档可以符合 should 中的条件,会影响相关性得分
GET test_search_index/_search
{
"query": {
"bool": {
"must": [
{}
],
"must_not": [
{}
],
"should": [
{}
],
"filter": [
{}
]
}
}
}

做精确匹配查询时最好用 filter,因为过滤语句能缓存数据

实例: 查询年龄为20的用户

{
"query": {
"bool": {
"filter": {
"term": {
"age": 20
}
}
}
}
}

4.4 分页与遍历

三种方案

  • from/size
  • scroll
  • search_after

from/size 方案

  • from 指明开始位置
  • size 指明获取总数
GET test_search_index/_search
{
"from": 1,
"size": 10
}

4.5 聚合分析

  • metric 聚合分析 : 分桶类型,类似 SQL 中的 group by 语法
  • bucket 聚合分析 :指标分析类型,如计算最大值,最小值,平均值等
  • pipeline 聚合分析 :管道分析类型,基于上一级的分析结果进行在分析

metric 聚合分析

  • 单值分析:只输出一个分析结果 ,min、max、avg、sum、cardinality
  • 多值分析:输出多个分析结果,stats、extend stats、percenttile、 percent rank、top hits

返回数值类字段最小值

GET test_search_index/_search
{
"size": 0,//不需要返回文档列表
"aggs": {
"min_age": {
"min": { //关键词
"field": "age"
}
}
}
}

4.6 过滤返回的字段

方法一

/<index>/_search?_source=username

方法二

/<index>/_search
{
"_source":["username","age"]
}

方法三

/<index>/_search
{
"_source":{
"includes":"*y*",//通配符
"excludes":"birth"
}
}

4.7 排序

/<index>/_search
{
"sort":{
"birth":"desc"
}
}

也可以一次性指定多个排序条件

/<index>/_search
{
"sort": [
{
"birth": "desc"
},
{
"_score": "desc"
},
{
"_doc": "desc"
}
]
}

五 全文搜索

全文搜索最重要的时两个方面

●相关性( Relevance ):它是评价查询与其结果间的相关程度.并根据这种相关程度对结果排名的一种能力,这
种计算方式可以是TF/IDF方法、地理位置邻近、模糊相似,或其他的某些算法。

●分词( Analysis) : 它是将文本块转换为有区别的、规范化的token的一个过程,目的是为了创建倒排索引以及
查询倒排索引.

重新创建好一个索引,指定使用中文分词

PUT /索引名
{
"settings": {
"index": {
"number_of_shards": 1,
"number_of_replicas": 0
}
},
"mappings": {
"person": {
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "integer"
},
"mail": {
"type": "keyword"
},
"hobby": {
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
}

5.1 单词搜索

POST /my_test_index/_search
{
"query": {
"match": {
"hobby": "音乐"
}
},
"highlight": {
"fields": {
"hobby": {}
}
}
}

过程说明

1.检查字段类型

爱好hobby字段是个text类型(指定了IK分词器)。这意味着询字符串本身也应该被分词。

2.分析查询字符串。

将查询的字符串"音乐"传入IK分词器中。输出的结果是单个项 音乐。因为只有一个单词项。所以match查询执
行的是单个底层term查询。

3.查找匹配文档。

用term查询在倒排索引中查找 "音乐” 然后获取组包含该项的文档 。

  1. 为每个文档打分

5.2 多词搜索

POST /my_test_index/_search
{
"query": {
"match": {
"hobby": "音乐 语文"
}
},
"highlight": {
"fields": {
"hobby": {}
}
}
}

可以看到,包含 音乐 或 语文 关键字的结果都被搜索到了,但是结果并不符合我们的预期,我们希望的结果是 既包含 音乐 又包含 语文 的,而这里的结果是 或者 的关系。

因此,可以指定连接条件

{
"query": {
"match": {
"hobby": {
"query": "音乐 语文",
"operator": "and"
}
}
},
"highlight": {
"fields": {
"hobby": {}
}
}
}

在实际场景中,其实并不会选择 “AND” 或“OR” 这两个极端,一般通过minimum_should_match 控制 需要匹配的单词数

GET test_search_index/_search
{
"query": {
"match": {
"job": {
"query": "java ruby engineer",
"minimum_should_match": 2
}
}
}
}

5.3 组合搜索

GET test_search_index/_search
{
"query": {
"bool": {
"must": {
"match": {
"hobby": "篮球"
}
},
"must_not": {
"match": {
"hobby": "音乐"
}
},
"should": {
"match": {
"hobby": "游泳"
}
}
}
},
"highlight": {
"fields": {
"hobby": {}
}
}
}

即搜索结果中必须包含篮球,不能包含音乐,如果包含了游泳,那么他的相似度更高

默认情况下,should中的内容不是必须匹配的,如果匹配语句中没有must,就会至少匹配其中的一个。当然了,也可以使用minimum_should_match 参数来控制。该参数既可以是数字也可以是百分比
例如

{
"query": {
"bool": {
"must": {
"match": {
"hobby": "篮球"
}
},
"must_not": {
"match": {
"hobby": "音乐"
}
},
"should": {
"match": {
"hobby": "游泳"
}
},
"minimum_should_match": 2
}
},
"highlight": {
"fields": {
"hobby": {}
}
}
}

5.4 权重

有些时候,我们可能需要对某些关键字增加权重来影响该条数据的得分,如下:

搜索关键字为 游泳篮球 ,如果结果中包含音乐 ,权重为10 ,如果包含跑步,权重为2

{
"query": {
"bool": {
"must": {
"match": {
"hobby": "游泳篮球",
"operator": "and"
}
},
"must_not": {
"match": {
"hobby": "音乐"
}
},
"should": {
"match": {
"hobby": {
"query": "跑步",
"boost": 2
}
}
}
}
},
"highlight": {
"fields": {
"hobby": {}
}
}
}

六 elasticsearch集群

ELasticsearch的集群是由多个节点组成的.通过cluster.name设置集群名称,并且用于区分其它的集群,每个节点
通过node.name指定节点的名称。

在Elasticsearch中。节点的类型主要有4种:

master节点

  • 配置文件中node.master属性为true(默认为true) .就有资格被选为master节点。
  • master节点用于控制整个集群的操作。比如创建或删除索引.管理其它非master节点等。

data节点

  • 配置文件中node.data属性为true(默认为true) .就有资格被设置成data节点。
  • data节点主要用于执行数据相关的操作。比如文档的CRUD.

客户端节点

  • 配置文件中node.master属性和node.data属性均为false.
  • 该节点不能作为master节点,也不能作为data节点。
  • 可以作为客户端节点,用于响应用户的请求,把请求转发到其他节点

部落节点

  • 当一个节点配置tribe.*的时候,它是一个特殊的客户端,它可以连接多个集群,在所有连接的集群上执行
    搜索和其他操作。






参考链接

ElasticSearch(四):关于 es 的一些基础知识讲解

ElasticSearch在线API