本文还有配套的精品资源,点击获取
简介:一个开箱即用的大数据推荐系统教学实践项目,后端用Java+Spark处理商品数据,支持MySQL连接、商品标题等文本的TF-IDF向量化,以及用户和商品维度的KMeans聚类;前端通过Python Flask提供轻量API接口,调用pyecharts渲染动态可视化图表,包含登录页、结果展示页等完整Web功能。项目结构清晰,含utils工具模块、dbtool数据库操作封装、templates页面模板、static静态资源、data样例数据(如advdata1_small.txt),以及详细本地运行说明。所有代码已在常见开发环境验证通过,启动简单,依赖明确(见requirements.txt和pom.xml)。适合高校大数据、人工智能、计算机相关专业用于课程实验、期末设计或毕设基础框架,后续可便捷接入协同过滤、实时流推荐等扩展模块。
1. 项目概述:这不是一个“玩具系统”,而是一套能跑通真实数据链路的教学级推荐工程
你有没有带过大数据课程实验?或者自己做过期末大作业,对着网上零散的“Spark协同过滤demo”和“Flask Hello World”拼凑一晚上,最后发现用户行为日志读不进RDD、TF-IDF向量维度对不上KMeans输入、pyecharts图表在模板里死活不渲染……这种挫败感我太熟悉了。这个项目,就是我连续三年在高校《大数据分析与应用》《智能推荐系统导论》两门课上,带着学生从零搭起、反复打磨、最终稳定交付的一套教学级推荐系统工程骨架。它不追求工业级吞吐或毫秒级响应,但每一步都踩在教学关键点上:MySQL连接不是为了存配置,而是让学生亲手看到结构化商品表如何与非结构化标题文本联动;TF-IDF不是调个sklearn函数就完事,而是用Spark MLlib原生实现,暴露词频统计、逆文档频率计算、稀疏向量构建的完整链条;KMeans聚类不是只跑出标签,而是强制要求学生对比不同K值下肘部法则曲线、轮廓系数变化,并把聚类结果反查回原始商品库生成可解释的“爆款集群”“长尾小众集群”;Flask API不是简单返回JSON,而是设计成前后端职责清晰的轻量胶水层——后端Spark模块专注计算,前端只管展示,中间用标准HTTP协议解耦。关键词里的“Spark推荐”“Flask接口”“TF-IDF聚类”“商品推荐系统”“pyecharts可视化”,每一个都不是标签,而是学生必须亲手敲代码、调参数、看日志、改bug才能打通的实操节点。它面向的是计算机、数据科学、人工智能专业的本科生,目标很实在:两周内能本地跑通、理解主干逻辑、修改样例数据生成自己的推荐结果、并基于此延展出课程设计报告。所以你看不到Docker编排、K8s部署、Redis缓存这些炫技内容,但你会看到pom.xml里精确到小版本的spark-sql_2.12依赖、requirements.txt中指定pyecharts==1.9.1(因为2.x版本API大改导致模板渲染失败)、templates/login.html里一个被注释掉的<script src="https://cdn.jsdelivr.net/npm/echarts@5"></script>——那是我帮学生踩过的坑,现在直接写进源码注释里。这不是一个“给你代码你就能用”的黑盒,而是一个“给你代码你必须动脑筋才能跑通”的教学沙盒。
2. 整体架构与设计思路:为什么是Java+Spark+Flask+pyecharts这个组合?
2.1 分层解耦:计算归计算,服务归服务,展示归展示
很多初学者一上来就想用Python一把梭:Pandas读数据、Scikit-learn做TF-IDF、KMeans聚类、再用Flask返回结果。这在小数据量下看似可行,但一旦数据量涨到几万条商品、几十万用户行为,内存OOM、单线程瓶颈、特征向量维度爆炸等问题立刻暴露。本项目采用明确分层、语言各司其职的设计:
数据处理层(Java + Spark):承担所有重计算任务。Spark的分布式内存计算模型天然适合大规模文本向量化与聚类。Java作为强类型语言,在编写Spark Job时能提前捕获大量运行时错误(比如RDD类型不匹配、UDF注册失败),这对教学场景至关重要——学生能第一时间看到编译报错,而不是在Spark UI里苦等十分钟才看到Executor OOM。更重要的是,Spark SQL与JDBC的集成成熟稳定,MySQL连接池配置、批量写入优化、字段类型映射(如MySQL的TEXT字段对应Spark的StringType)都有标准实践可循,避免学生陷入“为什么DataFrame读出来全是null”的泥潭。
服务接口层(Python + Flask):不做任何计算,只做三件事:接收HTTP请求、调用已编译好的Spark Jar包(通过subprocess或REST API桥接)、将计算结果格式化为JSON或HTML片段返回。Flask轻量、无侵入、路由定义直观(
@app.route('/recommend', methods=['POST'])),学生能快速理解“请求-响应”模型,把精力聚焦在业务逻辑而非框架语法上。它不碰数据,不碰模型,纯粹是“管道工”。可视化层(pyecharts + Jinja2模板):pyecharts的核心价值在于声明式绘图与前端无缝集成。你写
Bar().add_xaxis(['A','B','C']).add_yaxis('销量',[100,200,150]),它自动生成包含ECharts JS代码的HTML字符串,直接嵌入Flask的Jinja2模板即可。这比手写JavaScript+Ajax调用API再渲染图表,学习成本低一个数量级。更重要的是,pyecharts支持动态更新——当用户在login.html输入用户名提交后,index.html能根据后端返回的聚类ID,实时加载该用户所属商品簇的销售热力图、价格分布直方图、标题词云。这种“交互感”是教学演示的关键加分项。
提示:项目未采用Spring Boot整合Spark,是因为Spring Boot的自动配置会掩盖SparkContext初始化、序列化器设置、Hadoop配置加载等底层细节。教学目标是让学生看清“计算引擎怎么启动”,而不是“框架怎么帮我启动”。
2.2 数据流闭环:从MySQL到聚类结果的可追溯路径
整个系统的数据流设计成一条清晰、可审计的闭环,每个环节都有明确输入输出:
- 源头(MySQL):
goods表存储商品基础信息(id, title, category, price, sales_volume);user_behavior表模拟用户行为(user_id, goods_id, behavior_type, timestamp)。这是学生最熟悉的结构化数据入口。 - 特征工程(Spark Java):
- 读取goods.title字段,经分词(使用HanLP或结巴分词Java版)、停用词过滤、词干还原(可选),生成词序列;
- 调用org.apache.spark.ml.feature.TFIDF:先用HashingTF将词序列转为固定维度稀疏向量(默认2^18=262144维),再用IDF拟合逆文档频率,最终得到title_tfidf列(Vector类型);
- 对user_behavior表,按user_id聚合,统计每个用户的点击/收藏/购买频次,生成用户画像向量(如[click_cnt, fav_cnt, buy_cnt])。 - 聚类计算(Spark Java):
- 对商品TF-IDF向量,使用org.apache.spark.ml.clustering.KMeans进行聚类,K值设为5(可配置),输出goods_cluster表(goods_id, cluster_id);
- 对用户画像向量,同样用KMeans聚类,输出user_cluster表(user_id, cluster_id)。 - 服务对接(Flask Python):
- 用户登录后,Flask接收username,查询user_cluster表获取其cluster_id;
- 根据cluster_id,关联查询goods_cluster表,获取该用户簇内所有商品ID;
- 调用MySQL查询这些商品的title、price、sales_volume,组装为JSON;
- 同时,提取这些商品标题,用jieba分词+WordCloud生成词云数据,用pyecharts渲染为index.html中的<div id="wordcloud">。 - 前端呈现(HTML + pyecharts):
index.html通过Jinja2模板变量{{ chart_js }}注入pyecharts生成的完整JS代码,页面加载即渲染,无需额外Ajax请求。
这条链路确保学生能从数据库一条记录开始,一路追踪到最终网页上的一个柱状图,明白每个环节的数据形态(MySQL行 → Spark Row → Vector → Int → HTML DOM),这是建立大数据系统直觉的基础。
2.3 模块化结构:utils、dbtool、templates为何这样组织?
项目目录结构不是随意堆砌,每个模块解决一个具体教学痛点:
utils/:存放跨模块通用工具。例如TextPreprocessor.java封装了分词、停用词过滤、标点清洗的完整流程,学生只需调用preprocess(title)即可获得干净词序列,不必重复造轮子;ClusterEvaluator.java提供肘部法则计算方法,传入K值范围和聚类模型,自动返回SSE(误差平方和)数组,方便学生画图分析最优K值。这些工具类强制学生理解“复用”和“抽象”的价值。dbtool/:数据库操作封装。MySQLConnector.java统一管理连接池(HikariCP),GoodsDAO.java和UserBehaviorDAO.java分别封装商品和行为数据的CRUD。关键在于,所有DAO方法都显式抛出SQLException,并在Service层捕获处理。这教会学生:数据库不是永远可靠的,网络抖动、连接超时、主键冲突都必须有兜底逻辑,而不是让程序崩溃。templates/:前端页面模板。login.html极简,仅含用户名输入框和提交按钮;index.html是核心展示页,预留了<div id="bar-chart"></div>、<div id="pie-chart"></div>、<div id="wordcloud"></div>三个容器。pyecharts生成的JS代码通过{{ chart_js }}变量注入,完全解耦图表逻辑与HTML结构。学生修改图表类型,只需改Python代码,不用碰HTML。static/:存放静态资源。css/style.css提供基础样式,js/echarts.min.js是ECharts官方CDN的本地备份(防止学生网络不佳时图表白屏)。这里刻意不引入Vue/React,避免前端复杂度干扰核心推荐逻辑学习。
这种结构让学生一眼看懂“哪部分负责算,哪部分负责连,哪部分负责画”,降低认知负荷,把有限精力集中在推荐算法本身。
3. 核心细节解析与实操要点:TF-IDF向量化与KMeans聚类的Spark实现
3.1 商品标题TF-IDF:为什么不用Scikit-learn,而坚持Spark MLlib?
初学者常问:“Python里sklearn的TfidfVectorizer一行代码搞定,为啥要写几十行Java?”答案是:规模、一致性和教学目的。
规模适配:
advdata1_small.txt虽小(约5000条商品),但它的设计意图是让学生替换为真实电商数据集(如Amazon Product Data,百万级)。sklearn的TF-IDF在单机内存中处理百万文档极易OOM,而Spark MLlib的HashingTF+IDF天然分布式。HashingTF将任意词汇哈希到固定维度(如2^18),避免了传统CountVectorizer需要全局词汇表带来的Shuffle开销和内存压力。学生执行df.select("title").show(5)能看到原始标题,执行df.select("title_tfidf").show(5)则看到形如(262144,[12345,67890],[0.345,0.123])的稀疏向量——这就是分布式计算的具象化。一致性保障:在协同过滤等后续扩展中,用户行为向量(如点击序列)也需要TF-IDF化。Spark MLlib的API统一(
HashingTF.setInputCol("words").setOutputCol("tf")),学生只需复制粘贴修改列名,就能复用同一套流程处理不同文本字段,强化“特征工程模式”的认知。教学透明性:sklearn的
TfidfVectorizer是个黑盒,学生看不到词频统计如何并行、IDF如何全局聚合。而Spark代码强制暴露关键步骤:
```java
// 步骤1:分词(假设已用UDF完成)
Dataset wordsDF = df.withColumn(“words”, tokenizeUDF.apply(col(“title”)));
// 步骤2:HashingTF - 将词序列转为词频向量
HashingTF hashingTF = new HashingTF()
.setInputCol(“words”)
.setOutputCol(“rawFeatures”)
.setNumFeatures(1 << 18); // 262144维
Dataset featurizedData = hashingTF.transform(wordsDF);
// 步骤3:IDF - 计算逆文档频率,需fit
IDF idf = new IDF().setInputCol(“rawFeatures”).setOutputCol(“features”);
IDFModel idfModel = idf.fit(featurizedData);
// 步骤4:应用IDF模型,得到最终TF-IDF向量
Dataset rescaledData = idfModel.transform(featurizedData);`` 这四步清晰对应TF-IDF公式:TF-IDF(t,d) = TF(t,d) × IDF(t)。学生调试时,可单独查看rawFeatures列验证词频是否合理,再看features`列验证IDF衰减是否符合预期(高频词如“的”“和”权重应极低)。
注意:
HashingTF的NumFeatures必须是2的幂次,且足够大以避免哈希冲突。我们设为2^18,是因为5000条商品标题,经分词后总词数约10万,2^18=262144能保证冲突概率低于0.1%。若学生换用更大数据集,需按log2(总词数×10)估算新维度。
3.2 KMeans聚类:如何选择K值?聚类结果如何业务化解读?
KMeans的K值选择是教学重点,也是学生最容易“随便填个5”糊弄的地方。本项目强制要求学生动手计算并可视化:
肘部法则(Elbow Method)实现:
java // 在ClusterEvaluator.java中 public static double[] calculateSSE(Dataset<Row> vectorDF, int[] kRange) { double[] sseArray = new double[kRange.length]; for (int i = 0; i < kRange.length; i++) { int k = kRange[i]; KMeans kmeans = new KMeans() .setK(k) .setFeaturesCol("features") .setPredictionCol("prediction"); KMeansModel model = kmeans.fit(vectorDF); // 计算聚类内平方和(SSE) sseArray[i] = model.computeCost(vectorDF); } return sseArray; }
学生在Main.java中调用:java int[] ks = {2, 3, 4, 5, 6, 7, 8}; double[] sses = ClusterEvaluator.calculateSSE(goodsTfidfDF, ks); // 将ks和sses写入CSV,供Python脚本画图
生成的elbow_curve.csv被Flask读取,用pyecharts画出折线图。学生观察曲线拐点(SSE下降明显变缓处),确定最优K值。这比直接告诉他们“K=5”深刻得多。聚类结果业务化:聚类ID(0,1,2…)本身无意义。项目要求学生反查
goods表,对每个簇统计:- 平均价格、价格标准差(识别“高端集群”vs“平价集群”)
- 主要品类分布(Top3 category占比,识别“数码集群”、“服饰集群”)
- 标题高频词(对簇内所有商品标题合并分词,统计TF,取Top10)
这些统计结果存入goods_cluster_summary表,Flask在index.html中展示为:“您所在的【高性价比数码集群】中,平均价格¥899,85%商品为手机/耳机,高频词:旗舰、快充、高清、无线”。这才是可解释、可落地的推荐。
实操心得:KMeans对初始中心敏感。Spark MLlib默认使用
k-means||算法(并行KMeans++),比随机初始化更稳定。但学生仍需运行3次取最佳模型(最小SSE),代码中已封装runMultipleTimes()方法。另外,TF-IDF向量需归一化(L2 Norm),否则长标题会因词多而占据过大权重,VectorUtils.normalizeL2()已在utils中提供。
3.3 MySQL连接与数据流转:如何避免常见JDBC陷阱?
Spark读写MySQL是高频报错区。项目pom.xml中明确指定:
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency>关键配置在src/main/resources/application.properties:
# MySQL连接配置 mysql.url=jdbc:mysql://localhost:3306/recommender?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true mysql.user=root mysql.password=your_password # Spark JDBC读取优化 spark.jdbc.fetchsize=1000 spark.jdbc.numPartitions=4useSSL=false:本地开发环境通常无SSL证书,不加此参数会报SSLException。serverTimezone=UTC:避免MySQL时区与JVM时区不一致导致时间字段解析错误。fetchSize与numPartitions:fetchSize控制每次从MySQL拉取的行数,防内存溢出;numPartitions决定并行读取的分区数,提升速度。学生可尝试改为10,观察Spark UI中Stage的Task数变化。
写入时,goods_cluster表需提前建好:
CREATE TABLE goods_cluster ( goods_id BIGINT PRIMARY KEY, cluster_id INT NOT NULL );Spark写入代码:
clusteredGoodsDF.write() .format("jdbc") .option("url", mysqlUrl) .option("dbtable", "goods_cluster") .option("user", mysqlUser) .option("password", mysqlPassword) .option("truncate", "true") // 清空旧数据再写入 .mode(SaveMode.Overwrite) .save();truncate=true是关键,确保每次运行都是干净状态,避免历史聚类结果污染新实验。
4. 实操过程与核心环节实现:从零启动项目的完整步骤
4.1 环境准备:JDK、Spark、MySQL、Python版本的精确匹配
项目能在“常见开发环境”稳定运行,前提是版本严格匹配。以下是经过验证的组合(Windows/macOS/Linux均适用):
| 组件 | 推荐版本 | 为什么必须是这个版本 |
|---|---|---|
| JDK | 11.0.20 | Spark 3.3+要求JDK 11+,且11.0.20修复了早期JDK 11的G1 GC内存泄漏问题,避免Spark Driver OOM |
| Spark | 3.3.2 | 项目pom.xml中spark-sql_2.12依赖对应此版本,更高版本(如3.4)的API有微小变更(如DataFrameWriterV2) |
| MySQL | 8.0.33 | 驱动mysql-connector-java 8.0.33与此版本兼容性最佳,避免Unknown system variable 'query_cache_size'等报错 |
| Python | 3.8.10 | pyecharts 1.9.1在Python 3.9+中存在Jinja2模板渲染异常,3.8是稳定黄金版本 |
| Maven | 3.8.6 | 项目pom.xml使用maven-compiler-plugin 3.8.1,需Maven 3.6.3+ |
安装步骤(以macOS为例,Windows路径稍作调整):
安装JDK 11:
bash # 下载Oracle JDK 11.0.20或Adoptium Temurin 11.0.20 # 验证 java -version # 应输出 openjdk version "11.0.20" ... echo $JAVA_HOME # 应指向JDK 11安装路径安装Spark 3.3.2:
bash # 下载spark-3.3.2-bin-hadoop3.tgz,解压到/opt/spark export SPARK_HOME=/opt/spark export PATH=$SPARK_HOME/bin:$PATH # 验证 spark-shell --version # 应输出 Spark version 3.3.2安装MySQL 8.0.33:
bash # 使用Homebrew brew install mysql@8.0 brew services start mysql@8.0 # 初始化root密码 mysql_secure_installation # 创建数据库 mysql -u root -p -e "CREATE DATABASE recommender CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"配置Python环境:
bash # 创建虚拟环境(强烈推荐,避免包冲突) python3.8 -m venv ras_env source ras_env/bin/activate # 安装依赖 pip install -r requirements.txt # 验证 python -c "import pyecharts; print(pyecharts.__version__)" # 应输出 1.9.1
注意:
requirements.txt中pyecharts==1.9.1和jinja2==3.0.3是硬性绑定。若学生升级jinja2到3.1+,Flask模板中{{ chart_js | safe }}会失效,图表区域空白。这是pyecharts 1.x与新Jinja2的兼容性问题,已在项目说明.md中加粗警告。
4.2 数据准备与MySQL导入:advdata1_small.txt的正确解析方式
data/advdata1_small.txt是制表符(\t)分隔的纯文本,共5列:goods_id\ttitle\tcategory\tprice\tsales_volume。绝不能用Excel直接打开保存,会导致编码(UTF-8 with BOM)和分隔符(逗号)错乱。
正确导入步骤:
创建MySQL表结构:
sql USE recommender; CREATE TABLE goods ( goods_id BIGINT PRIMARY KEY, title TEXT NOT NULL, category VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, sales_volume INT NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;使用MySQL命令行导入(推荐,最稳定):
bash # 进入MySQL命令行 mysql -u root -p recommender # 执行导入(注意:文件路径需为MySQL服务器可访问路径,本地开发可用绝对路径) LOAD DATA LOCAL INFILE '/path/to/RAS43HRJcHAIn3Xucngl-master-511941c20c0e75480a7612793ea09e602acbb995/data/advdata1_small.txt' INTO TABLE goods FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n' (goods_id, title, category, price, sales_volume);验证数据:
sql SELECT COUNT(*) FROM goods; -- 应为5000 SELECT title FROM goods LIMIT 3; -- 检查中文是否乱码(应正常显示)
实操心得:若出现乱码,90%是文件编码问题。用VS Code打开
advdata1_small.txt,右下角确认编码为UTF-8,若显示UTF-8 with BOM,点击切换并保存。另外,LOAD DATA命令需在MySQL客户端启用local_infile:启动时加--local-infile=1,或在my.cnf中添加local_infile=1。
4.3 Spark后端编译与运行:mvn package的隐藏参数
项目根目录下的pom.xml已配置好所有依赖。编译命令看似简单,但有两个关键参数学生常忽略:
# 正确编译命令(在项目根目录执行) mvn clean package -DskipTests -Pspark332 # 参数解析: # -DskipTests:跳过单元测试,加快编译(教学项目测试非必需) # -Pspark332:激活pom.xml中profile id为spark332的配置,它指定了spark-sql_2.12版本为3.3.2编译成功后,生成target/sparkpro1-1.0-SNAPSHOT.jar。运行Spark Job:
# 设置环境变量(确保Spark和Hadoop配置正确) export HADOOP_CONF_DIR=$SPARK_HOME/conf # 运行商品聚类 spark-submit \ --class "com.ras.Main" \ --master "local[*]" \ # 本地模式,使用所有CPU核心 --driver-memory 2g \ --executor-memory 2g \ target/sparkpro1-1.0-SNAPSHOT.jar \ --mysql-url "jdbc:mysql://localhost:3306/recommender?useSSL=false&serverTimezone=UTC" \ --mysql-user "root" \ --mysql-password "your_password"--master "local[*]"是关键,它让Spark在本地模拟分布式环境,学生能在Spark UI(http://localhost:4040)中实时看到Stage划分、Task执行时间、Shuffle读写量——这是理解分布式计算本质的窗口。
4.4 Flask前端启动与图表渲染:pyecharts的Jinja2集成细节
Flask服务由index.py驱动。启动前需确认:
config.py中MySQL配置与Spark Job一致;templates/和static/目录存在;data/目录下有elbow_curve.csv(由Spark Job生成)。
启动命令:
cd webproject python index.py访问http://localhost:5000,看到login.html。输入任意用户名(如student1),提交。
此时Flask后端执行:
1. 查询user_cluster表,获取student1的cluster_id(假设为2);
2. 关联查询goods_cluster和goods表,获取簇2内所有商品;
3. 调用pyecharts.charts.Bar()等生成图表对象;
4. 调用.render_embed()方法,生成包含完整ECharts JS代码的HTML字符串;
5. 将字符串作为chart_js变量,传入render_template("index.html", chart_js=chart_js)。
index.html核心片段:
<!-- templates/index.html --> <div id="bar-chart" style="width: 100%; height: 400px;"></div> <script> // {{ chart_js | safe }} 会注入类似: // var chart_12345 = echarts.init(document.getElementById('bar-chart')); // chart_12345.setOption({ ... }); </script>注意:
| safe过滤器至关重要,它告诉Jinja2不要对chart_js内容进行HTML转义(否则<变成<,JS代码失效)。这是pyecharts与Flask集成的黄金法则。
5. 常见问题与排查技巧实录:那些让你抓狂半小时的“小问题”
5.1 Spark报错:java.lang.NoClassDefFoundError: org/apache/spark/sql/SparkSession
现象:运行spark-submit时,Driver刚启动就报此错,堆栈指向Main.class。
原因:pom.xml中spark-sql_2.12依赖范围(scope)被误设为provided。provided表示该依赖由运行环境(Spark)提供,但spark-submit默认不会将Spark自带的jar加入classpath,导致类找不到。
解决方案:
1. 检查pom.xml,找到spark-sql_2.12依赖,确认<scope>标签不存在或为compile(默认值);
2. 若存在<scope>provided</scope>,删除整行;
3. 重新mvn clean package。
排查技巧:用
jar -tf target/sparkpro1-1.0-SNAPSHOT.jar | grep SparkSession,若无输出,说明依赖未打包进去。
5.2 Flask报错:jinja2.exceptions.TemplateNotFound: login.html
现象:启动index.py后,访问http://localhost:5000显示此错误。
原因:Flask默认在templates/目录下找模板,但当前工作目录不是webproject/。学生常在项目根目录(含pom.xml处)运行python webproject/index.py,此时Flask的template_folder相对路径解析错误。
解决方案:
1.必须进入webproject/目录再运行:bash cd webproject python index.py
2. 或在index.py开头显式指定路径:python import os from flask import Flask app = Flask(__name__, template_folder=os.path.join(os.path.dirname(__file__), 'templates'), static_folder=os.path.join(os.path.dirname(__file__), 'static'))
5.3 pyecharts图表不渲染:页面空白或显示Loading...
现象:index.html打开后,<div id="bar-chart">区域为空,或显示“Loading…”后无反应。
原因:多重可能,按优先级排查:
1.chart_js变量未注入:检查index.py中render_template调用,确认chart_js=bar.render_embed()参数名与模板中{{ chart_js }}一致;
2.Jinja2转义未关闭:模板中必须是{{ chart_js | safe }},漏掉| safe会导致JS代码被转义;
3.ECharts JS未加载:检查static/js/echarts.min.js是否存在,且index.html中<script>标签路径正确(应为/static/js/echarts.min.js);
4.浏览器控制台报错:按F12,看Console是否有echarts is not defined(JS未加载)或Cannot set property 'innerHTML' of null(div ID不匹配)。
速查表:
| 现象 | 最可能原因 | 快速验证 |
|---|---|---|
| 页面完全空白,无任何文字 | login.html路径错误或Flask路由未匹配 | 访问http://localhost:5000/test(在index.py加一个@app.route('/test')返回"OK"),确认Flask服务正常 |
login.html正常,index.html空白 | chart_js未传入或| safe缺失 | 在index.html中临时添加<pre>{{ chart_js }}</pre>,看是否输出JS代码 |
显示Loading...后停止 | ECharts JS未加载或div ID不匹配 | 查看Network标签,确认/static/js/echarts.min.js返回200;检查<div id="bar-chart">与JS中getElementById('bar-chart')是否一致 |
| 图表渲染但数据为空 | Spark未成功写入goods_cluster表 | 直接MySQL查询SELECT * FROM goods_cluster LIMIT 5,确认有数据 |
5.4 MySQL连接拒绝:Communications link failure
现象:Spark或Flask日志中出现com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure。
原因:MySQL服务未启动,或连接参数错误。
解决方案:
1.确认MySQL服务状态:bash # macOS brew services list | grep mysql@8.0 # Linux sudo systemctl status mysql # Windows:服务管理器中检查MySQL80服务
2.检查连接URL:jdbc:mysql://localhost:3306/...中的localhost不能写成127.0.0.1(MySQL 8.0默认禁用root远程登录,localhost走socket,127.0.0.1走TCP,权限不同);
3.检查用户权限:登录MySQL,执行:sql SELECT host FROM mysql.user WHERE user='root'; -- 确认有'localhost'行 FLUSH PRIVILEGES;
最后分享一个小技巧:在
dbtool/MySQLConnector.java中,getConnection()方法里添加日志:java System.out.println("Attempting MySQL connection to: " + url); Connection conn = DriverManager.getConnection(url, user, password); System.out.println("MySQL connection SUCCESSFUL");
这样,只要看到“SUCCESSFUL”,就证明连接层没问题,问题一定出在SQL查询或数据处理逻辑中。这是我在实验室里教学生的第一条调试铁律:分层隔离,逐段验证。
这个项目没有魔法,它的价值恰恰在于把每一个“理所当然”的环节——从JDK版本选择、MySQL字符集设置、到Jinja2模板的| safe过滤器——都摊开在阳光下,让学生亲手触摸大数据系统的毛细血管。当你看到学生第一次在index.html上刷出属于自己“用户集群”的词云,指着“旗舰”“快充”兴奋地说“原来我的购物习惯是这样的!”,你就知道,这套代码的价值,早已超越了技术本身。
本文还有配套的精品资源,点击获取
简介:一个开箱即用的大数据推荐系统教学实践项目,后端用Java+Spark处理商品数据,支持MySQL连接、商品标题等文本的TF-IDF向量化,以及用户和商品维度的KMeans聚类;前端通过Python Flask提供轻量API接口,调用pyecharts渲染动态可视化图表,包含登录页、结果展示页等完整Web功能。项目结构清晰,含utils工具模块、dbtool数据库操作封装、templates页面模板、static静态资源、data样例数据(如advdata1_small.txt),以及详细本地运行说明。所有代码已在常见开发环境验证通过,启动简单,依赖明确(见requirements.txt和pom.xml)。适合高校大数据、人工智能、计算机相关专业用于课程实验、期末设计或毕设基础框架,后续可便捷接入协同过滤、实时流推荐等扩展模块。
本文还有配套的精品资源,点击获取