ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python+Flask打造豆瓣电影情感分析推荐系统

Python+Flask打造豆瓣电影情感分析推荐系统 1. 项目源头一个想解决“看完不知道看什么”的小工具做这个项目之前我其实是被一个特别常见的场景烦到了每次刷完一部电影想找下一部的时候豆瓣上那些评分还好但评论区的情绪和口风完全能影响判断。有人因为一个结局骂了整个剧组有人因为一句台词就给了五星简单看评分很容易踩雷。我就在想能不能把评论里的真实情绪拆出来再把“大家都觉得不错但不一定高分”的电影推到面前于是就有了这套基于 Python Flask 的豆瓣电影情感分析推荐系统。这个系统说白了就是三件事先把豆瓣电影的短评数据采集下来存进数据库再用情感分析模型把每一条评论算出一个情绪倾向分数最后基于这些情感分数做推荐通过 Flask 提供一个可以点开用的 Web 界面。整个项目把源码、数据库和文档都打包好了拿到手先看文档再配环境跑起来就能用。对正在学 Python Web 开发、或者对情感分析和推荐系统感兴趣的人来说这是一个完整的练手项目能一次性把爬虫、数据清洗、自然语言处理、协同过滤推荐和 Web 开发串起来。我选 Python Flask 而不是 Django理由很简单Flask 足够轻。这个系统的核心重点在算法和数据处理上Web 端只是展示和交互的壳Django 自带的后台、ORM 和中间件对我来说都是额外负担。Flask 的灵活度高想怎么写就怎么写而且它的上下文管理机制比很多新手想象中简单调试起来也更直观。数据库我用的是 MySQL但为了让项目跑起来更省事我也准备了 SQLite 的初始化脚本两种库都能直接切。项目推进过程中我最大的感受是真正花时间的不是写算法而是数据清洗和“接口怎么设计才顺手”。网上很多人一上来就调库跑模型把评论一输情感分数一出来就觉得情感分析完成了。但实际做下来才发现评论里的表情符号、网络用语、讽刺语气、否定结构每一个都是坑。系统里做得好不好基本都体现在这些细节处理上。2. 整体设计与数据库建模2.1 模块划分数据层、算法层、展示层我把系统拆成了四个模块数据采集模块、数据存储层、情感分析模块和推荐展示模块。模块之间用清晰的数据接口衔接这样任何一块出了问题都能单独排查。数据采集模块负责从豆瓣页面获取电影的标题、导演、主演、评分和热门短评。这里我用了 requests BeautifulSoup没有上 Scrapy因为数据量不大短评采集场景也简单Scrapy 对这种规模有点重。采集到的原始数据先进 pandas DataFrame 做一次临时处理再去重、补缺失值最后批量写入数据库。数据存储层是整套系统的地基。我用 MySQL 建了三张核心表电影信息表、评论表、用户行为表。电影信息表存元数据评论表存短评正文和对应的情感分数用户行为表用来记录用户对电影的交互比如点击、收藏、评分这些是后面推荐算法的重要输入。算法层分两个部分情感分析子模块负责给每条评论打分推荐子模块基于情感分数和电影关联做召回排序。我把两个子模块完全解耦接口统一成函数调用这样就算以后想换更强的预训练模型也只需要改一个文件不用动 Web 层。展示层就是 Flask 渲染的页面。首页展示电影列表点进详情页能看到情感分析结果用户可以给电影打标签或收藏推荐页会根据历史行为生成推荐列表。整体页面我没用前后端分离直接用了 Jinja2 模板因为这样部署最简单适合中小项目。2.2 数据库表结构不要一上来就铺十张表新手做这类项目最喜欢犯的错就是先把表建得特别全用户表、角色表、权限表、日志表全堆上结果写代码时一大半用不到。我的做法是让表跟着功能走先用三张表把核心流程跑通后续有需要再加。电影信息表 movie 的核心字段是 movie_id、title、director、actor、rating、comment_count。movie_id 是主键我用自增整数不用字符串 ID因为整数索引快关联查询也方便。title 加了一个唯一索引防止重复采集同一步电影。导演和演员字段我用了 VARCHAR 直接存格式化文本比如“导演A 导演B”没有单独拆表因为这里不做复杂的关联查询拆了反而麻烦。评论表 comment 的字段是 comment_id、movie_id、content、sentiment_score、sentiment_label、create_time。movie_id 做了外键索引content 存短评原文sentiment_score 是模型算出来的情感分值范围在 0 到 1 之间越接近 1 代表越正向。sentiment_label 是分数映射后的话术positive / neutral / negative。之所以同时存分数和标签是因为做报表时按标签分组容易做推荐排序时直接用分数方便。用户行为表 user_behavior 我自己加得比较轻只记录 user_id、movie_id、behavior_type、timestamp。behavior_type 用数字编码1 代表点击2 代表收藏3 代表手动评分。这套表的最大作用是服务协同过滤的“用户-物品”矩阵没有真实的用户登录体系也能跑先制造出可用的行为数据。建表时我会把排序字段都加上索引比如 comment 表的 movie_id、sentiment_scoremovie 表的 rating。这些字段是查询和推荐的高频条件加了索引之后速度会快很多。但我不会给 content 加索引因为短评正文只会用模糊查询索引在这种场景下收益很低还会拖慢写入速度。2.3 系统架构中 Flask 的位置Flask 在这个项目里承担的是胶水层角色它把所有模块粘起来对外提供 HTTP 接口。我用了 Flask 的蓝图机制把路由按业务拆分成 auth、movie、recommend 三个蓝图。刚开始写的时候我没用蓝图所有路由全堆在 app.py 里不到两百行代码就乱得不行后来才拆开。用蓝图之后每个业务的入口都很清楚加新功能也不会影响到旧路由。模板方面我用 Jinja2Flask 原生支持。首页、详情页、推荐页各一套模板公共部分抽成 base.html导航栏和底部信息直接继承。静态资源比如 CSS、JS 放在 static 目录Flask 会自动处理静态文件映射。整个 Web 层的逻辑不复杂但它是用户唯一能直接看到的部分所以我还是花了不少心思在页面交互上。3. 关键实现情感分析与推荐算法3.1 情感打分是怎么算出来的情感分析是整个项目的灵魂。我没有直接用现成的 API而是训练了一个基于朴素贝叶斯的分类器再配合情感词典做归一化。具体流程是先对评论分词我用的是 jieba因为它在中文分词上的稳定性和扩展性都比其他库好然后把分词结果转成词向量特征用 TF-IDF 做权重最后丢进朴素贝叶斯分类器打分。这里有一个很关键的细节朴素贝叶斯在短文本分类上表现非常好因为短评的长度比较短特征维度相对稀疏朴素贝叶斯对稀疏数据并不敏感。它假设特征之间相互独立电影短评里“剧情 不错 演员 演技 好”这种组合独立性假设基本成立所以效果不错。我用标注好的三万条豆瓣短评做训练集正负样本大致均衡测试集上的准确率能到 82% 左右。这个数据不惊艳但对推荐系统来说已经够用了毕竟我们要的不是百分百精准而是能区分出评论里的基本情绪倾向。模型打分出来的是类别概率我会直接把概率值当成情感分数存进数据库。概率是 0.97 的评论和 0.51 的评论放在一起推荐算法就能通过这个连续值做更细粒度的排序。如果只存 positive / negative 二分类信息就丢了。这是个很简单但容易忽略的点分类结果只是离散标签概率本身也是高价值特征。3.2 讽刺和否定句式情感分析最大的坑做情感分析最头疼的不是长篇大论而是短评里的反讽和否定结构。比如“这部电影居然没让我睡着太难得了”字面意思是负面词“睡着”实际上是在夸电影。这种句子朴素贝叶斯很容易判错。我的处理方法是加一层否定词和转折词规则先扫描分词结果遇到“不、没、别、难、居然、竟然”这类词就把窗口内的情感词极性反转。这个规则不能覆盖所有情况但确实能把准确率拉回来几个点。另一个坑是表情符号。豆瓣短评里大量使用“哈哈”“。”和 emoji分词器不一定能正确识别。我在预处理阶段把常见表情符号统一映射为情感词比如“实在开心”映射成“开心”“汗”映射成“尴尬”映射表放在一个单独的文件里。这样做的好处是规则可维护后续发现新的符号只需要加一行。和情感分析相关的还有领域迁移问题。用通用情感词典做电影评论会出现“文艺”这种词在影评语境里经常是负面的但在日常语境里是中性的。所以我从训练语料里筛了一遍电影评论专属的高频词单独扩展了领域词典。这一步很值得做因为通用词典在特定领域下的误差率远比你想象的高。3.3 推荐算法情感分数怎么变成推荐结果推荐模块我做了两种思路一种是基于用户的协同过滤一种是基于情感标签的简单召回。前者解决“相似用户喜欢什么”的问题后者解决“哪类情绪的电影适合我”的问题。基于用户的协同过滤实现起来不复杂先把用户行为表转成矩阵行是用户列是电影值是行为权重。点击算 1 分收藏算 2 分主动评分算 3 分然后用皮尔逊相关系数计算用户之间的相似度找到最相似的 K 个用户把这些人喜欢的且目标用户没看过的电影排序推荐出去。但冷启动问题立刻就会冒出来新用户没有行为记录协同过滤完全没有用。我的解决办法是加一个基于情感分数均值的召回策略算出每部电影所有评论的情感均值比如“近期口碑正向且情感均值高”的电影直接推荐给冷启动用户。这其实就是最简单的流行度推荐但加上了情感过滤避免只看评分榜导致高分但口碑两极分化的电影推荐错人。真正写代码时我建议把推荐算法相关的部分全部封装成一个 recommend_service.py对外暴露两个函数recommend_by_behavior(user_id) 和 recommend_by_sentiment(movie_id, count)。这样 Flask 路由层只需要调用函数不用关心内部实现。你别小看这个设计我见过太多项目在路由里写一大段算法代码最后代码又臭又长根本没法维护。3.4 Flask 接口与页面展示Flask 这边最重要的一个设计是请求链路要短。入口首页渲染电影列表时我直接调用 MovieService 的 get_movies_with_sentiment() 函数一次性联表查询电影信息和评论情感均值用列表包成 dict 传到模板。这里我不会在页面里再发一个 Ajax 请求去查情感分数因为完全没必要多一次请求多一次失败的可能。详情页是体验最丰富的页面。除了电影基本信息我把所有评论按情感标签分组展示正向评论显示一个绿色标签负向显示灰色中性显示蓝色。页面上还有一个很直观的情感占比条用 CSS 渲染三个色块的宽度比例。这个情感占比条看起来不难但它是很多用户愿意多停留几秒的原因直观可见。推荐页的逻辑是如果当前用户已有行为记录就调用协同过滤接口如果没有记录就走情感均值召回。为了不让用户看到空白页我会在前端判断推荐列表是否为空为空就自动切到“全站热门正向”列表。这种兜底逻辑对用户体验影响很大我强烈建议任何推荐项目都加上。4. 复现全流程从源码到跑通服务4.1 环境准备和依赖安装项目文档里我写清楚了推荐使用的 Python 版本是 3.9 以上太低有些库装不上。先创建虚拟环境再统一安装依赖。依赖文件 requirements.txt 是我整理过的不会把开发环境里所有包一股脑导出来只保留真正用到的核心库。# 创建并激活虚拟环境 python3 -m venv venv source venv/bin/activate # 安装核心依赖 pip install flask3.0.0 pip install pandas numpy scikit-learn pip install jieba pip install pymysql pip install beautifulsoup4 requests我特别不推荐直接 pip install flask 不锁版本项目今天能跑一个月后 Flask 升级可能就出兼容问题。锁版本是写在文档里的硬性要求。如果你的网络环境下载慢可以加国内镜像源比如清华的 PyPI 镜像速度会快很多。Python 环境这里我多提醒一句如果你装的是系统自带的 Python记得不要直接用系统环境跑项目虚拟环境可以避免很多权限和冲突问题。之前帮我测试的朋友就是因为没建虚拟环境装包时把系统环境搞坏了最后重装了系统。4.2 初始化数据库并导入数据数据库部分我给了两种初始化方式。第一种是用 MySQL先执行 project.sql 建库建表再跑 init_db.py 导入初始数据第二种是直接用 SQLite跑一下 sqlite_init.py 就能生成一个本地文件适合不想安装 MySQL 的人。MySQL 初始化命令大概是这样的mysql -u root -p project.sqlproject.sql里面除了建表语句还会插入一部分已经采集并且打好情感分数的电影数据。这部分初始数据是我从豆瓣公开页面里整理的数量不大但足够把系统跑起来看到效果。如果你有爬虫经验也可以用采集脚本自己抓更多数据抓完重新跑一遍情感分析即可。数据库连接我放在了 db.py 里用 PyMySQL 连接 MySQL用 Python 内置的 sqlite3 连接 SQLite。两个连接方式封装成同一个函数接口上层代码不用改。切换数据库类型时只需要改 config.py 里的配置项。这个设计我很得意因为一开始我是写死 MySQL 的后来有人反馈没有 MySQL 环境我才补了 SQLite 支持。4.3 启动 Flask 服务与功能验证确保数据库导入成功、依赖安装完成后启动服务就很简单了python app.py默认端口是 5000浏览器打开 http://127.0.0.1:5000 就能看到首页。验证功能时我先看三件事第一首页电影列表能否正常加载第二详情页情感占比条是否和数据库里的情感分数一致第三点击收藏几部电影之后推荐页是否出现变化。我写了一个简单的自检脚本 check_system.py它会自动跑通这几步并给出提示。刚开始调试时最常遇到的问题就是数据库配好了但程序连不上或者是连上了但查询结果为空。自检脚本会把每一步的执行状态打印出来能省不少排查时间。4.4 目录结构说明项目能顺利复现很大程度靠的是文档。我把目录结构在 README 里画得很清楚app.pyFlask 主入口config.py全局配置包括数据库连接和模型路径models/数据库表和查询封装service/业务逻辑包含情感分析服务和推荐服务templates/Jinja2 模板static/CSS 和 JavaScriptdata/训练数据、情感词典、初始电影数据docs/开发文档和部署文档我见过的很多开源项目源码是一大堆文件夹没有任何说明根本不知道从何入手。所以这次写文档时我格外注意每一条命令都验证过每一个目录都解释了用途。文档确实花了不少时间但反馈证明这是值得的照着文档走基本没有人在环境搭建这一步卡住超过半小时。5. 常见问题与排查技巧5.1 中文乱码数据库里看到一堆问号这是最经典的问题。豆瓣评论是 UTF-8 编码如果 MySQL 连接没指定字符集或者表结构默认用了 latin1就会把中文存成乱码。我在 db.py 的连接参数里显式加了 charsetutf8mb4建表语句里也把表默认字符集设为 utf8mb4。utf8mb4 比 utf8 多支持 emoji 和特殊符号豆瓣短评里这些符号很常见所以一定要用 utf8mb4。如果你已经导入了乱码数据不用慌直接清掉重导。重新创建表之前先检查一下表结构的字符集是不是 utf8mb4用 SHOW CREATE TABLE 就能看到。修好表结构再重新跑导入脚本问题就解决了。5.2 数据库连接池耗尽我的系统刚开始用 Flask PyMySQL 直连数据库每次请求都开一个新连接请求一多就报 “Too many connections”。原因很简单高并发场景下连接没有复用数据库连接数被瞬间打满。解决办法是加一个连接池我用了 DBUtils 库的 PooledDB配置一个合适的连接池大小比如 10 个连接请求之间复用。加连接池之后性能提升得很明显不仅仅是避免报错查询响应也稳定了很多。这里有个参数要点连接池最大连接数不要设置得太大MySQL 默认 max_connections 是 151你设 200 也没用反而会让连接排队更严重。设成 10 到 20 之间就够这个项目用了。5.3 情感分析准确率不够怎么调优如果你觉得模型的准确率不满意第一步不是换模型而是检查训练语料的分布。我最初训练集里负面评论数量不到正面的三分之一导致模型倾向于把评论判成正向准确率虚高但实际效果别扭。后来我把负样本补到和正样本差不多准确率才恢复正常。类别不平衡是影响这类模型最容易被忽视的问题。第二步是调优句子的预处理流程。去停用词时不要把“不”“没”这类否定词去掉这是我在项目里踩过的坑。通用停用词表里有“不”结果去掉之后“不好看”变成“好看”整个情感被反转了。我的停用词表是手动调整过的去掉情感否定词和转折词这里的逻辑用代码块记录了下来# 自定义停用词保留否定词和转折词 stop_words set() with open(data/stopwords.txt, r, encodingutf-8) as f: for line in f: word line.strip() if word not in {不, 没, 别, 无, 非, 虽然, 但是, 居然, 竟然}: stop_words.add(word)如果你有精力后续还可以引入预训练模型比如 BERT 做微调效果会更好但项目里用朴素贝叶斯是有意的选择训练快、部署轻、依赖少。对于一个推荐系统来说情感分数只是一个特征没必要为了几个点的准确率把部署成本拉高。5.4 推荐结果太单一怎么办协同过滤一个最容易出现的问题是推荐结果全是热门电影相似用户喜欢的电影来来回回就是那几部。这其实是“马太效应”热门电影行为数据多相似度计算时占据主导。我的解决办法是引入随机因子在推荐列表的最后一位随机插入一部冷门但情感分数较高的电影给用户一点探索空间。这个操作很小但对体验的提升非常大。另外如果某个用户的行为记录太少协同过滤的结果会很稀碎。我的经验是行为记录少于五个就不走协同过滤直接使用情感召回。这就是一个“算法路由”的策略规则很简单但能让推荐系统在冷启动阶段不至于出一个完全乱七八糟的列表。5.5 Flask 生产部署的注意点本地开发直接用 app.run() 没问题但生产环境一定要换一个像样的 WSGI 服务器。我项目文档里用的是 gunicorn配置三个 worker一条命令就能起来gunicorn -w 3 -b 0.0.0.0:8000 app:appFlask 自带的开发服务器性能有限而且不安全不建议在正式场景下暴露出去。换 gunicorn 之后还要注意一个问题静态文件的处理。gunicorn 对静态文件的性能不如 Nginx所以我的部署方案是 Nginx 反代到 gunicorn静态文件交给 Nginx 直接托管。这个方案是最常见的组合稳定可靠配置也不复杂。6. 项目经验与后续扩展做这个系统最大的收获不是学会了一个算法或者一个框架而是把完整流程走了一遍从数据采集到清洗存储再到建模分析和 Web 展示每一步都有坑每一步都要自己填。如果你只是照着教程调一个模型你不会遇到编码问题、连接池问题、冷启动问题但做项目时这些问题全都躲不掉。把这些问题一个一个解决能力才算真的长在身上。后续扩展我打算做三件事第一把情感分析模型换成预训练模型用 transformers 做微调准确率肯定能再上一个台阶第二引入基于物品的协同过滤让推荐结果不再只依赖用户相似度电影之间的关联会更自然第三把 Web 端重构成前后端分离Flask 只提供 JSON API前端用 Vue 或者 React 写界面交互能做得更细腻。但我不建议一上来就把所有扩展全部做完而是先把现有系统的每一行代码吃透理解数据是怎么流动的再动手改。我的经验是一上来就大改往往改完出现问题不知道是算法的问题还是代码的问题。这个项目最好的学习方式就是带着文档从头到尾复现一遍遇到问题再回来看这里的排查记录。最后再说一个小技巧每次跑完情感分析和推荐之后可以往数据库里多存一份结果快照表这样以后调参时可以直接对比新旧结果而不是重新算一遍。我当时就是没存快照每次调完参数都要重跑整个流程浪费了不少时间。希望你现在做的时候能少走这个弯路。
返回列表