电商评价分析系统
电商用户评价智能分析系统技术报告
基于AI的商业智能分析平台
一、项目概述
1.1 项目背景
随着电子商务的快速发展,用户评价数据呈指数级增长。传统的人工分析方式已无法满足企业对海量评价数据进行深度挖掘的需求。本项目基于人工智能技术,构建了一套完整的电商用户评价智能分析系统,旨在帮助企业快速洞察用户需求、发现产品问题、优化运营策略。
1.2 项目目标
- 数据采集:支持多平台(京东、拼多多、苏宁)数据爬取及CSV/Excel/JSON格式数据导入
- 情感分析:基于词典和规则的情感分类,实现正负面评价自动识别
- 用户画像:通过K-means聚类分析用户特征,构建用户分层模型
- 商业智能:竞品分析、负面评价预警、自动化报告生成
- 智能问答:基于RAG的检索增强问答系统
二、技术架构
2.1 系统架构
系统采用分层架构设计,主要分为数据采集层、数据处理层、分析引擎层和展示层四个层次:
数据采集层负责从多个电商平台爬取评价数据,同时支持本地数据文件导入;数据处理层进行缺失值处理、文本清洗、分词和特征提取;分析引擎层包含情感分析、用户画像分析和商业智能分析模块;展示层通过Streamlit构建交互式Web仪表盘。
2.2 技术栈
|
层级 |
技术 |
版本 |
说明 |
|
前端框架 |
Streamlit |
1.28.0 |
快速构建数据可视化Web应用 |
|
后端语言 |
Python |
3.10+ |
核心业务逻辑开发 |
|
数据分析 |
Pandas |
2.x |
数据处理与分析 |
|
机器学习 |
Scikit-learn |
1.x |
K-means聚类、TF-IDF特征提取 |
|
可视化 |
Plotly |
5.x |
交互式图表生成 |
|
词云生成 |
Wordcloud |
1.9.x |
关键词可视化 |
|
数据存储 |
ChromaDB |
0.4.x |
RAG向量数据库(可选) |
三、核心模块实现
3.1 数据预处理模块
文件路径:src/preprocessing/data_processor.py
数据预处理是分析的基础,主要包含缺失值处理、文本清洗、分词处理和特征提取四个步骤。
class DataProcessor:
@staticmethod
def handle_missing_values(df: pd.DataFrame) -> pd.DataFrame:
"""处理缺失值"""
df = df.dropna(subset=["content"])
fill_values = {
"rating": 0, "likes": 0, "reply_count": 0,
"browse_duration": 0, "click_count": 0, "purchase_count": 0
}
fill_values = {k: v for k, v in fill_values.items() if k in df.columns}
df = df.fillna(fill_values)
return df
设计亮点:支持动态列填充,兼容不同数据源;数据脱敏处理,保护用户隐私。
3.2 情感分析模块
文件路径:src/analysis/sentiment_analyzer.py
基于词典规则的情感分析实现,使用结巴分词对文本进行分词,统计正向词和负向词的数量,计算情感得分。
class SentimentAnalyzer:
def analyze_sentiment(self, text: str) -> tuple:
words = jieba.lcut(text)
pos_count = sum(1 for w in words if w in self.positive_words)
neg_count = sum(1 for w in words if w in self.negative_words)
score = (pos_count - neg_count) / max(pos_count + neg_count, 1)
if score > 0.1:
sentiment = "positive"
elif score < -0.1:
sentiment = "negative"
else:
sentiment = "neutral"
return sentiment, score
3.3 用户画像分析模块
文件路径:src/analysis/user_profile_analyzer.py
基于K-means的用户聚类分析,选取用户评分、点赞数、购买次数、客单价等特征进行聚类,将用户分为核心用户、活跃用户、潜力用户和沉默用户四类。
容错机制:当用户数量不足时跳过聚类;当sklearn出现兼容性问题时,使用分位数分组作为回退方案。
四、数据结构设计
4.1 样本数据字段
|
字段名 |
类型 |
说明 |
示例 |
|
content |
string |
评论内容 |
商品质量很好 |
|
rating |
int |
评分 |
5 |
|
likes |
int |
点赞数 |
128 |
|
platform |
string |
平台 |
京东 |
|
user_level |
string |
用户等级 |
VIP |
|
age_group |
string |
年龄组 |
25-34 |
|
gender |
string |
性别 |
female |
|
region |
string |
地区 |
华东 |
|
purchase_count |
int |
购买次数 |
3 |
|
avg_price |
float |
客单价 |
156.8 |
|
sentiment |
string |
情感标签 |
positive |
|
sentiment_score |
float |
情感得分 |
0.75 |
4.2 数据来源
参考阿里云天池数据集:电商直播用户行为数据集(https://tianchi.aliyun.com/dataset/124814)
五、Web界面设计
5.1 界面布局
采用现代化的顶部导航设计,替代传统侧边栏,包含概览、情感分析、评分分析、关键词分析、用户画像、竞品分析、预警系统、报告中心、智能问答和数据管理十个功能模块。
5.2 设计风格
界面采用毛玻璃现代UI设计,背景使用蓝紫色渐变,卡片和导航标签悬停时上浮并增强阴影,所有交互使用平滑过渡动画。
六、功能模块详解
6.1 概览页面
展示核心业务指标,包括总评价数、平均评分、正面评价率、负面评价率、加购率、购买转化率等关键指标。
6.2 情感分析页面
- 情感分布饼图
- 情感得分直方图
- 正负向评价示例
- 时间序列情感趋势
6.3 用户画像页面
- 用户等级分布
- 年龄/性别分布
- 用户聚类结果
- 各用户群特征分析
6.4 智能问答页面
基于RAG的问答系统,支持两种模式:ChromaDB向量数据库检索(需安装chromadb)和TF-IDF传统文本匹配(回退方案)。
七、部署说明
7.1 环境依赖
pip install streamlit pandas numpy scikit-learn
pip install plotly wordcloud jieba
pip install chromadb
7.2 启动命令
cd ecommerce_review_analysis
python -m streamlit run app.py --server.port 8501
八、技术亮点
8.1 系统健壮性
- 依赖容错:chromadb不可用时自动切换到TF-IDF模式
- 聚类容错:KMeans失败时自动使用分位数分组回退方案
- 数据兼容:动态列检测,支持不同数据源格式
8.2 隐私保护
- 用户ID脱敏处理
- 评论内容敏感信息过滤
- 数据本地存储,不上传第三方服务
8.3 用户体验
- 毛玻璃现代UI设计
- 响应式布局,支持移动端
- 平滑动画过渡效果
- 顶部横向导航,操作便捷
九、总结与展望
9.1 已完成功能
✓ 多平台数据采集与导入 ✓ 文本清洗与预处理 ✓ 基于词典的情感分析
✓ 用户画像聚类分析 ✓ 数据可视化仪表盘 ✓ RAG智能问答系统
9.2 未来优化方向
- 引入BERT预训练模型提升情感分析精度
- 增加实时数据监控功能
- 支持多维度自定义报表
- 移动端App开发
十、附录
10.1 项目结构
ecommerce_review_analysis/
├── app.py # Streamlit主应用
├── data/raw/sample_reviews.csv # 样本数据
├── src/analysis/ # 分析模块
├── src/preprocessing/ # 预处理模块
├── src/rag/ # RAG系统
└── src/crawler/ # 爬虫模块
10.2 运行环境要求
|
配置项 |
要求 |
|
操作系统 |
Windows/Linux/macOS |
|
Python版本 |
3.10+ |
|
内存 |
8GB+ |
|
磁盘空间 |
10GB+ |
10.3完整代码展示
"""
================================================================================
电商用户评价智能分析系统 - Streamlit 主程序
E-commerce Review Intelligence Analysis System
================================================================================
【模块定位】
本模块是整个系统的入口,负责:
1. 初始化 Streamlit Web 界面(页面配置、自定义样式)
2. 管理 10 个分析页面的路由分发
3. 协调各分析模块完成数据处理和可视化展示
【架构说明】
采用分层架构:展示层(本文件)→ 分析引擎层(src/analysis/)→ 数据层(src/preprocessing/ + src/crawler/)
同时可选接入 AI 服务层(src/models/、src/rag/)
【Streamlit 运行模型】
Streamlit 采用"脚本式"运行模型:每次用户交互都会从头到尾重新执行整个脚本。
因此使用 @st.cache_resource 和 @st.cache_data 来优化性能。
【适合人群】
适合学习 Web 数据应用开发、Python 数据分析可视化的学生和开发者。
"""
# ============================================================================
# 第一部分:模块导入
# ============================================================================
import sys
import os
import json
from pathlib import Path
from collections import Counter
from typing import Dict
import pandas as pd
import matplotlib.pyplot as plt
import streamlit as st
sys.path.append(str(Path(__file__).parent))
from config import *
from src.crawler.review_crawler import load_sample_data, CrawlerManager
from src.preprocessing.data_processor import process_pipeline
from src.analysis.sentiment_analyzer import SentimentAnalyzer
from src.analysis.user_profile_analyzer import UserProfileAnalyzer
from src.analysis.visualizer import (
SentimentVisualizer,
RatingVisualizer,
KeywordVisualizer,
UserProfileVisualizer,
)
from src.analysis.business_intelligence import BusinessIntelligence, AlertSystem, ReportGenerator
from src.rag.rag_system import IntelligentQA
# ============================================================================
# 第二部分:Streamlit 页面全局配置
# ============================================================================
def setup_page_config():
st.set_page_config(page_title="电商评价分析系统", page_icon="📊", layout="wide", initial_sidebar_state="collapsed")
# ============================================================================
# 第三部分:自定义 CSS 样式
# ============================================================================
def inject_custom_css():
st.markdown(
"""<style>
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family: 'PingFang SC','Microsoft YaHei',sans-serif; background: linear-gradient(135deg, #f0f4ff, #e8f0fe, #f0f9ff); min-height: 100vh; color: #1e293b; }
.header { background: rgba(255,255,255,0.95); backdrop-filter: blur(20px); padding: 20px 40px; position: sticky; top: 0; z-index: 100; }
.header-content { display: flex; align-items: center; gap: 20px; max-width: 1400px; margin: 0 auto; }
.header-title { font-size: 1.65rem; font-weight: 700; background: linear-gradient(135deg,#1e293b,#475569); background-clip: text; -webkit-background-clip: text; color: transparent; }
.header-subtitle { font-size: 0.85rem; color: #64748b; }
.guide-box { background: linear-gradient(135deg,#fff,#f8fafc); border:1px solid #e2e8f0; padding: 20px 24px; border-radius:16px; margin-bottom:24px; border-left:4px solid #3b82f6; }
.guide-box h3 { color: #1e293b; font-size:1rem; margin-bottom:8px; }
.guide-box p { color: #64748b; font-size:0.88rem; line-height:1.7; }
[data-testid='stRadio'] { flex-wrap:nowrap !important; overflow-x:auto; gap:8px; width:100%; display:flex !important; }
[data-testid='stRadio'] label { white-space:nowrap; padding:12px 24px; border-radius:12px; background:rgba(255,255,255,0.8); color:#64748b; font-size:0.9rem; font-weight:500; transition:all 0.3s; border:1px solid rgba(226,232,240,0.6); }
[data-testid='stRadio'] label:hover { background:rgba(59,130,246,0.06); color:#3b82f6; transform:translateY(-2px); }
[data-testid='stRadio'] [role='radio']:checked + label { background:linear-gradient(135deg,#3b82f6,#2563eb); color:white; }
.content-area { padding:24px 40px; max-width:1400px; margin:0 auto; }
.metric-card { background:rgba(255,255,255,0.95); padding:24px; border-radius:20px; box-shadow:0 4px 16px rgba(0,0,0,0.04); border:1px solid rgba(226,232,240,0.5); border-top:4px solid; transition:all 0.3s; }
.metric-card:hover { transform:translateY(-5px); }
.metric-label { font-size:0.78rem; color:#94a3b8; text-transform:uppercase; font-weight:600; }
.metric-value { font-size:2.4rem; font-weight:800; color:#1e293b; line-height:1; }
.chart-card { background:white; padding:20px; border-radius:16px; box-shadow:0 4px 12px rgba(0,0,0,0.05); border:1px solid #f1f5f9; }
.chart-title { font-size:1rem; font-weight:600; color:#1e293b; border-left:4px solid #0284c7; padding-left:12px; margin-bottom:16px; }
.insight-card { background:#f0f9ff; padding:16px; border-radius:10px; border-left:4px solid #0284c7; margin-bottom:12px; }
.section-title { font-size:1.3rem; font-weight:700; color:#1e293b; margin-bottom:20px; border-bottom:3px solid #0284c7; padding-bottom:10px; }
.stSidebar { display:none !important; }
</style>""",
unsafe_allow_html=True,
)
# ============================================================================
# 第四部分:页面路由映射
# ============================================================================
PAGES: Dict[str, str] = {
"📊 概览": "overview",
"🎭 情感分析": "sentiment",
"⭐ 评分分析": "rating",
"🔑 关键词": "keyword",
"👤 用户画像": "user",
"⚔️ 竞品分析": "competitor",
"🚨 预警系统": "alert",
"📋 报告中心": "report",
"🤖 智能问答": "qa",
"📁 数据管理": "data",
}
# ============================================================================
# 第五部分:页面引导文档字符串
# ============================================================================
OVERVIEW_GUIDE = """
<div class="guide-box">
<h3>📊 概览页面</h3>
<p>系统核心仪表板,集中展示评价总量、平均评分、情感分布、用户活跃度等关键指标。</p>
</div>
"""
SENTIMENT_GUIDE = """
<div class="guide-box">
<h3>🎭 情感分析</h3>
<p>通过NLP技术自动识别评价的情感倾向(正面/中性/负面),支持按时间维度观察情感趋势。</p>
</div>
"""
COMPETITOR_GUIDE = """
<div class="guide-box">
<h3>⚔️ 竞品分析</h3>
<p>多维度对比分析:平台对比、品类分析、产品对比,了解市场竞争格局。</p>
</div>
"""
ALERT_GUIDE = """
<div class="guide-box">
<h3>🚨 预警系统</h3>
<p>实时监控评价数据,自动发现负面激增、评分骤降等异常信号。</p>
</div>
"""
QA_GUIDE = """
<div class="guide-box">
<h3>🤖 智能问答</h3>
<p>基于RAG技术,用自然语言探索数据洞察,自动检索相关评价并生成回答。</p>
</div>
"""
# ============================================================================
# 第六部分:缓存初始化与数据加载
# ============================================================================
@st.cache_resource
def init_analyzers():
"""初始化所有分析器实例。"""
return {
"sentiment": SentimentAnalyzer(),
"user_profile": UserProfileAnalyzer(),
"bi": BusinessIntelligence(),
"alert": AlertSystem(),
"report": ReportGenerator(),
}
@st.cache_data(show_spinner="正在加载和预处理数据...")
def load_data():
"""加载并预处理数据。"""
df = load_sample_data()
df = process_pipeline(df)
analyzer = SentimentAnalyzer()
df = analyzer.analyze_dataframe(df)
return df
def mask_data(df):
"""数据脱敏处理。"""
masked_df = df.copy()
if "user_id" in masked_df.columns:
masked_df["user_id"] = masked_df["user_id"].apply(
lambda x: str(x)[:3] + "***" if isinstance(x, str) and len(str(x)) > 3 else "***"
)
# Convert list/array columns to strings for Streamlit display
for col in masked_df.columns:
if masked_df[col].dtype == object:
masked_df[col] = masked_df[col].apply(
lambda v: ", ".join(str(item) for item in v) if isinstance(v, (list, tuple, set))
else (str(v) if pd.notna(v) else "")
)
return masked_df
def render_header():
"""渲染页面标题栏。"""
st.markdown(
"""<div class="header"><div class="header-content">
<div class="header-logo">📊</div>
<div class="header-text">
<div class="header-title">电商用户评价智能分析系统</div>
<div class="header-subtitle">基于 AI 的商业智能分析平台</div>
</div></div></div>""",
unsafe_allow_html=True,
)
def render_navigation():
"""渲染导航标签栏。"""
selected = st.radio("导航", list(PAGES.keys()), index=0, horizontal=True, label_visibility="collapsed")
st.session_state.page = PAGES[selected]
# ============================================================================
# 第七部分:页面渲染函数
# ============================================================================
def render_overview(df, analyzers):
st.markdown('<div class="content-area">', unsafe_allow_html=True)
st.markdown(OVERVIEW_GUIDE, unsafe_allow_html=True)
st.markdown('<h2 class="section-title">📊 概览</h2>', unsafe_allow_html=True)
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("评价总数", len(df))
with col2:
avg_rating = df["rating"].mean() if "rating" in df.columns else 0
st.metric("平均评分", f"{avg_rating:.2f}")
with col3:
if "sentiment" in df.columns:
pos_ratio = (df["sentiment"] == "positive").mean() * 100
st.metric("正面评价比", f"{pos_ratio:.1f}%")
with col4:
if "user_id" in df.columns:
st.metric("涉及用户", df["user_id"].nunique())
col1, col2 = st.columns(2)
with col1:
st.markdown('<div class="chart-card"><div class="chart-title">情感分布</div>', unsafe_allow_html=True)
if "sentiment" in df.columns:
fig = SentimentVisualizer.plot_sentiment_distribution(df)
st.plotly_chart(fig, use_container_width=True)
st.markdown("</div>", unsafe_allow_html=True)
with col2:
st.markdown('<div class="chart-card"><div class="chart-title">评分分布</div>', unsafe_allow_html=True)
if "rating" in df.columns:
fig = RatingVisualizer.plot_rating_distribution(df)
st.plotly_chart(fig, use_container_width=True)
st.markdown("</div>", unsafe_allow_html=True)
if "sentiment" in df.columns and "time" in df.columns:
st.markdown('<div class="chart-card"><div class="chart-title">情感趋势</div>', unsafe_allow_html=True)
fig = SentimentVisualizer.plot_sentiment_trend(df)
st.plotly_chart(fig, use_container_width=True)
st.markdown("</div>", unsafe_allow_html=True)
insights = analyzers["bi"].get_actionable_insights(df) if "bi" in analyzers else []
if insights:
st.markdown('<h3 class="section-title">💡 商业洞察</h3>', unsafe_allow_html=True)
for insight in insights:
st.markdown(f'<div class="insight-card">{insight}</div>', unsafe_allow_html=True)
st.markdown("</div>", unsafe_allow_html=True)
def render_sentiment(df, analyzers):
st.markdown('<div class="content-area">', unsafe_allow_html=True)
st.markdown(SENTIMENT_GUIDE, unsafe_allow_html=True)
st.markdown('<h2 class="section-title">🎭 情感分析</h2>', unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
st.markdown('<div class="chart-card"><div class="chart-title">情感分布</div>', unsafe_allow_html=True)
fig = SentimentVisualizer.plot_sentiment_distribution(df)
st.plotly_chart(fig, use_container_width=True)
st.markdown("</div>", unsafe_allow_html=True)
with col2:
st.markdown('<div class="chart-card"><div class="chart-title">情感统计</div>', unsafe_allow_html=True)
if "sentiment" in df.columns:
s_counts = df["sentiment"].value_counts()
st.write(f'正面: {s_counts.get("positive",0)}条')
st.write(f'中性: {s_counts.get("neutral",0)}条')
st.write(f'负面: {s_counts.get("negative",0)}条')
st.markdown("</div>", unsafe_allow_html=True)
if "time" in df.columns:
st.markdown('<div class="chart-card"><div class="chart-title">情感趋势</div>', unsafe_allow_html=True)
fig = SentimentVisualizer.plot_sentiment_trend(df)
st.plotly_chart(fig, use_container_width=True)
st.markdown("</div>", unsafe_allow_html=True)
st.markdown("</div>", unsafe_allow_html=True)
def render_rating(df):
st.markdown('<div class="content-area">', unsafe_allow_html=True)
st.markdown('<h2 class="section-title">⭐ 评分分析</h2>', unsafe_allow_html=True)
# 第一行:核心指标卡片
if 'rating' in df.columns:
ratings = df['rating']
avg_r = ratings.mean()
med_r = ratings.median()
std_r = ratings.std()
star5_ratio = (ratings == 5).mean() * 100
total = len(ratings)
col_avg, col_med, col_std, col_star5, col_total = st.columns(5)
with col_avg: st.metric('🏆 平均评分', f"{avg_r:.2f}", delta=f"{'%+.2f' % (avg_r - 3.0)}")
with col_med: st.metric('🔢 中位数', f"{med_r:.1f}")
with col_std: st.metric('📊 标准差', f"{std_r:.2f}")
with col_star5: st.metric('⭐ 5星占比', f"{star5_ratio:.1f}%")
with col_total: st.metric('📝 评价总数', f"{int(total)}")
st.divider()
# 第一排:评分分布 + 评分占比
col1, col2 = st.columns(2)
with col1:
st.markdown('<div class="chart-card"><div class="chart-title">📊 评分分布</div>', unsafe_allow_html=True)
if 'rating' in df.columns:
fig = RatingVisualizer.plot_rating_distribution(df)
st.plotly_chart(fig, use_container_width=True)
st.markdown('</div>', unsafe_allow_html=True)
with col2:
st.markdown('<div class="chart-card"><div class="chart-title">🍰 各评分级别占比</div>', unsafe_allow_html=True)
if 'rating' in df.columns:
rc = df['rating'].value_counts().sort_index()
import plotly.graph_objects as go
colors = ['#e74c3c','#e67e22','#f1c40f','#2ecc71','#27ae60']
fig2 = go.Figure(data=[go.Pie(
labels=[f"{i}分" for i in rc.index],
values=rc.values,
marker_colors=[colors[i-1] for i in rc.index],
textinfo='label+percent',
hole=0.4,
)])
fig2.update_layout(height=380, margin=dict(t=0,b=0,l=0,r=0))
st.plotly_chart(fig2, use_container_width=True)
st.markdown('</div>', unsafe_allow_html=True)
# 第二排:评分趋势 + 各类别评分
col3, col4 = st.columns(2)
with col3:
st.markdown('<div class="chart-card"><div class="chart-title">📈 评分趋势</div>', unsafe_allow_html=True)
if 'time' in df.columns and 'rating' in df.columns:
try:
trend = df.copy()
trend['time'] = pd.to_datetime(trend['time'])
trend = trend.set_index('time').resample('ME')['rating'].mean().reset_index()
fig3 = go.Figure(data=[go.Scatter(
x=trend['time'], y=trend['rating'],
mode='lines+markers',
line=dict(color='#3498db', width=3),
marker=dict(size=8, color='#3498db'),
fill='tozeroy', fillcolor='rgba(52,152,219,0.1)',
)])
fig3.update_layout(
xaxis_title='时间', yaxis_title='平均评分',
yaxis_range=[0, 5], height=380
)
st.plotly_chart(fig3, use_container_width=True)
except Exception:
st.info('时间格式无法解析,无法显示趋势')
st.markdown('</div>', unsafe_allow_html=True)
with col4:
st.markdown('<div class="chart-card"><div class="chart-title">📊 各类别平均评分</div>', unsafe_allow_html=True)
if 'category' in df.columns and 'rating' in df.columns:
fig4 = RatingVisualizer.plot_rating_by_category(df)
st.plotly_chart(fig4, use_container_width=True)
st.markdown('</div>', unsafe_allow_html=True)
# 第三部分:评分统计表
if 'rating' in df.columns:
stats = df['rating'].describe()
st.markdown('<div class="chart-card"><div class="chart-title">📊 评分统计详情</div>', unsafe_allow_html=True)
sc1, sc2, sc3, sc4 = st.columns(4)
with sc1:
st.metric('𝗔数', f"{int(stats['count'])}")
st.metric('最小值', f"{stats['min']:.1f}")
with sc2:
st.metric('平均值', f"{stats['mean']:.2f}")
st.metric('25%位数', f"{stats['25%']:.1f}")
with sc3:
st.metric('中位数', f"{stats['50%']:.1f}")
st.metric('75%位数', f"{stats['75%']:.1f}")
with sc4:
st.metric('标准差', f"{stats['std']:.2f}")
st.metric('最大值', f"{stats['max']:.1f}")
st.markdown('</div>', unsafe_allow_html=True)
# 各评分级别详细统计
if 'rating' in df.columns:
st.markdown('<div class="chart-card"><div class="chart-title">📋 各评分级别统计</div>', unsafe_allow_html=True)
level_counts = df['rating'].value_counts().sort_index()
total = len(df)
data_rows = []
for r in range(1, 6):
cnt = level_counts.get(r, 0)
pct = cnt / total * 100
data_rows.append({'rating': f"{r}分", 'count': cnt, 'ratio': f"{pct:.1f}%"})
st.table(pd.DataFrame(data_rows).set_index('rating'))
st.markdown('</div>', unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
def render_keyword(df):
st.markdown('<div class="content-area">', unsafe_allow_html=True)
st.markdown('<h2 class="section-title">🔑 关键词</h2>', unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
st.markdown('<div class="chart-card"><div class="chart-title">词云</div>', unsafe_allow_html=True)
if "cleaned_content" in df.columns:
all_text = " ".join(df["cleaned_content"].dropna().tolist())
if all_text:
wc = KeywordVisualizer.generate_wordcloud(all_text)
if wc:
fig, ax = plt.subplots(figsize=(10, 5))
ax.imshow(wc, interpolation="bilinear")
ax.axis("off")
st.pyplot(fig, use_container_width=True)
st.markdown("</div>", unsafe_allow_html=True)
with col2:
st.markdown('<div class="chart-card"><div class="chart-title">高频关键词</div>', unsafe_allow_html=True)
if "tokens" in df.columns:
from collections import Counter
all_words = [w for tokens in df["tokens"].dropna() for w in tokens]
word_freq = Counter(all_words).most_common(20)
fig = KeywordVisualizer.plot_top_keywords(dict(word_freq))
st.plotly_chart(fig, use_container_width=True)
st.markdown("</div>", unsafe_allow_html=True)
st.markdown("</div>", unsafe_allow_html=True)
def render_user_profile(df, analyzers):
st.markdown('<div class="content-area">', unsafe_allow_html=True)
st.markdown('<h2 class="section-title">👤 用户画像</h2>', unsafe_allow_html=True)
user_analyzer = analyzers["user_profile"]
user_features = user_analyzer.extract_user_features(df)
if not user_features.empty:
user_features = user_analyzer.cluster_users(user_features)
col1, col2 = st.columns(2)
with col1:
st.markdown('<div class="chart-card"><div class="chart-title">用户聚类</div>', unsafe_allow_html=True)
fig = UserProfileVisualizer.plot_user_clusters(user_features)
st.plotly_chart(fig, use_container_width=True)
st.markdown("</div>", unsafe_allow_html=True)
with col2:
profiles = user_analyzer.get_cluster_profiles(user_features)
if profiles:
st.markdown('<div class="chart-card"><div class="chart-title">群体分布</div>', unsafe_allow_html=True)
fig = UserProfileVisualizer.plot_cluster_distribution(profiles)
st.plotly_chart(fig, use_container_width=True)
st.markdown("</div>", unsafe_allow_html=True)
behavior = user_analyzer.analyze_user_behavior(df)
st.markdown('<div class="chart-card"><div class="chart-title">用户行为统计</div>', unsafe_allow_html=True)
st.write(f'总用户: {behavior.get("total_users",0)}, 人均评价: {behavior.get("avg_reviews_per_user",0)}')
st.markdown("</div>", unsafe_allow_html=True)
else:
st.info("数据中没有 user_id 列,无法进行用户画像分析")
st.markdown("</div>", unsafe_allow_html=True)
def render_competitor(df, analyzers):
st.markdown('<div class="content-area">', unsafe_allow_html=True)
st.markdown(COMPETITOR_GUIDE, unsafe_allow_html=True)
st.markdown('<h2 class="section-title">⚔️ 竞品分析</h2>', unsafe_allow_html=True)
tab1, tab2, tab3 = st.tabs(["平台对比", "品类分析", "产品对比"])
with tab1:
if "platform" in df.columns:
p_stats = (
df.groupby("platform")
.agg(avg_rating=("rating", "mean"), review_count=("rating", "count"))
.round(2)
.reset_index()
)
col1, col2 = st.columns(2)
with col1:
st.bar_chart(p_stats, x="platform", y="review_count")
with col2:
st.bar_chart(p_stats, x="platform", y="avg_rating")
st.dataframe(p_stats, use_container_width=True)
else:
st.info("数据中没有平台信息")
with tab2:
if "category" in df.columns:
c_stats = (
df.groupby("category")
.agg(avg_rating=("rating", "mean"), review_count=("rating", "count"))
.round(2)
.reset_index()
.sort_values("review_count", ascending=False)
)
col1, col2 = st.columns(2)
with col1:
st.bar_chart(c_stats, x="category", y="review_count")
with col2:
st.bar_chart(c_stats, x="category", y="avg_rating")
st.dataframe(c_stats, use_container_width=True)
else:
st.info("数据中没有类别信息")
with tab3:
if "product_id" in df.columns:
pids = df["product_id"].unique()[:10]
sel = st.multiselect("选择产品", pids, default=pids[:3])
if sel:
pdf = (
df[df["product_id"].isin(sel)]
.groupby("product_id")
.agg(
avg_rating=("rating", "mean"),
count=("rating", "count"),
pos_ratio=("sentiment", lambda x: (x == "positive").sum() / len(x) if len(x) > 0 else 0),
)
.round(2)
)
st.dataframe(pdf, use_container_width=True)
col1, col2 = st.columns(2)
with col1:
st.bar_chart(pdf, x=pdf.index, y="avg_rating")
with col2:
st.bar_chart(pdf, x=pdf.index, y="pos_ratio")
else:
st.info("数据中没有产品信息")
st.markdown("</div>", unsafe_allow_html=True)
def render_alert(df, analyzers):
st.markdown('<div class="content-area">', unsafe_allow_html=True)
st.markdown(ALERT_GUIDE, unsafe_allow_html=True)
st.markdown('<h2 class="section-title">🚨 预警系统</h2>', unsafe_allow_html=True)
report = analyzers["alert"].generate_alert_report(df)
col1, col2, col3 = st.columns(3)
with col1:
st.metric("预警总数", report["total_alerts"])
with col2:
st.metric("高级预警", report["high_level"])
with col3:
st.metric("中级预警", report["medium_level"])
if report["alerts"]:
for a in report["alerts"]:
level = "⚠️" if a["level"] == "high" else "⚠️"
st.warning(f"{level} {a['message']}")
else:
st.success("暂无预警,系统运行正常")
st.markdown("</div>", unsafe_allow_html=True)
def render_report(df, analyzers):
st.markdown('<div class="content-area">', unsafe_allow_html=True)
st.markdown('<h2 class="section-title">📋 报告中心</h2>', unsafe_allow_html=True)
summary = analyzers["report"].generate_summary_report(df)
charts = analyzers["report"].generate_charts(df)
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("评价总数", summary["total_reviews"])
with col2:
st.metric("涉及用户", summary["total_users"])
with col3:
st.metric("涉及商品", summary["total_products"])
with col4:
avg = summary.get("rating_stats", {}).get("average", 0)
st.metric("平均评分", avg)
if charts:
names = list(charts.keys())
sel = st.multiselect("选择图表", names, default=names)
for n in sel:
st.plotly_chart(charts[n], use_container_width=True)
detail = analyzers["report"].generate_detailed_report(df)
st.download_button("下载报告", detail, "report.txt")
st.markdown("</div>", unsafe_allow_html=True)
def render_qa(df):
st.markdown('<div class="content-area">', unsafe_allow_html=True)
st.markdown(QA_GUIDE, unsafe_allow_html=True)
st.markdown('<h2 class="section-title">🤖 智能问答</h2>', unsafe_allow_html=True)
@st.cache_resource
def init_qa_system():
qa = IntelligentQA()
qa.initialize(df.head(500))
return qa
qa = init_qa_system()
questions = [
"用户对产品的整体满意度如何?",
"用户反映的主要问题有哪些?",
"有什么改违建议?",
"物流服务评价如何?",
"产品质量怎么样?",
]
col1, col2 = st.columns([2, 1])
with col1:
question = st.text_area("请输入您的问题:", height=80, key="qa_question")
with col2:
for q in questions:
if st.button(q, key=q):
st.session_state.qa_question = q
st.rerun()
if st.button("提交问题", type="primary", use_container_width=True):
if st.session_state.qa_question:
with st.spinner("正在分析..."):
result = qa.ask(st.session_state.qa_question)
st.markdown(f'<div class="insight-card">{result["answer"]}</div>', unsafe_allow_html=True)
with st.expander("查看相关评价"):
if result.get("sources", {}).get("documents", [[]])[0]:
for doc in result["sources"]["documents"][0][:5]:
st.write(doc)
else:
st.warning("请输入问题")
st.markdown("</div>", unsafe_allow_html=True)
def render_data_management(df):
import traceback
try:
st.markdown('<div class="content-area">', unsafe_allow_html=True)
st.markdown('<h2 class="section-title">\U0001f4c1 \u6570\u636e\u7ba1\u7406</h2>', unsafe_allow_html=True)
tab1, tab2, tab3, tab4 = st.tabs(['\u6570\u636e\u9884\u89c8', '\u6570\u636e\u7edf\u8ba1', '\u6570\u636e\u5bfc\u5165', '\u7cfb\u7edf\u8bbe\u7f6e'])
with tab1:
st.markdown('<div class="table-container">', unsafe_allow_html=True)
st.dataframe(mask_data(df), use_container_width=True)
st.markdown('</div>', unsafe_allow_html=True)
st.info('\U0001f512 \u5f53\u524d\u5904\u4e8e\u6570\u636e\u8131\u654f\u6a21\u5f0f\uff0c\u654f\u611f\u4fe1\u606f\u5df2\u88ab\u9690\u85cf')
with tab2:
st.write('\u6570\u636e\u5f62\u72b6:', df.shape)
st.write('\u5217\u540d:', list(df.columns))
st.write('\u6570\u636e\u7c7b\u578b:')
st.write(df.dtypes)
st.write('\u7f3a\u5931\u503c\u7edf\u8ba1:')
st.write(df.isnull().sum())
with tab3:
uploaded = st.file_uploader('\u4e0a\u4f20CSV/Excel\u6587\u4ef6', type=['csv','xlsx','xls'], key='dm_upload')
if uploaded:
try:
if uploaded.name.endswith('.csv'):
new_df = pd.read_csv(uploaded, encoding='utf-8-sig')
else:
new_df = pd.read_excel(uploaded)
st.success(f'\u6210\u529f\u5bfc\u5165 {len(new_df)} \u6761\u6570\u636e')
st.dataframe(mask_data(new_df).head())
except Exception as e:
st.error(f'\u5bfc\u5165\u5931\u8d25: {e}')
st.markdown('### \u722c\u866b\u91c7\u96c6')
platform = st.selectbox('\u9009\u62e9\u5e73\u53f0', ['\u4eac\u4e1c','\u62fc\u591a\u591a','\u82cf\u5b81'], key='dm_platform')
pid = st.text_input('\u8f93\u5165\u5546\u54c1ID', key='dm_pid')
pages = st.slider('\u722c\u53d6\u9875\u6570', 1, 20, 5, key='dm_pages')
if st.button('\u5f00\u59cb\u722c\u53d6', key='dm_crawl'):
if pid:
with st.spinner('\u6b63\u5728\u722c\u53d6...'):
cm = CrawlerManager()
pm = {'\u4eac\u4e1c':'jd','\u62fc\u591a\u591a':'pinduoduo','\u82cf\u5b81':'suning'}
try:
ndf = cm.crawl(pm[platform], pid, pages)
if not ndf.empty:
st.success(f'\u722c\u53d6\u6210\u529f\uff0c\u83b7\u53d6 {len(ndf)} \u6761\u8bc4\u4ef7')
st.dataframe(mask_data(ndf).head())
else:
st.warning('\u672a\u83b7\u53d6\u5230\u8bc4\u4ef7\u6570\u636e')
except Exception as e:
st.error(f'\u722c\u53d6\u5931\u8d25: {e}')
else:
st.warning('\u8bf7\u8f93\u5165\u5546\u54c1ID')
with tab4:
st.markdown('### \U0001f4ca \u5206\u6790\u914d\u7f6e')
model = st.selectbox('\u9ed8\u8ba4\u60c5\u611f\u5206\u6790\u6a21\u578b', ['\u89c4\u5219\u5f15\u64ce','BERT\u6a21\u578b'], index=0, key='dm_model')
min_cluster = st.slider('\u7528\u6237\u805a\u7c7b\u6700\u5c0f\u7fa4\u4f53\u5927\u5c0f', 2, 50, 5, key='dm_cluster')
st.markdown('### \U0001f514 \u9884\u8b66\u914d\u7f6e')
neg_thresh = st.slider('\u8d1f\u9762\u8bc4\u4ef7\u9884\u8b66\u9608\u503c(%)', 10, 50, 30, key='dm_neg')
rating_thresh = st.slider('\u4f4e\u8bc4\u5206\u9884\u8b66\u9608\u503c', 1.0, 4.0, 3.0, key='dm_rating')
st.markdown('### \U0001f4cb \u5f53\u524d\u914d\u7f6e')
st.json({
'\u60c5\u611f\u6a21\u578b': model,
'\u805a\u7c7b\u9608\u503c': min_cluster,
'\u8d1f\u9762\u9884\u8b66': f'{neg_thresh}%',
'\u8bc4\u5206\u9884\u8b66': rating_thresh,
})
if st.button('\u4fdd\u5b58\u914d\u7f6e', key='dm_save'):
cfg = {'model': model, 'cluster': min_cluster, 'neg_thresh': neg_thresh, 'rating_thresh': rating_thresh}
p = os.path.join(os.path.dirname(__file__), 'user_config.json')
with open(p, 'w', encoding='utf-8') as fh:
json.dump(cfg, fh, ensure_ascii=False, indent=2)
st.success('\u914d\u7f6e\u5df2\u4fdd\u5b58')
st.markdown('</div>', unsafe_allow_html=True)
except Exception as e:
st.error(f'\u6570\u636e\u7ba1\u7406\u9875\u9762\u51fa\u9519: {e}')
st.error(traceback.format_exc())
def main():
setup_page_config()
inject_custom_css()
if "page" not in st.session_state:
st.session_state.page = "overview"
df = load_data()
analyzers = init_analyzers()
render_header()
render_navigation()
page = st.session_state.page
if page == "overview":
render_overview(df, analyzers)
elif page == "sentiment":
render_sentiment(df, analyzers)
elif page == "rating":
render_rating(df)
elif page == "keyword":
render_keyword(df)
elif page == "user":
render_user_profile(df, analyzers)
elif page == "competitor":
render_competitor(df, analyzers)
elif page == "alert":
render_alert(df, analyzers)
elif page == "report":
render_report(df, analyzers)
elif page == "qa":
render_qa(df)
elif page == "data":
render_data_management(df)
# ============================================================================
# 第九部分:程序入口
# ============================================================================
if __name__ == "__main__":
main()
更多推荐



所有评论(0)