☰
Python深度学习豆瓣影评情感分析:从爬虫到LSTM模型实战
2026/9/26 18:36:20 网站建设 项目流程

简介:这是一份面向计算机相关专业学生与项目实战学习者的深度学习课程设计资料,围绕电影评论情感分析展开,可用于课程设计、期末大作业或自学练手。资源包共14个文件,包含csv数据集、txt文本语料、ipynb实验笔记、py源码脚本、pdf总结报告与pptx答辩演示,压缩包约21.28MB,覆盖从数据采集、模型训练到测试评估的完整流程。内容预览显示项目包含豆瓣电影评论数据、爬虫脚本、模型训练与测试代码,以及一份项目总结报告,便于读者理解情感分析任务的实现思路与实验组织方式。目前已有287人学习下载,适合希望快速搭建情感分析项目、参考完整代码结构与报告写法的学习者使用。

1. 一份能跑通的豆瓣影评情感分析项目,到底长什么样

豆瓣影评这种短文本,情感极性往往藏在“还行吧”“也就那样”这种模糊表达里,纯靠词典规则去判,准确率经常卡在七成上下晃。这份 Python 深度学习电影评论情感分析项目,走的是另一条路:用爬虫把豆瓣 Top250 的评论抓下来,清洗成二分类语料,再喂给深度学习模型训练,最后拿测试集验证效果。整套东西包含爬虫脚本、训练与测试 notebook、五万条评论原始数据、处理后的 CSV,还有一份项目总结报告 PDF 和答辩 PPT。它适合正在做课程设计、期末大作业的计算机专业学生,也适合想拿一个完整文本分类流程练手的人。你拿到手不是一堆散文件,而是一条从数据采集到模型评估的闭环链路,能直接复现,也能拆开改。

2. 数据从哪来:爬虫脚本与豆瓣评论采集的边界

2.1 先看清数据文件的分工

拿到压缩包解压后,别急着打开 notebook 就跑。先花两分钟把目录结构理一遍,后面调参和排错会省很多事。这个项目的文件大致分四类:爬虫相关、原始数据、处理后的数据、模型代码与文档。

文件/目录作用使用顺序
crawler/urls.txt待抓取的豆瓣电影条目链接爬虫输入
crawler/豆瓣top250电影urls.txtTop250 电影链接集合爬虫输入
crawler/豆瓣top250评论5w条.txt抓取落地的原始评论爬虫输出
crawler/crawler.ipynb爬虫主逻辑 notebook第一步运行
data/remaining_data_2type.csv二分类剩余语料训练备用
data/test_data1_2type.csv二分类测试集评估用
data/test_data.csv通用测试数据评估用
data/output.csv模型输出结果结果查看
code/model_train.ipynb模型训练主程序第二步运行
code/model_test.ipynb模型测试与评估第三步运行
豆瓣电影评论情感分析项目总结报告.pdf设计说明与结论写报告参考
豆瓣电影评论情感分析.pptx答辩演示答辩参考

提示:先确认crawler和data、code三个目录的相对位置没被解压工具打乱,notebook 里读文件的路径大多是相对路径,目录一乱就报 FileNotFoundError。

2.2 爬虫逻辑与请求节奏控制

豆瓣对高频请求是有风控的,直接循环猛拉,轻则返回空页,重则 IP 被临时限制。这个项目的爬虫 notebook 里,核心是构造条目页 URL、解析评论列表、翻页、落盘这几步。下面这段是常见写法的骨架,你可以对照自己的 crawler.ipynb 看结构是否一致。

import requests import time import random import pandas as pd from bs4 import BeautifulSoup HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36" } def fetch_comments(movie_id, start=0, limit=20): # start 控制翻页起点,豆瓣每页默认 20 条 url = f"https://movie.douban.com/subject/{movie_id}/comments?start={start}&limit={limit}&status=P" resp = requests.get(url, headers=HEADERS, timeout=10) if resp.status_code != 200: return [] soup = BeautifulSoup(resp.text, "html.parser") items = soup.select("div.comment-item") results = [] for it in items: try: content = it.select_one("span.short").text.strip() results.append(content) except AttributeError: continue return results all_comments = [] for mid in ["1292052", "1291546", "1292720"]: # 示例电影 id for page in range(0, 200, 20): # 每部抓 10 页 batch = fetch_comments(mid, start=page) all_comments.extend(batch) time.sleep(random.uniform(1.5, 3.5)) # 随机间隔,降低被封风险 pd.Series(all_comments).to_csv("豆瓣top250评论5w条.txt", index=False, header=False)

逻辑说明:fetch_comments负责单页抓取,start参数是翻页偏移量,豆瓣评论页每页 20 条,所以循环步长设 20。time.sleep加随机区间是血泪经验,固定间隔反而容易被识别成脚本。参数上,timeout=10防止某次请求卡死整个循环,status=P表示只看正常状态评论。落盘用to_csv存成纯文本,方便后面按行读取做清洗。

2.3 原始评论到二分类语料的清洗

抓下来的评论是纯文本,没有标签。这个项目用的是星级映射法:豆瓣评论自带评分,五星和四星归为正面,一星和二星归为负面,三星这种中性样本直接丢弃,避免模型学到模棱两可的边界。清洗环节要处理掉表情符号、URL、多余空白,还要过滤掉太短的评论。

import re import pandas as pd def clean_text(text): text = re.sub(r"http\S+", "", text) # 去 URL text = re.sub(r"[^\u4e00-\u9fa5a-zA-Z0-9,。!?]", "", text) # 只留中英文数字和常用标点 text = re.sub(r"\s+", " ", text).strip() return text df = pd.read_csv("豆瓣top250评论5w条.txt", header=None, names=["comment"]) df["comment"] = df["comment"].astype(str).apply(clean_text) df = df[df["comment"].str.len() >= 5] # 过滤过短样本 df.to_csv("data/remaining_data_2type.csv", index=False)

逻辑说明:正则第一条去链接,第二条把非中英文数字和常用标点的字符清掉,这一步会顺带干掉大部分表情。str.len() >= 5是经验阈值,太短的评论比如“好看”两个字,信息量不足以支撑训练。参数上,标点白名单可以根据你的语料再调,比如保留省略号有助于捕捉犹豫语气。

3. 模型怎么搭:从词向量到训练循环的落地细节

3.1 文本向量化方案的选择理由

中文短文本做情感分析,常见做法有两种:一种是先分词再训词向量,另一种是直接用字符级序列。这个项目的训练 notebook 里,走的是分词加词向量的路线,因为影评里“演技”“剧情”“拖沓”这类词的情感指向很明确,分词后词粒度特征更干净。分词工具用 jieba,词向量可以用预训练的中文词向量,也可以在自己的语料上训一个。

import jieba import numpy as np from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.preprocessing.sequence import pad_sequences def tokenize(text): return " ".join(jieba.lcut(text)) df["seg"] = df["comment"].apply(tokenize) MAX_WORDS = 20000 # 词表上限 MAX_LEN = 100 # 每条评论截断/补齐长度 tokenizer = Tokenizer(num_words=MAX_WORDS, oov_token="<UNK>") tokenizer.fit_on_texts(df["seg"]) sequences = tokenizer.texts_to_sequences(df["seg"]) X = pad_sequences(sequences, maxlen=MAX_LEN, padding="post", truncating="post")

逻辑说明:num_words=20000是词表上限,超出部分按频率截断,影评语料五万条左右,两万词表基本够用。MAX_LEN=100是序列长度,短评论补零,长评论截断,padding="post"表示在尾部补,和后面 Embedding 层的输入习惯保持一致。参数怎么改:如果你的语料更长,比如长影评,MAX_LEN可以提到 200 到 300,但显存和训练时间会跟着涨。

3.2 模型结构与训练参数配置

模型主体用 Embedding 加双向 LSTM 加全连接,这是文本分类里比较稳的结构,比纯 CNN 更能捕捉语序信息,又比 Transformer 轻量,适合课程设计这种算力有限的环境。下面这段是训练脚本的核心部分。

from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, Bidirectional, LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping EMBED_DIM = 128 model = Sequential([ Embedding(MAX_WORDS, EMBED_DIM, input_length=MAX_LEN), Bidirectional(LSTM(64, return_sequences=False)), Dropout(0.5), Dense(64, activation="relu"), Dropout(0.3), Dense(1, activation="sigmoid") # 二分类输出 ]) model.compile(optimizer="adam", loss="binary_crossentropy", metrics=["accuracy"]) model.summary() early_stop = EarlyStopping(monitor="val_loss", patience=3, restore_best_weights=True) history = model.fit( X_train, y_train, validation_split=0.2, epochs=20, batch_size=64, callbacks=[early_stop] )

逻辑说明:EMBED_DIM=128是词向量维度,太小表达力不够,太大容易过拟合,128 在五万条语料上是个平衡点。双向 LSTM 的 64 个隐藏单元,前后向各 64,拼接后是 128 维。两个 Dropout 分别放在 LSTM 后和全连接后,比例 0.5 和 0.3,是防过拟合的常规操作。EarlyStopping的patience=3表示验证损失连续三轮不降就停,restore_best_weights=True保证拿回最优那轮的权重,这个参数不加,最后存的可能是过拟合的模型。

3.3 训练过程监控与结果落盘

训练跑起来后,别只盯着最后那个准确率数字。history里存了每轮的训练损失、验证损失、训练准确率、验证准确率,把这几条曲线画出来,能看出模型是欠拟合还是过拟合。欠拟合表现为两条损失都高且降不下去,过拟合表现为训练损失继续降但验证损失开始抬头。

import matplotlib.pyplot as plt plt.plot(history.history["loss"], label="train_loss") plt.plot(history.history["val_loss"], label="val_loss") plt.legend() plt.savefig("loss_curve.png") # 测试集评估 loss, acc = model.evaluate(X_test, y_test, batch_size=64) print(f"test loss: {loss:.4f}, test acc: {acc:.4f}") # 预测结果落盘,对应 data/output.csv preds = (model.predict(X_test) > 0.5).astype(int) pd.DataFrame({"pred": preds.flatten(), "label": y_test}).to_csv("data/output.csv", index=False)

逻辑说明:model.predict输出的是 0 到 1 之间的概率,阈值 0.5 是默认切分点,如果正面样本明显多于负面,可以把这个阈值调低一点,让更多样本判为正面,具体调多少看验证集上的 F1。落盘的output.csv里同时存了预测值和真实标签,方便你后面算混淆矩阵,也方便写报告时贴结果。

4. 跑起来会遇到的坑:环境、路径与数据格式排查

4.1 环境依赖版本对不上

现象:notebook 一运行就报ImportError: cannot import name 'pad_sequences'或者 TensorFlow 相关模块找不到。原因:这份项目用的是 TensorFlow 2.x 的 Keras 接口,如果你本地装的是 TensorFlow 1.x,或者 Keras 单独装了一个版本,导入路径就对不上。解决:统一用 TensorFlow 2.x,pip install tensorflow==2.10这类版本,导入时写from tensorflow.keras.preprocessing.sequence import pad_sequences,不要写from keras.preprocessing...。jieba、pandas、numpy 这些也建议按 notebook 里 import 的版本装,避免 API 变动。

4.2 相对路径导致文件读不到

现象:FileNotFoundError: [Errno 2] No such file or directory: 'data/test_data.csv'。原因:notebook 的工作目录和你解压后的目录不一致,Jupyter 默认工作目录是启动时所在的文件夹,不是 notebook 文件所在文件夹。解决:在 notebook 开头加一段路径检查,或者用绝对路径。稳妥做法是先import os; print(os.getcwd())看当前目录,再用os.chdir()切到项目根目录,或者把读文件路径改成基于 notebook 位置的相对路径。

4.3 中文编码与 CSV 分隔符问题

现象:读 CSV 时报UnicodeDecodeError,或者读进来发现所有列挤在一列里。原因:豆瓣评论里可能混有特殊字符,文件保存时编码不是 UTF-8;另外 CSV 默认逗号分隔,如果评论内容里本身带逗号,列就会错位。解决:读的时候显式指定encoding="utf-8",如果还报错就试encoding="utf-8-sig"或gbk。分隔符问题用pd.read_csv(..., sep="\t")或sep=","按实际文件调整,落盘时用to_csv(..., index=False)避免多出一列索引。

4.4 标签与预测值维度不匹配

现象:model.evaluate时报 shape 不匹配,或者ValueError: logits and labels must have the same shape。原因:二分类的标签如果是 one-hot 编码成两列,而模型输出是单列 sigmoid,维度就对不上。解决:确认标签是 0/1 单列,不是[1,0]这种两列。如果原始标签是字符串“正面/负面”,先映射成 1/0。y_train和y_test的 shape 应该是(样本数,)或者(样本数,1),和模型输出对齐。

4.5 训练轮数设太多导致过拟合

现象:训练准确率冲到 0.98,测试准确率只有 0.82,验证损失曲线后期明显抬头。原因:影评语料里存在大量重复表达,模型把训练集背下来了。解决:EarlyStopping一定要加,patience设 2 到 3。另外可以适当增大 Dropout 比例,或者在 Embedding 层加dropout参数。数据层面,检查一下训练集和测试集有没有重叠样本,有的话去重。

5. 报告与答辩怎么用:把 notebook 结果转成能交的文档

5.1 从 output.csv 到混淆矩阵与指标表

课程设计报告里光写一个准确率是不够的,评审老师更想看你对结果的拆解。data/output.csv里存了预测值和真实标签,拿它算精确率、召回率、F1,再画个混淆矩阵,报告的技术含量就上来了。

from sklearn.metrics import classification_report, confusion_matrix import seaborn as sns df_out = pd.read_csv("data/output.csv") print(classification_report(df_out["label"], df_out["pred"], target_names=["负面", "正面"])) cm = confusion_matrix(df_out["label"], df_out["pred"]) sns.heatmap(cm, annot=True, fmt="d", cmap="Blues", xticklabels=["负面", "正面"], yticklabels=["负面", "正面"]) plt.xlabel("预测") plt.ylabel("真实") plt.savefig("confusion_matrix.png")

逻辑说明:classification_report会输出每个类别的精确率、召回率、F1 和支持度,正面和负面分开看,能发现模型是不是偏向某一类。混淆矩阵热力图直接贴进报告,比纯文字描述直观。参数上,target_names的顺序要和标签 0/1 对应,0 对应负面,1 对应正面,别写反了。

5.2 报告 PDF 与 PPT 的复用方式

项目里那份“豆瓣电影评论情感分析”项目总结报告 PDF,结构上一般包含选题背景、数据来源、模型设计、实验结果、结论几个部分。你可以把它当模板,把里面的数据集规模、模型参数、准确率数字替换成你自己跑出来的结果。PPT 同理,重点放三页:数据采集流程、模型结构图、实验结果对比。答辩时老师最爱问的两个问题,一是“为什么选 LSTM 不选 CNN”,二是“准确率这么高是不是过拟合了”,提前把损失曲线和混淆矩阵准备好,回答就有底气。

5.3 一个容易被忽略的验证习惯

跑完训练别只看测试集准确率就收工。我一般会从原始五万条评论里,随机抽二十条没进训练集的,手动过一遍模型预测,看看有没有明显判反的。这一步花不了几分钟,但经常能发现测试集分布和真实分布不一致的问题。比如测试集里正面样本偏多,模型就学会了倾向判正面,实际用的时候负面评论会被漏掉。发现这种情况,要么调整测试集采样,要么在损失函数里给少数类加权。从那以后我每次交课程设计前,都强制走一遍这个小样本人工抽检,比盯着一个准确率数字踏实得多。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询