README.md
电商搜索引擎 SaaS 系统
一个可配置的多租户搜索引擎,专为跨境电商独立站(店匠 Shoplazza)设计。
系统特性
- 多语言支持:支持中文、英文、俄文、阿拉伯文、西班牙文、日文,支持自动翻译
- 语义搜索:基于 BGE-M3 文本向量和 CN-CLIP 图片向量的语义检索
- 混合排序:结合 BM25 文本相关性和语义相似度
- 布尔表达式:支持 AND、OR、RANK、ANDNOT 操作符,支持括号优先级
- 灵活过滤:精确匹配过滤器和数值范围过滤器
- 分面搜索:动态生成过滤选项,提供分组统计
- 可配置化:客户特定的字段定义、分析器、排序表达式
- 多租户隔离:通过
tenant_id实现数据隔离,共享统一索引 - RESTful API:基于 FastAPI 的完整 API 服务
- 前端界面:提供可视化搜索测试界面
系统架构
数据模型
- SPU 级别索引:所有租户共享
search_products索引 - 嵌套结构:每个 SPU 文档包含嵌套的
variants数组(SKU 变体) - 租户隔离:通过
tenant_id字段实现多租户数据隔离 - 数据源:MySQL 数据库(
shoplazza_product_spu和shoplazza_product_sku表)
技术栈
- 后端框架:Python 3.8+ / FastAPI
- 搜索引擎:Elasticsearch 8.x
- 数据库:MySQL(店匠数据表)
- 向量模型:
- 文本向量:BGE-M3(1024维)
- 图片向量:CN-CLIP(1024维)
- 翻译服务:DeepL API
- 前端:HTML + JavaScript
快速开始
1. 环境准备
安装依赖
pip install -r requirements.txt
启动 Elasticsearch
# 使用 Docker
docker run -d \
--name elasticsearch \
-p 9200:9200 \
-e "discovery.type=single-node" \
-e "ES_JAVA_OPTS=-Xms2g -Xmx2g" \
elasticsearch:8.11.0
配置环境变量(可选)
创建 .env 文件:
DB_HOST=120.79.247.228
DB_PORT=3316
DB_DATABASE=saas
DB_USERNAME=saas
DB_PASSWORD=your_password
ES_HOST=http://localhost:9200
2. 脚本体系
项目提供统一的脚本系统,管理完整的工作流程:
脚本说明
| 脚本 | 功能 | 说明 |
|---|---|---|
restart.sh |
重启服务 | 停止并重新启动前后端服务 |
run.sh |
启动服务 | 启动前端和后端服务 |
scripts/mock_data.sh |
数据导入 | 将 Mock 数据或 CSV 数据导入 MySQL |
scripts/ingest.sh |
数据索引 | 从 MySQL 导入数据到 Elasticsearch |
使用示例
1. 导入 Mock 数据到 MySQL(tenant_id=1)
# 生成并导入 100 个 Mock SPU(默认)
./scripts/mock_data.sh
# 生成并导入指定数量的 SPU
./scripts/mock_data.sh 1 200
# 使用显式参数
./scripts/mock_data.sh --mode mock --tenant-id 1 --num-spus 200
2. 导入 CSV 数据到 MySQL(tenant_id=2)
# 导入 customer1 的 CSV 数据
./scripts/mock_data.sh --mode csv \
--csv-file data/customer1/goods_with_pic.5years_congku.csv.shuf.1w \
--tenant-id 2 \
--start-spu-id 1
3. 从 MySQL 导入数据到 Elasticsearch
# 导入 tenant_id=1 的数据
./scripts/ingest.sh 1
# 重建索引并导入数据
./scripts/ingest.sh 1 true
4. 启动服务
# 启动前端和后端
./run.sh
# 重启所有服务
./restart.sh
完整工作流程示例
# 1. 导入 Mock 数据(tenant_id=1)
./scripts/mock_data.sh 1 100
# 2. 导入 CSV 数据(tenant_id=2)
./scripts/mock_data.sh --mode csv \
--csv-file data/customer1/goods_with_pic.5years_congku.csv.shuf.1w \
--tenant-id 2
# 3. 将两个租户的数据导入 ES
./scripts/ingest.sh 1
./scripts/ingest.sh 2
# 4. 启动服务
./run.sh
3. 手动启动 API 服务(可选)
如果不想使用脚本,可以手动启动:
python -m api.app \
--host 0.0.0.0 \
--port 6002 \
--es-host http://localhost:9200 \
--reload
4. 测试搜索
简单搜索
curl -X POST http://localhost:6002/search/ \
-H "Content-Type: application/json" \
-d '{
"query": "蓝牙耳机",
"size": 10
}'
带过滤器的搜索
curl -X POST http://localhost:6002/search/ \
-H "Content-Type: application/json" \
-d '{
"query": "玩具",
"size": 10,
"filters": {
"categoryName_keyword": "玩具"
},
"range_filters": {
"min_price": {
"gte": 50,
"lte": 200
}
}
}'
布尔表达式搜索
curl -X POST http://localhost:6002/search/ \
-H "Content-Type: application/json" \
-d '{
"query": "蓝牙 AND (耳机 OR 音响)",
"size": 10
}'
图片搜索
curl -X POST http://localhost:6002/search/image \
-H "Content-Type: application/json" \
-d '{
"image_url": "https://oss.essa.cn/example.jpg",
"size": 10
}'
分面搜索
curl -X POST http://localhost:6002/search/ \
-H "Content-Type: application/json" \
-d '{
"query": "玩具",
"size": 10,
"facets": [
{
"field": "categoryName_keyword",
"label": "分类"
},
{
"field": "brandName_keyword",
"label": "品牌"
}
]
}'
项目结构
SearchEngine/
├── api/ # API 服务
│ ├── app.py # FastAPI 应用主入口
│ ├── models.py # 请求/响应模型
│ └── routes/ # API 路由
│ ├── search.py # 搜索接口
│ └── admin.py # 管理接口
├── config/ # 配置系统
│ ├── field_types.py # 字段类型定义
│ ├── config_loader.py # 配置加载器
│ └── schema/ # 租户配置文件
│ └── base/ # Base 配置(店匠通用)
│ └── config.yaml
├── indexer/ # 数据索引
│ ├── mapping_generator.py # ES mapping 生成器
│ ├── spu_transformer.py # SPU 数据转换器
│ ├── bulk_indexer.py # 批量索引器
│ └── ingest_shoplazza.py # 店匠数据导入脚本
├── query/ # 查询处理
│ ├── query_parser.py # 查询解析器
│ ├── language_detector.py # 语言检测
│ ├── translator.py # 翻译服务
│ └── query_rewriter.py # 查询改写
├── search/ # 搜索执行
│ ├── searcher.py # 主搜索器
│ ├── multilang_query_builder.py # 多语言查询构建器
│ ├── boolean_parser.py # 布尔表达式解析器
│ ├── es_query_builder.py # ES 查询构建器
│ └── ranking_engine.py # 排序引擎
├── embeddings/ # 向量编码
│ ├── text_encoder.py # BGE-M3 文本编码器
│ └── image_encoder.py # CN-CLIP 图片编码器
├── utils/ # 工具类
│ ├── db_connector.py # MySQL 连接器
│ ├── es_client.py # ES 客户端封装
│ └── cache.py # 向量缓存
├── scripts/ # 脚本工具
│ ├── mock_data.sh # Mock 数据导入脚本
│ ├── ingest.sh # 数据索引脚本
│ ├── generate_test_data.py # 生成测试数据
│ ├── import_customer1_csv.py # CSV 导入脚本
│ └── import_test_data.py # 数据导入脚本
├── frontend/ # 前端界面
│ └── unified.html # 统一搜索界面
├── data/ # 数据文件
│ └── customer1/ # customer1 测试数据
├── run.sh # 启动脚本
├── restart.sh # 重启脚本
└── requirements.txt # Python 依赖
配置系统
配置文件结构
配置文件位于 config/schema/{tenant_id}/config.yaml,Base 配置位于 config/schema/base/config.yaml。
配置内容
1. 字段定义 (fields)
定义 ES 索引的字段结构:
fields:
title_zh:
type: TEXT
analyzer: chinese_ecommerce
index: true
store: true
boost: 2.0
title_en:
type: TEXT
analyzer: english
index: true
store: true
categoryName_keyword:
type: KEYWORD
index: true
store: true
min_price:
type: FLOAT
index: true
store: true
title_embedding:
type: TEXT_EMBEDDING
dimension: 1024
similarity: dot_product
2. 查询域配置 (indexes)
定义多域查询配置:
indexes:
default:
fields: ["title_zh", "title_en", "title_ru"]
weights: {"title_zh": 2.0, "title_en": 1.5}
title:
fields: ["title_zh", "title_en"]
brand:
fields: ["brandName_keyword"]
3. 查询配置 (query_config)
多语言和翻译配置:
query_config:
languages: ["zh", "en", "ru"]
auto_translate: true
translation_api: "deepl"
enable_embeddings: true
embedding_model: "bge-m3"
4. 排序配置 (ranking)
相关性排序表达式:
ranking:
expression: "bm25() + 0.2*text_embedding_relevance() + general_score*2"
enable_function_score: true
5. SPU 配置 (spu_config)
SPU 聚合配置:
spu_config:
enabled: true
spu_field: "product_id"
inner_hits_size: 3
字段类型
| 类型 | 说明 | 示例 |
|---|---|---|
TEXT |
文本字段,支持分词 | 商品标题、描述 |
KEYWORD |
关键词字段,精确匹配 | 分类、品牌 |
TEXT_EMBEDDING |
文本向量(1024维) | 语义搜索 |
IMAGE_EMBEDDING |
图片向量(1024维) | 图片搜索 |
INT/LONG |
整数类型 | 库存、ID |
FLOAT/DOUBLE |
浮点数类型 | 价格 |
DATE |
日期类型 | 创建时间 |
BOOLEAN |
布尔类型 | 是否上架 |
分析器
| 分析器 | 说明 | 适用语言 |
|---|---|---|
chinese_ecommerce |
中文电商分词器(Ansj) | 中文 |
english |
英文分析器 | 英文 |
russian |
俄文分析器 | 俄文 |
arabic |
阿拉伯文分析器 | 阿拉伯文 |
spanish |
西班牙文分析器 | 西班牙文 |
japanese |
日文分析器 | 日文 |
standard |
标准分析器 | 通用 |
keyword |
关键词分析器 | 精确匹配 |
API 接口
搜索接口
1. 文本搜索
端点: POST /search/
请求示例:
{
"query": "蓝牙耳机",
"size": 10,
"from": 0,
"filters": {
"categoryName_keyword": "电子产品"
},
"range_filters": {
"min_price": {
"gte": 50,
"lte": 500
}
},
"facets": [
{
"field": "categoryName_keyword",
"label": "分类"
}
],
"sort_by": "min_price",
"sort_order": "asc"
}
2. 图片搜索
端点: POST /search/image
请求示例:
{
"image_url": "https://oss.essa.cn/example.jpg",
"size": 10,
"filters": {
"categoryName_keyword": "玩具"
}
}
3. 获取文档
端点: GET /search/{doc_id}
管理接口
1. 健康检查
端点: GET /admin/health
2. 获取配置
端点: GET /admin/config?tenant_id=1
3. 索引统计
端点: GET /admin/stats?tenant_id=1
4. 查询改写规则
端点:
GET /admin/rewrite-rules?tenant_id=1- 获取规则POST /admin/rewrite-rules- 更新规则
详细 API 文档请参考 API_DOCUMENTATION.md。
高级功能
布尔表达式
支持的操作符(优先级从高到低):
()- 括号ANDNOT- 排除AND- 必须全部匹配OR- 任意匹配RANK- 排序提升
示例:
蓝牙 AND (耳机 OR 音响) ANDNOT 便宜
laptop AND (gaming OR professional) ANDNOT cheap
查询改写
配置品牌/分类映射:
rewrite_dictionary:
"苹果": "brand:苹果 OR name:iPhone"
"玩具": "category:玩具"
排序表达式
可配置的相关性排序:
bm25() + 0.2*text_embedding_relevance() + general_score*2 + timeliness(end_time)
支持的函数:
bm25()- BM25 相关性分数text_embedding_relevance()- 文本向量相似度image_embedding_relevance()- 图片向量相似度field_value(field_name)- 字段值timeliness(date_field)- 时间衰减
多语言查询
系统自动检测查询语言,并支持:
- 自动翻译:将查询翻译到配置的所有语言
- 语言路由:根据语言选择对应的字段
- 混合查询:同时查询多个语言字段
SPU 聚合
启用 SPU 聚合后,每个 SPU 只返回一个代表性 SKU:
spu_config:
enabled: true
spu_field: "product_id"
inner_hits_size: 3 # 每个 SPU 内部返回的 SKU 数量
数据导入
MySQL 表结构
系统使用店匠的标准表结构:
- SPU 表:
shoplazza_product_spu - SKU 表:
shoplazza_product_sku
数据导入流程
- 生成 SQL:使用
scripts/generate_test_data.py或scripts/import_customer1_csv.py生成 SQL 文件 - 导入 MySQL:使用
scripts/mock_data.sh或scripts/import_test_data.py导入数据 - 索引到 ES:使用
scripts/ingest.sh或scripts/ingest_shoplazza.py将数据索引到 Elasticsearch
CSV 数据格式
CSV 文件应包含以下字段:
skuId- SKU IDname- 商品名称(中文)name_pinyin- 拼音create_time- 创建时间ruSkuName- 俄文名称enSpuName- 英文名称categoryName- 分类名称supplierName- 供应商名称brandName- 品牌名称file_id- 文件 IDid- 商品 IDimageUrl- 图片 URL
性能优化
1. 向量缓存
启用向量缓存可以避免重复计算:
# 在配置中启用缓存
cache:
enabled: true
ttl: 3600 # 缓存过期时间(秒)
2. 批量处理
调整批量大小以优化性能:
- 数据转换批量大小:默认 100(根据内存调整)
- 索引批量大小:默认 500(根据 ES 性能调整)
3. GPU 加速
使用 GPU 加速向量计算:
# 安装 CUDA 版本的 PyTorch
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118
4. ES 分片配置
根据数据量配置 ES 分片:
# 在 mapping 生成器中配置
settings:
number_of_shards: 3
number_of_replicas: 1
开发指南
运行测试
pytest tests/
代码格式化
black .
类型检查
mypy .
日志查看
# 查看服务日志
tail -f logs/search_service.log
常见问题
1. 如何添加新的租户?
- 创建配置文件
config/schema/{tenant_id}/config.yaml - 导入数据到 MySQL(使用
scripts/mock_data.sh) - 索引数据到 ES(使用
scripts/ingest.sh {tenant_id})
2. 如何修改排序规则?
编辑配置文件中的 ranking.expression 字段。
3. 如何添加新的分析器?
在 config/field_types.py 中定义新的分析器,然后在字段配置中使用。
4. 向量计算很慢怎么办?
- 启用向量缓存
- 使用 GPU 加速
- 减少批量大小
5. 如何调试搜索查询?
在 API 请求中设置 debug: true,返回详细的调试信息。
相关文档
- API 文档:
API_DOCUMENTATION.md - 设计文档:
设计文档.md - 部署指南:
DEPLOYMENT.md - 用户指南:
USER_GUIDE.md
许可证
专有软件 - 保留所有权利