ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

电商商品标题结构化解析:从正则到NER的实践指南

电商商品标题结构化解析:从正则到NER的实践指南 在电商数据分析和供应链系统开发中商品标题的解析一直是一个容易被低估、但实际非常磨人的问题。比如你手上拿到一条“喜临门 漾plus 床垫 1.8m2.0m 26CM”这样的原始标题看起来信息很完整但要让计算机自动把它拆成“品牌喜临门、系列漾plus、品类床垫、尺寸1.8m2.0m、厚度26CM”这样的结构化字段却没有想象中那么简单。标题里的“漾plus”到底是系列名还是型号“1.8m*2.0m”和“26CM”分别对应哪个属性如果数据量只有几十条人工复制粘贴还能接受一旦面临上万条 SKU 数据必须走自动化解析。本文就从这条床垫标题出发聊一聊商品标题结构化解析的完整思路。我会先讲清楚标题解析到底难在哪里然后给出三种可落地的解析方案基于正则表达式的规则解析、基于分词与词表匹配的字段抽取、基于序列标注的 NER 识别。每种方案都会配上可运行的 Python 代码并给出运行结果和排查思路。如果你正在做电商数据清洗、商品库建设、搜索推荐系统的商品理解模块这篇文章应该能帮你省下不少踩坑时间。1. 商品标题解析业务价值与技术难点先下一个判断商品标题解析不是一个“写个正则就完事”的小工具它是电商数据中台里非常基础但又牵一发动全身的环节。搜索、推荐、定价、库存管理、竞品分析几乎每个下游系统都需要结构化后的商品属性。比如搜索“喜临门 床垫”和搜索“喜临门 漾plus 床垫 1.8m”的用户意图完全不同前者是品牌品类后者可能还带有明确的系列偏好和尺寸偏好。如果标题没有被正确解析搜索召回和排序都会受影响。从技术角度看中文商品标题解析有三个天然难点。第一标题是非严格语法的短文本。用户看到的是“喜临门 漾plus 床垫 1.8m*2.0m 26CM”但它不是一句完整的话而是品牌词、系列词、品类词、规格词的线性拼接。不同商家在编辑标题时的顺序不统一有的把尺寸放前面有的把厚度放前面有的还混入“正品”“包邮”“特价”等营销词。第二属性值存在歧义。这里的“26CM”虽然是数字单位看起来很好识别但如果没有上下文它可能是床垫厚度也可能是床头高度或床箱高度。同理“1.8m*2.0m”是长乘宽还是宽乘长从规格上看并不影响最终面积但如果要与商品库中的字段一一映射就必须定义清楚。第三品牌和系列之间没有显式分隔符。“喜临门”和“漾plus”之间没有任何标点如果缺少品牌词表或系列词表计算机很难自动判断边界。更麻烦的是品牌词本身也可能作为普通词出现在标题的其他位置。所以商品标题解析本质上是一个“在高度灵活的短文本中做字段级信息抽取”的任务。它不能只靠单一方案解决而需要结合规则、词表和模型形成一个分层处理的流水线。2. 核心概念SKU、属性字段与实体抽取在进行代码实战之前先把几个高频概念讲清楚。第一个是SKUStock Keeping Unit即库存保有单位。在电商系统中每个 SKU 对应一个具体的、可下单的商品规格组合。比如“喜临门 漾plus 床垫 1.8m2.0m 26CM”和“喜临门 漾plus 床垫 1.5m2.0m 26CM”就是两个不同的 SKU它们的尺寸属性不同。第二个是属性字段Attribute。商品标题解析的目标就是把非结构化的标题字符串映射到一组预定义的属性字段上。常见字段包括字段名示例值说明品牌 brand喜临门商品所属品牌系列 series漾plus商品系列或型号名称品类 category床垫商品所属类目尺寸 size1.8m*2.0m床垫长宽规格厚度 thickness26CM床垫厚度字段的定义取决于业务需要。如果平台主卖乳胶枕可能还需要“材质”“填充物”“密度”等字段。因此在开始写解析器之前先和运营、商品团队确认字段清单比直接写代码重要得多。第三个是实体抽取Named Entity Recognition, NER。这原本是自然语言处理中的任务目的是从文本中识别出具有特定意义的实体比如人名、地名、机构名。放到商品标题场景中实体就是品牌、品类、尺寸、颜色等商品属性。它和通用 NER 的差别在于领域强相关标题里很少出现复杂语法但品牌名和系列名可能是生造词比如“漾plus”词典和正则都容易失手这时就需要从数据中学习实体边界。理解这三个概念之后再看标题解析方案就会清晰很多规则方案适合识别数值和单位词表方案适合识别品牌和品类序列标注方案适合解决生造词和边界切分问题。3. 环境准备与数据约定本文代码使用 Python 3 编写主要依赖 re 模块和 jieba 分词库。如果你没有装 jieba可以先执行pip install jieba建议使用 Python 3.8 及以上版本操作系统不限。版本细节以你本机环境为准本文演示的是通用思路。我们用一个最小数据集来演示文件路径为titles.csv内容如下id,title 1001,喜临门 漾plus 床垫 1.8m*2.0m 26CM 1002,喜临门 漾plus 床垫 1.5m*2.0m 22CM 1003,喜临门 深睡系列 床垫 1.8m*2.0m 24CM 1004,喜临门 乳胶床垫 1.8m*2.0m 26CM 1005,雅兰 床垫 1.8m*2.0m 20CM 硬睡感这个数据虽然很小但已经覆盖了几种典型情况带系列词、不带系列词、带营销语、品牌不同。需要提醒的是真实项目的标题数据通常更脏存在全角半角混用、多余空格、促销语堆叠、甚至错别字。建议在解析前先做一次统一清洗。接下来我们把标题解析流程拆成三步先清洗再识别字段最后输出结构化结果。每一步都有对应的代码模块便于后续按需调整。4. 基于正则表达式的尺寸与厚度解析先从一个最容易见效的部分开始尺寸和厚度。这类属性的特征是包含数字、单位和分隔符比较符合规则识别的范畴。床垫标题中的尺寸常见写法包括1.8m*2.0m1800mm*2000mm1.8*2.0米180*200cm厚度常见写法包括26CM26cm26公分加厚26cm观察规律后可以写出一个正则解析函数。这里要强调的是在同一套解析代码中优先识别尺寸再识别厚度避免“26CM”中的“CM”被尺寸模式误吞。import re def parse_size_thickness(title): 从商品标题中解析尺寸和厚度。 返回 (size, thickness) 元组未识别返回 None。 # 归一化统一剔除空格方便匹配 text title.replace( , ).replace(\u3000, ) size None thickness None # 尺寸模式0.9m、1.8m、1.5m、1.8*2.0米、1800mm*2000mm size_patterns [ r(\d\.?\d*m\s*\*\s*\d\.?\d*m), # 1.8m*2.0m r(\d\.?\d*\s*\*\s*\d\.?\d*\s*米), # 1.8*2.0米 r(\d{3,4}mm\s*\*\s*\d{3,4}mm), # 1800mm*2000mm r(\d{2,3}cm\s*\*\s*\d{2,3}cm), # 180cm*200cm ] for pattern in size_patterns: m re.search(pattern, text) if m: size m.group(1) break # 厚度模式26CM、26cm、26公分、加厚26cm # 需要先去掉已经识别出的尺寸部分避免误匹配 remaining_text text if size: remaining_text text.replace(size, ) thickness_patterns [ r(\d\.?\d*\s*[c][m]), # 26CM r(\d\.?\d*\s*公分), r加厚\s*(\d\.?\d*\s*[c][m]), ] for pattern in thickness_patterns: m re.search(pattern, remaining_text) if m: thickness m.group(1) break return size, thickness if __name__ __main__: titles [ 喜临门 漾plus 床垫 1.8m*2.0m 26CM, 喜临门 漾plus 床垫 1.5m*2.0m 22CM, 喜临门 深睡系列 床垫 1.8m*2.0m 24CM, 喜临门 乳胶床垫 1.8m*2.0m 26CM, 雅兰 床垫 1.8m*2.0m 20CM 硬睡感, ] for t in titles: size, thickness parse_size_thickness(t) print(f{t} size{size}, thickness{thickness})运行这段代码预期输出如下喜临门 漾plus 床垫 1.8m*2.0m 26CM size1.8m*2.0m, thickness26CM 喜临门 漾plus 床垫 1.5m*2.0m 22CM size1.5m*2.0m, thickness22CM 喜临门 深睡系列 床垫 1.8m*2.0m 24CM size1.8m*2.0m, thickness24CM 喜临门 乳胶床垫 1.8m*2.0m 26CM size1.8m*2.0m, thickness26CM 雅兰 床垫 1.8m*2.0m 20CM 硬睡感 size1.8m*2.0m, thickness20CM这里真正容易踩坑的地方在于厚度的单位“CM”和尺寸中的“m”之间有包含关系。如果先写一个通用的“数字字母”正则很容易把“1.8m”里的“m”当成厚度单位来处理。所以我先做了一次尺寸识别并把已识别出的尺寸字符串从原文本中移除再做厚度识别这样能显著降低误匹配概率。正则方案的优势是零成本、可解释。它的局限也很明显一旦标题中出现“席梦思1.8m加厚26cm乳胶床垫”这种语序或者商家写成“1.8米乘2米”正则就需要不断补充新规则规则之间还可能互相冲突。5. 基于 jieba 分词与词表匹配的品牌系列识别尺寸和厚度解决后下一个任务是识别品牌和系列。这部分不再适合继续堆正则因为“喜临门”和“漾plus”在字符模式上没有明显规律更像是一个查询词表的问题。思路是提前准备品牌词表、品类词表和系列词表。实际项目中品牌词表可以从商家入驻资料中获取品类词表可以参考平台类目树系列词表则需要从历史商品数据中挖掘。import jieba BRAND_WORDS {喜临门, 雅兰, 慕思, 梦百合, 穗宝} CATEGORY_WORDS {床垫, 乳胶床垫, 床, 席梦思} SERIES_WORDS {漾plus, 漾, 深睡系列, 深睡} def load_title_words(title): 使用 jieba 对标题分词同时保留全文本用于词表匹配。 words jieba.lcut(title) return [w.strip() for w in words if w.strip()]分词结果是后续匹配的基础。但要注意jieba.lcut(喜临门 漾plus 床垫 1.8m*2.0m 26CM)默认词典可能把“喜临门”切成一个词却把“漾plus”切成“漾”和“plus”两个 token。这时单纯遍历分词结果去匹配词表会漏掉“漾plus”。一个更稳的做法是先做“最大正向匹配”式的词表扫描或者先直接把词表中的长词在标题中做子串匹配。def parse_brand_category_series(title): 基于词表匹配品牌、品类、系列。 优先级长词优先防止短词误判。 result {brand: None, category: None, series: None} def match_longest(title, vocab): matched None best_len 0 for word in vocab: if word in title and len(word) best_len: matched word best_len len(word) return matched result[brand] match_longest(title, BRAND_WORDS) result[category] match_longest(title, CATEGORY_WORDS) result[series] match_longest(title, SERIES_WORDS) return result从实现上看这个函数很简单但它的一个重要细节是“长词优先”。如果品牌词表中只有“喜临门”没有问题但如果品牌词表里既有“雅兰”又有“雅兰床垫”就必须优先匹配更长的词否则会提前截断品牌字段。在实际数据中系列词的来源往往是最难维护的。同一个系列的写法可能是“漾plus”也可能是“漾 PLUS”“漾Plus”“漾plus升级款”。工程上常用两步处理第一步做字符归一化把大写转小写、全角转半角、去掉空格第二步以归一化后的文本作为匹配主键同时保留原始文本用于输出。import unicodedata def normalize_title(title): 全角转半角、大写转小写、去掉空白字符。 text unicodedata.normalize(NFKC, title) text text.lower() text .join(text.split()) return text归一化之后的标题“喜临门漾plus床垫1.8m*2.0m26cm”更容易被词表精确命中。不过归一化也会带来一个副作用原始字段中的大写单位“26CM”被改成了“26cm”输出时如果需要保留原始样式就需要记录位置映射稍显复杂。对大多数下游系统来说属性值存的都是规范值所以统一小写和半角反而是合理的。我把词表方案和正则方案放在一起是为了说明一个分层原则能用规则解决的数值属性用规则能用词表解决的枚举属性用词表不要一上来就训练模型。6. 进阶方案基于序列标注的标题属性抽取当规则和词表覆盖不了所有场景时就要考虑序列标注方案。比如品牌词表中没有收录某个新兴品牌系列词表无法覆盖五花八门的型号写法这时基于 jieba 和正则的方案都会失效。而序列标注模型可以从标注数据中学习规律即使遇到没见过的词也能根据字符上下文推测边界。序列标注的基本做法是把标题看成一个字符序列为每个字符打一个标签。以“喜临门 漾plus 床垫 1.8m*2.0m 26CM”为例如果我们把字符标签定义为B-brand、I-brand、B-series、I-series、B-category、O等那么“喜”是 B-brand“临”是 I-brand“门”是 I-brand空格是 O“漾”是 B-series以此类推。这里给出一个简化版实现使用 sklearn 的 CRF 思路会涉及较多依赖为了演示方便我用一个纯 Python 的字符级规则统计方法来说明核心逻辑。实际生产项目建议使用 spaCy、Hugging Face Token Classification 或专门的 CRF 库比如sklearn-crfsuite。# 文件路径simple_sequence_tagging.py # 演示序列标注思想不依赖深度学习框架 def build_vocab_from_titles(titles): 从标题中统计字符频率用于构造简单的特征。 char_freq {} for title in titles: for ch in title: char_freq[ch] char_freq.get(ch, 0) 1 return char_freq def simple_rule_tagger(title, vocab): 一个极简的字符级打标器。 这里的规则只用于演示 - 数字和字母连续段 - SIZE - 品牌词表中的词 - BRAND 真实项目应替换为 CRF 或序列标注模型。 tags [] i 0 while i len(title): ch title[i] if ch.isdigit() or ch.isalpha(): j i while j len(title) and (title[j].isdigit() or title[j].isalpha() or title[j] in .*·): j 1 segment title[i:j] if * in segment or x in segment.lower(): tags.append((segment, SIZE)) else: tags.append((segment, ATTR)) i j elif ch : tags.append((ch, O)) i 1 else: tags.append((ch, O)) i 1 return tags序列标注虽然效果上限更高但它对数据标注质量的要求也很高。一个 5000 条标题的标注任务如果字段定义不清晰标注一致性会非常差。我见过不少团队花了两周标数据结果模型上线后发现很多错误来自标注阶段本身。所以在决定引入模型之前先问自己三个问题词表规模是否已经超过可维护范围标题的书写格式是否足够多样化下游系统需要字段级精确度还是允许模糊匹配如果回答都偏保守那还是先优化规则和词表更划算。7. 完整解析流程组合三种策略到这一步我们已经有了三个可用的模块尺寸厚度解析、品牌系列词表匹配、字符级标签初步识别。实际项目中很少只用其中一个更常见的做法是把它们串成一条流水线。下面给出一个完整的整合示例它按顺序执行清洗、尺寸厚度解析、品牌品类系列匹配、剩余文本分析。# 文件路径title_parser.py import re import unicodedata import jieba BRAND_WORDS {喜临门, 雅兰, 慕思, 梦百合, 穗宝} CATEGORY_WORDS {床垫, 乳胶床垫, 床, 席梦思} SERIES_WORDS {漾plus, 漾, 深睡系列, 深睡} def normalize_title(title): text unicodedata.normalize(NFKC, title) text text.lower() text .join(text.split()) return text def parse_size_thickness(title): text title.replace( , ).replace(\u3000, ) size None thickness None size_patterns [ r(\d\.?\d*m\s*\*\s*\d\.?\d*m), r(\d\.?\d*\s*\*\s*\d\.?\d*\s*米), r(\d{3,4}mm\s*\*\s*\d{3,4}mm), r(\d{2,3}cm\s*\*\s*\d{2,3}cm), ] for pattern in size_patterns: m re.search(pattern, text) if m: size m.group(1) break remaining_text text.replace(size, ) if size else text thickness_patterns [ r(\d\.?\d*\s*[c][m]), r(\d\.?\d*\s*公分), r加厚\s*(\d\.?\d*\s*[c][m]), ] for pattern in thickness_patterns: m re.search(pattern, remaining_text) if m: thickness m.group(1) break return size, thickness def match_longest(text, vocab): matched None best_len 0 for word in vocab: if word.lower() in text and len(word) best_len: matched word best_len len(word) return matched def parse_brand_category_series(title): normalized normalize_title(title) brand match_longest(normalized, {w.lower() for w in BRAND_WORDS}) category match_longest(normalized, {w.lower() for w in CATEGORY_WORDS}) series match_longest(normalized, {w.lower() for w in SERIES_WORDS}) return brand, category, series def parse_title(title): size, thickness parse_size_thickness(title) brand, category, series parse_brand_category_series(title) return { original_title: title, brand: brand, series: series, category: category, size: size, thickness: thickness, } if __name__ __main__: titles [ 喜临门 漾plus 床垫 1.8m*2.0m 26CM, 喜临门 漾plus 床垫 1.5m*2.0m 22CM, 喜临门 深睡系列 床垫 1.8m*2.0m 24CM, 喜临门 乳胶床垫 1.8m*2.0m 26CM, 雅兰 床垫 1.8m*2.0m 20CM 硬睡感, ] for t in titles: print(parse_title(t))运行这段代码输出类似{ original_title: 喜临门 漾plus 床垫 1.8m*2.0m 26CM, brand: 喜临门, series: 漾plus, category: 床垫, size: 1.8m*2.0m, thickness: 26CM }这个组合流程的优点是每一层都有独立的失败处理空间。如果尺寸正则没有命中输出中的 size 为 None下游可以触发人工审核。如果品牌词表有更新只需要修改BRAND_WORDS集合不需要动其他代码。8. 运行结果验证与准确率评估在小样本上跑通流程只是第一步真正决定解析器能否上线的是批量数据集上的准确率。这里给出一个简单的评估脚本假设你已经手工标注了一部分标题标注入口写在golden.csv中。# 文件路径evaluate.py import csv from title_parser import parse_title FIELD_NAMES [original_title, brand, series, category, size, thickness] def evaluate(golden_path): rows [] with open(golden_path, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: rows.append(row) total len(rows) correct_count 0 for row in rows: pred parse_title(row[original_title]) is_correct True for field in FIELD_NAMES[1:]: gold_value row.get(field, ).strip() pred_value (pred.get(field) or ).strip() if gold_value ! pred_value: is_correct False print(f[{field}] 标题: {row[original_title]} | gold{gold_value} | pred{pred_value}) if is_correct: correct_count 1 accuracy correct_count / total if total else 0 print(f样本总数: {total}, 完全正确: {correct_count}, 准确率: {accuracy:.2%}) if __name__ __main__: evaluate(golden.csv)从工程角度建议不只统计“整条标题全部字段正确”的指标还要按字段统计每个字段的精确率和召回率。比如尺寸字段的准确率可能达到 98%但厚度字段因为单位写法多样准确率可能只有 90%。如果只给一个总的准确率很难定位到底哪一段逻辑需要优化。统计结果只是第一步还要把预测错误的样本保存成 CSV定期人工复核。错误样本是最好的规则优化来源。比如看到多条错误都出在“乳胶床垫”被识别为品类而实际上平台类目树中“床垫”才是标准品类“乳胶”只是材质属性那就可以在品类词表之外增加材质词表把“乳胶”归入材质字段。9. 常见问题与排查思路在商品标题解析的开发过程中有几个高频问题几乎每个项目都会遇到列成一张排查表方便对照。问题现象可能原因排查方式解决方案尺寸字段匹配不到标题中含全角乘号、字母 x 或大写 M打印清洗后的文本观察是否被 normalize 处理在正则前统一做 NFKC 归一化和大小写转换厚度被尺寸正则误吞正则没有按“先尺寸后厚度”的顺序执行输出中间 remaining_text调整执行顺序识别尺寸后先剔除再匹配厚度品牌词表匹配不准品牌词表中有子串关系如“雅兰”与“雅兰床垫”打印 match_longest 返回结果和命中的词改为长词优先或使用 Aho-Corasick 多模式匹配系列词缺失商家对同一系列有多种写法检查归一化后文本是否包含系列词维护同义词表并把“漾PLUS”“漾 Plus”归一化为“漾plus”分词结果把完整词切开jieba 默认词典未收录领域词查看分词结果添加自定义词典jieba.add_word(漾plus)线上解析和本地解析结果不一致版本不同或依赖缺失对比两边的环境和依赖锁文件固定 Python 和依赖版本统一部署镜像这里要特别提醒的是“归一化后再匹配”的双刃剑效应。虽然全角转半角、大写转小写能让匹配更稳定但如果你在输出时直接使用归一化后的文本可能会把用户原始标题中的品牌字样变化丢掉导致下游展示层看起来和商家填的不一样。更稳妥的做法是只把归一化文本用于匹配判断而输出的属性值仍然从原始标题中切片。代码在文章里已经能跑通但如果你直接复制到真实环境可能会遇到unicodedata.normalize带来的个别字符不可见问题比如某些空格字符被合并或删除导致标题拼接异常。建议在清洗函数中保留一份原始标题便于问题回溯。10. 工程化最佳实践标题解析这个任务写原型很快真正上生产环境却有很多隐藏细节。下面几条是团队落地时最常见的经验。第一把词表和规则当配置管理不要硬编码在业务代码中。品牌词表、系列词表、正则模板都应该放到单独的配置文件或配置中心里。业务人员日常会新增品牌和系列如果每次都要改代码重新发版效率太低。建议用 JSON 或 YAML 维护词表解析服务启动时加载并支持热更新或者定时刷新。# 文件路径dict_config.yaml brand_words: - 喜临门 - 雅兰 - 慕思 - 梦百合 category_words: - 床垫 - 乳胶床垫 - 床 series_words: - 漾plus - 漾 - 深睡系列 - 深睡 size_patterns: - \\d\\.?\\d*m\\s*\\*\\s*\\d\\.?\\d*m - \\d\\.?\\d*\\s*\\*\\s*\\d\\.?\\d*\\s*米第二用配置项控制“严格模式”和“宽松模式”。在严格模式下所有字段必须全部解析成功否则标记为待人工审核在宽松模式下允许部分字段为空但下游系统要做好空值兼容。这两种模式分别适合清洗入库和在线查询。比如搜索系统只需要尺寸和品类严格模式会误杀很多标题但商品库建设时所有属性都必须补齐。第三异常数据要进入专门的“低置信队列”。不要试图用一个解析器把所有标题都解析成功这不现实。覆盖率做到 95% 已经很好剩下的 5% 让质检团队或者运营同事接手处理比写死规则追求 100% 更高效。第四从成本角度考虑如果标题数据量只有每天几千条用 Python 单机跑就够。如果每天有数百万条再考虑引入 Spark 或 Flink 做分布式批处理。大多数中小电商项目单机多线程完全够用过早引入流式计算只会增加运维复杂度。第五解析结果要埋点和监控。每一条标题的解析是否成功、解析耗时、命中了哪些字段都应该有日志记录。当某一天解析准确率突然下降通常是因为商家开始使用新的标题模板或者词表被误更新。有了监控问题会暴露得更早。11. 总结与后续学习方向回到最初那条“喜临门 漾plus 床垫 1.8m2.0m 26CM”现在你应该能清晰回答品牌是喜临门系列是漾plus品类是床垫尺寸是 1.8m2.0m厚度是 26CM。这背后的解析流程并不依赖某一种神奇算法而是规则、词表与模型三层策略的组合。如果你是第一次接触商品标题解析建议先用本文的正则和词表方案跑通一个最小原型再尝试用jieba自定义词典增强分词效果等积累了足够多的异常样本后再决定是否引入 CRF 或深度学习序列标注模型。不同阶段有不同成本收益比一上来就上大模型做信息抽取在标题字段这种业务强相关、数据量集中、可解释性要求高的任务中未必是最优解。接下来值得深入的方向有三个一是多模式字符串匹配的工程实现比如 Aho-Corasick 算法用于大规模词表下的高效匹配二是条件随机场在短文本序列标注中的应用理解它对字符边界预测的优势三是基于大语言模型的少样本抽取用 Prompt 方式在零标注条件下快速验证新字段的可行性。每个方向都能把标题解析这件事再向前推一层。如果你正在做电商中台或商品数据治理相关项目建议先把标题解析的结果做成一份可视化的质量报表让运营同事也能看到哪些字段经常缺失。许多复杂的数据问题都是从一条看起来普通的商品标题开始的。
返回列表