ARTICLE DETAIL

资讯详情

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

Python电影数据库大作业:从建表到分析报告全流程

Python电影数据库大作业:从建表到分析报告全流程 简介这是一份面向计算机相关专业在校学生与教师的数据库课程设计完整资料围绕电影数据查询系统展开适合作为课程大作业、毕设立项或Python数据库入门练手项目。资源包共77个文件约4.96MB包含10个Python源码文件、4个HTML页面、5个JavaScript脚本及配套样式文件另有15份Markdown文档、17张PNG与13张JPG截图覆盖MongoDB安装配置、复制集搭建、数据导入云服务器、索引优化、数据分析与前端接口说明等环节。项目实现按用户ID检索观影记录并按时间倒序展示前三标签及关联度、关键词模糊查询电影、风格热门Top20等功能界面含输入框与任务提交按钮结果分页展示。已有359人学习文档中附有E-R图、数据表结构图与排错记录可帮助读者理解从数据导入到Web查询的完整链路并参考其报告与进度说明完成答辩准备。1. 电影数据库大作业从建表到分析报告一套能直接交的 Python 方案每年学期末总有一批人对着「数据库大作业」四个字发愁。选题选了电影数据库听起来简单——不就是几部电影、几个演员、几条评分吗真动手才发现ER 图怎么画才不被老师挑刺、评分和评价到底要不要拆表、Python 连 SQLite 还是 MySQL、数据分析部分用什么图才显得有工作量、操作报告又该写哪些内容。这套「基于 Python 的电影数据库数据系统」本质上是一个完整闭环用 Python 建库建表、灌入电影数据集、做增删改查、跑数据分析、最后输出可视化图表和操作报告。它适合三类人数据库课程期末要交大作业的学生、想用一个真实小项目练手 Python SQL 的入门者、以及需要一份「能跑起来、能讲清楚」的数据分析案例的从业者。下面我按实际做一遍的顺序把建库、灌数据、查询、分析、报告这条链路拆开讲参数怎么设、坑在哪都写清楚。2. 建库建表电影数据库的 ER 图怎么画才经得起追问2.1 先定实体边界别一上来就画 ER 图很多人拿到「电影数据库」这个题目第一反应是打开画图工具开始画 ER 图。这是典型的翻车起点。ER 图是结果不是起点。你得先想清楚这个系统里到底有哪些「东西」需要被独立记录。电影数据库最常见的实体有五个电影movie、导演director、演员actor、用户user、评分rating。评价review要不要单独拆取决于你的作业要求里有没有「文字评论」这一项。如果只有打分评分表就够了如果要写影评文字就得把 review 从 rating 里拆出来否则一张表里既有数字又有长文本范式上不好看老师一问就露馅。我一般会先列一张实体-属性草表确认每个实体的主键和关键属性再动手画图。这一步花十分钟能省掉后面改表结构的两小时。实体主键关键属性说明moviemovie_idtitle, year, genre, duration电影基本信息directordirector_idname, country导演actoractor_idname, gender演员useruser_idusername, register_date系统用户ratingrating_iduser_id, movie_id, score, rate_time评分记录reviewreview_iduser_id, movie_id, content, review_time文字评价可选这张表不是给你交的是给你自己理思路的。确认无误后再画 ER 图实体之间的关系就一目了然电影和导演是多对一一个导演可以拍多部电影电影和演员是多对多需要中间表 movie_actor用户和电影通过 rating 形成多对多。2.2 用 Python SQLite 建库建表的最小可跑代码选 SQLite 而不是 MySQL理由很直接大作业场景下SQLite 零配置、单文件、Python 标准库自带 sqlite3 模块不需要额外装数据库服务。老师要看的是你的表结构设计和 SQL 能力不是你的运维水平。当然如果课程明确要求 MySQL把连接部分换掉即可建表语句基本通用。import sqlite3 # 连接数据库如果文件不存在会自动创建 conn sqlite3.connect(movie_db.sqlite) cursor conn.cursor() # 开启外键约束SQLite 默认关闭不开启外键形同虚设 cursor.execute(PRAGMA foreign_keys ON) # 电影表 cursor.execute( CREATE TABLE IF NOT EXISTS movie ( movie_id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, release_year INTEGER, genre TEXT, duration INTEGER, director_id INTEGER, FOREIGN KEY (director_id) REFERENCES director(director_id) ) ) # 导演表 cursor.execute( CREATE TABLE IF NOT EXISTS director ( director_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, country TEXT ) ) # 演员表 cursor.execute( CREATE TABLE IF NOT EXISTS actor ( actor_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, gender TEXT ) ) # 电影-演员中间表多对多 cursor.execute( CREATE TABLE IF NOT EXISTS movie_actor ( movie_id INTEGER, actor_id INTEGER, role_name TEXT, PRIMARY KEY (movie_id, actor_id), FOREIGN KEY (movie_id) REFERENCES movie(movie_id), FOREIGN KEY (actor_id) REFERENCES actor(actor_id) ) ) # 用户表 cursor.execute( CREATE TABLE IF NOT EXISTS user ( user_id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, register_date TEXT ) ) # 评分表 cursor.execute( CREATE TABLE IF NOT EXISTS rating ( rating_id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, movie_id INTEGER, score REAL CHECK(score 0 AND score 10), rate_time TEXT, FOREIGN KEY (user_id) REFERENCES user(user_id), FOREIGN KEY (movie_id) REFERENCES movie(movie_id) ) ) conn.commit() conn.close() print(数据库和表创建完成)这段代码有几个关键点值得说。第一PRAGMA foreign_keys ON必须显式开启SQLite 默认不强制外键很多人建了外键但根本没生效插入脏数据也不报错答辩时被问到就尴尬了。第二CHECK(score 0 AND score 10)是评分范围约束加上它后面插入异常分数时数据库会直接拒绝比在 Python 层做校验更可靠。第三中间表movie_actor用联合主键(movie_id, actor_id)防止同一部电影同一个演员被重复插入。第四建表顺序有讲究——movie 引用了 director所以 director 要先建否则外键约束会报错。2.3 数据集怎么来、怎么灌进去数据集来源通常有三种老师给的 CSV、自己从公开数据整理、或者用脚本生成模拟数据。不管哪种灌数据的核心逻辑是一样的读源数据 → 清洗 → 按依赖顺序插入。假设你有一个movies.csv字段是 title, year, genre, duration, director_name, actors分号分隔。灌数据的脚本要处理两个问题导演和演员需要先去重插入拿到自增 ID 后再插入电影和中间表。import sqlite3 import csv conn sqlite3.connect(movie_db.sqlite) cursor conn.cursor() cursor.execute(PRAGMA foreign_keys ON) def get_or_create_director(name, countryNone): 导演不存在则插入返回 director_id cursor.execute(SELECT director_id FROM director WHERE name ?, (name,)) row cursor.fetchone() if row: return row[0] cursor.execute(INSERT INTO director (name, country) VALUES (?, ?), (name, country)) return cursor.lastrowid def get_or_create_actor(name, genderNone): cursor.execute(SELECT actor_id FROM actor WHERE name ?, (name,)) row cursor.fetchone() if row: return row[0] cursor.execute(INSERT INTO actor (name, gender) VALUES (?, ?), (name, gender)) return cursor.lastrowid with open(movies.csv, r, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: # 先处理导演 director_id get_or_create_director(row[director_name]) # 插入电影 cursor.execute( INSERT INTO movie (title, release_year, genre, duration, director_id) VALUES (?, ?, ?, ?, ?) , (row[title], int(row[year]), row[genre], int(row[duration]), director_id)) movie_id cursor.lastrowid # 处理演员列表 if row.get(actors): for actor_name in row[actors].split(;): actor_name actor_name.strip() if actor_name: actor_id get_or_create_actor(actor_name) cursor.execute( INSERT OR IGNORE INTO movie_actor (movie_id, actor_id) VALUES (?, ?) , (movie_id, actor_id)) conn.commit() conn.close() print(数据导入完成)get_or_create这个模式在数据导入里非常常用核心思路是「先查后插」避免重复数据。INSERT OR IGNORE用在中间表上即使遇到重复的 (movie_id, actor_id) 组合也不会报错中断。注意cursor.lastrowid返回的是刚插入那行的自增主键这个值在同一个连接里是准确的但多线程环境下要小心。灌完数据后一定要做个行数校验确认每张表的记录数和源数据对得上cursor.execute(SELECT COUNT(*) FROM movie) print(电影数:, cursor.fetchone()[0]) cursor.execute(SELECT COUNT(*) FROM movie_actor) print(电影-演员关系数:, cursor.fetchone()[0])如果电影数是 0检查 CSV 编码和字段名是否匹配如果中间表数量明显偏少检查演员分隔符是不是分号有些数据集用的是逗号或竖线。3. 增删改查与数据分析从 SQL 到可视化图表3.1 增删改查不是目的是验证表结构的手段很多人的大作业里增删改查就是几条孤立的 SQL 语句跟后面的分析完全脱节。其实增删改查是验证你表结构设计是否合理的最好方式。比如删除一个导演时他名下的电影怎么办如果外键设了ON DELETE CASCADE电影会被一起删掉如果设了ON DELETE SET NULL电影的 director_id 会变成 NULL如果什么都没设删除会直接报错。这三种行为对应三种业务逻辑你在报告里写清楚选了哪种、为什么就是加分项。# 增插入一个新用户 cursor.execute(INSERT INTO user (username, register_date) VALUES (?, ?), (test_user, 2025-01-15)) # 删删除评分低于 2 分的记录注意先删子表再删主表 cursor.execute(DELETE FROM rating WHERE score 2) # 改把某部电影的时长修正 cursor.execute(UPDATE movie SET duration ? WHERE title ?, (148, 示例电影)) # 查查询每部电影的平均分和评分人数 cursor.execute( SELECT m.title, ROUND(AVG(r.score), 2) AS avg_score, COUNT(r.rating_id) AS num_ratings FROM movie m LEFT JOIN rating r ON m.movie_id r.movie_id GROUP BY m.movie_id HAVING num_ratings 1 ORDER BY avg_score DESC ) for row in cursor.fetchall(): print(row)这里有个细节LEFT JOIN保证即使电影没有评分也会出现在结果里HAVING过滤掉评分人数为 0 的记录。如果用INNER JOIN没评分的电影直接消失做分析时容易漏数据。3.2 用 pandas matplotlib 做电影数据分析数据分析部分是大作业里最容易拉开差距的地方。同样一份数据有人只画了个柱状图有人能做出类型分布、评分趋势、导演产量对比、演员合作网络四五个维度。关键不在于图多而在于每个图能回答一个具体问题。import sqlite3 import pandas as pd import matplotlib.pyplot as plt # 设置中文字体否则图表里的中文会变成方块 plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False conn sqlite3.connect(movie_db.sqlite) # 读取电影和评分数据 df_movie pd.read_sql(SELECT * FROM movie, conn) df_rating pd.read_sql(SELECT * FROM rating, conn) # 合并计算每部电影的平均分 df_avg df_rating.groupby(movie_id)[score].agg([mean, count]).reset_index() df_avg.columns [movie_id, avg_score, rating_count] df_merged df_movie.merge(df_avg, onmovie_id, howleft) # 图1各类型电影数量分布 genre_counts df_movie[genre].value_counts() genre_counts.plot(kindbar, title各类型电影数量分布) plt.tight_layout() plt.savefig(genre_distribution.png, dpi150) plt.close() # 图2评分与评分人数的散点图 plt.scatter(df_merged[rating_count], df_merged[avg_score], alpha0.6) plt.xlabel(评分人数) plt.ylabel(平均分) plt.title(电影评分与评分人数关系) plt.tight_layout() plt.savefig(rating_scatter.png, dpi150) plt.close() conn.close() print(图表已保存)pd.read_sql直接把 SQL 查询结果转成 DataFrame省去手动拼列表的麻烦。groupby agg是 pandas 里做分组统计的标准写法agg([mean, count])一次算出均值和计数。merge的howleft保证没评分的电影也保留在结果里avg_score会是 NaN后续画图时自动跳过。中文字体那两行是血泪经验。不设的话图表标题和轴标签全是方框交上去老师第一眼就觉得你没跑通。Windows 用SimHeiMac 用Arial Unicode MSLinux 服务器上如果没有中文字体考虑用英文标签或者提前装字体。3.3 操作报告该写什么、不该写什么操作报告不是代码的复制粘贴。老师想看的是你的设计决策和验证过程。一份合格的操作报告至少包含系统功能概述一段话说清楚做了什么、ER 图及设计说明为什么这样拆表、表结构定义每张表的字段、类型、约束、核心功能实现增删改查各举一例附 SQL 和运行结果、数据分析结论每个图说明了什么、遇到的问题及解决方式。不该写的大段代码原文、安装 Python 的步骤截图、跟本系统无关的技术介绍。报告控制在 8-15 页重点放在设计思路和结果分析上。4. 避坑与排查电影数据库大作业里最容易翻车的 5 个点4.1 中文乱码从 CSV 到图表全链路排查现象CSV 读进来电影名是乱码或者图表里中文显示为方块。原因CSV 文件编码可能是 GBK 而不是 UTF-8matplotlib 默认字体不支持中文。解决读 CSV 时先试encodingutf-8报错就换encodinggbk。图表中文问题在代码开头加plt.rcParams[font.sans-serif] [SimHei]和plt.rcParams[axes.unicode_minus] False。如果 SimHei 不存在用matplotlib.font_manager查一下系统里有哪些中文字体。4.2 外键约束不生效插入了脏数据却没人报错现象插入了一条 rating 记录user_id 填了一个不存在的用户数据库居然接受了。原因SQLite 默认不开启外键约束每次连接都要手动PRAGMA foreign_keys ON。解决在每次建立连接后立即执行这个 PRAGMA。注意它是连接级别的不是数据库级别的换个连接就得重新设。如果用的是 MySQLInnoDB 引擎默认开启外键但也要确认表引擎不是 MyISAM。4.3 自增 ID 错乱删了数据后新插入的 ID 不连续现象删除了 movie_id 为 3 的记录下一条插入的电影 ID 是 4 而不是 3。原因AUTOINCREMENT 的行为就是单调递增不会复用已删除的 ID。这是正常行为不是 bug。解决如果作业要求 ID 连续可以在删除后手动重置sqlite_sequence表但一般不推荐这么做。ID 不连续不影响功能报告里说明一下即可。4.4 评分平均值算错NULL 值把结果拉偏了现象某部电影的平均分算出来是 0但实际上有评分记录。原因用了AVG(score)但 JOIN 产生了 NULL 行或者 WHERE 条件过滤掉了有效数据。解决用AVG(COALESCE(score, 0))处理 NULL或者用LEFT JOIN后在 Python 层用 pandas 的dropna()过滤。更稳妥的做法是先确认数据里有没有 NULL 的 score再决定处理策略。4.5 图表保存后空白plt.show() 和 plt.savefig() 的顺序问题现象保存的图片是空白的但运行时不报错。原因先调了plt.show()窗口关闭后画布被清空再调plt.savefig()就保存了个空白图。解决先savefig再show或者干脆不在脚本里show只保存文件。批量生成图表时每张图之后要plt.close()否则内存里会堆积画布图多了会卡死。5. 进阶技巧把大作业变成能写进简历的项目如果你想让这个电影数据库大作业不只是「交完就忘」有几个方向可以往下挖。第一个方向是加一个简单的 Web 界面。用 Flask 或 Streamlit 把查询和图表包一层浏览器里能点能看。Streamlit 尤其适合这种场景十几行代码就能把 pandas 的 DataFrame 和 matplotlib 的图表渲染成网页。面试时你说「我做过一个电影数据系统有 Web 界面」比「我写过 SQL」有说服力得多。第二个方向是引入更真实的数据分析。比如用networkx做演员合作网络图两个演员合作过同一部电影就连一条边用中心性指标找出「合作最多的演员」。或者用scipy.stats做评分分布的假设检验看看不同类型电影的评分差异是否显著。这些分析不需要很深的统计学背景但能让你的报告从「描述性统计」升级到「推断性分析」。第三个方向是把 SQLite 换成 MySQL 或 PostgreSQL加上连接池和索引优化。在 rating 表的(movie_id, user_id)上建联合索引然后对比建索引前后的查询耗时。这个对比实验写进报告就是实打实的性能优化经验。# 建索引前后对比查询耗时 import time cursor.execute(CREATE INDEX IF NOT EXISTS idx_rating_movie ON rating(movie_id)) start time.time() cursor.execute(SELECT movie_id, AVG(score) FROM rating GROUP BY movie_id) cursor.fetchall() print(f建索引后耗时: {time.time() - start:.4f} 秒)索引不是越多越好。rating 表如果只有几千条数据建不建索引差别不大但数据量到十万级以上GROUP BY movie_id这种查询没有索引就会明显变慢。报告里写清楚「在什么数据量下、什么查询模式下、索引带来了多少提升」比单纯说「我建了索引」有价值。最后一个建议把整个项目整理成一个 GitHub 仓库README 里写清楚项目结构、运行方式、依赖列表。数据集如果太大就放个采样版本代码和文档完整放上去。这个仓库本身就是你数据库和 Python 能力的最好证明。我见过太多人把大作业交完就扔等到找工作时想展示项目发现代码早没了。养成把每个课程项目整理归档的习惯后面会省很多事。希望帮到你。本文还有配套的精品资源点击获取
返回列表