简介针对电商手机评论的文本挖掘入门项目面向希望掌握中文文本分析流程的数据学习者可完整覆盖数据预处理清洗、分词、去停用词、LDA主题模型提取特征词、情感极性判断与程度计算、回归模型预测销量排序四个环节。资源包共11个文件含3个Python脚本、4个CSV评论数据集原始评论文本、2个说明文档和2个编译缓存文件压缩包约4.1MB结构轻量便于快速上手。已有173人学习下载适合作为自然语言处理与机器学习综合应用的练习素材。通过运行代码可完整体验从原始评论到主题挖掘、情感量化再到销量预测的链路便于对照理解每一步的原理与实现细节并迁移到其他商品评论分析场景。代码注释完整目录结构清晰学习者可基于自带数据集直接运行调试。1. 手机评论的文本挖掘到底能挖出什么从「好评差评」到「销量排名」电商运营盯评论最常看的就是好评率。但好评率 98% 的机型销量常常打不过好评率 92% 的机型因为消费者真正传播的不是那个百分比而是评论里反复出现的主题——屏幕、续航、手感、物流——以及这些主题上情绪的强弱。标题里这个压缩包就是把「手机评论的文本挖掘」串成一条能跑的初体验链路先用数据预处理把评论洗干净用 LDA 模型从语料里提取特征词再做情感极性判断和程度计算最后把文本特征喂给回归模型预测销量排序。适合电商或消费电子领域的数据分析师、想入门文本挖掘但不想只跑 demo 的工程师以及需要给运营一个可解释销量归因的产品同学。不用一开始就吃透全部原理照着这条链路跑一遍等特征和销量排名对得上再回头调参。2. 数据预处理把原始评论变成能喂给主题模型的干净语料解压 zip 之后先别急着装库先用一句话说清楚目标LDA 吃进去的是词袋情感计算吃进去的是词序列回归吃进去的是特征矩阵。三者都从同一份原始评论来所以预处理做得好不好直接决定后面 LDA 主题是否漂移、情感分数是否可信、回归特征是否有区分度。这一段就是整个链路的地基。2.1 解压后第一件事先看数据形态再定清洗规则拿到压缩包后我一般先把目录结构列出来看一遍。这类包通常会有三块东西评论明细评论内容、评分、评论时间、手机型号、销量或榜单数据、若干词典文件。字段名各家不一样假设你手头的评论表字段是 comment、score、model先读进来看看长什么样。import pandas as pd # 电商导出的评论数据表头常见为评论内容、评分、手机型号 df pd.read_csv(phone_comments.csv, encodingutf-8-sig) print(df.shape) print(df[[comment, score, model]].head(3).to_string())read_csv的encoding参数值得多说一句国内电商导出的 CSV 很多是 GBK 编码如果读出来乱码或直接报 UnicodeDecodeError就把编码换成 gbk。df.shape的用途是看样本量评论少于两三千条时后面 LDA 的主题数 K 要往小调否则每个主题里都是噪声head打印前几行是为了确认 comment 字段里有没有夹 HTML 标签、占位符、追评拼接这些脏数据。此时不要急着写清洗正则先看垃圾大多长什么样。常见情况是「此用户未填写评价内容」这类占位符、促销链接、以及「快递给力」这种跟手机本体无关的句子。占位符整行过滤链接用正则删掉物流这类词是否保留取决于你后面想不想要「物流」这个主题——想保留就把「快递」「物流」加进领域词典不想保留就丢进停用词表。2.2 清洗、分词与去停用词跑通最小预处理流水线我给的最小流水线是四步去 HTML 与链接、压缩重复字符、分词、去停用词。压缩重复字符这步很多人会漏但电商评论里「好好好好好」「卡卡卡」特别多不压缩的话 jieba 会把它们切成一堆无意义单字直接污染词袋。import re import jieba def clean_comment(text): text re.sub(r[^], , text) # 去掉 HTML 标签 text re.sub(rhttps?://\S, , text) # 去掉链接 text re.sub(r(\D)\1{2,}, r\1, text) # “好好好好”压缩成“好” return text.strip() df[clean] df[comment].astype(str).apply(clean_comment) stopwords set() with open(cn_stopwords.txt, encodingutf-8) as f: # 通用中文停用词表 stopwords set(line.strip() for line in f) def tokenize(text): words jieba.lcut(text) # 精确模式分词 keep [] for w in words: if w in stopwords or w in ( , \n): continue keep.append(w) return keep df[words] df[clean].apply(tokenize) df[words_join] df[words].apply( .join)这段代码里我刻意没有加len(w) 1这种过滤因为「不」「卡」「烫」「沉」都是单字却是后面情感判断和主题里的关键信号。去停用词时也注意不要把「不」「没」「很」「太」收进停用词表否则情感计算的原料就没了。jieba.lcut默认精确模式对「骁龙8Gen3」这类词条原始词典经常切错所以下一步要把领域词塞进用户词典。停用词表的来源不用纠结顺手找一份通用中文停用词表就行但一定要人工过一遍把「手机」「评论」「购买」这类电商高频泛词留下还是删掉取决于你想让 LDA 主题更聚焦于产品属性。泛词太多主题会变成「手机、一个、就是」这种废话集合。2.3 用户词典与否定词表两个小文件解决分词与情感基线手机评论里大量出现骁龙、天玑、曲面屏、快充、光学防抖默认词典会切成「骁龙/8」「曲面/屏」导致后面 LDA 里「骁龙」和「8」被当成两个词主题直接散掉。常见做法是准备一个领域词典文件逐行add_word。import jieba domain_words [ 骁龙8Gen3, 天玑9300, 曲面屏, 光学防抖, 屏下指纹, 无线充电, 灵动岛, 散热背夹 ] for w in domain_words: jieba.add_word(w, freq100000) # 给大频率强制优先切分 neg_words {不, 没, 无, 别, 莫, 未必, 不太, 不怎么} degree_words { 极其: 2.0, 非常: 1.8, 很: 1.5, 比较: 1.2, 稍微: 0.7, 有点: 0.6, 一般般: 0.5 } with open(domain_words.txt, w, encodingutf-8) as f: f.write(\n.join(domain_words)) with open(neg_words.txt, w, encodingutf-8) as f: f.write(\n.join(neg_words))freq参数是个容易翻车的地方给太小比如默认一万不一定能压过 jieba 的既有切分给到十万以上基本稳定。否定词表和程度词表单独存成文件是为了第 4 章情感模块直接复用不用回头改代码。注意这里把「不太」「不怎么」直接收进否定词表是因为它们的语义和单一「不」不一样后面情感计算里要单独处理不能简单乘负一就完事。分词这事多少有点玄学同样的「手感不错」不同型号评论里可能被切成「手感/不错」或「手/感/不错」。所以跑完分词后我习惯随机抽 20 条评论打印words_join出来肉眼检查一遍。分词结果稳定LDA 和情感计算才稳定这一步省不了。3. LDA 模型获取特征词从评论里自动捞出「大家在聊什么」数据预处理做完手头是干净的评论词序列。这个压缩包的第二个板块是用 LDA 模型获取特征词。这里要明确LDA 给的不是「哪些词单独重要」而是「哪些词经常一起出现」每个主题就是一坨特征词比如屏幕主题可能是「分辨率、曲面屏、亮度、绿边」续航主题可能是「续航、充电、掉电、发热」。这比简单数词频高出整整一个层级。3.1 为什么选 LDA主题模型和 TF-IDF 的边界TF-IDF 能告诉你「独特」的词但词之间是什么关系它说不出来。「曲面屏」和「绿边」各自 TF-IDF 都很高可你无法从 TF-IDF 知道它们其实在聊同一个问题——屏幕。LDA 把词聚成主题输出两个分布主题-词分布和文档-主题分布。前者就是我们说的特征词后者可以直接当作回归模型的一列特征这条评论有多少比例在聊屏幕、多少比例在聊续航。LDA 在短文本上有个著名问题单条手机评论往往只有十几个字主题信号非常稀疏直接训练会出现主题漂移同一个词在不同 run 里被分到完全不同的主题。行业里常见做法是拼伪文档把同一个手机型号、同一个时间窗口内的所有评论拼接成一篇长文档再训练。型号级别的 LDA 主题分布正好对得上后面回归模型的粒度。这个取舍很重要因为单条评论的文本太短靠 LDA 硬挖特征词基本是撞大运。3.2 词袋矩阵与 LDA 训练困惑度和一致性怎么选 K训练 LDA 我用 gensim流程是先把词序列转成词袋再建词典、过滤极端词、训练模型。过滤这一步别跳过低频词和高频泛词都会把主题搅浑。from gensim.corpora import Dictionary from gensim.models import LdaModel, CoherenceModel # 按型号拼伪文档降低短文本主题漂移 pseudo_docs df.groupby(model)[words].apply( lambda x: [w for doc in x for w in doc] ).tolist() dictionary Dictionary(pseudo_docs) # 低频词至少出现 5 次高频词出现超过 50% 语料就丢弃 dictionary.filter_extremes(no_below5, no_above0.5) corpus [dictionary.doc2bow(doc) for doc in pseudo_docs] def train_lda(k): model LdaModel(corpuscorpus, id2worddictionary, num_topicsk, passes10, random_state42) cm CoherenceModel(modelmodel, textspseudo_docs, dictionarydictionary, coherencec_v) return model, cm.get_coherence() for k in range(4, 9): model, coh train_lda(k) print(fK{k}, coherence{coh:.3f})no_below5的意思是词至少在 5 篇伪文档里出现否则视为低频噪声直接滤掉no_above0.5表示词不能在超过一半的文档里出现否则像「手机」这种词没有区分度。passes10是 LDA 迭代遍历整个语料的次数太小模型不收敛太大训练时间成倍涨。random_state42这行千万别删否则你每次跑出来的主题词都不一样后面对结果没法交代。选 K 只看困惑度会翻车困惑度在短文本上经常忽悠人。我一般以主题一致性coherence c_v为主再人工看主题词。K 从 4 到 8 跑一遍选 coherence 最高且主题词能看出业务含义的那个数。注意 LDA 有随机性同 K 跑三次取稳定结果别拿一次好运的 coherence 当真。3.3 特征词落业务主题命名与词分布检查模型训练完用show_topic把每个主题的特征词打出来人工命名。这一步是黑匣子开盖也是整个文本挖掘里最需要判断力的地方。for topic_id in range(model.num_topics): words model.show_topic(topic_id, topn10) print(fTopic {topic_id}:, .join(w for w, p in words))主题命名我一般遵循三个原则看前十个词里有没有核心名词看词之间的共同诉求主题词里出现「手机」「快递」「这家」这类泛词说明 K 太小或 filter 太松。给个常见对照主题词的「曲面屏、分辨率、亮度、绿边、发白」可以命名「屏幕」「续航、掉电、发热、充电快」命名「续航与发热」「物流、包装、发货、态度」命名「服务体验」。命名结果可以直接作为特征词的业务口径后面给运营解释时不用讲 LDA直接讲「屏幕、续航、服务」三个主题的占比变化就好。主题-词分布表里每个词后面跟着权重一般只取前 10 个词看。权重最大的词就是主题标签但真正说明问题的往往是第 4 到第 8 个词——比如「屏幕」主题里出现「绿边」这就是负面信号比「分辨率」更能预测销量下滑。4. 情感极性判断与程度计算把「不太满意」从「不满意」里分离LDA 告诉我们大家在哪情感计算告诉我们态度如何。这个压缩包的第三个板块是情感极性判断与程度计算。极性的意思是这段评论是正向还是负向程度是这个情感有多强。「不满意」和「不太满意」都是负向但程度差很多如果只分正负等于把「续航一般」和「续航尿崩」混在一起。这一步要输出的是一组连续的情感分数而不是简单的 0/1 标签。4.1 情感词典基准极性分从哪里来情感计算最稳的基线是词典法准备一份正向情感词表和一份负向情感词表把评论分词后逐个查表命中正向加一分命中负向减一分。不用上模型的原因很简单标注样本不够时词典法可解释、可干预运营问起来你能说清楚为什么这条评论得负分。# pos_words / neg_words 从公开中文情感词典整理而来 def sentiment_score(words): score 0.0 for w in words: if w in pos_words: score 1.0 elif w in neg_words: score - 1.0 return score df[sentiment] df[words].apply(sentiment_score)这段是最朴素的基线正负各一分。实际跑出来会有两个问题一是「不太满意」会被算成「满意」减一分得到 -1和「非常不满意」一样二是「续航还可以」里「可以」命中正向但「还」这个程度词把语气削弱了词典法却给了满分。所以下一步必须处理否定词和程度副词。4.2 否定词与程度副词两条规则把程度算出来程度计算我用两条规则情感词前一个位置是程度副词按权重放大缩小情感词前一到两个位置是否定词极性反转或弱化。这里的关键是「不太」「不怎么」这类组合拆开看是否定加程度合起来其实是弱化负向。weak_neg {不太: 0.5, 不怎么: 0.4, 没那么: 0.5} def sentiment_score_v2(words): score 0.0 for i, w in enumerate(words): if w in pos_words: s 1.0 if i 0 and words[i-1] in neg_words: s * -0.8 if i 0 and words[i-1] in degree_words: s * degree_words[words[i-1]] if i 1 and .join(words[i-1:i1]) in weak_neg: s * -0.5 score s elif w in neg_words: score - 0.2 # 孤立否定词给轻微负向防止漏判 return score窗口宽度取 2 是权衡结果只往前看一个词识别不了「不太满意」往前看三个词容易误伤比如「没有想象中满意」里的「没有」其实修饰的不是「满意」。-0.8表示否定后极性反转但保留一点余地因为「不满意」不等于绝对负向的「很差」weak_neg的 0.5 表示「不太满意」负向程度只有「不满意」的一半左右。这些系数不是玄学是拿 200 条人工标注试出来的基本符合直觉。程度副词表在前面预处理阶段已经存成文件这里直接读进来用。注意「比较」和「稍微」的权重不同「比较满意」是正向 1.2 倍「稍微满意」只有 0.7 倍虽然都正向但对销量排序的贡献完全不同——这个区分度在回归里会体现出来。4.3 评论级与属性级聚合从词分数到型号特征单条评论的情感分是浮动的直接丢给回归模型太碎。常见做法是把情感分聚合到型号级同时把 LDA 主题信息揉进来形成「屏幕主题情感」「续航主题情感」这类属性级特征。属性级特征比评论级好用得多同样是负向「屏幕发绿」对销量的杀伤力远大于「物流慢」。聚合后的特征初步如下按型号计算作为后面回归的输入。特征名计算方式说明sent_mean该型号所有评论情感分求平均整体情绪强度neg_ratio情感分为负的评论数 / 总评论数负向评论占比pos_ratio情感分为正的评论数 / 总评论数正向评论占比topic_screen_neg屏幕主题文档的负向情感均值属性级负向信号topic_battery_neg续航主题文档的负向情感均值属性级负向信号comment_cnt评论总数销量体量的间接信号这三个字段都是连续值比「好评率」一个数信息量大得多。售价评论区经常出现「一分钱一分货」这类词正面负面词典可能都命中最终分接近零没问题因为聚合后它就不是主信号了。5. 回归模型预测销量排序的避坑与做法从特征构造到模型对照前面几章产出的 LDA 主题权重、情感特征词分数到这一章变成回归模型的输入预测目标是销量排序。标题里写「回归模型」实际落地思路是用型号级别的文本特征回归销量或排名分数再按预测值排序和运营用的销量榜对齐。先讲坑是因为回归建模的坑大多集中在特征构造和评估方式上模型本身反而不是重点。5.1 标签与特征构造销量排序怎么来特征用哪些标签构造是第一关。如果数据里直接有销量回归目标就用销量最后按预测销量排序如果只有榜单排名就把排名归一化到 0 到 1 之间当标签。我个人偏好用对数销量因为销量分布通常长尾头部型号销量是尾部的几十倍直接回归原始销量会被大数带偏。特征矩阵用型号粒度聚合LDA 输出的文档-主题分布按型号求均值得到每个型号在「屏幕」「续航」「服务」等主题上的权重情感模块输出型号级情感均值、负向占比、属性级负向分数再加评论数和平均评分。代码示意如下# topic_weights: 每行是一个型号的主题分布来自 LDA 输出 feat df.groupby(model).agg( sent_mean(sentiment, mean), neg_ratio(sentiment, lambda x: (x 0).mean()), comment_cnt(sentiment, count), avg_score(score, mean) ).reset_index() feat feat.merge(topic_weights, onmodel, howleft) feat feat.merge(sales_df[[model, log_sales]], onmodel)这里最容易犯的错误是特征泄漏评论是消费者购买后写的而销量是评论产生之后一段时间发生的两者有时间差。用 3 月的评论预测 3 月的销量没问题但用 3 月的评论预测 1 月的销量就是拿未来预测过去。实际操作要严格按时间窗口切分T1 到 T2 的评论特征对齐 T2 到 T3 的销量。注意特征泄漏是文本挖掘预测里最隐蔽的翻车点模型指标再漂亮窗口一对不齐就是自欺欺人。5.2 岭回归与 GBDT 对照小样本下的稳定选择型号数量往往只有几十个特征十几个这个规模下第一个该试的是岭回归。岭回归的 L2 正则能压住特征之间的共线性LDA 主题权重之间天然存在此消彼长的关系用普通最小二乘很容易过拟合。from sklearn.linear_model import Ridge from sklearn.model_selection import train_test_split from scipy.stats import spearmanr X feat.drop(columns[model, log_sales]) y feat[log_sales] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42) model Ridge(alpha1.0) model.fit(X_train, y_train) pred model.predict(X_test) rho, _ spearmanr(y_test, pred) print(fSpearman rho{rho:.3f})alpha是正则强度我一般从 0.01、0.1、1、10 四个值里网格搜选测试集 Spearman 最高的。别一上来就调大 alpha太大所有预测值都往均值缩排序反而没区分度。test_size0.3在样本只有几十条时意味着测试集只有十几个型号单次划分运气成分很大建议跑多次取平均值。GBDT 可以作为对照样本量小的时候它更容易过拟合但有时能抓到非线性关系。LightGBM 里num_leaves控制在 8 以下min_child_samples设 10 以上learning_rate0.02 左右先把过拟合压住再谈精度。跑出来的结论经常是岭回归和 GBDT 的 Spearman 差不多但岭回归的系数可解释能直接看出「屏幕负向情感」比「评论数」对销量排序影响更大这个解释能力对运营汇报很重要。5.3 避坑记录回归预测里的 5 个高频问题以下五条是按次数排序的真实踩坑每一条都交过学费。现象预测分数全挤在均值附近排序完全没有区分度。原因特征方差被 LDA 归一化和情感聚合压得太平或者岭回归 alpha 过大。解决先看特征的标准差如果某一列标准差接近 0说明这个特征没有区分度删掉alpha 用网格搜别拍脑袋定。现象评论数越多的型号情感分越接近 0负向占比反而失真。原因大数定律评论多了正负抵消。解决不要只依赖情感均值把comment_cnt直接放进特征让模型自己去权衡或者对评论数做分箱在箱内再看情感分布。现象LDA 主题权重在同一批数据上跑两次结果完全不同回归特征跟着变。原因LDA 训练没固定随机种子或者伪文档构建不完整。解决random_state固定伪文档按型号构造后保存成中间文件一次跑完不再重新训练。现象模型在训练集上指标很好上线后排序惨不忍睹。原因评论窗口和销量窗口没对齐模型学到的是「当期评论预测当期销量」的假规律。解决严格按时间窗切分训练用 T1→T2 评论预测 T2→T3 销量测试用更晚的窗口模拟真实上线。现象MSE 很低但排在最前面的型号完全不对运营一看就说不靠谱。原因MSE 衡量的是整体偏差排序测的是相对顺序两者根本不是一回事。解决评估指标换成 Spearman 相关系数加 Top-K 命中率这两个指标跟业务目标直接相关MSE 只做参考。6. 销量排序回归的离线验证技巧用 Top-K 命中率代替 MSEMSE 在排序任务上会骗人。预测值整体比实际高 0.3但排序完全正确MSE 很难看预测值整体很准但第一名第二名互换MSE 反而漂亮。运营真正关心的是「销量榜前几名的型号你有没有给筛出来」。所以我最后会把 Top-K 命中率当成核心离线指标。def topk_hit_rate(y_true, y_pred, k3): # 真实销量和预测销量各自取前 k 名看重合度 true_topk set(pd.Series(y_true).sort_values(ascendingFalse).head(k).index) pred_topk set(pd.Series(y_pred).sort_values(ascendingFalse).head(k).index) return len(true_topk pred_topk) / ksort_values(ascendingFalse)表示销量高的排前面head(k)取前 k 个型号两个集合求交集再除以 k。K 取 3 还是 5 看业务头部型号就是 K3覆盖主流型号就 K5。这个指标比 MSE 直观多了运营问起「准不准」直接说「前 3 名能命中 2 个」比「均方误差 0.3」好理解一百倍。第二个验证技巧是滚动时间窗口。把数据按时间切成三段T1→T2 做训练集T2→T3 做验证集每次只往前挪一个窗口模拟真实的上线节奏。随机切分在这个场景里太乐观因为同一个型号的相邻时期评论高度相似随机切分等于让模型偷看了部分未来。第三个技巧是检查主题-情感交叉特征是否真的在工作。全局情感均值对销量排序的解释力通常一般因为评论里有大量跟产品无关的物流好评。把 LDA 的「屏幕」「续航」主题和情感分交叉后往往能明显看到「屏幕负向得分高的型号销量排名靠后」这个信号。这个信号一旦稳定就是文本挖掘真正有价值的地方——不是预测对了排名而是告诉运营到底是哪个属性在拖后腿。我最早跑这个包的时候只盯 MSE模型在测试集上 MSE 漂亮得很Top-K 命中率却只有两成差点把假信号当成真理交给运营。后来把 Spearman 和 Top-K 命中率做成固定检查项才敢说这个初体验流程是真的能落地。回归预测销量排序不是玄学是把文本信号翻译成排名前提是验证方式跟着业务走。希望帮到你。本文还有配套的精品资源点击获取