简介:面向电商运营、数据分析与自然语言处理初学者,提供一套基于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.txt | LDA 主题结果 | 正面评论的主题词输出 |
| 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:编码和分隔符是第一个坑
先看 CSV:
import pandas as pd df = pd.read_csv('data/huizong.csv', encoding='utf-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', encoding='utf-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', encoding='utf-8-sig') # 假设评论文本列叫 content,按实际数据改这行 df['content'].dropna().to_csv('meidi_jd.txt', index=False, header=False, sep='\t', encoding='utf-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', encoding='utf-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', encoding='utf-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', encoding='utf-8') as f: rows = [line.strip() for line in f if line.strip()] with open('meidi_jd_process_2.txt', 'w', encoding='utf-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', encoding='utf-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_all=False) if w.strip()] cut_lines.append(' '.join(words)) with open('meidi_jd_process_3_cut.txt', 'w', encoding='utf-8') as f: f.write('\n'.join(cut_lines))参数说明:cut_all=False 是精确模式。很多人第一次跑会写成 cut_all=True,切出来的词又碎又重复,后面统计词频时全是噪声。load_userdict 放在循环外面,只需要执行一次,放进循环里虽然不影响正确性,但会有没必要的重复加载开销。
myDict.txt 的格式是“词 + 空格 + 词频 + 空格 + 词性”:
美的 200 nz 变频 180 n 一级能效 150 nz 送货上门 120 n 退换货 100 n词频越大,jieba 在动态规划时越倾向把这个词当成整体,一般设 100 到 200 就足够。词性可以填 n(名词)、nz(专名),不影响切分逻辑,但对后续按词性过滤有帮助。最简单的验证方式是加载词典后单独切一句话:
print('/'.join(jieba.cut('一级能效变频空调', cut_all=False))) # 期望输出:一级能效/变频/空调如果还是切出“一级/能效/变频/空调”,先看 myDict.txt 编码是不是 UTF-8,再看词频是不是太低,重点检查词条内部是不是混入了空格。
3.3 停用词过滤:stoplist.txt 要放两类词
停用词表的设计直接决定 LDA 主题的质量。stoplist.txt 里应该有两类词:一类是“的、了、在、是、就、都”这类通用虚词,另一类是领域高频噪音词,比如“京东”“物流”“客服”“快递”——它们几乎出现在每一句评论里,但在任何主题里都没有区分度,留着只会把每个主题都拉向同一个方向。
stopwords = set() with open('stoplist.txt', 'r', encoding='utf-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', encoding='utf-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', encoding='utf-8') neg_f = open('meidi_jd_process_end_负面情感结果.txt', 'w', encoding='utf-8') with open('meidi_jd_process_end.txt', 'w', encoding='utf-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;换成 encoding='utf-8' 后能读,但中文内容全是乱码。
原因:网页端导出的文件是 UTF-8 with BOM,pandas 默认 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', encoding='utf-8') as f: docs = [line.strip() for line in f if line.strip()] vectorizer = CountVectorizer(max_features=2000, min_df=5, max_df=0.8) X = vectorizer.fit_transform(docs) lda = LatentDirichletAllocation(n_components=5, max_iter=50, random_state=42) lda.fit(X)参数说明:max_features=2000 把词表截断到 2000 个词,防止稀疏矩阵太大;min_df=5 过滤只在 1-4 条评论里出现的词,这些词通常是错别字或噪声;max_df=0.8 过滤在 80% 以上评论里都出现的词,对应第 5 章里的领域噪声词。n_components=5 是主题数,random_state=42 固定随机种子,保证结果可复现。
输出主题词:
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(f'topic {topic_idx}: {", ".join(top_words)}')6.2 主题数怎么调,别只信困惑度
主题数 n_components 的选法有点玄学。sklearn 自带 perplexity 方法,但数值会随主题数增加单调下降,直接看数值会把主题数选得过大。我一般会跑 3、5、8 三组,每组都人工扫一遍主题 top 词,看每组词是否逻辑一致。比如一组主题词是“制冷、快、效果、安静”,另一组是“安装、师傅、服务、专业”,那这两组就能讲出故事;如果某个主题词是“美的、京东、物流、客服”,说明停用词没加干净,回去补词表,不需要调参数。
这份资源里 meidi_jd_pos_cut_LDA_result.txt 就是这步的输出,可以直接拿来做结果汇报模板。从那以后我每次跑这类评论分析,都强制自己把“词典更新、停用词检查、主题词人工阅读”三步走一遍,宁可多花 10 分钟在词典维护上,也不让模型在脏数据上白跑。希望帮到你。
本文还有配套的精品资源,点击获取