分面数据问题诊断.md 7.97 KB

分面数据问题诊断报告

问题描述

前端显示的分面结果都是空的:

  • Category: 空
  • Color: 空
  • Size: 空
  • Material: 空

ES的聚合查询结果也是空的。

数据流程分析

1. 数据生成阶段(csv_to_excel_multi_variant.py)

生成的数据

分类信息:

  • Excel字段:'专辑名称': csv_data['categoryName']
  • 示例值:"电子产品""服装/男装"(从CSV的categoryName字段读取)

属性信息(M+P类型商品):

  • Excel字段(M行主商品):
    • '款式1': 'color'(选项名称)
    • '款式2': 'size'(选项名称)
    • '款式3': 'material'(选项名称)
  • Excel字段(P行子款式):
    • '款式1': 'Red'(选项值,从COLORS列表随机选择)
    • '款式2': '5'(选项值,1-30随机选择)
    • '款式3': '塑料'(选项值,从商品标题提取)

2. Excel导入店匠系统 → MySQL

预期映射

分类字段:

  • Excel '专辑名称' → MySQL shoplazza_product_spu.category_path category 字段
  • 问题:店匠系统可能将"专辑名称"映射到category字段,而不是category_path字段

属性字段:

  • Excel '款式1/2/3'(M行)→ MySQL shoplazza_product_option.nameposition
  • Excel '款式1/2/3'(P行)→ MySQL shoplazza_product_sku.option1/2/3

3. MySQL → ES转换阶段(spu_transformer.py)

category1_name 构建逻辑(第228-240行):

if pd.notna(spu_row.get('category_path')):
    category_path = str(spu_row['category_path'])
    # 解析category_path获取多层级分类名称
    path_parts = category_path.split('/')
    if len(path_parts) > 0:
        doc['category1_name'] = path_parts[0].strip()

问题:如果MySQL中的category_path字段为空,category1_name不会被设置!

specifications 构建逻辑(第328-347行):

# 构建option名称映射(position -> name)
option_name_map = {}
if not options.empty:
    for _, opt_row in options.iterrows():
        position = opt_row.get('position')
        name = opt_row.get('name')
        if pd.notna(position) and pd.notna(name):
            option_name_map[int(position)] = str(name)

# 构建specifications
if pd.notna(sku_row.get('option1')) and 1 in option_name_map:
    specifications.append({
        'sku_id': sku_id,
        'name': option_name_map[1],  # 使用option表的name字段
        'value': str(sku_row['option1'])
    })

问题:如果shoplazza_product_option表中没有记录,或者name字段值不是英文(如"color"),会导致:

  1. option_name_map为空,无法构建specifications
  2. 即使有值,如果name不是"color"/"size"/"material",前端也无法正确匹配

问题根源

问题1:category1_name 为空

原因

  1. MySQL的category_path字段可能为空
  2. Excel的"专辑名称"可能被映射到category字段而不是category_path
  3. 如果category_path为空,category1_name不会被设置

验证方法

SELECT COUNT(*) as total,
       COUNT(category_path) as has_category_path,
       COUNT(category) as has_category
FROM shoplazza_product_spu
WHERE tenant_id = 162 AND deleted = 0;

问题2:specifications 为空

原因

  1. shoplazza_product_option表可能没有数据
  2. option表的name字段值可能不是英文(不是"color"、"size"、"material")

验证方法

SELECT DISTINCT name, position, COUNT(*) as count
FROM shoplazza_product_option
WHERE tenant_id = 162 AND deleted = 0
GROUP BY name, position
ORDER BY position, name;

解决方案

方案1:修复 spu_transformer.py - 支持从category字段生成category1_name

修改indexer/spu_transformer.py_transform_spu_to_doc方法,如果category_path为空,使用category字段作为备选:

# Category相关字段
if pd.notna(spu_row.get('category_path')):
    category_path = str(spu_row['category_path'])
    doc['category_path_zh'] = category_path
    doc['category_path_en'] = None

    # 解析category_path获取多层级分类名称
    path_parts = category_path.split('/')
    if len(path_parts) > 0:
        doc['category1_name'] = path_parts[0].strip()
    if len(path_parts) > 1:
        doc['category2_name'] = path_parts[1].strip()
    if len(path_parts) > 2:
        doc['category3_name'] = path_parts[2].strip()
elif pd.notna(spu_row.get('category')):
    # 如果category_path为空,使用category字段作为category1_name
    category = str(spu_row['category'])
    doc['category1_name'] = category.strip()
    # 如果category包含"/",也尝试解析
    if '/' in category:
        path_parts = category.split('/')
        if len(path_parts) > 0:
            doc['category1_name'] = path_parts[0].strip()
        if len(path_parts) > 1:
            doc['category2_name'] = path_parts[1].strip()
        if len(path_parts) > 2:
            doc['category3_name'] = path_parts[2].strip()

方案2:检查并修复 option 表的 name 字段值

需要确保shoplazza_product_option表的name字段值是英文:

  • position=1 的name应该是 "color"
  • position=2 的name应该是 "size"
  • position=3 的name应该是 "material"

如果值不对,需要更新:

-- 查看当前的name值
SELECT DISTINCT name, position
FROM shoplazza_product_option
WHERE tenant_id = 162 AND deleted = 0
ORDER BY position;

-- 如果需要更新(示例)
-- UPDATE shoplazza_product_option
-- SET name = CASE position
--     WHEN 1 THEN 'color'
--     WHEN 2 THEN 'size'
--     WHEN 3 THEN 'material'
-- END
-- WHERE tenant_id = 162 AND deleted = 0;

方案3:验证数据完整性

使用诊断脚本检查数据:

python scripts/check_data_source.py \
    --tenant-id 162 \
    --db-host <mysql_host> \
    --db-port 3316 \
    --db-database saas \
    --db-username saas \
    --db-password <password>

诊断步骤

步骤1:检查MySQL数据

运行诊断脚本:

cd /home/tw/SearchEngine
source /home/tw/miniconda3/etc/profile.d/conda.sh
conda activate searchengine
python scripts/check_data_source.py --tenant-id 162 --db-host <host> --db-database saas --db-username saas --db-password <password>

步骤2:根据检查结果修复

如果 category_path 为空:

  • 使用方案1:修改spu_transformer.py支持从category字段生成category1_name

如果 option 表没有数据或name值不对:

  • 检查Excel导入是否正确
  • 如果需要,手动更新option表的name字段值

步骤3:重新导入数据到ES

python scripts/recreate_and_import.py \
    --tenant-id 162 \
    --db-host <host> \
    --db-database saas \
    --db-username saas \
    --db-password <password> \
    --es-host http://localhost:9200

步骤4:验证ES数据

检查ES索引中的文档:

curl -X GET "http://localhost:9200/search_products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "query": {
    "term": {
      "tenant_id": "162"
    }
  },
  "size": 1,
  "_source": ["spu_id", "title_zh", "category1_name", "specifications", "option1_name"]
}'

预期结果

修复后,ES文档应该包含:

  1. category1_name字段

    {
     "category1_name": "电子产品"
    }
    
  2. specifications字段

    {
     "specifications": [
       {"sku_id": "123", "name": "color", "value": "Red"},
       {"sku_id": "123", "name": "size", "value": "5"},
       {"sku_id": "123", "name": "material", "value": "塑料"}
     ]
    }
    
  3. option1_name/2_name/3_name字段

    {
     "option1_name": "color",
     "option2_name": "size",
     "option3_name": "material"
    }
    

总结

问题可能出现在:

  1. MySQL数据层面category_path字段为空,或者shoplazza_product_option表没有正确的数据
  2. 数据转换层面spu_transformer.py没有处理category_path为空的情况

建议先运行诊断脚本检查MySQL数据,然后根据检查结果进行修复。