Search-API-Examples.md 23.2 KB

API 使用示例

本文档提供了搜索引擎 API 的详细使用示例,包括各种常见场景和最佳实践。


目录

  1. 基础搜索
  2. 过滤器使用
  3. 分面搜索
  4. 排序
  5. 图片搜索
  6. 布尔表达式
  7. 完整示例

基础搜索

示例 1:最简单的搜索

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "芭比娃娃"
  }'

响应

{
  "hits": [...],
  "total": 118,
  "max_score": 8.5,
  "took_ms": 45,
  "query_info": {
    "original_query": "芭比娃娃",
    "detected_language": "zh",
    "translations": {"en": "barbie doll"}
  }
}

示例 2:指定返回数量

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "size": 50
  }'

示例 3:分页查询

# 第1页(0-19)
curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "size": 20,
    "from": 0
  }'

# 第2页(20-39)
curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "size": 20,
    "from": 20
  }'

过滤器使用

精确匹配过滤器

示例 1:单值过滤

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "filters": {
      "category.keyword": "玩具"
    }
  }'

示例 2:多值过滤(OR 逻辑)

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "娃娃",
    "filters": {
      "category.keyword": ["玩具", "益智玩具", "儿童玩具"]
    }
  }'

说明:匹配类目为"玩具" "益智玩具" "儿童玩具"的商品。

示例 3:多字段过滤(AND 逻辑)

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "娃娃",
    "filters": {
      "category.keyword": "玩具",
      "vendor.keyword": "美泰"
    }
  }'

说明:必须同时满足"类目=玩具" 并且 "品牌=美泰"。

范围过滤器

示例 1:价格范围

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "range_filters": {
      "price": {
        "gte": 50,
        "lte": 200
      }
    }
  }'

说明:价格在 50-200 元之间(包含边界)。

示例 2:只设置下限

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "range_filters": {
      "price": {
        "gte": 100
      }
    }
  }'

说明:价格 ≥ 100 元。

示例 3:只设置上限

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "range_filters": {
      "price": {
        "lt": 50
      }
    }
  }'

说明:价格

示例 4:多字段范围过滤

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "range_filters": {
      "price": {
        "gte": 50,
        "lte": 200
      },
      "days_since_last_update": {
        "lte": 30
      }
    }
  }'

说明:价格在 50-200 元 并且 最近30天内更新过。

组合过滤器

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "filters": {
      "category.keyword": ["玩具", "益智玩具"],
      "vendor.keyword": "乐高"
    },
    "range_filters": {
      "price": {
        "gte": 50,
        "lte": 500
      }
    }
  }'

说明:类目是"玩具"或"益智玩具" 并且 品牌是"乐高" 并且 价格在 50-500 元之间。


分面搜索

简单模式

示例 1:基础分面

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "size": 20,
    "facets": ["category.keyword", "vendor.keyword"]
  }'

响应

{
  "hits": [...],
  "total": 118,
  "facets": [
    {
      "field": "category.keyword",
      "label": "category.keyword",
      "type": "terms",
      "values": [
        {"value": "玩具", "count": 85, "selected": false},
        {"value": "益智玩具", "count": 33, "selected": false}
      ]
    },
    {
      "field": "vendor.keyword",
      "label": "vendor.keyword",
      "type": "terms",
      "values": [
        {"value": "乐高", "count": 42, "selected": false},
        {"value": "美泰", "count": 28, "selected": false}
      ]
    }
  ]
}

高级模式

示例 1:自定义分面大小

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "facets": [
      {
        "field": "category.keyword",
        "size": 20,
        "type": "terms"
      },
      {
        "field": "vendor.keyword",
        "size": 30,
        "type": "terms"
      }
    ]
  }'

示例 2:范围分面

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "facets": [
      {
        "field": "price",
        "type": "range",
        "ranges": [
          {"key": "0-50", "to": 50},
          {"key": "50-100", "from": 50, "to": 100},
          {"key": "100-200", "from": 100, "to": 200},
          {"key": "200+", "from": 200}
        ]
      }
    ]
  }'

响应

{
  "facets": [
    {
      "field": "price",
      "label": "price",
      "type": "range",
      "values": [
        {"value": "0-50", "count": 23, "selected": false},
        {"value": "50-100", "count": 45, "selected": false},
        {"value": "100-200", "count": 38, "selected": false},
        {"value": "200+", "count": 12, "selected": false}
      ]
    }
  ]
}

示例 3:混合分面(Terms + Range)

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "facets": [
      {"field": "category.keyword", "size": 15},
      {"field": "vendor.keyword", "size": 15},
      {
        "field": "price",
        "type": "range",
        "ranges": [
          {"key": "低价", "to": 50},
          {"key": "中价", "from": 50, "to": 200},
          {"key": "高价", "from": 200}
        ]
      }
    ]
  }'

排序

示例 1:按价格排序(升序)

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "size": 20,
    "sort_by": "min_price",
    "sort_order": "asc"
  }'

示例 2:按创建时间排序(降序)

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "size": 20,
    "sort_by": "create_time",
    "sort_order": "desc"
  }'

示例 3:排序+过滤

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "filters": {
      "category.keyword": "益智玩具"
    },
    "sort_by": "min_price",
    "sort_order": "asc"
  }'

图片搜索

示例 1:基础图片搜索

curl -X POST "http://localhost:6002/search/image" \
  -H "Content-Type: application/json" \
  -d '{
    "image_url": "https://example.com/barbie.jpg",
    "size": 20
  }'

示例 2:图片搜索+过滤器

curl -X POST "http://localhost:6002/search/image" \
  -H "Content-Type: application/json" \
  -d '{
    "image_url": "https://example.com/barbie.jpg",
    "size": 20,
    "filters": {
      "category.keyword": "玩具"
    },
    "range_filters": {
      "price": {
        "lte": 100
      }
    }
  }'

布尔表达式

示例 1:AND 查询

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具 AND 乐高"
  }'

说明:必须同时包含"玩具"和"乐高"。

示例 2:OR 查询

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "芭比 OR 娃娃"
  }'

说明:包含"芭比"或"娃娃"即可。

示例 3:ANDNOT 查询(排除)

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具 ANDNOT 电动"
  }'

说明:包含"玩具"但不包含"电动"。

示例 4:复杂布尔表达式

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具 AND (乐高 OR 芭比) ANDNOT 电动"
  }'

说明:必须包含"玩具",并且包含"乐高"或"芭比",但不包含"电动"。

示例 5:域查询

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "brand:乐高"
  }'

说明:在品牌域中搜索"乐高"。


完整示例

Python 完整示例

#!/usr/bin/env python3
import requests
import json

API_URL = "http://localhost:6002/search/"

def search_products(
    query,
    size=20,
    from_=0,
    filters=None,
    range_filters=None,
    facets=None,
    sort_by=None,
    sort_order="desc",
    debug=False
):
    """执行搜索查询"""
    payload = {
        "query": query,
        "size": size,
        "from": from_
    }

    if filters:
        payload["filters"] = filters
    if range_filters:
        payload["range_filters"] = range_filters
    if facets:
        payload["facets"] = facets
    if sort_by:
        payload["sort_by"] = sort_by
        payload["sort_order"] = sort_order
    if debug:
        payload["debug"] = debug

    response = requests.post(API_URL, json=payload)
    response.raise_for_status()
    return response.json()


# 示例 1:简单搜索
result = search_products("芭比娃娃", size=10)
print(f"找到 {result['total']} 个结果")
for hit in result['hits'][:3]:
    product = hit['_source']
    print(f"  - {product['name']}: ¥{product.get('price', 'N/A')}")

# 示例 2:带过滤和分面的搜索
result = search_products(
    query="玩具",
    size=20,
    filters={
        "category.keyword": ["玩具", "益智玩具"]
    },
    range_filters={
        "price": {"gte": 50, "lte": 200}
    },
    facets=[
        {"field": "vendor.keyword", "size": 15},
        {"field": "category.keyword", "size": 15},
        {
            "field": "price",
            "type": "range",
            "ranges": [
                {"key": "0-50", "to": 50},
                {"key": "50-100", "from": 50, "to": 100},
                {"key": "100-200", "from": 100, "to": 200},
                {"key": "200+", "from": 200}
            ]
        }
    ],
    sort_by="min_price",
    sort_order="asc"
)

# 显示分面结果
print(f"\n分面统计:")
for facet in result.get('facets', []):
    print(f"\n{facet['label']} ({facet['type']}):")
    for value in facet['values'][:5]:
        selected_mark = "✓" if value['selected'] else " "
        print(f"  [{selected_mark}] {value['label']}: {value['count']}")

# 示例 3:分页查询
page = 1
page_size = 20
total_pages = 5

for page in range(1, total_pages + 1):
    result = search_products(
        query="玩具",
        size=page_size,
        from_=(page - 1) * page_size
    )
    print(f"\n第 {page} 页:")
    for hit in result['hits']:
        product = hit['_source']
        print(f"  - {product['name']}")

JavaScript 完整示例

// 搜索引擎客户端
class SearchClient {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
    }

    async search({
        query,
        size = 20,
        from = 0,
        filters = null,
        rangeFilters = null,
        facets = null,
        sortBy = null,
        sortOrder = 'desc',
        debug = false
    }) {
        const payload = {
            query,
            size,
            from
        };

        if (filters) payload.filters = filters;
        if (rangeFilters) payload.range_filters = rangeFilters;
        if (facets) payload.facets = facets;
        if (sortBy) {
            payload.sort_by = sortBy;
            payload.sort_order = sortOrder;
        }
        if (debug) payload.debug = debug;

        const response = await fetch(`${this.baseUrl}/search/`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(payload)
        });

        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }

        return await response.json();
    }

    async searchByImage(imageUrl, options = {}) {
        const payload = {
            image_url: imageUrl,
            size: options.size || 20,
            filters: options.filters || null,
            range_filters: options.rangeFilters || null
        };

        const response = await fetch(`${this.baseUrl}/search/image`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(payload)
        });

        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }

        return await response.json();
    }
}

// 使用示例
const client = new SearchClient('http://localhost:6002');

// 简单搜索
const result1 = await client.search({
    query: "芭比娃娃",
    size: 20
});
console.log(`找到 ${result1.total} 个结果`);

// 带过滤和分面的搜索
const result2 = await client.search({
    query: "玩具",
    size: 20,
    filters: {
        category.keyword: ["玩具", "益智玩具"]
    },
    rangeFilters: {
        price: { gte: 50, lte: 200 }
    },
    facets: [
        { field: "vendor.keyword", size: 15 },
        { field: "category.keyword", size: 15 }
    ],
    sortBy: "price",
    sortOrder: "asc"
});

// 显示分面结果
result2.facets.forEach(facet => {
    console.log(`\n${facet.label}:`);
    facet.values.forEach(value => {
        const selected = value.selected ? '✓' : ' ';
        console.log(`  [${selected}] ${value.label}: ${value.count}`);
    });
});

// 显示商品
result2.hits.forEach(hit => {
    const product = hit._source;
    console.log(`${product.name} - ¥${product.price}`);
});

前端完整示例(Vue.js 风格)

// 搜索组件
const SearchComponent = {
    data() {
        return {
            query: '',
            results: [],
            facets: [],
            filters: {},
            rangeFilters: {},
            total: 0,
            currentPage: 1,
            pageSize: 20
        };
    },
    methods: {
        async search() {
            const response = await fetch('http://localhost:6002/search/', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    query: this.query,
                    size: this.pageSize,
                    from: (this.currentPage - 1) * this.pageSize,
                    filters: this.filters,
                    range_filters: this.rangeFilters,
                    facets: [
                        { field: 'category.keyword', size: 15 },
                        { field: 'vendor.keyword', size: 15 }
                    ]
                })
            });

            const data = await response.json();
            this.results = data.hits;
            this.facets = data.facets || [];
            this.total = data.total;
        },

        toggleFilter(field, value) {
            if (!this.filters[field]) {
                this.filters[field] = [];
            }

            const index = this.filters[field].indexOf(value);
            if (index > -1) {
                this.filters[field].splice(index, 1);
                if (this.filters[field].length === 0) {
                    delete this.filters[field];
                }
            } else {
                this.filters[field].push(value);
            }

            this.currentPage = 1;
            this.search();
        },

        setPriceRange(min, max) {
            if (min !== null || max !== null) {
                this.rangeFilters.price = {};
                if (min !== null) this.rangeFilters.price.gte = min;
                if (max !== null) this.rangeFilters.price.lte = max;
            } else {
                delete this.rangeFilters.price;
            }
            this.currentPage = 1;
            this.search();
        }
    }
};

调试与优化

启用调试模式

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "debug": true
  }'

响应包含调试信息

{
  "hits": [...],
  "total": 118,
  "debug_info": {
    "query_analysis": {
      "original_query": "玩具",
      "normalized_query": "玩具",
      "rewritten_query": "玩具",
      "detected_language": "zh",
      "translations": {"en": "toy"}
    },
    "es_query": {
      "query": {...},
      "size": 10
    },
    "stage_timings": {
      "query_parsing": 5.3,
      "elasticsearch_search": 35.1,
      "result_processing": 4.8
    }
  }
}

设置最小分数阈值

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "min_score": 5.0
  }'

说明:只返回相关性分数 ≥ 5.0 的结果。


常见使用场景

场景 1:电商分类页

# 显示某个类目下的所有商品,按价格排序,提供品牌筛选
curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "*",
    "filters": {
      "category.keyword": "玩具"
    },
    "facets": [
      {"field": "vendor.keyword", "size": 20},
      {
        "field": "price",
        "type": "range",
        "ranges": [
          {"key": "0-50", "to": 50},
          {"key": "50-100", "from": 50, "to": 100},
          {"key": "100-200", "from": 100, "to": 200},
          {"key": "200+", "from": 200}
        ]
      }
    ],
    "sort_by": "min_price",
    "sort_order": "asc",
    "size": 24
  }'

场景 2:搜索结果页

# 用户搜索关键词,提供筛选和排序
curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "芭比娃娃",
    "facets": [
      {"field": "category.keyword", "size": 10},
      {"field": "vendor.keyword", "size": 10},
      {"field": "price", "type": "range", "ranges": [
        {"key": "0-50", "to": 50},
        {"key": "50-100", "from": 50, "to": 100},
        {"key": "100+", "from": 100}
      ]}
    ],
    "size": 20
  }'

场景 3:促销专区

# 显示特定价格区间的商品
curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "*",
    "range_filters": {
      "price": {
        "gte": 50,
        "lte": 100
      }
    },
    "facets": ["category.keyword", "vendor.keyword"],
    "sort_by": "min_price",
    "sort_order": "asc",
    "size": 50
  }'

场景 4:新品推荐

# 最近更新的商品
curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "*",
    "range_filters": {
      "days_since_last_update": {
        "lte": 7
      }
    },
    "sort_by": "create_time",
    "sort_order": "desc",
    "size": 20
  }'

错误处理

示例 1:参数错误

# 错误:range_filters 缺少操作符
curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "range_filters": {
      "price": {}
    }
  }'

响应

{
  "error": "Validation error",
  "detail": "至少需要指定一个范围边界(gte, gt, lte, lt)",
  "timestamp": 1699800000
}

示例 2:空查询

# 错误:query 为空
curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": ""
  }'

响应

{
  "error": "Validation error",
  "detail": "query field required",
  "timestamp": 1699800000
}

性能优化建议

1. 合理使用分面

# ❌ 不推荐:请求太多分面
{
  "facets": [
    {"field": "field1", "size": 100},
    {"field": "field2", "size": 100},
    {"field": "field3", "size": 100},
    // ... 10+ facets
  ]
}

# ✅ 推荐:只请求必要的分面
{
  "facets": [
    {"field": "category.keyword", "size": 15},
    {"field": "vendor.keyword", "size": 15}
  ]
}

2. 控制返回数量

# ❌ 不推荐:一次返回太多
{
  "size": 100
}

# ✅ 推荐:分页查询
{
  "size": 20,
  "from": 0
}

3. 使用适当的过滤器

# ✅ 推荐:先过滤后搜索
{
  "query": "玩具",
  "filters": {
    "category.keyword": "玩具"
  }
}

高级技巧

技巧 1:获取所有类目

# 使用通配符查询 + 分面
curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "*",
    "size": 0,
    "facets": [
      {"field": "category.keyword", "size": 100}
    ]
  }'

技巧 2:价格分布统计

curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "size": 0,
    "facets": [
      {
        "field": "price",
        "type": "range",
        "ranges": [
          {"key": "0-50", "to": 50},
          {"key": "50-100", "from": 50, "to": 100},
          {"key": "100-200", "from": 100, "to": 200},
          {"key": "200-500", "from": 200, "to": 500},
          {"key": "500+", "from": 500}
        ]
      }
    ]
  }'

技巧 3:组合多种查询类型

# 布尔表达式 + 过滤器 + 分面 + 排序
curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "(玩具 OR 游戏) AND 儿童 ANDNOT 电子",
    "filters": {
      "category.keyword": ["玩具", "益智玩具"]
    },
    "range_filters": {
      "price": {"gte": 20, "lte": 100},
      "days_since_last_update": {"lte": 30}
    },
    "facets": [
      {"field": "vendor.keyword", "size": 20}
    ],
    "sort_by": "min_price",
    "sort_order": "asc",
    "size": 20
  }'

测试数据

如果你需要测试数据,可以使用以下查询:

# 测试类目:玩具
curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{"query": "玩具", "size": 5}'

# 测试品牌:乐高
curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{"query": "brand:乐高", "size": 5}'

# 测试布尔表达式
curl -X POST "http://localhost:6002/search/" \
  -H "Content-Type: application/json" \
  -d '{"query": "玩具 AND 乐高", "size": 5}'

文档版本: 3.0
最后更新: 2024-11-12
相关文档: API_DOCUMENTATION.md