做京东商品评论情感分析的时候,很多人一上来就调LSTM、上BERT,结果数据还没洗干净,跑出来的准确率惨不忍睹。我接过不少这类项目,也和做毕业设计的同学聊过,发现真正拉开差距的往往不是模型多高级,而是数据处理那几步有没有做扎实。尤其是“数据去重复”这个环节,看着不起眼,实际影响非常大——一条好评被重复爬了十几次,模型训练时就把这条样本的权重放大了十几倍,情感分类自然会被带偏。这篇博客就围绕京东商品评论这个场景,把数据去重和文本预处理整个流程拆开讲透,再延伸到情感分析建模的选型和实操,最后聊聊这套流程怎么迁移到景区舆情分析这类项目里。
内容适合三类人:正在做NLP课程设计或毕业设计的同学,刚接触电商评论分析想搭一套完整流程的工程师,还有从数据角度想了解评论预处理的运营和分析师。我会把每一步为什么这么做、坑在哪里、代码怎么写都讲清楚,能直接照着抄作业。
1. 数据去重:不止是去掉重复行这么简单
1.1 评论数据里的重复是怎么产生的
京东商品评论数据不会乖乖躺在数据库里等你下载,多数场景下是爬虫抓的、或者平台导出的。这两种渠道都会带来大量重复数据。
爬虫重复最常见的原因有三个。第一,断点续爬时没有维护好去重队列,同一页被爬了多次。第二,翻页参数有问题,比如按时间排序时新评论插入导致分页错位,后面的页面会反复包含前面的评论。第三,接口超时后重试,第一次其实提交成功了,但客户端认为失败又请求了一次,于是同一条评论被保存了两遍。平台导出也会重复,比如导出了全量数据后又增量导出,或者同一个SKU对应多个父商品ID,子商品页面上的评论互相重叠。
还有一种更隐蔽的重复——近似重复。同一条评论内容基本一样,只是中间多了一个空格、一个表情符号,或者“京东”和“jd”这种大小写差异。这些肉眼看起来是重复的,但字符串比较时hash值完全不同,普通去重根本发现不了。
1.2 去重的层次:精确去重、语义去重与业务去重
去重不能一把梭,要先分清楚你要处理的是哪一层重复。
第一层是精确去重。最简单有效的方案是先对文本做统一标准化,再计算哈希值,用哈希集合去重。标准化至少包括:把全角字符转半角,英文字母统一小写,删除所有空白字符(包括换行和Tab),删除HTML标签。做完这些标准化后,再来计算MD5或者SHA1,重复数据的哈希值就能对上了。
import hashlib import re def normalize_text(text): # 全角转半角 text = text.replace(',', ',').replace('。', '.').replace('!', '!') text = text.replace('?', '?').replace('“', '"').replace('”', '"') # 转小写 text = text.lower() # 去掉HTML标签 text = re.sub(r'<[^>]+>', '', text) # 去掉所有空白字符 text = re.sub(r'\s+', '', text) return text.strip() def md5_dedup(df, text_col): seen = set() keep_index = [] for idx, text in enumerate(df[text_col]): norm = normalize_text(str(text)) h = hashlib.md5(norm.encode('utf-8')).hexdigest() if h not in seen: seen.add(h) keep_index.append(idx) return df.iloc[keep_index]第二层是近似去重。有些重复内容标准化后仍然不完全相同,比如“这个商品非常好用”和“这个商品非常 好用”中间多了空格,标准化能解决;但“很好”和“挺好的”这种语义近似、字面不同的情况,就需要SimHash或MinHash这类指纹算法来判断。SimHash的思路是把文本转成64位的指纹,两个指纹的汉明距离小于等于3,就认为是近似重复。
第三层是业务去重。电商评论里有很多“同一用户、同一商品、相似时间、高度相似内容”的评论,这种不只是数据重复,还可能是刷单或恶意评论。对这种数据,我会结合user_id、item_id、评论内容三列做联合判断,比如同一用户对同一商品在24小时内发布了多条内容高度相似的评论,就只保留其中一条。业务去重没有标准答案,核心是要想清楚你的分析目标是什么——如果是做情感分布统计,那么一个用户重复刷的好评会严重高估正向比例,必须处理掉。
1.3 去重顺序与注意事项
很多人习惯先做清洗再去做重,其实先做粗去重效率更高。先按原始文本的MD5去一遍,数据量能降下来一大半,后面做标准化、分词这些耗时操作就轻松很多。然后再做标准化后的去重,最后根据业务规则去重。这个顺序不绝对,但我实测下来能省不少内存和时间。
去重时保留哪一条也有讲究。如果评论带时间字段,一般保留第一条或时间最早的那条,因为后续评论可能被修改过。如果评论带辅助信息,比如追加评论、点赞数、回复数,那么要保留信息最全的一条。还有些场景需要保留评分和评论的关联,这时候不能只看文本重复就删,还要校验评分字段是否一致。我之前就遇到过文本完全一样但评分一个是5星一个是1星的情况,这种属于恶意评论或系统异常,需要单独打标而不是简单去重。
2. 文本预处理:情感分析之前的“洗菜”环节
2.1 文本清洗:正则、去HTML、去广告
京东评论的原始文本里噪声种类很多。爬虫导出的评论经常带着HTML标签,比如<span class="color-goods">这种;有些评论里嵌入了“此用户未填写评价内容”的占位符;还有那种“商品不错,就是快递慢,下次还会再来,推荐购买【京东自营】”里带品牌后缀的推广语。
清洗时我会按顺序做这几件事:先去掉HTML标签,再去掉URL、邮箱、电话号码,然后处理特殊符号和表情,最后清理无意义的空白字符。表情符号这里有个坑:中文情感分析里表情是强特征,比如“好评”和“好评😄”情感强度完全不同,直接删掉会损失信息。更好的做法是把表情符号映射成文字标签,比如[微笑]、[生气],或者保留为单独特征喂给模型。
def clean_comment(text): text = re.sub(r'<[^>]+>', '', str(text)) text = re.sub(r'https?://\S+|www\.\S+', '', text) text = re.sub(r'\d{11}', '', text) # 手机号 text = re.sub(r'[〔〕【】《》]', ' ', text) text = re.sub(r'[ \t]+', ' ', text).strip() return text清洗完要做一步“空值判断”。很多评论清洗后只剩一个空字符串,或者只剩下“此用户未填写评价内容”这类占位符,这些都要统一填充为缺失值或直接删除。不删的话,后面分词得到空列表,建模时会造成维度混乱。
2.2 中文分词与停用词
中文文本没有空格分词,所以要用jieba这类工具。京东商品评论有两个特点:一是商品名和品牌词非常多,比如“iPhone 15 Pro Max”、“美的”、“海尔”,默认词库切得稀碎;二是评价领域的口语化表达特殊,比如“性价比高”、“物流快”、“客服态度好”。
针对这两个特点,我会准备自定义词典。把商品品类词、品牌词、评价术语加进去,让jieba能正确识别。词典格式很简单,一行一个词,还可以指定词频和词性:
iPhone 15 Pro Max 10 n 性价比高 5 nz 物流快 3 a 客服态度 3 n 京东超市 10 n停用词表我常用哈工大停用词表和百度停用词表,再叠加自己整理的电商领域停用词。注意,停用词不能盲目删,尤其是否定词和转折词。“不”“没”“别”这类词删了,“不好”会变成“好”,“不喜欢”会变成“喜欢”,情感极性直接反转。正确做法是不要把否定词放进停用词表,而是在分词后做否定词处理,比如把“不 喜欢”合并成“不喜欢”再作为整体特征。
2.3 拼音、谐音与网络用语的处理
这部分是京东评论预处理里最花时间的环节,也是最容易出彩的地方。电商评论里“绝绝子”、“yyds”、“666”、“辣鸡”、“集美们”这些词的字面意思和真实情感完全不一致。如果让模型直接学,小样本下很难学对。
我的做法是建立一个领域情感映射表,把这些网络用语映射到规范表达或者直接赋予情感倾向。比如“yyds”映射为“永远的神”或直接标记正向,“辣鸡”映射为“垃圾”标负向,“666”根据上下文可能是正向也可能中性,需要人工判断。映射表可以手工维护,也可以从语料里用词频和共现信息挖掘候选词再人工筛选。这块工作很琐碎,但对最终效果提升巨大。
emo_slang_dict = { 'yyds': '永远的神', '绝绝子': '特别好', '辣鸡': '垃圾', '集美们': '姐妹们', '666': '厉害' } def replace_slang(text): for k, v in emo_slang_dict.items(): text = text.replace(k, v) return text3. 情感分析模型怎么选:从词典到LSTM的实战对比
3.1 为什么先做规则基线
拿到干净数据后,不要急着训练深度学习模型。先搭一个基于情感词典的规则模型,把它当作基线,能帮你快速判断数据质量,也能给后面的复杂模型提供对比参考。
规则模型的核心是情感词典,最常用的是BosonNLP情感词典,它每个词都带情感分值,正负向都有,比较适合电商场景。再叠加HowNet词典和自建的电商领域情感词表,比如“质量好”+2、“发货慢”-1、“售后差”-2。计算方式就是把一条评论分词后,把命中的情感词分值累加,得到情感得分。
import jieba from collections import defaultdict sentiment_dict = defaultdict(float) # 加载词典 # with open('boson_emotion.txt', encoding='utf-8') as f: # for line in f: # word, score = line.strip().split() # sentiment_dict[word] = float(score) def rule_predict(text): words = jieba.lcut(text) score = sum(sentiment_dict.get(w, 0) for w in words) return 1 if score > 0 else (0 if score < 0 else -1)规则模型能跑通之后,先人工抽100条看结果。如果规则模型的错误集中在某类表达上,比如反语、程度副词、转折句,那就说明预处理和词典还有优化空间,而不是急着调模型。这一点很多人会忽略,上来就调LSTM,结果模型学到的噪声比信号还多。
3.2 LSTM中文文本情感分析的实战配置
数据量不大(几千到几万条)的情况下,LSTM是一个性价比非常高的选择。相比BERT这类大模型,LSTM在CPU机器上也能训练,而且参数可控,适合做毕业设计和工程落地。
我的标准流程是:分词 → 转成序列 → Embedding层 → BiLSTM层 → Attention池化 → 全连接输出。分词后要限制序列长度,京东评论一般不超过100个字,pad到50或80就够。这里有个经验值:先统计你样本的评论长度分布,取95%分位数作为max_len,而不是拍脑袋定一个数。
from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, LSTM, Dense, Bidirectional, Dropout model = Sequential([ Embedding(input_dim=vocab_size, output_dim=128, input_length=max_len), Bidirectional(LSTM(64, return_sequences=True, dropout=0.2)), Bidirectional(LSTM(32, return_sequences=False, dropout=0.2)), Dense(64, activation='relu'), Dropout(0.3), Dense(3, activation='softmax') # 负/中/正 ]) model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])这里的关键参数有三个。一是embedding维度,小语料128就够,不用太大。二是LSTM单元数,64到128之间一般够用,再大容易过拟合。三是dropout,至少0.2,否则训练集准确率上去了验证集一塌糊涂。另外,类别要是做二分类(正向/负向),最后一层激活函数换sigmoid,loss换binary_crossentropy。
3.3 数据不平衡处理与评估
商品评论的情感分布天然不平衡,好评占多数。如果你的目标是把“差评”识别出来,而差评只占10%,那么模型即使全部预测好评准确率也有90%,看起来不错,实际上一点用没有。
处理不平衡有三个常用手段。一是欠采样,从多数类里随机抽一部分,适合数据量大的场景。二是过采样,用SMOTE对少数类生成合成样本,但文本数据直接SMOTE容易产生不自然的句子,我更推荐对文本向量化之后再用SMOTE,或者干脆用类权重。三是修改loss函数的class_weight,告诉模型对少数类更敏感。
class_weight = {0: 1.0, 1: 1.0, 2: 3.0} # 差评权重加大 model.fit(X_train, y_train, epochs=10, batch_size=64, validation_data=(X_val, y_val), class_weight=class_weight)评估指标一定要看F1,不能只看准确率。二分类场景下,我习惯同时打印精确率、召回率和F1,并且按分类维度看混淆矩阵。差评的召回率低,就说明大量真正差评被漏掉了,要增加差评权重或补充差评语料;精确率低,则说明模型把很多中好评误判成了差评,要看看是不是预处理里否定词处理的锅。
4. 从京东评论到景区舆情:这套流程还能怎么复用
4.1 场景迁移:商品评论与景区舆情的异同
很多同学看到“基于Python的景区舆情情感分析系统设计与实现——以云南热门景区为例”这类题目会觉得陌生,其实整套流程和京东评论分析高度相似。都是文本数据 → 去重 → 清洗 → 分词 → 情感分类,差别主要在数据源和语义空间。
数据源变了,噪声类型就变了。景区评论主要来自微博、大众点评、携程和马蜂窝,微博文本带@用户和话题标签,大众点评的评论有LV等级和商家回复,这些都要在清洗阶段处理。另外,景区评论里大量出现地名、景点名、交通方式,比如“大理”、“玉龙雪山”、“拼车”、“高反”,自定义词典要相应调整。
语义空间也大不相同。商品评论里的“大”可能指尺寸大,景区评论里的“大”可能指景区面积大,也可能是“性价比不高”的省略说法。情感词表要重新拉一遍,像“人多”、“排队”、“风景好”、“出片”这些景区特有表达都要补充进词典。
4.2 一个完整系统的模块划分与实现思路
如果要做成一个“系统设计”,我建议按五个模块拆:数据采集模块、数据预处理模块、情感分析模块、可视化报表模块、管理后台模块。数据采集可以写爬虫或者接API,预处理模块把去重、清洗、分词封装成独立函数,情感分析模块提供规则模型和LSTM模型两种选择,可视化用Flask+ECharts展示舆情趋势、情感占比、热门话题词云。
系统架构上有一点要提醒:预处理模块必须设计成可以重跑的。因为舆情数据是持续更新的,每次抓完新数据就要对全量数据重新预处理,如果预处理逻辑分散在不同脚本里,后面维护就是一场灾难。我会把预处理流水线写成类似Spark的transform链,每个环节一个函数,输入输出都是DataFrame,方便串起来也方便单独调试。
def preprocess_pipeline(df): df = df.pipe(dedup_exact) df = df.pipe(dedup_business) df = df.pipe(clean_text) df = df.pipe(replace_slang) df = df.pipe(tokenize_and_filter) return df4.3 毕业设计与工程落地的差异化建议
这个流程既可以做毕业设计,也可以做小规模工程应用,两者的侧重点完全不同。
毕业设计更看重分析链路完整性和对比实验。建议先做基于词典的情感分析,再做基于LSTM的模型,最后对比两者在不同评价维度上的表现,这样论文里能讲出故事。如果能把“数据去重”单独作为一个研究点,分析不同程度去重对情感分类准确率的影响,那就有创新点了,也比单纯堆模型高级得多。
工程落地则更看重稳定性和可解释性。线上跑情感分析模型时,你不可能每来一条评论都重新跑一次预处理全流程,要把词表、映射表、模型参数固化下来,做成离线更新加在线预测的模式。还有一点,工程场景里规则模型仍然有它的位置,比如“一键投诉”这种高敏感评论,宁可查得严一点,也不能漏掉。
5. 实际操作中踩过的坑与排查技巧
5.1 去重时踩过的坑
第一个坑是全角半角符号导致hash不一致。同样是逗号,中文评论里可能是“,”也可能是“,”,标准化时忘了处理的话,两条内容相同的评论hash值完全不同,精确去重直接失效。排查方法很简单,去重之后随机抽100条重复率比较高的评论文本,肉眼看看是不是有很多“看起来一样但没被去掉”的数据。
第二个坑是URL参数不同导致的近似重复。京东商品链接后面带着一堆跟踪参数,评论里有人发链接时,你会看到同一链接被爬下来版本很多。处理方式是,在清洗阶段把URL统一替换为[url]占位符,这样去重时不会被URL细节干扰。
第三个坑跟业务相关:文本相同但情感标签不同。有些促销评论比如“很好,但是快递慢”,会被系统拆成两条,一条评分高一条评分低。这种数据去重时不能光看文本,要联合其它字段判断。我踩过这个坑之后,去重逻辑里加了“同一文本,评分不同则不合并”的规则。
5.2 预处理时踩过的坑
分词结果里出现空字符串和纯空格,这是最常见的bug。原因通常是正则清洗时把两个汉字之间的空格删掉了,但句首句尾的空格没删干净,或者原文本里就有大量换行。解决方法是分词后统一过滤:[w for w in jieba.lcut(text) if w.strip()],但要注意别把“不”这种单字词滤掉,所以过滤条件要写清楚。
停用词删除时误删“不”也是个高频坑。之前我用一份网上常见的停用词表,里面居然有“不”这个字。结果“不好”变成“好”,“不太行”变成“太行”,情感完全反了。后来我审查停用词表时,凡是可能改变情感极性的词一律排除。
编码问题在中文NLP里永远存在。京东导出数据常见GBK编码,但爬虫抓到的可能是UTF-8。读取DataFrame时如果没指定编码,一堆乱码会让正则清洗完全失效。我的建议是,所有数据入库前统一转成UTF-8,并且写一个编码检测函数自动处理。
5.3 情感分析时踩过的坑
短文本过拟合是我遇到最多的问题。京东评论平均长度不到30个字,LSTM很容易把训练集背下来,验证集却一塌糊涂。解决方法是增强数据,简单有效的是回译增强,把中文评论翻译成英文再翻译回来,能产生大量语义一致的变体。另一个方法是在Embedding层做dropout,而不是只在全连接层做。
还有数据泄漏这个坑,和去重直接相关。如果训练集和验证集是从同一份数据里随机切分的,但没有先做全局去重,那么一条评论可能在训练集和验证集里各出现一次,模型评估结果虚高。正确做法是先全量去重,再做训练验证切分。这个点我在帮人看代码时发现很多人都会犯,而且不容易察觉,因为准确率看起来很高,但上线后效果大幅缩水。
写在最后的几个实操心得
回头再看整个京东商品评论情感分析流程,我最大的感受是:数据去重和文本预处理不是“前戏”,而是决定项目成败的主战场。我自己刚开始做的时候,拿到数据就急着分词、跑模型,结果模型效果差也不知道问题出在哪。后来养成一个习惯,每次建模之前先把去重率、清洗前后样本量变化、分词后的词频分布这几项统计打印出来,一目了然。
还有一个小技巧分享给大家:在预处理阶段就把所有“中间产物”保存下来。比如去重后的数据保存一份,清洗后的保存一份,分词后的再保存一份。这样做的好处有两个——复现问题方便,写报告和论文也有素材。很多人跑完整个流程模型效果不好,想排查都不知道从哪里下手,其实问题往往就出在中间某一步。
最后,无论是做商品评论还是景区舆情,核心思路是通用的:先理解数据从哪来,想清楚哪些噪声会伤害你的分析目标,再用合适的工具把数据整理干净,最后才轮到模型登场。这个过程没有捷径,但每一步都有方法可循,希望这篇内容能帮你少踩几个坑。