拿到“汤姆·福特BB霜 品类推荐【汤姆·福特BB霜_TOM FORD T气垫粉底液 #0.7 Pearl 12g】省231元七夕礼物”这类电商商品标题,很多开发者的第一反应是把它当成一个普通字符串存进数据库。实际做搜索、推荐、价格监测和运营活动管理的时候,这种存法很快就会暴露问题:标题里的品牌、品类、色号、规格、促销信息全部混在一起,无法直接筛选、统计和关联。本文不会讨论美妆产品本身,而是把这条标题当作一条典型商品数据样本,讲解如何用 Python 做商品标题解析、字段标准化、品类推荐标签生成,以及在生产环境落地时需要考虑的规则、词典和模型选型问题。学完后,你可以把类似的标题解析思路复用到自己的商品库、爬虫数据清洗或中台标签系统中。
1. 为什么要把商品标题拆成结构化字段
电商平台上的商品标题往往是运营人员为搜索流量、促销氛围和品牌展示写出来的长文本。它既是商品属性的载体,也是活动信息、价格文案和用户感知的混合体。要在大规模数据下做筛选、聚合、推荐和监控,就必须先把长字段拆成结构化的小字段。
1.1 长标题直接存库的痛点:关键字段只能靠模糊匹配
假设商品表里只有一个title字段,数据量小的时候还能通过LIKE '%粉底液%'查出品类,但数据量大了以后会遇到几个明显问题:
- 同义词无法统一。
BB霜、BB cream、气垫BB在业务上可能是同一类目,但字符串匹配不出来。 - 色号、规格没法比较。
#0.7 Pearl和#07 Pearl拆不出来,无法按色号聚合。 - 促销词和属性词混在一起。
省231元、七夕礼物这些词如果进入搜索分词,会造成无关召回。 - 品牌归属不清晰。
TOM FORD和汤姆·福特同时出现,不统一会导致品牌维度统计失真。
所以第一步不是写更复杂的 SQL,而是先把标题里的字段抽出来。
1.2 结构化字段能支撑哪些业务场景
商品标题解析不是纯文本处理的作业题,它直接服务下游业务:
- 品类运营:统计
BB霜、气垫粉底液、粉底液的 SKU 分布。 - 搜索优化:把色号、规格作为过滤条件,而不是参与相关性匹配。
- 推荐系统:品牌、品类、色号、价格区间可以组合成用户偏好特征。
- 活动监控:识别
七夕礼物、省231元等促销场景,评估活动包装对商品流量的影响。 - 数据治理:同一款商品在多个标题下出现时,能通过品牌+品类+色号+规格做归一化对齐。
1.3 样本标题能拆出哪些字段
以样例标题为例:
汤姆·福特BB霜 品类推荐【汤姆·福特BB霜_TOM FORD T气垫粉底液 #0.7 Pearl 12g】省231元七夕礼物
可以拆出如下字段:
| 字段 | 提取结果 | 说明 |
|---|---|---|
| 品牌 | TOM FORD / 汤姆·福特 | 中英文品牌词需要映射统一 |
| 品类 | BB霜、气垫粉底液 | 标题中出现了两个品类词 |
| 产品系列 | T | 通常紧跟品牌或品类之后 |
| 色号 | 0.7 Pearl | #号后面是色号主体 |
| 规格 | 12g | 数字 + 单位组合 |
| 促销词 | 省231元 | 价格文案 |
| 场景词 | 七夕礼物 | 节日场景标签 |
这些字段不会在真实标题里规规矩矩地排列,所以需要一套带优先级和兜底策略的解析规则。
2. 标题解析方案选型:正则、规则引擎还是机器学习
很多初学者会直接上网找现成的 NLP 模型来抽实体,但商品标题并不完全等同于新闻文本。标题长度短、噪音词多、符号密集,直接上模型反而可能因为训练数据不匹配而出错。更稳妥的做法是先看数据规律,再选择合适的技术强度。
2.1 先看商品标题的常见形态
商品标题通常遵循一套不严格的模板:
[品牌] + [品类/产品名] + [系列名] + [色号] + [规格] + [促销词/场景词]但真实数据会出现各种变体:
品牌 品类 分类推荐【品牌_英文品牌 产品线 品类 色号 规格】促销场景 英文品牌 品类 色号 规格 促销词 品类 品牌 系列 色号 规格 活动词还有极短标题、纯英文标题、中文品牌和英文品牌同时出现的情况。正因为格式不稳定,才不能用一套固定正则从头解到尾。
2.2 正则表达式能解决一部分问题
正则适合处理格式稳定的部分。例如规格通常是数字后面跟g、ml、mL等单位;色号经常以#开头;促销词往往包含省、券、礼、减等关键词。
import re COLOR_PATTERN = re.compile(r'#\s*([0-9A-Za-z .]+?)(?=[\s_】\]\)]|$)', re.IGNORECASE) SPEC_PATTERN = re.compile(r'(\d+(?:\.\d+)?)\s*(g|ml|mL|kg|片|支|个)', re.IGNORECASE)这两个正则适合做初筛。难点在于#0.7 Pearl 12g这一段,色号里的空格和英文单词容易和后面的规格混在一起。正则写得太宽会把12g吞进色号,写得太窄又会漏掉0.7 Pearl。
2.3 规则加词典的混合方案更稳妥
推荐的做法是分层解析:
- 先做文本清洗,统一全角半角、空格和大小写。
- 用词典匹配品牌,因为品牌数量可控,维护品牌表成本低。
- 用规则提取规格、色号、促销词,这些格式相对稳定。
- 去掉已匹配片段后,剩余文本做品类关键词匹配。
- 对剩余不可识别部分走兜底逻辑,保留到
raw_remain字段。
这种方案的可解释性强,出错了能知道是哪条规则导致的,也方便运营人员直接维护关键词表。
2.4 什么时候才需要考虑序列标注模型
当商品标题来源非常多、写法极不统一,并且需要持续发现新品类、新色号时,规则词典方案会显得维护成本高。这种时候可以考虑序列标注模型,比如 BERT 微调 NER。但前提是有足够多的已标注语料,否则模型效果不会比规则好多少。
我的建议是:先用规则词典方案跑通,把历史数据沉淀出标注样本,等样本量足够大再评估模型。没有必要一上来就引入模型训练链路。
3. 用 Python 实现一个商品标题解析器
这里的实现目标不是做一个生产级的分布式解析服务,而是构造一个最小可运行、可扩展的解析器。你可以在 Jupyter Notebook 里直接跑完,也可以把解析类封装后接入 FastAPI。
3.1 环境准备
建议使用 Python 3.10 或更高版本。依赖库如下:
pandas==2.0.3 jieba==0.42.1安装命令:
pip install -r requirements.txtpandas用于结果展示和数据验证,jieba用于剩余文本的分词和品类匹配。如果只是验证解析逻辑,不装pandas也能运行。
3.2 定义词典和规则
先定义品牌、品类、促销词和正则规则。这里用简化的词典示例:
import re BRANDS = { "TOM FORD": "TOM FORD", "TF": "TOM FORD", "汤姆·福特": "TOM FORD", "汤姆福特": "TOM FORD", "YSL": "YSL", "圣罗兰": "YSL", "LANCOME": "LANCOME", "兰蔻": "LANCOME", } CATEGORY_KEYWORDS = { "BB霜": "BB霜", "气垫粉底液": "气垫粉底液", "气垫BB": "气垫BB", "粉底液": "粉底液", "粉饼": "粉饼", "口红": "口红", "唇釉": "唇釉", } PROMO_KEYWORDS = ["省", "满减", "券", "礼盒", "礼物", "七夕", "情人节", "双十一", "618"]这里需要理解一个原则:词典不是越大越好,而是要和业务范围对齐。如果商品库只做彩妆,就不需要加入护肤品类;如果后续加入护肤商品,再扩展面霜、精华、眼霜等关键词。
3.3 编写解析器类
下面是一个最小可运行的解析器:
class TitleParser: def __init__(self): self.brand_map = BRANDS self.category_map = CATEGORY_KEYWORDS self.promo_keywords = PROMO_KEYWORDS self.color_pattern = re.compile( r'#\s*([0-9A-Za-z][0-9A-Za-z .]{0,30}?)(?=[\s_】\]\)]|$)', re.IGNORECASE ) self.spec_pattern = re.compile( r'(\d+(?:\.\d+)?)\s*(g|ml|mL|KG|kg)', re.IGNORECASE ) def normalize(self, text): text = text.strip() text = text.replace('【', '[').replace('】', ']') text = text.replace('(', '(').replace(')', ')') text = re.sub(r'\s+', ' ', text) return text def parse(self, raw_title): title = self.normalize(raw_title) result = { "raw_title": raw_title, "brand": None, "category": [], "color": None, "spec": None, "promo": [], "remain": title, } # 1. 品牌匹配 upper_title = title.upper() for brand_key, brand_std in self.brand_map.items(): if brand_key in upper_title: result["brand"] = brand_std result["remain"] = re.sub(re.escape(brand_key), '', result["remain"], flags=re.IGNORECASE) break # 2. 规格匹配 spec_match = self.spec_pattern.search(result["remain"]) if spec_match: result["spec"] = spec_match.group(0) result["remain"] = self.spec_pattern.sub('', result["remain"], count=1) # 3. 色号匹配 color_match = self.color_pattern.search(result["remain"]) if color_match: result["color"] = color_match.group(1).strip() result["remain"] = self.color_pattern.sub('', result["remain"], count=1) # 4. 促销词匹配 for promo in self.promo_keywords: if promo in result["remain"]: result["promo"].append(promo) result["remain"] = result["remain"].replace(promo, '') # 5. 品类匹配 for category_key, category_std in self.category_map.items(): if category_key in result["remain"]: result["category"].append(category_std) result["remain"] = result["remain"].replace(category_key, '') result["remain"] = re.sub(r'\s+', ' ', result["remain"]).strip() return result这个解析器有两个地方要特别注意:
第一,品牌匹配放在最前面,因为TOM FORD会出现在后半段方括号内,如果先用品类匹配,剩余文本里仍然会残留品牌词。
第二,规格匹配放在色号之前,否则#0.7 Pearl 12g里的12g容易被色号正则误吞。先用规格正则拿走12g,再看色号,剩余文本就从#0.7 Pearl开始,结果更干净。
3.4 运行解析器
构造测试样本:
parser = TitleParser() samples = [ "汤姆·福特BB霜 品类推荐【汤姆·福特BB霜_TOM FORD T气垫粉底液 #0.7 Pearl 12g】省231元七夕礼物", "TOM FORD T气垫粉底液 #07 Pearl 12g 情人节礼物", "YSL圣罗兰小金条口红 #21 2.2g 生日礼物", "兰蔻菁纯粉底液 #110 30ml 保湿遮瑕", ] for sample in samples: result = parser.parse(sample) print(result)运行后你会看到类似输出。由于词典和正则都写得比较保守,同一个色号在#0.7 Pearl和#07 Pearl这两种写法下可能会被识别成不同的字符串。这就是后面需要做字段标准化的原因。
3.5 字段标准化
色号标准化可以去掉多余空格、统一大小写,并建立一个色号别名表:
COLOR_ALIAS = { "0.7 Pearl": "0.7 Pearl", "07 Pearl": "0.7 Pearl", "0.7 PEARL": "0.7 Pearl", } def normalize_color(color): if color is None: return None key = color.strip() return COLOR_ALIAS.get(key, key)规格也可以统一成标准单位:
def normalize_spec(spec): if spec is None: return None spec = spec.lower().replace(" ", "") if spec.endswith("ml"): return spec if spec.endswith("g"): return spec return spec标准化的目标是让12g和12 g都变成同一形式,为后续聚合去重做准备。
4. 品类推荐标签是怎么生成和匹配的
标题解析出来之后,下一步通常是生成推荐标签,或者在已有商品池里找到相似商品。这里的核心不是某个花哨的模型,而是把字段变成可比较的标签向量。
4.1 从解析字段到标签
一个商品可以生成如下标签:
品牌:TOM FORD 品类:气垫粉底液 品类:BB霜 色号:0.7 Pearl 规格:12g 场景:七夕礼物标签体系可以分为四类:
- 属性标签:品牌、色号、规格。
- 类目标签:BB霜、气垫粉底液。
- 场景标签:七夕、情人节、生日。
- 价格标签:省、券、满减。
这些标签在推荐场景里各有作用。类目标签决定了召回范围,属性标签决定了同款匹配,场景标签决定了活动召回,价格标签用于价格敏感人群。
4.2 基于类目词加权匹配
最简单的推荐相似度可以建立在类目集合上:
def category_similarity(cat1, cat2): set1 = set(cat1) set2 = set(cat2) if not set1 or not set2: return 0.0 return len(set1 & set2) / len(set1 | set2)这种方式的缺点是只考虑品类,忽略了品牌和场景。可以给不同字段设定权重:
FIELD_WEIGHT = { "brand": 0.4, "category": 0.3, "color": 0.2, "spec": 0.1, "promo": 0.0, }4.3 用词集合权重生成推荐分数
下面是一个可运行的推荐匹配函数:
def similarity_score(parsed_a, parsed_b): score = 0.0 if parsed_a.get("brand") and parsed_a["brand"] == parsed_b.get("brand"): score += FIELD_WEIGHT["brand"] cat_a = set(parsed_a.get("category", [])) cat_b = set(parsed_b.get("category", [])) if cat_a and cat_b: inter = len(cat_a & cat_b) union = len(cat_a | cat_b) score += FIELD_WEIGHT["category"] * (inter / union if union else 0) color_a = parsed_a.get("color") color_b = parsed_b.get("color") if color_a and color_b and normalize_color(color_a) == normalize_color(color_b): score += FIELD_WEIGHT["color"] if parsed_a.get("spec") and parsed_a["spec"] == parsed_b.get("spec"): score += FIELD_WEIGHT["spec"] return score这里不建议把促销词加入相似度权重,因为省231元、七夕礼物这类词会频繁变化,对判断商品是否同款没有正向价值。
4.4 推荐结果样例
用前面的测试样本做两两匹配,会有这样的大致结果:
汤姆·福特BB霜和TOM FORD T气垫粉底液因为品牌相同、品类有重叠,会得到较高相似度。YSL圣罗兰小金条口红和兰蔻菁纯粉底液因为品牌、品类都不同,相似度接近 0。
这种结果符合预期。如果发现相似度始终偏高或偏低,需要检查词典覆盖和权重分配,而不是先调模型。
5. 运行验证与结果分析
解析器写完以后,不能只看一两条输出就觉得完成了。需要设计一组覆盖正常、边界和异常情况的测试用例,再统计准确率和召回率。
5.1 设计测试用例
测试用例要覆盖下面这些情况:
| 测试场景 | 示例标题 | 期望结果 |
|---|---|---|
| 中英文品牌同时出现 | 汤姆·福特BB霜 品类推荐【TOM FORD T气垫粉底液 #0.7 Pearl 12g】 | 品牌 TOM FORD,品类 BB霜+气垫粉底液,色号 0.7 Pearl,规格 12g |
| 色号带空格和英文 | LANCOME 粉底液 #110 30ml | 品牌 LANCOME,色号 110 |
| 规格和色号相邻 | TOM FORD T气垫粉底液 #0.7 Pearl 12g | 色号 0.7 Pearl,规格 12g |
| 促销词在中间 | 兰蔻粉底液 30ml 七夕礼物 保湿 | 规格 30ml,场景词 七夕 |
| 无品牌标题 | 气垫粉底液 #0.7 12g | 品牌 None,品类 气垫粉底液 |
没有品牌、没有规格的标题在真实数据里很常见,解析器要能容忍这些空字段,而不是报错。
5.2 查看解析输出
可以使用 pandas 汇总结果:
import pandas as pd parsed_list = [parser.parse(sample) for sample in samples] df = pd.DataFrame(parsed_list) print(df[["raw_title", "brand", "category", "color", "spec", "promo", "remain"]])输出表格后,重点看两个字段:
remain是否残留了本应被提取的关键词。category是否因为BB霜和气垫粉底液同时存在而出现两个品类,这是正常行为。
5.3 评估指标:准确率和召回率
如果要长期迭代解析规则,建议建立一份人工标注的测试集,并计算字段级别的准确率和召回率:
准确率 = 正确提取的字段数 / 解析器提取的字段总数 召回率 = 正确提取的字段数 / 标注中应提取的字段总数 F1 = 2 * 准确率 * 召回率 / (准确率 + 召回率)字段可以按品牌、品类、色号、规格分开统计。这样能快速定位是哪个字段拖低了整体效果。
5.4 解析失败时先观察哪些现象
解析结果偏离预期时,优先检查:
- 品牌词是否在词典里,大小写是否统一。
- 色号正则是否因为空格符号导致匹配过长。
- 规格正则是否把
2.2g拆成了2和2g。 - 剩余文本
remain里是否还残留明显关键词。
这些现象能直接指向规则问题,比盲目改正则更有效。
6. 常见问题和排查路径
规则解析最常见的并不是代码复杂,而是规则之间互相干扰。下面列出几个高频问题以及排查方式。
6.1 品牌识别错误:词典没覆盖或大小写不统一
现象:Tom Ford被识别成功,但TOM FORD因为判断逻辑使用in且没有大写转换,导致漏识别。
原因:解析器在品牌匹配时,词典可能是中文,标题是英文。
检查方式:打印upper_title和参与匹配的brand_key。
解决方案:先把标题转成大写,再执行品牌匹配;同时把品牌表统一为大写形式。
预防建议:品牌表单独维护,在新增品牌时同时维护中文名、英文名和缩写。
6.2 色号 #0.7 Pearl 被切碎
现象:色号输出只有0.7或只有Pearl。
原因:色号正则匹配到空格就停止,或者后面被12g干扰。
检查方式:输出正则匹配到的完整字符串,而不是只看最终字段。
解决方案:把色号正则可匹配字符范围调整为[0-9A-Za-z .],并设置最小长度,同时先提取规格再提取色号。
预防建议:建立色号样例库,把线上出现的色号样本定期加入单元测试。
6.3 规格 12g 和色号里的数字冲突
现象:#0.7 Pearl 12g被解析成色号0.7 Pearl 12g,规格为None。
原因:色号正则过于贪婪,把规格也吞掉了。
检查方式:查看remain在处理规格前和处理后的差异。
解决方案:调整执行顺序——先提取规格,再从剩余文本提取色号。同时色号正则使用非贪婪匹配,并约束规格单位g、ml前不能直接归属于色号。
预防建议:把规格和色号相邻的用例显式加入测试集。
6.4 促销词把品类遮蔽
现象:七夕礼物被识别成品类,或者礼盒被当成商品名称的一部分。
原因:品类匹配和促销词匹配的执行顺序不合理。
检查方式:观察remain中是否还残留七夕礼物。
解决方案:先做促销词提取,再做品类匹配,这样七夕礼物不会干扰品类判断。
预防建议:促销词表中加入常见节日词和价格文案词,并区分场景标签和类目标签。
6.5 中文分词和英文混排问题
现象:气垫粉底液被jieba切成气垫、粉底液,导致品类匹配失败。
原因:直接对原始文本分词,而不是先做关键词匹配。
检查方式:打印分词结果,观察是否有停顿。
解决方案:品类匹配采用完整关键词优先匹配,而不是先分词再匹配。
预防建议:保留一份完整品类词表,避免完全依赖分词结果。
可以把这些常见问题整理成排错表:
| 问题现象 | 常见原因 | 检查方式 | 处理方案 |
|---|---|---|---|
| 品牌漏识别 | 词表没覆盖或大小写不一致 | 打印匹配前标题 | 统一大写并维护品牌别名表 |
| 色号被切碎 | 正则匹配范围太窄 | 输出正则完整匹配 | 扩展字符范围并调整匹配顺序 |
| 规格被吞 | 色号正则贪婪 | 分步骤打印 remain | 先提取规格再提取色号 |
| 品类被促销词干扰 | 匹配顺序不对 | 查看剩余文本 | 促销词优先匹配 |
| 中文分词拆散品类 | 误用分词做品类匹配 | 打印 jieba 结果 | 用完整关键词表匹配 |
7. 从学习案例到生产落地的差异
上面的解析器是一个典型的验证型脚本。学习环境里,它能帮助你快速理解规则和词典的工作方式;但生产环境里还需要考虑数据接入、配置管理、监控、回滚等问题。
7.1 学习环境:管道式脚本足够
学习阶段只需要把脚本整理成函数和类,在 Notebook 里跑通即可。重点理解:
- 字段提取的顺序为什么重要。
- 正则非贪婪匹配和贪婪匹配的区别。
- 词典维护对解析效果的影响。
- 为什么要用
remain字段暴露中间结果。
不要在学习阶段过度设计,比如引入消息队列、分布式 NLP 服务,这些会增加大量调试成本。
7.2 生产环境:数据接入、版本管理、失败重试、监控
生产环境要把标题解析做成一个独立的解析服务或离线任务,需要补齐以下能力:
- 数据接入:从商品库或消息队列读取标题,保证断点续跑。
- 配置外置:品牌表、品类表、促销词表放在配置中心或数据库,不能写死在代码里。
- 版本管理:规则和词典更新要能回滚。
- 失败重试:解析失败时写入错误队列,而不是直接丢弃。
- 监控告警:解析成功率和字段覆盖率需要按小时统计。
- 日志链路:记录原始标题、解析参数、解析结果和中间状态,方便复盘。
如果使用定时离线任务,建议新增一张解析结果表:
CREATE TABLE product_parse_result ( product_id VARCHAR(64) PRIMARY KEY, raw_title TEXT, brand VARCHAR(64), category_json TEXT, color VARCHAR(128), spec VARCHAR(64), promo_json TEXT, remain TEXT, parse_version VARCHAR(32), created_at DATETIME, updated_at DATETIME );解析结果落表后,下游可以直接读取,不需要每次重复解析。
7.3 数据质量和维护机制
规则词典方案的核心工作量不在代码,而在数据维护。没有一套完善的词表更新机制,解析准确率会随着商品种类增加而下降。
推荐建立以下机制:
- 每周收集无法识别或解析失败的标题样本。
- 人工抽检
remain非空的数据,判断是新增品牌、新增品类还是规则冲突。 - 把新增词条写入配置表,并附带操作人、生效时间。
- 每次更新词典后跑一次回归测试集,确认准确率没有下降。
这里要注意,不要让运营人员直接修改线上代码里的词典。更好的是提供一个管理后台或接口,让词条变更走审批流程,避免误操作。
8. 最佳实践与扩展方向
规则加词典的标题解析方案,最大的优势是可控、可解释、易维护。在商品标题数量级不是特别大,且品牌和品类相对稳定的场景下,它比直接训练模型更实用。
8.1 可复用的落地清单
在把类似解析器接入业务之前,建议逐项检查:
- [ ] 标题清洗逻辑是否覆盖全角半角、空格、中括号、小括号。
- [ ] 品牌表是否包含中文名、英文名、缩写和常见别名。
- [ ] 品类匹配是否使用完整关键词优先策略。
- [ ] 规格提取是否在色号提取之前执行。
- [ ] 色号正则是否支持
#0.7 Pearl、#07、#110等常见格式。 - [ ] 促销词是否与品类词分开维护。
- [ ] 是否有
remain字段兜底保留未识别文本。 - [ ] 是否准备了含正常、边界、异常三类样本的回归测试集。
- [ ] 是否统计了品牌、品类、色号、规格的准确率和召回率。
- [ ] 生产环境是否允许词典在线更新并支持回滚。
8.2 对新手最有价值的练习
如果你刚开始接触这个方向,建议不要直接去写大量正则,而是先做一个手工标注小数据集。收集 100 条真实的商品标题,用 Excel 记录品牌、品类、色号、规格、促销词。这个动作会帮助你理解字段的多样性,也能让你在写规则时心里有数。
完成标注后,再用本文的TitleParser去解析,对比人工标注和机器输出。凡是机器漏掉的,就是需要补规则或补词典的地方。
8.3 进一步扩展方向
当数据量继续增大,规则词典开始维护吃力时,可以往两个方向扩展:
- 向量召回:把标准化后的品牌、品类、色号、规格拼接成文本,用向量模型做相似商品召回。
- 小样本模型:用历史解析结果作为弱标注数据,微调一个轻量级序列标注模型,专门处理非标准化标题。
但这两个方向都建立在稳定的基础数据之上。先把规则、词典、回归测试集做好,再引入模型,才能避免模型迭代过程中出现不可控的准确率波动。对本案例来说,把这些规则跑通,比追求一个“智能解析”更符合实际工程需要。