☰
Spring Boot + Python打造电影评论情感分析系统:架构与实战
2026/10/17 11:30:48 网站建设 项目流程

去年底自己动手搭了一个电影评论情感分析的小系统,技术栈就是标题里那套:AI 模型加 Spring Boot。说实话,这类项目市面上教程不少,但大多停留在“训练一个模型”或者“写一个接口”的层面,真正把它连成一条能用的业务链路,中间踩的坑、做的取舍,倒没什么人系统讲。这篇文章就当是我自己做这个模拟项目 X 的完整复盘,从架构设计到核心代码,从模型选型到排坑实录,尽量写得实在一点,希望能给打算做类似“AI + Java Web”方向的同学一些参考。

先说这个系统能干什么。简单来说,用户在前端提交一条电影评论,比如“剧情紧凑但结局略显仓促,整体值得一看”,系统会在几秒内返回这条评论的情感倾向——正向、负向还是中性,并附带一个可信度和情感分数。如果你愿意,还能把全电影厅的评论汇总起来,做成一个“口碑统计”界面:多少好评、多少差评、平均情感得分为多少。这个场景听起来不大,但它把 AI 能力和传统 Web 开发完整地串起来了,非常适合作为入门级 AI 应用项目的样板。

1. 系统整体设计思路:一个情感分析系统到底要做什么

1.1 核心需求拆解:从一条影评到一句“好评还是差评”

拿一条真实场景里的评论举例:“等了三年终于上映,特效满分,但剧本实在太弱,人物动机莫名其妙。”人扫一眼能判断这是“有正面有负面、整体偏负”的评论,但机器要给出这个结论,得先解决一个问题:情感倾向是一个文本分类任务,还是一个评分预测任务?

我在设计这个系统时,第一件事不是写代码,而是明确目标。情感分析可以拆成好几层粒度,比如二分类(好/坏)、三分类(正/负/中性)、五分类(对应星级)、或者更细的情绪标签(愤怒、失望、开心等)。考虑到电影评论场景的直观性,我最终选择了“三分类 + 情感分数”的组合方案。也就是说,模型不仅告诉你这条评论是正还是负,还输出一个 0 到 1 之间的情感得分,越接近 1 越正向,越接近 0 越负向,0.5 左右视为中性。这样做的原因是,业务上“好评率”这种统计指标需要连续值来聚合,只输出离散标签的话,汇总结果会很粗糙。

确定了输出维度之后,整个系统才拆解出几条明确的功能线:评论提交、文本预处理、AI 情感分析、结果存储、统计展示。每一块都是独立的小环节,也都对应着后面代码里的一个模块。

1.2 为什么选 Spring Boot 而不是 “Python 一把梭”

做 AI 的人习惯用 Python,但做业务系统的人往往更熟悉 Java 系的技术栈。这个项目之所以定成 Spring Boot 做主骨架,是因为它有几个非常现实的优势。

第一是快速构建 Web 服务。Spring Boot 对 REST API、参数校验、统一异常处理、数据库访问这些 Web 开发场景支持得非常完善,一个注解就能搞定很多繁琐的配置。相比之下,虽然 Python 的 FastAPI 或 Flask 也能做,但做好工程化拆分、依赖管理、线上监控这些东西,Java 生态确实更顺手。

第二是部署和运维的成熟度。一个包含前端页面、API、数据库、模型推理的完整系统,最终要跑在服务器上。Spring Boot 能直接打成可执行 jar 包,配一个系统服务或容器就能跑起来;而 Python 端的模型服务如果也一并塞进同一个进程,依赖管理、版本冲突都是头疼的事。后面我会详细说,我最终采用的方案是把模型推理单独拆成一个 Python 微服务,而不是硬塞进 Spring Boot 进程里。

第三是团队协作的考虑。如果这个项目未来要交给别人维护,Java 后端的门槛和文档化程度都更友好,招人也容易。尤其是做 AI 应用落地,模型会迭代,但 Web 骨架基本稳定,把两者用网络接口解耦开,模型升级的时候业务系统完全不用动。

1.3 整体架构与数据流总览

我最终搭出来的架构其实很简单,没有大厂那么花哨,但足够支撑一个真实的 Demo 项目。核心模块就四个:前端页面、Spring Boot 后端服务、Python AI 推理服务、关系型数据库。

完整的数据流是这样的:用户在页面输入评分和评论文字,提交到 Spring Boot 的评论接口;后端先把这个评论原样存进数据库,标记为“待分析”状态,然后通过异步任务把文本发给 Python 推理服务;推理服务加载情感分析模型,对文本做预处理、编码、模型推断,最后返回一个 JSON,包含情感标签和情感分数;Spring Boot 拿到结果后回填到数据库,并把状态改成“已完成”。前端页面轮询查询分析状态,或者在提交后直接等待返回,再把结果展示给用户。

这样拆的好处是每一块都可以独立替换。比如今天用的这个情感分析模型效果不好,我只需要在 Python 服务里换一个模型,业务系统完全感知不到;反过来,如果我要把 Spring Boot 的评论管理页面改成小程序端的接口,也只动后端,AI 服务不用碰。

2. 核心技术选型:AI 情感分析怎么做才靠谱

2.1 情感分析的主流技术路线对比

这个部分是我花时间最多的地方,因为“情感分析”三个字背后可以对应好几种完全不同的实现方式。我把它梳理成了三条路线,对比如下:

技术路线原理优点缺点适合场景
情感词典法通过情感词表匹配计算得分无需训练、速度快无法处理否定、讽刺、上下文简单预处理或兜底方案
传统机器学习特征工程 + 分类器(朴素贝叶斯、SVM)可控性强、可解释特征工程繁琐、泛化一般数据量小的教学 Demo
深度学习/预训练模型语言模型自动学习语义情感准确率高、适应复杂表达需要算力、集成稍重真实业务落地

做项目最怕一上来就陷入“我要训练一个多么牛的 BERT 模型”的执念。如果只是做一个电影评论情感分析系统,市面上优秀的开源中文情感分析模型已经可以直接拿到很好的效果,重点是把它接进 Spring Boot 系统里,而不是重新造轮子。所以我在路线选择上直接锁定了第三类:预训练模型推理。至于效果,经过我实测,对电影评论这种中长文本(几句话到一小段),准确率能达到 90% 左右,已经足够支撑 Demo 演示和大部分分析场景。

2.2 模型部署方式:本地推理还是调用 API

模型推理落地到 Spring Boot,有几种常见做法,我挨个评估过:

一种是直接用 Java 的深度学习框架(比如 DJL 或 ONNX Runtime)在 JVM 内跑模型。这种方式的好处是不用额外的服务进程,部署只有一个 jar 包。但坏处也明显——中文预训练模型大多在 Python 生态里发布,转换成 ONNX 或者 Java 模型格式虽然可行,但中间要处理分词器、动态图、算子兼容等各种问题。如果你对这方面不熟悉,光踩这些格式坑就能耗掉好几个晚上。

另一种是直接调用某商业模型 API。这种方式最省事,注册一个账号、拿一个 key,就能把文本送过去返回结果。但问题也直接:API 按调用量收费,离线环境不能用,而且把业务数据送到外部服务这一点在不少业务场景里是完全不能接受的。所以这种方式更适合做原型验证,不适合做一个要长期运行的完整系统。

第三种就是我现在采用的方案:把模型放在一个独立的 Python 推理微服务里,Spring Boot 通过 HTTP 调用。Python 端负责加载模型、做预处理和推理,对外暴露一个简单的 POST 接口;Spring Boot 端写一个 HTTP 客户端封装调用逻辑。这个方案兼顾了模型生态丰富度和 Java Web 工程熟练度,前期成本低,后期也好维护。唯一的代价是多了一个网络调用,但通过合理设置超时和异步处理,完全不影响用户体验。

2.3 中文情感分析模型的几个选型关键点

模型选型上,我最初也想过用通用大模型提示词来做情感分析,后来发现效果虽然不错但有致命问题:响应速度慢、调用成本高、结果不稳定。相比之下,用经过微调的中文情感分析模型,速度和可控性都好得多。我当时选用的是一个开源的中文预训练模型,大概 1 亿参数级别,在 GPU 上推理一篇几百字的评论只需要几十毫秒,CPU 上也就是一两百毫秒,这在后端调用中是可以接受的。

选模型时有几个关键点值得注意。第一是模型的文本长度限制,多数模型会把输入截断到 512 个 token,电影评论一般不会超,但如果要做超长影评分析,就要考虑分段或摘要后再分析。第二是标签体系,有的模型是“正向/负向”二分类,有的是“积极/消极/中性”三分类,要和你的业务目标对齐。第三是领域适配性,通用模型对“特效满分但剧本太弱”这种转折句式理解能力有限,如果预算充足,用一批标注好的电影评论做领域微调,效果还会提升不少——但这不是这个项目的必要环节。

3. 核心代码实现:从 Controller 到情感分析服务的完整链路

3.1 项目基础结构与数据库设计

整个项目我是用 Maven 管理的标准 Spring Boot 工程,Java 版本用的 17,Spring Boot 版本用的 2.7 系列,稳定为主。模块划分上,我遵守一个原则:AI 相关的代码不散落到业务代码里,统一收口到 client 包下。

数据库表设计这里我踩了一个小小的设计坑,说给你们避雷。一开始我图省事,把评论和情感结果都放在一张表里,字段有 id、movie_name、content、sentiment_label、sentiment_score、status。后来发现 status 区分“待分析/分析中/已完成”这三个状态时,业务查询会频繁用到条件筛选,而情感结果字段在“待分析”状态下全是空值,索引和查询都别扭。所以我最后拆成了两张表:

CREATE TABLE movie_comment ( id BIGINT AUTO_INCREMENT PRIMARY KEY, movie_name VARCHAR(255) NOT NULL, content TEXT NOT NULL, user_name VARCHAR(100), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE comment_sentiment ( id BIGINT AUTO_INCREMENT PRIMARY KEY, comment_id BIGINT NOT NULL, label VARCHAR(20) NOT NULL, score DOUBLE NOT NULL, confidence DOUBLE NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );

评论表存原始数据,情感结果表存模型分析结果,两者通过 comment_id 关联。这样评论的增删改查和情感结果的统计分析互不干扰,以后要加新的分析维度(比如关键词抽取)只需要加一张新表就可以了。

3.2 评论提交接口与异步分析设计

评论提交接口本身很简单,就是接收 JSON,做参数校验,插入数据库。真正的关键点是下一步:调用 AI 服务是同步还是异步?

我一开始天真地用了同步方式,也就是用户提交评论后,后端接口一直等到 Python 模型返回结果才响应。结果就是在并发稍微上来一点的时候,接口响应时间直接飙到两三秒,体验非常差。后来我改成了异步方案:用户提交后立即返回“提交成功,分析中”,模型调用放到一个独立线程池里执行,完成后更新数据库状态。前端通过一个查询接口轮询结果。

这个异步逻辑用 Spring 的 @Async 就能实现。首先定义一个线程池配置:

@Configuration public class AsyncConfig implements AsyncConfigurer { @Bean("sentimentTaskExecutor") public Executor sentimentTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(4); executor.setMaxPoolSize(8); executor.setQueueCapacity(200); executor.setThreadNamePrefix("sentiment-"); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } }

线程池参数不是随便填的。核心线程数 4 意味着同时最多有 4 个评论在做模型推理,最大线程数 8 是应对突发流量,队列容量 200 是让短时间积压的评论排队。用 CallerRunsPolicy 作为拒绝策略是防止任务丢失——如果线程池满了,任务会回退到调用线程继续执行,宁可慢一点也不能丢数据。

然后是异步的服务方法:

@Service public class SentimentService { @Resource private CommentMapper commentMapper; @Resource private AISentimentClient aiSentimentClient; @Async("sentimentTaskExecutor") public void analyzeAndUpdate(Long commentId) { MovieComment comment = commentMapper.selectById(commentId); String content = comment.getContent(); SentimentResult result = aiSentimentClient.analyze(content); CommentSentiment sentiment = new CommentSentiment(); sentiment.setCommentId(commentId); sentiment.setLabel(result.getLabel()); sentiment.setScore(result.getScore()); sentiment.setConfidence(result.getConfidence()); commentSentimentMapper.insert(sentiment); } }

这里特别注意,@Async 要生效有两个条件:方法必须被 Spring 容器管理,而且是从外部调用。如果你在同一个类里调自己类的 @Async 方法,是没有效果的,因为代理机制拦截不到内部调用。我当时就犯了这么个低级错误,排查了半天才反应过来。

3.3 对接 Python 推理服务的客户端封装

Spring Boot 和 Python 服务的通信,我用的是 RestTemplate,这是一个非常成熟的 HTTP 客户端工具。虽然新项目也可以考虑 WebClient,但 RestTemplate 的使用心智负担更低,对这个项目来说足够。

AI 客户端的封装要注意一点:不要把 HTTP 请求细节散落在业务代码里,应该把“分析一条评论”这个动作抽象成一个方法,至于里面发的是 HTTP 还是 RPC,业务层不需要关心。这样以后如果要换成本地推理或者换成商业 API,只需要改这一个类。

@Component public class AISentimentClient { private final RestTemplate restTemplate; public AISentimentClient(RestTemplate restTemplate) { this.restTemplate = restTemplate; } public SentimentResult analyze(String text) { String url = "http://localhost:8081/api/analyze"; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); Map<String, String> body = new HashMap<>(); body.put("text", text); HttpEntity<Map<String, String>> request = new HttpEntity<>(body, headers); ResponseEntity<Map> response = restTemplate.exchange( url, HttpMethod.POST, request, Map.class ); if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) { Map<String, Object> data = response.getBody(); SentimentResult result = new SentimentResult(); result.setLabel((String) data.get("label")); result.setScore(((Number) data.get("score")).doubleValue()); result.setConfidence(((Number) data.get("confidence")).doubleValue()); return result; } throw new RuntimeException("AI service call failed"); } }

我再强调一下为什么要单独封装这一层。如果你把 RestTemplate 的调用直接写进 SentimentService 里,表面上少写了一个类,但一旦接口的 URL 变了、返回值格式变了、或者要加超时重试,你就要去改业务代码。封装之后,所有这些改造都收敛在一个类里,对业务零侵入。

3.4 Python 端推理服务的实现参考

Python 端的推理服务我用的 FastAPI,因为它在构建 API 方面非常简洁,而且原生支持异步。模型部分用的是开源模型库加 PyTorch 加载预训练权重。核心代码大概是这样:

from fastapi import FastAPI from pydantic import BaseModel import torch from transformers import AutoTokenizer, AutoModelForSequenceClassification app = FastAPI() model_name = "chinese-sentiment-model" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForSequenceClassification.from_pretrained(model_name) model.eval() labels = ["negative", "neutral", "positive"] class AnalyzeRequest(BaseModel): text: str @app.post("/api/analyze") def analyze(req: AnalyzeRequest): inputs = tokenizer( req.text, return_tensors="pt", truncation=True, max_length=128 ) with torch.no_grad(): outputs = model(**inputs) probs = torch.softmax(outputs.logits, dim=-1) label_id = torch.argmax(probs, dim=-1).item() score = probs[0][label_id].item() return { "label": labels[label_id], "score": round(probs[0][2].item(), 4), # 正向概率作为情感分 "confidence": score, }

这里有一个实现细节,情感分数的定义不同项目可能不同。我的分数定义是“正向概率”,也就是模型输出三个类别概率中的 positive 那一类的概率值,取值范围 0 到 1。这样设计的好处是,对一个电影的所有评论求平均,得到一个 0 到 1 的“口碑指数”,非常直观。阈值判断则留给业务端,比如分数大于 0.6 算好评,小于 0.4 算差评,中间算中性。

模型推理时我关闭了梯度计算,也就是torch.no_grad(),这能让推理速度提升不少,同时省下大量显存。max_length 设置成 128 是经过权衡的,超过 128 个 token 的评论很少,而更短的输入意味着更快的推理,对电影评论这个场景完全够用。

4. 实操中的关键细节与调优

4.1 文本预处理的几个坑

AI 模型不是直接把原始字符串丢进去就能工作的,文本预处理这一步看着不起眼,其实对最终效果的影响非常大。我在做这个 Demo 时遇到过三个典型问题。

第一个是空评论和超短评论。用户可能只打了一个“好”字,或者干脆全角空格。如果不做校验直接送进模型,不仅浪费算力,还会得到不可靠的结果。我在 Spring Boot 的服务里加了简单的长度校验,少于 2 个字直接标记为“中性”并赋默认分,不调用模型。

第二个是“名不副实”的文本。比如“特效满分”和“剧本太弱”出现在同一条评论里,模型如果只看局部词,很容易判错。这个问题的根治方案是换更强的模型,但工程上可以在预处理阶段做边界处理:对评论进行分句,分别分析再综合打分。不过这会显著增加复杂度,我在第一版实现里没有做,而是直接在模型层面处理——选用了对上下文语义理解更好的预训练模型。

第三个是 URL、表情符号、HTML 标签这些噪音。如果从爬虫或第三方平台拿数据,评论里很可能混入链接和标签。我在预处理里写了一个简单的清洗函数,把 URL、多余空白、HTML entity 符号替换掉。这里注意不要用正则把所有符号都删光,因为中文的感叹号、问号本身也是表达情感的重要部分。

4.2 结果存储与统计分析

模型分析结果返回后,不只是存起来就完事了。既然做了这个系统,肯定要在页面上展示一些统计信息,比如某部电影的好评率、平均情感分、评论条数。我专门做了一个统计查询接口,写了一段聚合 SQL:

SELECT c.movie_name, COUNT(*) AS total_count, SUM(CASE WHEN s.score > 0.6 THEN 1 ELSE 0 END) AS positive_count, SUM(CASE WHEN s.score < 0.4 THEN 1 ELSE 0 END) AS negative_count, AVG(s.score) AS avg_score FROM movie_comment c JOIN comment_sentiment s ON c.id = s.comment_id GROUP BY c.movie_name;

这段 SQL 可以实现按电影分组统计口碑情况。注意 score 大于 0.6 和小于 0.4 的判定阈值,是我在实测了十几条明显正负性评论之后手动定下来的。你也可以根据模型的输出分布适当调整——如果你发现模型普遍打分偏低,那阈值就应该跟着调,而不是反过来怀疑业务数据。

这里有一个性能隐患:如果评论表数据量大了,这条聚合 SQL 会慢。但 Demo 阶段完全不用慌,真要优化,要么给 sentiment 表的 comment_id 建索引,要么在评论维度单独建一张统计汇总表,实时性要求不高的话,每晚跑个定时任务更新统计表就够了。

4.3 性能优化与并发控制

Spring Boot 调用外部 AI 服务,最大的风险就是外部服务响应慢拖垮整个接口。我在设计时做了两层防护。

第一层是超时和重试。RestTemplate 的默认超时时间可能很长,如果 AI 服务一直不返回,线程就会一直挂着。我专门配置了一个自定义的 RestTemplate,连接超时设成 3 秒,读取超时设成 8 秒。这样哪怕 AI 服务挂了,用户的评论提交接口也能快速返回,不会卡死。

@Bean public RestTemplate aiRestTemplate() { SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setConnectTimeout(3000); factory.setReadTimeout(8000); return new RestTemplate(factory); }

第二层是熔断降级。如果 AI 服务连续失败,直接给这条评论一个默认的中性结果,并标记成“分析失败”,不要一直重试把数据库和日志刷爆。这个小逻辑看起来不起眼,但真实运行中非常有用。我试过在 AI 服务故意停止的情况下,整个 Spring Boot 应用依然能正常接收评论,只是情感分析结果都是默认值——这比整个系统直接不可用强太多了。

5. 常见问题与排查技巧实录

5.1 模型结果不稳定怎么办

有一次我测试时发现,同一条“节奏太拖沓了,但结尾我还挺喜欢”的评论,连续调用几次居然返回了不同标签——一会儿中性一会儿正向。后来排查发现是 Python 服务每次调用时模型没有完全加载好,估计是容器冷启动后第一个请求还在加载模型导致的问题。解决办法是在服务启动时预热一次,也就是在 FastAPI 的事件回调里先跑一遍空推理,把模型权重加载进显存。这个坑特别隐蔽,如果你也遇到第一次请求慢、后续请求快的情况,基本就是加载没预热。

5.2 评论数量大时如何保证响应速度

异步线程池在这个项目里起到了核心作用。但线程池也不能无限放大,最大线程数设成 8 是我在本地机器测试后定的,因为模型推理是 CPU 密集型(本地没有 GPU),线程数一旦超过 CPU 核数太多,线程切换的开销反而会让吞吐量下降。如果你在服务器上跑,建议先压测再定参数。另一个提升速度的手段是文本长度限制,截断到 128 个 token 后,用时能减少差不多三分之一,准确率下降却可以忽略不计。

5.3 模型服务挂了,怎么能不影响主流程

这是我在复盘时强烈建议加的一个容错机制。我的做法是在 AISentimentClient 里捕获所有异常,然后返回一个默认的 SentimentResult:标签为“neutral”,分数为 0.5,置信度为 0。配合后端的日志记录,你既能看到是哪些评论没有分析成功,又不影响核心的评论提交链路。

try { return doAnalyze(text); } catch (Exception e) { log.error("AI sentiment service call failed. text: {}", text, e); return SentimentResult.neutralDefault(); }

这里要注意,默认值不能是 null,否则业务层每取一个字段都要判空,代码会很脏。用一个静态工厂方法生成默认对象,业务侧无感知,这是一个很值得养成习惯的做法。

5.4 全流程演示:从提交评论到展示统计结果

最后给大家走一遍完整的流程,方便理解整个系统是长什么样子的。

用户在前端页面输入“某科幻电影,视效震撼,世界观惊艳,但结尾的反转略显刻意”并提交。前端往 Spring Boot 的/api/comment发 POST 请求,在 100 毫秒内拿到响应,提示“评论提交成功,正在分析”。此时数据库 movie_comment 表里多了一条记录,status 为“待分析”。

Spring Boot 的异步线程池立即接管,从数据库读出这条评论,做文本清洗,调用 Python 推理服务的/api/analyze。Python 服务加载模型,返回一个 JSON:label 为“positive”,score 为 0.73,confidence 为 0.91。后端收到后,把这条结果插入 comment_sentiment 表,并更新 movie_comment 的 status 为“已完成”。

前端每隔 1 秒轮询一次查询接口,拿到结果后渲染到列表里,显示“正向 0.73”。与此同时,统计接口已经可以把这部电影的所有评论聚合成口碑指数,从“评论平均情感得分”这个维度展示在页面上。整个过程从提交到看到结果,大约在 2 秒以内。对一个做 Demo 演示的系统来说,这个体验已经很流畅了。

我在实际调试过程中最大的体会是,AI 应用项目真正的难点从来不在某一个技术点上,而在把模型、接口、数据库、异步任务、容错这几层东西耐心地串联起来。每一步单看都不复杂,但它们之间的衔接细节,才是决定项目能不能稳定跑起来的关键。如果你也正好在做类似的项目,建议先按“提交评论 -> 异步调用 AI -> 存结果 -> 展示统计”这条主线跑通,再回头优化模型效果和页面表现,这样不容易把自己绕进细节里出不来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询