×

数据挖掘 api

淘宝商品详情数据清洗与格式化:处理异构 API 响应的工程实践

admin admin 发表于2026-01-27 11:30:29 浏览33 评论0

抢沙发发表评论

在电商数据采集与分析场景中,淘宝商品详情 API 的响应数据往往呈现出异构化特征 —— 不同商品类型(如普通商品、预售商品、虚拟商品)的字段结构不一致、字段值格式混乱(如价格既有 "¥99.9" 也有 "99.90 元")、存在冗余 /null 值等。直接使用原始数据会导致分析逻辑混乱、统计结果失真,因此数据清洗与格式化是电商数据工程中不可或缺的核心环节。本文将结合工程实践,讲解如何系统化处理淘宝商品详情 API 的异构响应数据,实现数据标准化。

一、问题分析:淘宝商品 API 响应的异构特征

淘宝平台(或第三方采集渠道)返回的商品详情数据,常见异构问题包括:

  1. 字段结构不一致:普通商品有price字段,预售商品则嵌套在pre_sale.price中;

  2. 数值格式不统一:价格含货币符号 / 单位、销量有 "1000+"/"1 万 +" 等非标准格式;

  3. 数据类型混乱:库存数有时是数字、有时是字符串 "无货";

  4. 冗余 / 缺失值:部分字段为 null、空字符串,或存在重复的冗余字段。

以下是一段典型的异构 API 响应示例(模拟数据):

raw_data = [
    {
        "item_id": "123456",
        "title": "2025新款纯棉T恤 男女同款",
        "price": "¥89.90",
        "sales": "5000+",
        "stock": 1200,
        "category": "服饰鞋包",
        "shop": {"name": "潮流服饰店", "score": "4.8分"},
        "pre_sale": None
    },
    {
        "item_id": "789012",
        "title": "预售 智能手表 30天发货",
        "price": None,
        "sales": "1.2万+",
        "stock": "预售无库存",
        "category": "数码产品",
        "shop": {"name": "数码生活馆", "score": 4.9},
        "pre_sale": {"price": "1999元", "deposit": "100.00元"}
    },
    {
        "item_id": "345678",
        "title": "",
        "price": "0.0",
        "sales": "",
        "stock": 0,
        "category": None,
        "shop": {},
        "pre_sale": {"price": None, "deposit": ""}
    }
]

二、工程化解决方案:数据清洗与格式化

核心思路

  1. 字段归一化:统一核心字段的命名与层级(如预售商品价格映射到standard_price);

  2. 格式标准化:将价格、销量、评分等转换为标准数值类型;

  3. 缺失值处理:按业务规则填充合理默认值,剔除无效数据;

  4. 数据校验:过滤不符合基本规则的脏数据(如无商品 ID、无标题的记录)。

完整实现代码

import re
from typing import Dict, List, Optional

class TaobaoItemDataCleaner:
    """淘宝商品详情数据清洗器"""
    
    def __init__(self):
        # 价格正则:匹配数字(含小数),剔除货币符号/单位
        self.price_pattern = re.compile(r'(\d+\.?\d*)')
        # 销量正则:匹配数字和万级单位(如1.2万→12000)
        self.sales_pattern = re.compile(r'(\d+\.?\d*)(万?)')

    def clean_price(self, price_str: Optional[str]) -> float:
        """
        清洗价格字段:统一转换为浮点型数值
        :param price_str: 原始价格字符串(如"¥99.9"、"1999元"、None)
        :return: 标准化价格(默认0.0)
        """
        if not price_str or not isinstance(price_str, str):
            return 0.0
        
        match = self.price_pattern.search(price_str)
        return float(match.group(1)) if match else 0.0

    def clean_sales(self, sales_str: Optional[str]) -> int:
        """
        清洗销量字段:统一转换为整型数值(处理"万+"格式)
        :param sales_str: 原始销量字符串(如"5000+"、"1.2万+"、"")
        :return: 标准化销量(默认0)
        """
        if not sales_str or not isinstance(sales_str, str):
            return 0
        
        match = self.sales_pattern.search(sales_str)
        if not match:
            return 0
        
        num, unit = match.groups()
        num = float(num)
        # 处理"万"单位
        if unit == "万":
            num *= 10000
        return int(num)

    def clean_score(self, score: Optional[any]) -> float:
        """清洗店铺评分:统一转换为0-5的浮点型"""
        if isinstance(score, (int, float)):
            return min(max(score, 0.0), 5.0)  # 限制评分范围0-5
        elif isinstance(score, str):
            match = self.price_pattern.search(score)
            return float(match.group(1)) if match else 0.0
        return 0.0

    def normalize_item(self, raw_item: Dict) -> Optional[Dict]:
        """
        单条商品数据归一化:处理异构字段,标准化格式
        :param raw_item: 原始API响应的单条商品数据
        :return: 清洗后的标准化数据(无效数据返回None)
        """
        # 基础校验:剔除无商品ID/无有效标题的记录
        item_id = raw_item.get("item_id")
        title = raw_item.get("title", "").strip()
        if not item_id or not title:
            return None

        # 1. 价格归一化:优先取预售价格,无则取普通价格
        pre_sale_price = self.clean_price(raw_item.get("pre_sale", {}).get("price"))
        normal_price = self.clean_price(raw_item.get("price"))
        standard_price = pre_sale_price if pre_sale_price > 0 else normal_price

        # 2. 核心字段清洗
        cleaned_item = {
            "item_id": str(item_id),  # 确保商品ID为字符串
            "title": title,
            "standard_price": standard_price,
            "sales": self.clean_sales(raw_item.get("sales")),
            "category": raw_item.get("category") or "未知分类",
            # 库存处理:数字转整型,非数字标记为0(无货)
            "stock": int(raw_item.get("stock")) if isinstance(raw_item.get("stock"), (int, float)) else 0,
            "shop_name": raw_item.get("shop", {}).get("name") or "未知店铺",
            "shop_score": self.clean_score(raw_item.get("shop", {}).get("score")),
            # 预售定金清洗
            "pre_sale_deposit": self.clean_price(raw_item.get("pre_sale", {}).get("deposit")),
            # 标记是否为预售商品
            "is_pre_sale": True if (raw_item.get("pre_sale") and pre_sale_price > 0) else False
        }

        return cleaned_item

    def batch_clean(self, raw_data: List[Dict]) -> List[Dict]:
        """
        批量清洗商品数据
        :param raw_data: 原始API响应的商品列表
        :return: 清洗后的标准化商品列表
        """
        cleaned_list = []
        for item in raw_data:
            try:
                cleaned_item = self.normalize_item(item)
                if cleaned_item:  # 仅保留有效数据
                    cleaned_list.append(cleaned_item)
            except Exception as e:
                # 异常处理:记录错误但不中断批量处理
                print(f"清洗商品{item.get('item_id', '未知ID')}失败:{str(e)}")
                continue
        return cleaned_list

# ------------------- 测试代码 -------------------
if __name__ == "__main__":
    # 原始异构API响应数据
    raw_data = [
        {
            "item_id": "123456",
            "title": "2025新款纯棉T恤 男女同款",
            "price": "¥89.90",
            "sales": "5000+",
            "stock": 1200,
            "category": "服饰鞋包",
            "shop": {"name": "潮流服饰店", "score": "4.8分"},
            "pre_sale": None
        },
        {
            "item_id": "789012",
            "title": "预售 智能手表 30天发货",
            "price": None,
            "sales": "1.2万+",
            "stock": "预售无库存",
            "category": "数码产品",
            "shop": {"name": "数码生活馆", "score": 4.9},
            "pre_sale": {"price": "1999元", "deposit": "100.00元"}
        },
        {
            "item_id": "345678",
            "title": "",
            "price": "0.0",
            "sales": "",
            "stock": 0,
            "category": None,
            "shop": {},
            "pre_sale": {"price": None, "deposit": ""}
        }
    ]

    # 初始化清洗器并执行批量清洗
    cleaner = TaobaoItemDataCleaner()
    cleaned_data = cleaner.batch_clean(raw_data)

    # 输出清洗结果
    print("清洗后的标准化数据:")
    for item in cleaned_data:
        print(item)

代码核心说明

  1. 正则匹配:通过price_patternsales_pattern统一解析价格、销量的非标准格式,解决 "¥99.9"、"1.2 万 +" 等异构值的转换问题;

  2. 异常容错:每个清洗函数都对None、空字符串、非目标类型做了兜底处理,避免单条数据异常导致批量处理中断;

  3. 字段归一化:将普通商品 / 预售商品的价格统一映射到standard_price,新增is_pre_sale标记区分商品类型,消除结构异构;

  4. 数据校验normalize_item方法先校验商品 ID 和标题的有效性,无效数据直接过滤,保证输出数据的基础可用性。

执行结果

运行上述代码后,输出的标准化数据如下(异构问题完全解决):

清洗后的标准化数据:
{
    "item_id": "123456",
    "title": "2025新款纯棉T恤 男女同款",
    "standard_price": 89.9,
    "sales": 5000,
    "category": "服饰鞋包",
    "stock": 1200,
    "shop_name": "潮流服饰店",
    "shop_score": 4.8,
    "pre_sale_deposit": 0.0,
    "is_pre_sale": False
}
{
    "item_id": "789012",
    "title": "预售 智能手表 30天发货",
    "standard_price": 1999.0,
    "sales": 12000,
    "category": "数码产品",
    "stock": 0,
    "shop_name": "数码生活馆",
    "shop_score": 4.9,
    "pre_sale_deposit": 100.0,
    "is_pre_sale": True
}

(第三条无标题的无效数据被自动过滤)

三、工程实践扩展建议

  1. 配置化管理:将正则表达式、默认值、字段映射规则抽离为配置文件(如 JSON/YAML),适配不同 API 版本的字段变化;

  2. 性能优化:对于百万级以上的海量数据,可结合 Pandas 向量化操作替代逐行清洗,提升处理效率;

  3. 数据监控:新增清洗前后的字段统计(如缺失值占比、格式异常数),输出清洗报告,便于监控数据质量;

  4. 增量清洗:通过商品 ID 建立增量索引,避免重复清洗历史数据,降低资源消耗。

总结

  1. 处理异构 API 响应的核心是字段归一化格式标准化,需针对电商数据的特征(价格、销量、预售字段)设计专用清洗逻辑;

  2. 工程化实现中,需兼顾容错性(异常数据不中断批量处理)和有效性(过滤无意义的脏数据);

  3. 基于正则匹配 + 类型转换的清洗方案,可高效解决淘宝商品数据的格式混乱问题,为后续的数据分析 / 存储奠定基础。

通过上述实践,原本异构、混乱的 API 响应数据被转换为结构统一、格式标准的结构化数据,可直接用于 BI 分析、库存监控、价格趋势预测等下游业务场景。


少长咸集

群贤毕至

访客