电商评论情感分析Python源码包:从数据清洗到LDA主题挖掘
简介面向电商运营、数据分析与自然语言处理初学者提供一套基于Python的电商产品评论情感分析完整源码。整体共22个文件含1个Python主程序、20个TXT文本与1个CSV数据集包体约18MB涵盖评论采集汇总、中文分词、停用词过滤、正负面情感判别、LDA主题分析等关键环节并附有中间过程结果与停用词表便于对照复现。目前已有4039人学习下载适合用于课程设计、毕业设计或入门级情感分析项目实践。压缩包内包含美的京东评论原始数据及多阶段处理结果从数据清洗到模型输出形成闭环可直接修改路径运行省去大量数据预处理时间。通过源码与结果文件结合可快速理解Python在中文文本挖掘与情感极性分析中的典型应用流程。1. 电商产品评论数据情感分析一份能直接跑的 Python 源码包做电商产品评论数据情感分析最耗时间的往往不是模型而是把脏数据洗到能喂模型。京东评论区里“此用户未填写评价内容”、表情符号、繁体字和超短无意义评论混在一起直接分词后做情感判断结果基本没法看。这份电商产品评论数据情感分析 Python 源码包把整条链路补完整了从 data/huizong.csv 评论汇总和 meidi_jd.txt 原始文本开始经过 process_1/2/3 三轮清洗、myDict.txt 自定义词典分词、stoplist.txt 停用词过滤再到正负面情感打分和 LDA 主题挖掘每个阶段都有中间结果文件code.py 可以对着跑。不管你是做商品舆论监测还是课程设计需要一份能复现的文本挖掘流程这份资源都能帮你省掉大半天预处理时间。适合电商运营、爬虫工程师和数据挖掘方向的初学者。2. 数据文件先盘明白huizong.csv、meidi_jd.txt 与中间产物的对应关系2.1 文件清单从原始评论到 LDA 结果每一步都留了一手先把源码包里的文件按流程位置排一下。这份资源的文件命名本身就能看出处理顺序但如果不对着流程看第一次打开容易被二十来个文件绕晕。我建议你把文件的读取顺序和 code.py 的流程对上号整体数据流向基本是data/huizong.csv → meidi_jd.txt → process_1/2/3 → process_end → 正负面结果 → pos_cut/neg_cut → LDA_result。文件作用说明data/huizong.csv评论汇总表评论区抓取后的结构化数据CSV 格式code.py 的入口之一meidi_jd.txt原始评论文本每行一条评论未清洗meidi_jd_process_1.txt清洗第一步结果去重、去占位评论meidi_jd_process_2.txt清洗第二步结果过滤短文本和特殊符号meidi_jd_process_3.txt清洗第三步结果格式统一为分词做准备meidi_jd_process_end.txt情感分析全量结果每行文本后带情感得分meidi_jd_process_end_正面/负面情感结果.txt正负面原始文本按得分切分后的文本meidi_jd_pos.txt / meidi_jd_neg.txt正负面文本精简版和上面结果文件内容对应meidi_jd_pos_cut.txt / meidi_jd_neg_cut.txt正负面分词文本空格分隔供 LDA 使用meidi_jd_pos_cut_LDA_result.txtLDA 主题结果正面评论的主题词输出myDict.txt自定义分词词典品牌词、产品词、情感词stoplist.txt停用词表无意义高频词code.py / 导入模块说明.txt主脚本与依赖流程入口和 pip 依赖清单tmp缓存目录一般放中间临时文件不用管这个文件设计有个好处每一步都保留了中间结果方便做对比。比如 process_1 之后行数少了多少、cut 之后有没有空行、LDA 输入文本的分词质量如何都能直接从文件里查不用重新跑一遍全流程。导入模块说明.txt 里要求的那几个包都是文本挖掘标配pandas、jieba、scikit-learn、numpy。环境里没装齐的话直接pip install pandas jieba scikit-learn一次装完网上那些 python 安装教程里的虚拟环境步骤在这里不需要全局安装就行。2.2 读取 huizong.csv编码和分隔符是第一个坑先看 CSVimport pandas as pd df pd.read_csv(data/huizong.csv, encodingutf-8-sig, sep,) print(df.shape) # 行数、列数 print(df.columns.tolist()) # 列名这段代码有两个参数需要留意。encoding 用 utf-8-sig 而不是 utf-8因为网页端导出的 CSV 大概率带 BOM直接用 utf-8 读会在第一列开头混入不可见字符sep 默认是逗号但如果文件用 Excel 另存过分隔符可能变成分号或制表符读之前最好用文本编辑器打开第一行确认不要凭经验猜。列名建议打印出来看一遍不同数据源的字段名可能叫“评论内容”也可能叫“content”代码里写死列名的话换数据源就崩。接着看原始文本文件with open(meidi_jd.txt, r, encodingutf-8) as f: lines [line.strip() for line in f if line.strip()] print(meidi_jd.txt total lines:, len(lines)) print(lines[0][:60])这里用列表推导式一次性读入lines 里就是逐条评论。计算 len(lines) 的习惯建议保留下来后面每一个 process 文件生成后都对比一次行数能快速发现哪一步误删数据。比如 process_1 之后如果行数只剩一半那基本可以断定去重或短评过滤阈值出了问题。提示读 CSV 报 UnicodeDecodeError 时先别急着换编码用二进制模式读前三个字节b\xef\xbb\xbf是 UTF-8 BOM用 utf-8-sig 一次解决。2.3 从 CSV 导出 txt两条路径的取舍源码包里的数据有两个形态一个是 DataFrame一个是纯文本文件。常见做法是先用 pandas 把 CSV 里的评论文本列取出来单独导出成 meidi_jd.txt后面清洗、分词全在纯文本文件上操作df pd.read_csv(data/huizong.csv, encodingutf-8-sig) # 假设评论文本列叫 content按实际数据改这行 df[content].dropna().to_csv(meidi_jd.txt, indexFalse, headerFalse, sep\t, encodingutf-8)dropna() 是为了丢掉空评论sep\t 是因为评论文本本身可能含逗号用制表符做分隔更安全。这个文件就是整个流程的起点。DataFrame 适合做统计分析和评分字段筛选txt 适合做逐行文本处理后面 jieba、LDA 的输入都是列表从 txt 读比反复操作 DataFrame 更直观内存占用也更小。3. 从原始评论到干净分词process_1/2/3、myDict.txt 与 stoplist.txt 的配合3.1 清洗三步为什么每一步都单独存一个文件原始评论里最常见的是两类垃圾数据一类是“此用户未填写评价内容”这种平台占位符一类是同一个用户复制粘贴的重复评价。process_1.txt 做的就是去重和清除占位内容process_2.txt 再往前走一步把表情符号、emoji、特殊标点清掉process_3.txt 做格式统一。清洗这一步值得多说两句。很多人拿到文本直接上 jieba其实脏数据对分词的影响远大于对模型的影响——“此用户未填写评价内容”被切成一堆没有意义的词还会污染后面 LDA 的主题分布。保留三个中间文件的好处是清洗效果可以量化process_1 出来剩多少行process_2 出来剩多少行肉眼就能看出哪一步过滤得最激进也方便逐步排查。process_1 的去重和占位清理参考实现import re PLACEHOLDERS [此用户未填写评价内容, 此用户没有填写评价内容, 默认好评] def clean_basic(text: str) - str: for p in PLACEHOLDERS: text text.replace(p, ) text re.sub(r[\s\u3000], , text) # 统一空白字符 return text.strip() seen set() result [] with open(meidi_jd.txt, r, encodingutf-8) as f: for line in f: line line.strip() if not line: continue cleaned clean_basic(line) if cleaned in seen or len(cleaned) 5: continue seen.add(cleaned) result.append(cleaned) with open(meidi_jd_process_1.txt, w, encodingutf-8) as f: f.write(\n.join(result)) print(after process_1:, len(result))逻辑说明seen 是 Python 的 set判断重复是 O(1) 的几万条评论也不会慢。len(cleaned) 5 这个阈值是保守过滤只丢弃“很好”“不错”这种超短评论如果发现有效短评被误伤把它降到 2 就行。PLACEHOLDERS 这个表按实际数据源补不同平台的占位文案不一样。process_2 的符号清理# 清洗第二步只留中英文和数字 def clean_symbols(text: str) - str: text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9], , text) text re.sub(r\s, , text).strip() return text with open(meidi_jd_process_1.txt, r, encodingutf-8) as f: rows [line.strip() for line in f if line.strip()] with open(meidi_jd_process_2.txt, w, encodingutf-8) as f: f.write(\n.join([clean_symbols(r) for r in rows]))这个正则的代价是小数点被吞掉“4.5 分”会变成“45 分”。如果后面要做评分相关分析评分字段应该单独从 CSV 里拿不要依赖评论文本里的数字。这步做完process_2 里剩下的基本就是纯中文、英文和数字给分词省了很多干扰。process_3 的格式统一一般就是全角转半角、去除行首行尾多余空白逻辑和上面类似这里不重复贴。3.2 jieba 分词myDict.txt 的格式与权重逻辑分词是决定后续情感判断和主题分析质量的关键步骤。jieba 默认词库对通用词处理得不错但电商场景有很多专有表达比如“一级能效”“变频静音”“送货上门”默认切分经常把它们切成“一级/能效”“变频/静音”情感词“不错”反而被切走。这里的解法就是 load_userdict 加载 myDict.txt。import jieba jieba.load_userdict(myDict.txt) # 注意只加载一次 with open(meidi_jd_process_3.txt, r, encodingutf-8) as f: lines [line.strip() for line in f if line.strip()] cut_lines [] for line in lines: words [w for w in jieba.cut(line, cut_allFalse) if w.strip()] cut_lines.append( .join(words)) with open(meidi_jd_process_3_cut.txt, w, encodingutf-8) as f: f.write(\n.join(cut_lines))参数说明cut_allFalse 是精确模式。很多人第一次跑会写成 cut_allTrue切出来的词又碎又重复后面统计词频时全是噪声。load_userdict 放在循环外面只需要执行一次放进循环里虽然不影响正确性但会有没必要的重复加载开销。myDict.txt 的格式是“词 空格 词频 空格 词性”美的 200 nz 变频 180 n 一级能效 150 nz 送货上门 120 n 退换货 100 n词频越大jieba 在动态规划时越倾向把这个词当成整体一般设 100 到 200 就足够。词性可以填 n名词、nz专名不影响切分逻辑但对后续按词性过滤有帮助。最简单的验证方式是加载词典后单独切一句话print(/.join(jieba.cut(一级能效变频空调, cut_allFalse))) # 期望输出一级能效/变频/空调如果还是切出“一级/能效/变频/空调”先看 myDict.txt 编码是不是 UTF-8再看词频是不是太低重点检查词条内部是不是混入了空格。3.3 停用词过滤stoplist.txt 要放两类词停用词表的设计直接决定 LDA 主题的质量。stoplist.txt 里应该有两类词一类是“的、了、在、是、就、都”这类通用虚词另一类是领域高频噪音词比如“京东”“物流”“客服”“快递”——它们几乎出现在每一句评论里但在任何主题里都没有区分度留着只会把每个主题都拉向同一个方向。stopwords set() with open(stoplist.txt, r, encodingutf-8) as f: for line in f: w line.strip() if w: stopwords.add(w) filtered_lines [] for line in cut_lines: words [w for w in line.split() if w not in stopwords and len(w) 1] filtered_lines.append( .join(words)) with open(meidi_jd_process_3_cut_filtered.txt, w, encodingutf-8) as f: f.write(\n.join(filtered_lines))len(w) 1 是把单字词全扔掉中文里大部分单字是虚词或语素留着只会让词频矩阵变稀疏。注意判断顺序先查停用词 set 再判断长度对单字虚词直接跳过能省一次 set 查询数据量大时差别明显。过滤完之后建议打印前 500 行分词结果肉眼看一遍漏网的词手动补进 stoplist。我遇到过一个很典型的情况停用词表里没加“美的”LDA 跑出来每个主题第一个词都是“美的”四个主题相互之间完全没有区分度。把“美的”加进停用词表重新跑主题才真正分开。这个教训后面避坑章还会提到。4. 正负面情感判定从 process_end 到 pos_cut 的切分逻辑4.1 为什么是情感词典打分而不是直接上深度学习模型做电商评论情感分析时最常被问到的问题就是“为什么不用 BERT”这个源码包对应的场景是典型的无标注数据、交付周期短、结果还要能让业务看懂。京东评论没有给你标好正负面的训练集手工标注几千条的成本很高。现在多模态情感分析研究确实很热但纯文本场景下情感词典打分依然是成本最低、最好解释的方案。这个资源里 myDict.txt 实际承担了双重角色在分词阶段它是 jieba 的自定义词表在情感判定阶段它又作为情感词来源。正面词和负面词可以从里面拆出来匹配评论分词后的词列表正面词 1负面词 -1最后看总分正负。process_end.txt 里的情感得分就是这么来的。词典法的短板是覆盖度但通过持续往 myDict.txt 里补词覆盖度可以越做越好而且每一步都可以追溯。4.2 打分实现否定词翻转是容易漏掉的一环一个朴素的情感打分实现如果不处理否定词会把“客服态度不好”里的“不”漏掉导致整句误判成正面。处理否定词是词典法的最低要求POS_WORDS set([好, 满意, 喜欢, 值得, 不错, 快, 实惠]) NEG_WORDS set([差, 失望, 后悔, 噪音, 漏水, 退货, 故障, 贵]) NEGATE_WORDS set([不, 没, 没有, 别, 不要, 不太]) def sentiment_score(words: list) - int: score 0 negate_flag False for w in words: if w in NEGATE_WORDS: negate_flag True continue if w in POS_WORDS or w in NEG_WORDS: v 1 if w in POS_WORDS else -1 if negate_flag: v -v # 否定词翻转极性 negate_flag False score v return score逻辑说明negate_flag 只影响紧随否定词之后的那一个情感词不去翻转整句这是为了避免过度修正。代价是“不是不好”这种双重否定会被记成 0但真实评论里双重否定占比极低不影响整体分布。POS_WORDS 和 NEG_WORDS 这两个 set 最好从 myDict.txt 里按词性拆出来维护不要写死在代码里因为词典会持续更新。如果你发现“太贵”被切成“太/贵”导致没有命中就把“太贵”直接加进 NEG_WORDS用词对匹配代替单字匹配。打分之后切分正负面同时生成 process_end 和两个结果文件results [] for line in cut_lines: words line.split() score sentiment_score(words) results.append((score, .join(words))) pos_f open(meidi_jd_process_end_正面情感结果.txt, w, encodingutf-8) neg_f open(meidi_jd_process_end_负面情感结果.txt, w, encodingutf-8) with open(meidi_jd_process_end.txt, w, encodingutf-8) as f: for score, text in results: f.write(f{text}\t{score}\n) if score 0: pos_f.write(text \n) elif score 0: neg_f.write(text \n) pos_f.close() neg_f.close()这里的输出结构是 process_end.txt 每行文本后面带一个 tab 和一个得分方便后面只调阈值而不重新跑分。注意 score 0 的评论被丢掉了这在语义上是中性评论。如果你需要保留中性类做三分类把等于 0 的情况单独写一个文件否则丢掉即可。4.3 pos_cut 和 neg_cut 的关系为什么要有两套正负面文件源码包里同时有 meidi_jd_process_end_正面情感结果.txt 和 meidi_jd_pos_cut.txt两者看起来重复实际用途不同。前者是原始文本给人读的用来抽查判定结果准不准后者是分词后的文本给机器读的直接作为 LDA 的输入。从文件命名也能推断出流程pos_cut 就是在正面结果的基础上再走一遍 jieba 分词生成的。常规顺序是从 meidi_jd_process_end_正面情感结果.txt 读出正面原始文本对每一行做 jieba.cut 精确模式分词再过一遍停用词写入 meidi_jd_pos_cut.txt每行分词用空格连接负面文本同理生成 meidi_jd_neg_cut.txt。这样在做 LDA 时就不用回头重跑全量数据。这份资源里 meidi_jd_pos_cut_LDA_result.txt 的生成就是拿 meidi_jd_pos_cut.txt 喂给 LDA 模型的结果细节放到第 6 章展开。5. 电商评论情感分析常见问题数据、分词、判定三条线的踩坑记录做这类源码包的复盘踩得最多的坑其实不在算法全都在数据上岸和词典维护上。下面按实际发生的频率排列每一条都是跑完一遍代码之后才真正理解的照着排查能省不少时间。5.1 读 CSV 报错或者读出乱码现象pd.read_csv(data/huizong.csv) 直接抛 UnicodeDecodeError换成 encodingutf-8 后能读但中文内容全是乱码。原因网页端导出的文件是 UTF-8 with BOMpandas 默认 utf-8 会把开头的 BOM 当成不可见字符导致第一列错位另一种情况是文件被 Excel 重新保存过编码被改成 GBK。解决先用二进制读前三个字节判断文件头再决定用什么编码with open(data/huizong.csv, rb) as f: print(f.read(3)) # b\xef\xbb\xbf - 用 utf-8-sig # b\xd6\xd0 - 用 gbk 或 gb18030如果是 GBK直接用 gb18030它覆盖的字符集更宽不容易漏字。这个判断习惯在读取任何爬虫落地的 CSV 时都适用。5.2 自定义词典不生效词还是被切碎现象myDict.txt 里加了“变频静音”jieba.cut 还是切出“变频/静音”。原因最常见的是词频权重太低。jieba 自定义词典的默认频率是 1在动态规划里几乎不起作用另一个常见问题是词条内部有空格或者文件本身是 GBK 编码jieba 读不出来。解决把词频写大100 到 200 之间比较靠谱确认 myDict.txt 是 UTF-8 编码加载后立刻用单条文本验证不要等全量跑完才发现验证方法就是那句print(/.join(jieba.cut(变频静音)))。5.3 负面率低到 2%明显不符合常识现象几千条评论跑完被判为负面的只有几十条人工抽查发现“客服态度不好”“空调噪音大”全被判成正面。原因打分代码没有处理否定词而且负面词表只覆盖了“差、失望、后悔”这类强负面词“噪音、贵、退货”这些弱负面表达没进词表。解决给打分逻辑加上否定词翻转把“太贵”“偏小”“有点差”这类常见的弱负面组合直接加进 NEG_WORDS。加完以后重新跑一遍 process_end对比负面率变化。如果负面率从 2% 变成 20%并且抽查结果合理就说明词表补齐生效了。5.4 process_1 之后行数掉了一半现象meidi_jd.txt 有 8000 行process_1 只剩 4000 行。原因set 全量去重把同文本评论全合并了。京东评论区大量相似评论是同一个用户对不同商品复制粘贴的简单按文本去重会误杀另一处是 len(cleaned) 5 把“很好”“不错”这类短正面评论也丢掉了。解决先统计一下完全重复文本的占比如果超过 30% 再考虑全量去重否则把去重键改成“同一用户 ID 评论文本”的哈希短评阈值降到 2 或直接去掉。这个源码包里没有用户 ID 列保守做法是把阈值降到 2先保数据量再谈清洗。5.5 LDA 主题词全是品牌名和物流词现象LDA 跑出来 5 个主题每个主题的 top 词都包含“美的”“京东”“物流”“快递”主题之间几乎没有区分。原因停用词表没有覆盖领域高频词。“美的”“京东”在大多数评论里都出现作为高词频词会把每个主题的分布都拉高真正的功能卖点词被挤到后面。解决把品牌词、平台词、通用服务词加进 stoplist.txt重新跑分词过滤和 LDA。加完以后对比两次的主题 top 词基本每次都能看到主题从“名词堆砌”变成“功能卖点”。我把“美的、京东、物流、客服、快递、安装”这六个词加进去之后主题词立刻变成了“制冷、静音、省电、外观、售后”这才是业务真正关心的维度。6. 把 LDA 主题挖掘接到正负面文本上从 pos_cut 到可读结论6.1 LDA 输入要求与参考实现文本聚类到主题这一步是让情感分析真正落地的关键。正面评论判出来之后业务想知道用户满意的是哪些点负面评论判出来之后想知道抱怨集中在哪些点。直接拿原始文本做 LDA 基本不可用因为 LDA 需要的是词级别序列。meidi_jd_pos_cut.txt 正好满足这个输入要求——每行一个文档空格分词。from sklearn.feature_extraction.text import CountVectorizer from sklearn.decomposition import LatentDirichletAllocation with open(meidi_jd_pos_cut.txt, r, encodingutf-8) as f: docs [line.strip() for line in f if line.strip()] vectorizer CountVectorizer(max_features2000, min_df5, max_df0.8) X vectorizer.fit_transform(docs) lda LatentDirichletAllocation(n_components5, max_iter50, random_state42) lda.fit(X)参数说明max_features2000 把词表截断到 2000 个词防止稀疏矩阵太大min_df5 过滤只在 1-4 条评论里出现的词这些词通常是错别字或噪声max_df0.8 过滤在 80% 以上评论里都出现的词对应第 5 章里的领域噪声词。n_components5 是主题数random_state42 固定随机种子保证结果可复现。输出主题词feature_names vectorizer.get_feature_names_out() for topic_idx, topic in enumerate(lda.components_): top_words [feature_names[i] for i in topic.argsort()[:-11:-1]] print(ftopic {topic_idx}: {, .join(top_words)})6.2 主题数怎么调别只信困惑度主题数 n_components 的选法有点玄学。sklearn 自带 perplexity 方法但数值会随主题数增加单调下降直接看数值会把主题数选得过大。我一般会跑 3、5、8 三组每组都人工扫一遍主题 top 词看每组词是否逻辑一致。比如一组主题词是“制冷、快、效果、安静”另一组是“安装、师傅、服务、专业”那这两组就能讲出故事如果某个主题词是“美的、京东、物流、客服”说明停用词没加干净回去补词表不需要调参数。这份资源里 meidi_jd_pos_cut_LDA_result.txt 就是这步的输出可以直接拿来做结果汇报模板。从那以后我每次跑这类评论分析都强制自己把“词典更新、停用词检查、主题词人工阅读”三步走一遍宁可多花 10 分钟在词典维护上也不让模型在脏数据上白跑。希望帮到你。本文还有配套的精品资源点击获取