简介:这是一套面向计算机专业本科生的毕业设计级电影数据分析实战项目,专为毕设开发、课程设计及Python数据科学进阶学习者打造,聚焦电影评分、票房等多维数据的采集、存储、可视化与预测全流程。资源包含38个文件,涵盖6个核心Python脚本(如movie_detail.py、database.py)、3个Jupyter Notebook(含predict.ipynb票房预测建模与visualization_pandas.ipynb可视化分析)、24张结果图表PNG(覆盖数据库结构、各维度可视化图及双模型预测对比)、1份PDF说明文档和1份MySQL建库SQL脚本,压缩包仅5.18MB,轻量易部署。已有4332人学习下载,所有代码均经严格调试,支持爬取豆瓣TOP250与猫眼票房数据,提供CSV与MySQL双持久化方案,并基于真实数据关系完成多角度可视化分析及票房影响因子建模预测,开箱即用,可直接作为毕设答辩系统提交。
1. 这不是又一个“爬豆瓣+画柱状图”的毕设Demo:它把票房预测嵌进可视化闭环,让答辩老师追问模型可解释性
你肯定见过那种“用 matplotlib 画了 5 张图、调了 3 次 seaborn 颜色、最后导出 PNG 放 PPT 里就交差”的电影数据分析毕设。但这份基于 Python 的电影数据可视化分析系统不一样——它在predict.ipynb里真跑通了线性回归 + 决策树双模型对比,在visualization_sql.ipynb中用 SQL 聚合结果反向驱动 Pandas 可视化逻辑,在movie_detail.py里把豆瓣详情页的 HTML 结构解析封装成可复用函数,甚至在db_struct.png里画出了带外键约束和索引标注的 MySQL 表关系图。它解决的不是“怎么画图”,而是“怎么让图说话、让图推导出可部署的预测逻辑”。适合正在卡在毕设第三章(系统设计)写不出技术深度、或被导师质疑“分析停留在表面”的计算机类本科生;也适合想补全“数据获取→清洗→建模→验证→可视化反馈”完整链路的转行学习者。它不教你怎么写爬虫原理,但告诉你豆瓣反爬触发阈值在哪、MySQL 插入失败时 pandas.to_sql 的if_exists='append'和chunksize=100怎么配才不丢数据、为什么predict.ipynb里必须先做StandardScaler再切训练集——这些才是答辩现场真正被问住的点。
2. 数据层:从豆瓣TOP250到MySQL建表,爬虫、清洗、双持久化不是并列选项而是因果链条
2.1 爬虫逻辑藏在 movie_basic.py 里:不是 requests.get 就完事,关键在请求头与节流控制
项目没用 Scrapy 这类重型框架,而是用原生requests+BeautifulSoup实现轻量爬取,这反而更贴近本科毕设实际能力边界。核心逻辑集中在movie_basic.py的crawl_douban_top250()函数中:
import requests from bs4 import BeautifulSoup import time import random def crawl_douban_top250(): headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", "Connection": "keep-alive", "Referer": "https://movie.douban.com/top250" } all_movies = [] for start in range(0, 250, 25): # 分页抓取,每页25条 url = f"https://movie.douban.com/top250?start={start}&filter=" try: response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() soup = BeautifulSoup(response.text, 'html.parser') for item in soup.find_all('div', class_='item'): title = item.find('span', class_='title').get_text(strip=True) rating = float(item.find('span', class_='rating_num').get_text(strip=True)) # 提取导演、主演、年份等字段(省略中间解析逻辑) movie_data = { 'title': title, 'rating': rating, 'year': extract_year(item), 'director': extract_director(item), 'actors': extract_actors(item), 'genre': extract_genre(item) } all_movies.append(movie_data) # 关键节流:每页后随机休眠 1~3 秒,避免触发豆瓣频率限制 time.sleep(random.uniform(1.2, 2.8)) except Exception as e: print(f"第 {start//25 + 1} 页抓取失败: {e}") continue return all_movies这段代码的关键不在语法,而在三个实操细节:
- User-Agent 必须动态更新:豆瓣会校验 UA 字符串长度和浏览器版本号,固定 UA 很快返回 403。项目中虽只写了一个 UA,但你在本地运行时应按
random.choice(USER_AGENTS)方式轮换(USER_AGENTS列表可自行补充 5~8 个主流浏览器 UA)。 - Referer 不可省略:豆瓣 TOP250 页面有 Referer 校验,缺失会导致响应体为空或跳转到验证码页。
- time.sleep() 是保命符:不是“为了礼貌”,而是防止 IP 被临时封禁。
random.uniform(1.2, 2.8)比固定sleep(2)更难被识别为脚本行为——这是某高校毕设答辩时被问及“如何规避反爬”的标准答案。
提示:
movie_basic.py同时支持猫眼票房榜爬取(函数名crawl_maoyan_boxoffice()),但需注意猫眼对 IP 请求频次更敏感,建议将sleep区间扩大至3.0~5.5秒,并在headers中增加X-Requested-With: XMLHttpRequest。
2.2 数据清洗与双持久化:pandas DataFrame 是中转站,MySQL 才是生产级存储
爬取后的原始数据存于内存列表all_movies,接下来进入清洗环节。项目在database.py中定义了clean_movie_data()函数,重点处理三类脏数据:
| 脏数据类型 | 典型表现 | 清洗方式 | 为什么必须做 |
|---|---|---|---|
| 年份字段混杂 | "2023 / 2022"、"2023年"、" " | 正则提取首年份re.search(r'^(\d{4})', year_str) | 后续时间序列分析、票房趋势建模依赖统一数值型年份 |
| 导演/演员字段多空格/换行 | "张艺谋<br/>陈凯歌"、"\n 吴京 \t" | strip()+replace('<br/>', '/')+re.sub(r'\s+', ' ', text) | 避免后续分词、统计导演作品数时因格式不一致导致计数错误 |
| 评分字段异常 | "8.9"(正常)、"暂无评分"(字符串)、None(空值) | pd.to_numeric(..., errors='coerce')转为 float,NaN值用median()填充 | 保证predict.ipynb中模型输入特征维度稳定,否则sklearn报ValueError: Input contains NaN |
清洗完成后,数据进入双持久化路径:
CSV 存储(快速验证):调用
pandas.DataFrame.to_csv('data/movies_cleaned.csv', index=False, encoding='utf-8-sig')。注意encoding='utf-8-sig'是 Windows 系统 Excel 打开中文不乱码的关键,index=False防止多一列序号干扰后续读取。MySQL 存储(答辩硬指标):
database.py中init_mysql_db()函数执行建表语句,其核心 DDL 如下:
CREATE TABLE IF NOT EXISTS movies ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(255) NOT NULL, rating FLOAT CHECK (rating >= 0 AND rating <= 10), year YEAR NOT NULL, director VARCHAR(100), actors TEXT, genre VARCHAR(200), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_year (year), INDEX idx_rating (rating) );这里有两个易被忽略的设计点:
CHECK (rating >= 0 AND rating <= 10)约束确保数据质量,避免爬虫误抓非评分字段(如“想看人数”)污染模型输入;INDEX idx_year和idx_rating是为后续visualization_sql.ipynb中高频GROUP BY year和WHERE rating > 8.5查询加速,没有索引时 250 条数据虽不明显,但扩展到 5000+ 条时查询延迟会从毫秒级升至秒级。
注意:MySQL 连接参数(host/user/password/db)硬编码在
database.py的DB_CONFIG字典中。你必须修改为本地环境值,且确保 MySQL 服务已启动、用户有movies库的INSERT权限。若用 Docker,推荐mysql:8.0镜像并挂载初始化 SQL(douban.sql即为此用途)。
2.3 douban.sql:不只是建表语句,它是数据库设计思维的实体化
douban.sql文件内容远不止CREATE TABLE。打开它,你会看到:
-- 创建数据库(兼容不同字符集) CREATE DATABASE IF NOT EXISTS movie_analytics DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE movie_analytics; -- 建表语句(同上) -- 插入示例数据(用于快速验证) INSERT INTO movies (title, rating, year, director, actors, genre) VALUES ('肖申克的救赎', 9.7, 1994, '弗兰克·德拉邦特', '蒂姆·罗宾斯 / 摩根·弗里曼', '剧情'), ('霸王别姬', 9.6, 1993, '陈凯歌', '张国荣 / 张丰毅 / 巩俐', '剧情 / 爱情'); -- 创建视图:简化高频查询 CREATE VIEW high_rated_movies AS SELECT title, rating, year, director FROM movies WHERE rating >= 9.0;这个 SQL 文件的价值在于:
utf8mb4_unicode_ci字符集支持 emoji 和生僻汉字(如某些导演名含古字),避免插入时报Incorrect string value;INSERT示例数据让你无需等待爬虫,python database.py运行后立刻有数据可查,极大缩短调试周期;high_rated_movies视图是典型“业务语义封装”——答辩时被问“如何快速获取高分电影清单”,你直接SELECT * FROM high_rated_movies,比写WHERE rating >= 9.0更体现工程素养。
3. 可视化层:pandas 与 SQL 双引擎驱动,不是“画图”,而是“用图验证假设”
3.1 visualization_pandas.ipynb:用 Pandas 原生方法做探索性分析(EDA)
该 Notebook 是整个分析流程的起点,所有图表均基于pandas.DataFrame直接绘图,不依赖额外 SQL 查询。核心分析逻辑如下:
import pandas as pd import matplotlib.pyplot as plt import seaborn as sns # 读取清洗后 CSV(或从 MySQL 读取,见下节) df = pd.read_csv('data/movies_cleaned.csv') # 1. 评分分布直方图(验证数据质量) plt.figure(figsize=(10, 6)) sns.histplot(df['rating'], bins=20, kde=True, color='steelblue') plt.title('豆瓣TOP250电影评分分布', fontsize=14) plt.xlabel('评分') plt.ylabel('频数') plt.grid(True, alpha=0.3) plt.show() # 2. 年份-评分散点图(探索时间趋势) plt.figure(figsize=(12, 6)) sns.scatterplot(data=df, x='year', y='rating', hue='genre', palette='tab10', s=60, alpha=0.7) plt.title('电影年份与评分关系(按类型着色)', fontsize=14) plt.xlabel('上映年份') plt.ylabel('豆瓣评分') plt.xticks(rotation=45) plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left') plt.tight_layout() plt.show() # 3. 类型占比环形图(业务洞察) genre_counts = df['genre'].str.split('/').explode().str.strip().value_counts() plt.figure(figsize=(8, 8)) plt.pie(genre_counts.values, labels=genre_counts.index, autopct='%1.1f%%', startangle=90) plt.title('电影类型分布占比', fontsize=14) plt.axis('equal') plt.show()这段代码背后是三个关键决策:
sns.histplot(..., kde=True):叠加核密度估计曲线,比纯直方图更能看出分布偏态(如评分是否右偏),这是答辩时解释“为什么多数电影集中在 7~8.5 分”的依据;sns.scatterplot(..., hue='genre'):用颜色区分类型,直观暴露“爱情片是否普遍评分更高”这类假设,后续predict.ipynb中“类型”会被 one-hot 编码为特征;str.split('/').explode().str.strip():处理genre字段多值问题(如"剧情/爱情/同性"),这是电影数据特有的清洗难点,直接value_counts()会把整个字符串当一个类别。
提示:
visualization_pandas.ipynb中所有图表均调用plt.tight_layout(),这是防止中文标签被截断的后悔药。若你本地显示异常,检查 Matplotlib 字体配置——在main.py开头添加:import matplotlib matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS'] matplotlib.rcParams['axes.unicode_minus'] = False
3.2 visualization_sql.ipynb:SQL 聚合先行,Pandas 只负责渲染
这个 Notebook 的哲学是:“计算交给数据库,展示交给 Python”。它不读 CSV,而是通过pymysql直连 MySQL,用 SQL 完成聚合,再将结果转为 DataFrame 绘图。例如“各年代平均评分趋势”:
import pymysql import pandas as pd import matplotlib.pyplot as plt # 连接数据库(使用 database.py 中的 DB_CONFIG) conn = pymysql.connect(**DB_CONFIG) # SQL 聚合:按十年分组计算平均评分 query = """ SELECT FLOOR(year/10)*10 AS decade, ROUND(AVG(rating), 2) AS avg_rating, COUNT(*) AS movie_count FROM movies WHERE year >= 1930 -- 过滤掉测试数据中的异常年份 GROUP BY decade ORDER BY decade; """ df_decade = pd.read_sql(query, conn) # 绘图(仅渲染,不计算) plt.figure(figsize=(10, 6)) bars = plt.bar(df_decade['decade'].astype(str), df_decade['avg_rating'], color='lightcoral', alpha=0.8, width=8) plt.title('各年代电影平均评分趋势', fontsize=14) plt.xlabel('年代') plt.ylabel('平均评分') plt.ylim(7.0, 9.5) # 固定Y轴范围,突出变化幅度 # 在柱子上方标注数值 for bar, rating in zip(bars, df_decade['avg_rating']): plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.05, f'{rating}', ha='center', va='bottom', fontsize=10) plt.grid(True, alpha=0.3) plt.show()这种模式的优势在于:
- 性能可控:250 条数据虽小,但若未来扩展到 10 万条,
GROUP BY在数据库层执行比 Pandasgroupby().mean()快 3~5 倍; - 逻辑清晰:SQL 语句本身即文档,答辩时指着
FLOOR(year/10)*10解释“为何用十年为单位”,比说“我用 Pandas 分组”更有说服力; - 可复用性强:同一 SQL 可被
predict.ipynb直接调用生成特征,避免重复计算。
注意:
visualization_sql.ipynb中所有 SQL 查询都加了WHERE year >= 1930。这是血泪经验——豆瓣 TOP250 中存在少量year=0或year=1900的测试/异常数据,不加过滤会导致FLOOR(0/10)*10=0,在图表中出现荒谬的“0年代”柱子。
3.3 可视化避坑:五条让答辩不翻车的硬核经验
现象 → 原因 → 解决,全是真实踩过的坑:
现象:
output.png图表中中文全部显示为方块(□□□)
原因:Matplotlib 默认字体不支持中文,且未设置rcParams或字体路径
解决:在main.py或每个 Notebook 开头执行字体配置(见上文提示),或下载simhei.ttf放入项目fonts/目录,用matplotlib.font_manager.FontProperties(fname='fonts/simhei.ttf')指定现象:
p (1).png到p (19).png中部分图片空白或尺寸极小
原因:Jupyter Notebook 中plt.show()被多次调用,或plt.figure()未指定figsize导致默认尺寸过小
解决:每个绘图块前加plt.figure(figsize=(10, 6)),绘图后加plt.tight_layout(),禁止在循环中反复plt.show()现象:
visualization_sql.ipynb运行报错pymysql.err.OperationalError: (1045, "Access denied for user...")
原因:database.py中DB_CONFIG的密码含特殊字符(如@,/,:),URL 解析失败
解决:对密码进行 URL 编码,urllib.parse.quote_plus(password),或改用简单密码(毕设环境可接受)现象:
p (12).png(导演作品数排行榜)中张艺谋显示为“张艺谋 / 陈凯歌”
原因:director字段清洗时未处理/分隔符,导致value_counts()将组合字符串当单个导演
解决:在clean_movie_data()中对director字段执行split('/')[0].strip(),只取首位导演现象:
db.png(数据库ER图)在 PowerPoint 中模糊不清
原因:db_struct.png是 PNG 格式,缩放失真;且原始绘图分辨率不足
解决:用graphviz重绘 ER 图(pip install graphviz),导出为 SVG 矢量图,或用plt.savefig('db_struct.svg', format='svg', dpi=300)生成高清 PNG
4. 预测层:从可视化结论到可解释模型,predict.ipynb是毕设技术深度的分水岭
4.1 特征工程:把“导演名气”“类型热度”这些玄学概念量化成数字
predict.ipynb的核心价值,不在于用了什么高大上算法,而在于它把可视化发现的业务规律,转化成了可输入模型的结构化特征。例如:
- 导演影响力得分:从
visualization_pandas.ipynb的“导演作品数排行榜”中,取前 20 位导演,为其赋分(如张艺谋=10,冯小刚=8),其余导演归为“其他”=1。这不是拍脑袋,而是基于value_counts()排序的客观数据。 - 类型热度系数:从
visualization_pandas.ipynb的“类型占比环形图”中,计算各类型出现频次,归一化后作为权重。如“剧情”占比 35%,则系数为 0.35;“爱情”占比 22%,系数为 0.22。 - 年代衰减因子:基于
visualization_sql.ipynb的“年代平均评分趋势”,发现 2000 年代后评分呈缓慢下降,故对year字段做1/(2024 - year + 1)变换,使新片权重更高。
这些特征在代码中体现为:
# 基于已有 DataFrame df(从 MySQL 读取) director_score_map = {'张艺谋': 10, '陈凯歌': 8, '冯小刚': 8, '姜文': 7, '王家卫': 7} df['director_score'] = df['director'].map(director_score_map).fillna(1) # 类型热度(需先 explode genre) genre_popularity = df['genre'].str.split('/').explode().value_counts(normalize=True) df['genre_popularity'] = df['genre'].map(lambda x: sum(genre_popularity.get(g.strip(), 0) for g in x.split('/'))) # 年代衰减 df['year_decay'] = 1 / (2024 - df['year'] + 1)关键点:所有特征构造都引用自前面 Notebook 的可视化结果,形成“分析→结论→建模”的闭环。答辩时被问“为什么选这几个特征”,你指着
p (12).png和p (3).png就能回答,而不是说“网上教程这么写的”。
4.2 双模型对比:线性回归打底,决策树验证非线性关系
项目未用复杂模型,而是务实选择两个可解释性强的算法:
from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression from sklearn.tree import DecisionTreeRegressor from sklearn.preprocessing import StandardScaler from sklearn.metrics import mean_absolute_error, r2_score # 特征列(不含目标变量 rating) feature_cols = ['year_decay', 'director_score', 'genre_popularity', 'year'] X = df[feature_cols] y = df['rating'] # 关键预处理:必须标准化!否则决策树虽不敏感,但线性回归系数会失真 scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # 划分训练/测试集(固定 random_state 保证可复现) X_train, X_test, y_train, y_test = train_test_split( X_scaled, y, test_size=0.2, random_state=42 ) # 线性回归 lr = LinearRegression() lr.fit(X_train, y_train) y_pred_lr = lr.predict(X_test) # 决策树 dt = DecisionTreeRegressor(max_depth=5, random_state=42) dt.fit(X_train, y_train) y_pred_dt = dt.predict(X_test) # 评估 print("Linear Regression MAE:", mean_absolute_error(y_test, y_pred_lr)) print("Decision Tree MAE:", mean_absolute_error(y_test, y_pred_dt)) print("Linear Regression R²:", r2_score(y_test, y_pred_lr)) print("Decision Tree R²:", r2_score(y_test, y_pred_dt))这里必须强调三点:
StandardScaler不可省略:虽然决策树不依赖特征尺度,但线性回归的系数大小直接反映特征重要性。若year_decay(值域 0.01~0.1)和director_score(值域 1~10)不标准化,模型会错误认为后者更重要;max_depth=5是平衡点:过深(如 10)导致过拟合,r2_score训练集 0.95 但测试集 0.6;过浅(如 3)欠拟合,无法捕捉类型与年代的交互效应;random_state=42是答辩刚需:保证每次运行结果一致,避免答辩时“刚才还对,现在错了”的尴尬。
4.3 模型可解释性:用tree.plot_tree和系数表让评委看懂你的思路
predict.ipynb最后部分不是画预测 vs 真实值散点图,而是输出可解释性分析:
# 线性回归系数解读 coef_df = pd.DataFrame({ 'Feature': feature_cols, 'Coefficient': lr.coef_, 'Abs_Coefficient': np.abs(lr.coef_) }).sort_values('Abs_Coefficient', ascending=False) print("线性回归特征重要性(按系数绝对值排序):") print(coef_df) # 决策树可视化(仅前3层,避免图过大) plt.figure(figsize=(20, 10)) plot_tree(dt, max_depth=3, feature_names=feature_cols, filled=True, fontsize=10, rounded=True, class_names=['Rating'], proportion=False) plt.title("决策树前3层结构(max_depth=3)", fontsize=14) plt.show()输出表格类似:
| Feature | Coefficient | Abs_Coefficient |
|---|---|---|
| director_score | 0.42 | 0.42 |
| year_decay | 0.31 | 0.31 |
| genre_popularity | 0.18 | 0.18 |
| year | -0.05 | 0.05 |
这直接回答了“哪个因素影响最大”——导演影响力(0.42)> 年代新鲜度(0.31)> 类型热度(0.18)。而决策树图则显示:第一层分裂是director_score >= 5.5,第二层是year_decay >= 0.03,证明模型确实学到了“知名导演+新片更可能高分”的业务规则。
提示:
plot_tree需要graphviz支持。Windows 用户安装graphviz-2.38.msi后,需在系统环境变量PATH中添加C:\Program Files\Graphviz2.38\bin和C:\Program Files\Graphviz2.38\graphviz,否则报FileNotFoundError: 'dot' not found。
5. 系统集成与答辩技巧:main.py是入口,README.pdf是你的隐形答辩稿
5.1 main.py:一行命令启动全流程,但必须理解每个模块的依赖顺序
main.py是整个系统的门面,其结构清晰体现了工程化思维:
if __name__ == "__main__": print("=== 电影数据可视化分析系统启动 ===\n") # 步骤1:数据获取(可选,若已有数据可跳过) if input("是否重新爬取豆瓣TOP250?(y/n): ").lower() == 'y': from src.movie_basic import crawl_douban_top250 movies = crawl_douban_top250() print(f"成功爬取 {len(movies)} 部电影\n") else: print("跳过爬取,使用现有数据\n") # 步骤2:数据清洗与持久化 from src.database import clean_movie_data, init_mysql_db, save_to_mysql df_clean = clean_movie_data() # 从 CSV 或数据库读取原始数据 init_mysql_db() # 确保表结构存在 save_to_mysql(df_clean) # 写入 MySQL print("数据清洗与MySQL存储完成\n") # 步骤3:可视化分析(生成PNG) print("开始生成可视化图表...") # 这里本应调用 visualization_pandas.ipynb 和 visualization_sql.ipynb 的核心逻辑 # 但为简化,项目采用手动运行 Notebook 方式,main.py 仅作流程提示 print("请依次运行:visualization_pandas.ipynb → visualization_sql.ipynb → predict.ipynb\n") # 步骤4:预测模型(需手动运行 predict.ipynb) print("预测模型已就绪,请在 Jupyter 中运行 predict.ipynb 查看结果")这个脚本的价值不在于自动化,而在于显式声明依赖关系:
- 爬虫(
movie_basic.py)是源头,但可跳过; - 清洗(
database.py)必须在可视化前完成,否则图表数据不准; - 可视化(
.ipynb)是中间产物,其输出(如p (1).png)是答辩 PPT 的素材; - 预测(
predict.ipynb)是终点,其R²和特征重要性表是技术深度的证明。
答辩时被问“系统如何运行”,你不必背代码,只需指着main.py说:“它按四步走:获取→清洗→分析→预测,每一步输出都可验证,比如db.png是清洗后数据库状态,p (1).png是分析结果,预测1.png是模型输出。”
5.2 README.pdf:不是说明书,而是你提前写给评委的答辩问答提纲
打开README.pdf,你会发现它根本不是功能列表,而是按答辩逻辑组织的问答体:
Q:为什么选豆瓣TOP250和猫眼票房榜?
A:TOP250 代表口碑标杆(长尾效应弱),猫眼代表市场热度(短期爆发强),二者结合可分析“口碑与票房是否正相关”——这正是predict.ipynb中未直接建模、但可视化已暗示的核心问题。Q:MySQL 和 CSV 双存储是否冗余?
A:否。CSV 用于快速迭代(改代码后pandas.read_csv重跑),MySQL 用于模拟生产环境(visualization_sql.ipynb直接查库,体现工程规范)。答辩时可演示SELECT COUNT(*) FROM movies和SELECT * FROM high_rated_movies LIMIT 5。Q:预测模型准确率不高(R²≈0.6),是否失败?
A:否。电影票房受太多不可控因素(宣发、档期、社会事件)影响,R²>0.5 在此类问题中已具参考价值。本项目重点是建立可解释的特征工程 pipeline,而非追求黑箱精度。coef_df表明导演影响力权重最高,这与行业认知一致。Q:如何扩展到实时数据?
A:当前为离线分析。若需实时,可将crawl_douban_top250()改为定时任务(APScheduler),清洗后用pandas.to_sql(if_exists='replace')覆盖表,可视化 Notebook 设置自动刷新(%run -i visualization_sql.ipynb)。
这份 PDF 的厉害之处在于:它把评委最可能问的 4~5 个尖锐问题,提前用技术事实回答了。你不需要临场发挥,只需说:“这个问题在README.pdf第3页有详细说明。”
5.3 答辩现场必做的三件事:截图、对比、讲清“为什么”
从那以后我每次准备毕设答辩,都强制走一遍这三步:
- 截图
db_struct.png+p (12).png+预测1.png三张图,放在 PPT 同一页,用箭头标出“数据库设计 → 导演分析 → 预测输入”的数据流向,证明不是拼凑; - 对比
Linear Regression R²和Decision Tree R²,强调“我们尝试了两种范式,线性模型更易解释,树模型捕捉非线性,最终选择前者因毕设重在逻辑而非精度”; - 讲清每一个
if和for的“为什么”:比如movie_basic.py中for start in range(0, 250, 25),必须说明“豆瓣TOP250分10页,每页25条,range 步长25确保不漏页,上限250防止越界”。
这三步做完,评委的问题基本都在你预判范围内。他们不再问“这个怎么写的”,而是问“这个思路很有趣,你是怎么想到的?”——那一刻,你就从学生变成了工程师。希望帮到你。
本文还有配套的精品资源,点击获取