017.Elasticsearch搜索操作入门篇

1. 多种搜索方式

1.1 Query String Search:在请求URL中包括search的参数

# 语法
curl -X GET "ip:port/index_name/type_name/_search?q=key1=value1&key2=value2"

# 举例
# 查看全部文档
curl -X GET "node01:9200/shop/product/_search"
# 搜索商品名称中包含“Toothpaste”的商品,而且按照price降序排序
curl -X GET "node01:9200/shop/product/_search?q=name:Toothpaste&sort=price:desc"

# 扩展
GET /index_name/type_name/_search?q=key:value
GET /index_name/type_name/_search?q=+key:value
GET /index_name/type_name/_search?q=-key:value

# name的值包含"Tom"
GET /test_index/test_type/_search?q=name:Tom
# 无论那个field,只要其值包含"Tom"即可
GET /test_index/test_type/_search?q=Tom
# =+ 与 = 的效果相同
GET /test_index/test_type/_search?q=+name:Tom
# =- 的意思是:field不是指定的字符串(精确匹配,例如本例子不会过滤name=Tommy的document)
GET /test_index/test_type/_search?q=-name:Tom
  • "q=value"原理分析

假设有如下的document:

{
    "name": "Tom",
    "age": 25,
    "sex": "male",
    "country": "China"
}

在插入这条数据后,ES会自动将多个field的值,全部用字符串的方式串联起来,组成一个长字符串,作为_all field的值,同时建立索引,本例中,"_all field"="Tom 25 male China",当使用Query String Search:GET /test_index/test_type/_search?q=Tom,实际上并不会去逐一比较每个field的值,而是直接与"_all field"的分词结果做比较

  • Query String Search的适用场景

适用于临时的在命令行使用一些工具,比如curl,快速的发出请求,来检索想要的信息;但是如果查询请求很复杂,是很难去构建的,在生产环境中,几乎很少使用Query String Search

1.2 Query String Search实战

# 准备测试数据
PUT /my_index
{
  "mappings": {
    "my_type": {
      "properties": {
        "post_date": {
          "type": "text"
        }
      }
    }
  }
}

PUT /my_index/my_type/1
{
  "post_date": "2020-07-01"
}

PUT /my_index/my_type/2
{
  "post_date": "2020-07-02"
}

PUT /my_index/my_type/3
{
  "post_date": "2020-07-03"
}

# 测试分词结果
GET _analyze
{
  "text": "2020-07-01"
}

{
  "tokens" : [
    {
      "token" : "2020",
      "start_offset" : 0,
      "end_offset" : 4,
      "type" : "<NUM>",
      "position" : 0
    },
    {
      "token" : "07",
      "start_offset" : 5,
      "end_offset" : 7,
      "type" : "<NUM>",
      "position" : 1
    },
    {
      "token" : "01",
      "start_offset" : 8,
      "end_offset" : 10,
      "type" : "<NUM>",
      "position" : 2
    }
  ]
}

# 那么这3条文档建立的倒排索引如下:
---------------------
word  docId
---------------------
2020  1、2、3
07    1、2、3
01    1
02    2
03    3

# 直接与"_all field"的分词结果做比较,于是搜索出3条结果
GET /my_index/my_type/_search?q=2020

{
  "took" : 5,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : 0.2876821,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "my_type",
        "_id" : "2",
        "_score" : 0.2876821,
        "_source" : {
          "post_date" : "2020-07-02"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "my_type",
        "_id" : "1",
        "_score" : 0.2876821,
        "_source" : {
          "post_date" : "2020-07-01"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "my_type",
        "_id" : "3",
        "_score" : 0.2876821,
        "_source" : {
          "post_date" : "2020-07-03"
        }
      }
    ]
  }
}

# 同样,将"2020-07-01"分词后,再去与文档匹配,查询出3条结果
GET /my_index/my_type/_search?q=2020-07-01
{
  "took" : 15,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : 0.8630463,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "my_type",
        "_id" : "1",
        "_score" : 0.8630463,
        "_source" : {
          "post_date" : "2020-07-01"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "my_type",
        "_id" : "2",
        "_score" : 0.5753642,
        "_source" : {
          "post_date" : "2020-07-02"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "my_type",
        "_id" : "3",
        "_score" : 0.5753642,
        "_source" : {
          "post_date" : "2020-07-03"
        }
      }
    ]
  }
}

# 指定field后,就不会进行分词了,而是精确匹配,于是搜索1条数据
# 注意这里需要给完全匹配的字符串加双引号,否则还是查询到3条数据
GET /my_index/my_type/_search?q=post_date:"2020-07-01"

{
  "took" : 5,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 0.8630463,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "my_type",
        "_id" : "1",
        "_score" : 0.8630463,
        "_source" : {
          "post_date" : "2020-07-01"
        }
      }
    ]
  }
}

# GET /my_index/my_type/_search?q=post_date:"2020"
# GET /my_index/my_type/_search?q=post_date:2020
# 以上两条搜索的结果都是3条数据,原理待更新

1.3 Query DSL(Domain Specified Language):查询领域专用语言

  • 适用场景:更加适合生产环境的使用,可以构建复杂的查询

  • 基本语法

    GET /index_name/type_name/_search?key=value
    {
      "key1": "value1"
    }
    # 说明:
    # 1.HTTP协议,一般不允许get请求带上request body,但是因为get更加适合描述查询数据的操作,因此还是这么用了
    # 2.很多浏览器或服务器都支持GET+request body模式,如果不支持,也可以用POST /_search,例如
    GET /index1/type1/_search?from=0&size=2
    POST /index1/type1/_search
    {
      "from": 0,
      "size": 2
    }
    # 3.查询条件的json中,可以进行复杂的json嵌套,来实现复杂的搜索
    
  • 入门案例

    # 查询某索引的全部文档
    GET /myindex/my_type/_search
    {
      "query": {
          "match_all": {}
          }
    }
    
  • timeout参数

    GET /myindex/my_type/_search?timeout=1s
    {
      "query": {
          "match_all": {}
          }
    }
    

    查询的时候可以指定一个timeout参数,默认没有值,这个timeout比较特殊,是说,最多查询指定的时间,比较index总共有1万条数据,全部查询出来需要1min,那么用户体验肯定是不好的,设置timeout为1s,就是说,在1s秒内,能查到多少就给我返回多少,当然了能全部返回是最好的,常用的单位有毫秒(ms)、秒(s)、分钟(m)

1.4 查询结果元数据分析

{
  "took": 6,
  "timed_out": false,
  "_shards": {
    "total": 6,
    "successful": 6,
    "failed": 0
  },
  "hits": {
    "total": 10,
    "max_score": 1,
    "hits": [
      {
        "_index": ".kibana",
        "_type": "config",
        "_id": "5.2.0",
        "_score": 1,
        "_source": {
          "buildNum": 14695
        }
      }
    ]
  }
}
  • took:整个搜索请求花费了多少毫秒

  • hits.total:本次搜索,返回了几条结果

  • hits.max_score:本次搜索的所有结果中,最大的相关度分数是多少,这个分数越大,排名越靠前,说明这条文档越符合搜索条件

  • hits.hits:默认查询前10条数据,按照"_score"降序排序

  • shards.total:此文档在几个shard上存在(包括主分片和其副本)

  • shards.successful:在这些shard上请求这条数据,可以响应的shard的个数

  • shards.failed:在这些shard上请求这条数据失败的shard个数

  • hits.hits._index:文档所属index

  • hits.hits._type:文档所属type

  • hits.hits._id:文档id

  • hits.hits._source:文档内容

2. 词条查询(term)和全文检索(full text)

  • 词条查询:词条查询不会分析查询条件,只有当词条和查询字符串串完全匹配时,才匹配搜
    索。
  • 全文查询:ElasticSearch引擎会先分析查询字符串,将其拆分成多个单词,只要已分析的字
    段中包含词条的任意一个,或全部包含,就匹配查询条件,返回该文档;如果不包含任意一
    个分词,表示没有任何文档匹配查询条件
  • 查询结果与使用termmatch与数据本身的类型息息相关

2.1 准备测试数据

# text:用于全文检索,该类型的字段将通过分词器进行分词
# keyword:不分词,只能搜索该字段完整的值
PUT /shop
{
  "mappings": {
    "product": {
      "properties": {
        "name": {
          "type": "keyword"
        },
        "desc": {
          "type": "text"
        }
      }
    }
  }
}

PUT shop/product/1
{
    "name": "Yunnanbaiyao Toothpaste",
    "desc": "Yunnanbaiyao Toothpaste"
}

PUT /shop/product/2
{
    "name": "Darlie Toothpaste",
    "desc": "Darlie Toothpaste"
}

PUT /shop/product/3
{
    "name": "ZhongHua Toothpaste",
    "desc": "ZhongHua Toothpaste"
}

2.2 term测试

# 词条查询

# 没有结果
POST /shop/product/_search
{
    "query": {
        "term": {
            "name": "Toothpaste"
        }
    }
}

# 有结果(1条)
POST /shop/product/_search
{
    "query": {
        "term": {
            "name": "Darlie Toothpaste"
        }
    }
}

# 没有结果
POST /shop/product/_search
{
    "query": {
        "term": {
            "desc": "Toothpaste"
        }
    }
}

# 没有结果
# 这个没有结果,是因为"Darlie Toothpaste"分词后分成了"darlie"和"toothpaste"
# 所有完全匹配"Darlie Toothpaste"是无法匹配到的
POST /shop/product/_search
{
    "query": {
        "term": {
            "desc": "Darlie Toothpaste"
        }
    }
}


# 有3条结果
POST /shop/product/_search
{
    "query": {
        "term": {
            "desc": "toothpaste"
        }
    }
}

2.3 terms测试

# 没有数据
POST /shop/product/_search
{
    "query": {
        "terms": {
            "name": ["Darlie", "Toothpaste"]
        }
    }
}

# 两条结果
POST /shop/product/_search
{
    "query": {
        "terms": {
            "name": ["Darlie Toothpaste", "Yunnanbaiyao Toothpaste"]
        }
    }
}

# 一条结果
POST /shop/product/_search
{
    "query": {
        "terms": {
            "name": ["Darlie", "Yunnanbaiyao Toothpaste"]
        }
    }
}

# 没有结果
POST /shop/product/_search
{
    "query": {
        "terms": {
            "desc": ["Darlie Toothpaste", "Yunnanbaiyao Toothpaste"]
        }
    }
}

# 1个结果
POST /shop/product/_search
{
    "query": {
        "terms": {
            "desc": ["darlie", "Yunnanbaiyao Toothpaste"]
        }
    }
}

2.4 match测试

# 有结果(1条)
GET /shop/product/_search
{
    "query": {
        "match": {
            "name": "Darlie Toothpaste"
        }
    }
}

# 没有结果
GET /shop/product/_search
{
    "query": {
        "match": {
            "name": "Toothpaste"
        }
    }
}

# 有结果(3条)
GET /shop/product/_search
{
    "query": {
        "match": {
            "desc": "Toothpaste"
        }
    }
}

# 有结果(3条)
GET /shop/product/_search
{
    "query": {
        "match": {
            "desc": "Darlie Toothpaste"
        }
    }
}

2.5 multi_match测试

PUT /shop/product/5
{
    "name": "Apple Toothpaste",
    "desc": "Apple Darlie"
}

PUT /shop/product/6
{
    "name": "Orage Darlie",
    "desc": "Orage"
}

# 3条结果
POST /shop/product/_search
{
    "query": {
        "multi_match": {
            "query": "Darlie",
            "fields": ["name", "desc"]
        }
    }
}

# 4条结果
POST /shop/product/_search
{
    "query": {
        "multi_match": {
            "query": "Orage Darlie",
            "fields": ["name", "desc"]
        }
    }
}

2.6 match_phrase测试

# 有结果(1条)
POST /shop/product/_search
{
    "query": {
        "match_phrase": {
            "desc": "Darlie Toothpaste"
        }
    }
}

# 有结果(3条)
POST /shop/product/_search
{
    "query": {
        "match_phrase": {
            "desc": "Toothpaste"
        }
    }
}

# 没有结果
POST /shop/product/_search
{
    "query": {
        "match_phrase": {
            "name": "Toothpaste"
        }
    }
}

# 有结果(1条)
POST /shop/product/_search
{
    "query": {
        "match_phrase": {
            "name": "Darlie Toothpaste"
        }
    }
}

2.7 match_all测试

# 查询全部数据
POST /shop/product/_search
{
    "query": {
        "match_all": {}
    }
}

GET /shop/product/_search
{
    "query": {
        "match_all": {
            "desc": "Darlie Toothpaste"
        }
    }
}

# 结果
{
    "error":{
        "root_cause":[
            {
                "type":"parsing_exception",
                "reason":"[5:13] [match_all] unknown field [desc], parser not found","line":5,
                "col":13
            }
        ],
        "type":"parsing_exception",
        "reason":"[5:13] [match_all] unknown field [desc], parser not found",
        "line":5,
        "col":13,
        "caused_by":{
            "type":"x_content_parse_exception",
            "reason":"[5:13] [match_all] unknown field [desc], parser not found"}
        },
        "status":400
    }
}

# 分页
GET /shop/product/_search
{
    "query": {
        "match_all": {}
    },
    "from": 0,
    "size": 10
}

2.8 match_phrase_prefix测试

PUT /shop/product/7
{
    "name": "Darlie Pro Toothpaste",
    "desc": "Darlie Pro Toothpaste"
}

# 有结果(1条)
POST /shop/product/_search
{
    "query": {
        "match_phrase_prefix": {
            "name": "Darlie Toothpaste"
        }
    }
}

# 没有结果
POST /shop/product/_search
{
    "query": {
        "match_phrase_prefix": {
            "name": "Darlie"
        }
    }
}

# 有结果(2条)
POST /shop/product/_search
{
    "query": {
        "match_phrase_prefix": {
            "desc": "Darlie"
        }
    }
}

# 有结果(1条)
POST /shop/product/_search
{
    "query": {
        "match_phrase_prefix": {
            "desc": "Darlie Pro"
        }
    }
}

# 有结果(1条)
POST /shop/product/_search
{
    "query": {
        "match_phrase_prefix": {
            "desc": "Darlie Toothpaste"
        }
    }
}

2.9 总结

keyword text
term 完全匹配才返回 完全匹配分词后的单词才返回
terms 传入多个字符串,返回那些可以完全匹配的结果 每个传入的单词,在分词后的所有单词中进行匹配,完全匹配才返回
match_all 查询全部数据,不能传入任何参数 查询全部数据,不能传入任何参数
match 完全匹配才返回 对输入字符串进行分词,指定的字段文本分词后的词语中包含任意一个输入字符串的分词词语,就算匹配,就可以作为结果返回
multi_match 指定的多个字段都完全匹配才返回 对输入字符串进行分词,指定的字段文本分词后的词语中包含任意一个输入字符串的分词词语,就算匹配,就可以作为结果返回
match_phrase 完全匹配才返回 输入字符串不分词,指定的字段文本分词后的词语中包含完整的输入字符串,才可以算匹配,才能作为结果返回
match_phrase_prefix 完全匹配才返回 输入一个单词,例如"hello",只要指定的字段文本分词后的词语中有一个词语是以"hello"作为前缀,就算匹配,输入一个短语例如"hello world tom",那么先匹配分词的后的词语中包含"hello world"的文档,然后在这些文档中过滤,只要这些文档的词语中包含以"tom"开头的词语,就算匹配

3. 范围查询

GET /company/employee/_search
{
  "query": {
    "range": {
      "age": {
        "gte": 31
      }
    }
  }
}

4. query上下文和filter上下文

  • query上下文:在搜索中,计算每个满足条件的document的相关度,进行评分,即给document的"_score"赋值,并进行倒序排序,然后返回结果,使用query上下文查询的结果无法缓存

  • filter上下文:在搜索中,只是过滤出符合条件的document,不计算相关度,使用filter上下文查询的结果将被缓存,以提高整体的查询效率,缓存不需要太多的内存,它只缓存哪些文档与此filter条件相匹配

  • 说明:这里说的query上下文和filter上下文是描述的两种现象,而非使用"query"就是query上下文,使用"filter"就是filter上下文,例如:

    # 既有query上下文,也有filter上下文
    GET /_search
    {
      "query": { 
        "bool": { 
          "must": [
            {"match": {"title": "Search"}},
            {"match": {"content": "Elasticsearch" }}
          ],
          "filter": [ 
            {"term": {"status": "published" }},
            {"range": {"publish_date": {"gte": "2015-01-01" }}}
          ]
        }
      }
    }
    
    # 这个查询,虽然DSL语句中有"query"这个单词,但是它本质上只有一个filter上下文
    GET /_search
    {
        "query" : {
            "bool" : {
                "filter" : {
                    "term" : {
                        "author_id" : 1
                    }
                }
            }
        }
    }
    
    # 这个查询也是一个filter上下文查询,并且"constant_score"规定将符合条件的document的score都设置为"bootst"指定的值
    # 如果没有boost,则查询出的document的score都为1.0
    GET /_search
    {
        "query": {
            "constant_score" : {
                "filter" : {
                    "term" : { "user" : "kimchy"}
                },
                "boost" : 1.2
            }
        }
    }
    

一般来说,如果是进行搜索,需要将最匹配搜索条件的数据先返回,那么用query上下文;如果只是要根据条件筛选出一部分数据,不关注其排序,那么用filter,如果希望越符合搜索条件的document排名越靠前,就把这些搜索条件要放在query上下文中,如果只是想查询到数据并不关注其排名,就放到filter上下文中。

5. 多条件组合查询

# 语法
GET /index_name/type_name/_search
{
    "query": {
        "bool": {
            "must": [],
            "should": [],
            ...
        }
    }
}
    
# 示例
POST _search
{
  "query": {
    "bool" : {
      "must" : {
        "term" : { "user" : "kimchy" }
      },
      "filter": {
        "term" : { "tag" : "tech" }
      },
      "must_not" : {
        "range" : {
          "age" : { "gte" : 10, "lte" : 20 }
        }
      },
      "should" : [
        { "term" : { "tag" : "wow" } },
        { "term" : { "tag" : "elasticsearch" } }
      ],
      "minimum_should_match" : 1
    }
  }
}

# bool中可以放置的内容
must,must_not,should,filter
  • must:属于query上下文,单个或多个条件必须都满足,参与评分
  • filter:属于filter上下文,根据条件进行过滤,不参与评分
  • should:如果查询条件组合同时存在should和query上下文,那么should中的一个或者多个条件都可以不满足,如果查询条件组合中只有should或者只有should和filter上下文,那么should中的一个或者多个条件必须满足一个,should的条件参与评分
  • must_not:属于filter上下文,单个或者多个条件必须都不满足,不参与评分,使用must_not查询出的document的分数都为0
  • minimum_should_match:使用这个参数来指定should的具体行为
    • 正数,例如3,那么should的多个条件中必须满足3个条件
    • 负数,例如-2,代表可以有2个条件不满足,其他都应该满足
    • 百分比正数:代表should条件总数的百分比个条件应该满足,例如总共10个条件,百分比为30%,那么至少3个条件应该满足,需满足条件的个数向下取整
    • 百分比负数:代表占此比例的条件可以不满足,其余的均需要满足,计算结果向下取整
    • 百分比和数字组合:3<90%,如果条件个数<=3,那么必须全部满足,否则,满足90%(向下取整)即可
    • 多个组合(空格隔开):2<-25% 9<-3,如果条件个数<=2,则必须都满足,如果条件个数为[3,9],则需要25%的条件满足,否则,只能有3个条件不满足,其余都需要满足
  • must、must_not、should、filter之间是&的关系,即这些条件都应该满足

6. 自定义排序规则

默认情况下,是按照"_score"降序排序的,使用"sort"参数来自定义排序规则

GET /company/employee/_search
{
  "query": {
    "range": {
      "age": {
        "gte": 30
      }
    }
  },
  "sort": [
    {
      "join_date": {
        "order": "desc"
      }
    }
  ]
}

GET /company/employee/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "age": {
            "gte": 30
          }
        }
      }
    }
  },
  "sort": [
    {
      "join_date": {
        "order": "asc"
      }
    }
  ]
}

7. DSL校验

构建好一个复杂的查询之后,可以先校验一下语句,通过返回的异常信息来修改语句,校验语法如下:

GET /test_index/test_type/_validate/query?explain
{
  "query": {
    "math": { # 这里故意把match写为了math
      "test_field": "test"
    }
  }
}

{
  "valid": false,
  "error": "org.elasticsearch.common.ParsingException: no [query] registered for [math]"
}

GET /test_index/test_type/_validate/query?explain
{
  "query": {
    "match": {
      "test_field": "test"
    }
  }
}

{
  "valid": true,
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "explanations": [
    {
      "index": "test_index",
      "valid": true,
      "explanation": "+test_field:test #(#_type:test_type)"
    }
  ]
}

8. 搜索参数总结

  • preference:决定了哪些shard会被用来执行搜索操作,有以下值可选:
    • _primary:只在主分片中查询,在6.1版本已经废弃
    • _primary_first:优先在主分片中查询,在6.1版本已经废弃
    • _only_local:只在本地查询,查询结果可能不全
    • _local:优先在本地的分片中查询
    • _prefer_nodes:abc,xyz:在指定的node上查询,abc和xyz是node的id,查询顺序不确定
    • _only_nodes:abc*,x*yz,...:在指定的node上查询,这里可以使用正则表达式
    • _shards:2,3:在指定shard上查询
GET /my_index/my_type/_search?preference=_primary
{
  "query": {
    "match_all": {}
  }
}

GET /my_index/my_type/_search?preference=_primary_first
{
  "query": {
    "match_all": {}
  }
}

GET /my_index/my_type/_search?preference=_only_local
{
  "query": {
    "match_all": {}
  }
}

GET /my_index/my_type/_search?preference=_local
{
  "query": {
    "match_all": {}
  }
}

GET /my_index/my_type/_search?preference=_shards:1,2,3
{
  "query": {
    "match_all": {}
  }
}

GET /my_index/my_type/_search?preference=_prefer_nodes:bfkgPqlhSbWH8O6fL1R47Q
{
  "query": {
    "match_all": {}
  }
}

Bouncing Result问题:

两个Document排序,Field值相同,但是请求到不同的shard上,排序结果可能不同,每次请求轮询到不同的shard上,每次页面上看到的搜索结果的排序可能都不一样,这就是Bouncing Result问题,解决问题就是利用preference设置每次请求都到同一个shard上。

解决方案就是将preference设置为一个字符串,比如说user_id,让每个user每次搜索的时候,都使用同一个replica shard去执行,就不会看到bouncing results了

  • timeout:限定在一定时间内,将获取到的部分数据直接返回,避免查询耗时过长

  • routing:使用routing的值来获取document所在的shard编号,默认为document的id,在一些场景下可以使用routing=user_id(或者其他业务数据),这样的话可以让同一个user对应的数据到一个shard上去

  • search_type:默认值为"query_then_fetch",使用"dfs_query_then_fetch"可以提升相关度排序的精准度

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,200评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,526评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,321评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,601评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,446评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,345评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,753评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,405评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,712评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,743评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,529评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,369评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,770评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,026评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,301评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,732评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,927评论 2 336