我用 Python 搭了一套文本情感分析系统:从用户评论中自动提取正面负面情绪
我用 Python 搭了一套文本情感分析系统:从用户评论中自动提取正面负面情绪
适合需要分析用户评论、产品评价、社交媒体情感倾向的运营和开发者。
本文用 Python 搭了一套完整的情感分析系统,从数据采集到情感打分到可视化报告。
背景:为什么需要情感分析
每天有大量用户评论、产品评价、社交媒体帖子。手动看 100 条还行,10000 条就看不过来了。
情感分析能自动判断每条评论是"正面"还是"负面",快速发现:
- 产品哪里被吐槽最多
- 用户对新功能的反馈是好是坏
- 竞品的口碑怎么样
方案对比
| 方案 | 准确率 | 成本 | 适合 |
|---|---|---|---|
| 调 LLM API | 最高 | 高(按 token 收费) | 少量高质量分析 |
| 预训练模型 | 高 | 低(本地推理) | 中等规模 |
| 规则匹配 | 中 | 极低 | 简单场景 |
| SnowNLP | 中 | 极低 | 快速原型 |
我的组合:预训练模型做主力 + LLM 做精细分析。
模块 1:基础情感分析(SnowNLP)
最简单的方案,5 行代码搞定:
fromsnownlpimportSnowNLPdefsimple_sentiment(text):"""简单情感分析(返回 0-1,越大越正面)"""s=SnowNLP(text)returns.sentiments# 示例texts=["这个产品太好用了,强烈推荐!","质量很差,用了一天就坏了","还行吧,一般般","客服态度非常好,问题解决很快","垃圾,浪费钱"]fortextintexts:score=simple_sentiment(text)label="正面"ifscore>0.5else"负面"print(f"[{label}{score:.2f}]{text}")输出:
[正面 0.95] 这个产品太好用了,强烈推荐! [负面 0.08] 质量很差,用了一天就坏了 [负面 0.42] 还行吧,一般般 [正面 0.88] 客服态度非常好,问题解决很快 [负面 0.05] 垃圾,浪费钱模块 2:预训练模型(更准确)
用 HuggingFace 的预训练情感分析模型:
fromtransformersimportpipeline# 加载中文情感分析模型classifier=pipeline("sentiment-analysis",model="uer/roberta-base-finetuned-chinanews-chinese")defpretrained_sentiment(text):"""预训练模型情感分析"""result=classifier(text)[0]return{"label":result["label"],"score":result["score"]}# 示例texts=["这个产品太好用了","质量很差,不推荐","服务态度不错,但发货太慢"]fortextintexts:result=pretrained_sentiment(text)print(f"[{result['label']}{result['score']:.2f}]{text}")模块 3:LLM 精细分析
对于需要更细粒度分析的场景(如提取具体的情感维度),用 LLM:
fromopenaiimportOpenAI client=OpenAI()defllm_sentiment(text):"""LLM 精细情感分析"""prompt=f"""分析以下用户评论的情感,返回 JSON 格式: 评论:{text}返回格式: {{ "overall": "正面/负面/中性", "score": 1-10, "aspects": [ {{"aspect": "产品功能", "sentiment": "正面/负面", "detail": "具体点"}}, {{"aspect": "服务态度", "sentiment": "正面/负面", "detail": "具体点"}} ], "keywords": ["关键词1", "关键词2"] }}"""response=client.chat.completions.create(model="gpt-4",messages=[{"role":"user","content":prompt}],temperature=0.1)importjsonreturnjson.loads(response.choices[0].message.content)# 示例review="产品功能很强大,但界面设计太丑了,客服回复也慢"result=llm_sentiment(review)print(json.dumps(result,ensure_ascii=False,indent=2))输出:
{"overall":"中性","score":5,"aspects":[{"aspect":"产品功能","sentiment":"正面","detail":"功能强大"},{"aspect":"界面设计","sentiment":"负面","detail":"界面太丑"},{"aspect":"客服响应","sentiment":"负面","detail":"回复慢"}],"keywords":["功能强大","界面丑","客服慢"]}模块 4:批量分析
importpandasaspddefbatch_analyze(texts,method="snownlp"):"""批量情感分析"""results=[]fortextintexts:ifmethod=="snownlp":score=simple_sentiment(text)label="正面"ifscore>0.5else"负面"elifmethod=="pretrained":r=pretrained_sentiment(text)label,score=r["label"],r["score"]elifmethod=="llm":r=llm_sentiment(text)label,score=r["overall"],r["score"]/10results.append({"text":text,"label":label,"score":score})df=pd.DataFrame(results)returndf# 使用comments=["这个产品太好用了","质量很差,不推荐","服务态度不错","发货太慢了","性价比很高","包装破损,差评","功能齐全,值得购买","客服态度恶劣"]df=batch_analyze(comments,method="snownlp")# 统计print(f"总数:{len(df)}")print(f"正面:{len(df[df['label']=='正面'])}({len(df[df['label']=='正面'])/len(df)*100:.1f}%)")print(f"负面:{len(df[df['label']=='负面'])}({len(df[df['label']=='负面'])/len(df)*100:.1f}%)")print(f"平均分:{df['score'].mean():.2f}")模块 5:可视化报告
importstreamlitasstimportplotly.expressaspx st.title("情感分析报告")# 上传文件uploaded_file=st.file_uploader("上传评论数据(CSV)",type=["csv"])ifuploaded_file:df=pd.read_csv(uploaded_file)text_col=st.selectbox("选择评论列",df.columns)# 批量分析withst.spinner("分析中..."):results=batch_analyze(df[text_col].tolist())df["sentiment"]=results["label"]df["score"]=results["score"]# 图表col1,col2=st.columns(2)withcol1:# 情感分布饼图fig1=px.pie(df,names="sentiment",title="情感分布",color_discrete_map={"正面":"#4CAF50","负面":"#F44336"})st.plotly_chart(fig1,use_container_width=True)withcol2:# 情感分数分布fig2=px.histogram(df,x="score",title="情感分数分布",nbins=20,color_discrete_sequence=["#2196F3"])st.plotly_chart(fig2,use_container_width=True)# 负面评论 TOP 10st.subheader("负面评论 TOP 10")negative=df[df["sentiment"]=="负面"].sort_values("score").head(10)st.dataframe(negative[[text_col,"score"]])# 导出st.download_button("下载分析结果",df.to_csv(index=False).encode("utf-8-sig"),"sentiment_results.csv","text/csv")模块 6:定时监控
importscheduledefdaily_sentiment_report():"""每日情感分析报告"""# 1. 采集新评论(从数据库/API)new_comments=fetch_new_comments()# 2. 情感分析df=batch_analyze(new_comments)# 3. 统计positive_rate=len(df[df["label"]=="正面"])/len(df)*100negative_rate=len(df[df["label"]=="负面"])/len(df)*100# 4. 告警ifnegative_rate>30:send_alert(f"⚠️ 负面评论占比{negative_rate:.1f}%,超过阈值 30%")# 5. 生成报告report=f""" 每日情感分析报告 - 总评论数:{len(df)}- 正面:{positive_rate:.1f}% - 负面:{negative_rate:.1f}% - 平均分:{df['score'].mean():.2f}"""save_report(report)# 每天早上 9 点运行schedule.every().day.at("09:00").do(daily_sentiment_report)踩坑记录
坑 1:SnowNLP 对反讽不准确
症状:"真是好极了(反讽)"被判断为正面。
原因:SnowNLP 基于词典,不理解反讽语义。
解决:反讽场景用 LLM 分析,它能理解上下文。
坑 2:预训练模型不支持 GPU
症状:10000 条评论分析要 1 小时。
原因:默认用 CPU 推理。
解决:安装torch的 GPU 版本,模型会自动用 CUDA 加速。
坑 3:LLM 分析成本太高
症状:10000 条评论用 GPT-4 分析,API 费用 500 元。
解决:先用 SnowNLP 粗筛,只对"不确定"的评论用 LLM 精分析。或者用 GPT-3.5-turbo(便宜 50 倍)。
坑 4:中文分词问题
症状:“不太好用"被分成"不”+“太”+“好”+“用”,"不好"这个语义被拆散了。
原因:分词器对否定词处理不好。
解决:用预训练模型(它内部有更好的分词),或者在预处理时把"不好""很差"等否定词组作为整体。
坑 5:情感分数阈值不好定
症状:0.5 作为阈值,很多"中性"评论被分到负面。
解决:用三分类(正面/中性/负面),中性区间设为 0.4-0.6。
总结
3 条核心经验:
按数据量选方案。100 条以内用 LLM,1000 条用预训练模型,10000 条以上用 SnowNLP + LLM 混合。
反讽和否定是最难的。“好极了(反讽)”"不太好"这类表达,简单模型处理不好,需要 LLM 或预训练模型。
情感分析的价值在于趋势。单条评论的分析意义不大,关键是看趋势变化——负面率突然升高,说明出了问题。
你有做过情感分析吗?用什么方案?评论区交流。
