
简介这是一份面向计算机相关专业学生的数据库课程大作业完整资料围绕电影数据库查询系统展开适合课程设计、期末作业或入门级Web数据库项目参考。资源包共77个文件约4.96MB包含10个Python源码文件、4个HTML页面、5个JavaScript脚本及配套样式文件另有15份Markdown文档、17张PNG与13张JPG截图覆盖MongoDB安装、复制集配置、数据导入、索引优化、数据分析等环节。项目实现按用户ID查询观影记录并按时间倒序展示前三个标签及关联度、关键词检索电影、风格热门Top20等功能界面含输入框与提交按钮结果支持滚动展示。文档中附有E-R图、数据表结构图、云服务器登录指南与排错记录便于理解整体架构与部署流程。已有359人学习适合需要完整源码、数据集与操作报告的学习者参考。1. 电影数据库大作业从建表到数据分析一套能交差的完整链路期末周最怕的不是写代码是打开课程群看到那句“数据库大作业自选主题要求建库、增删改查、数据分析、提交源代码和操作报告”。选题选电影是因为数据好找、字段直观、评分和票房天然适合做分析老师也容易看懂。但真动手就会发现坑不少ER 图画完不会转表、SQLite 建了库不知道怎么塞数据、pandas 读出来中文乱码、报告里图表和结论对不上。这篇就把“基于 Python 的电影数据库数据系统”从零拆一遍——建库建表、增删改查、数据分析、报告组织每一步都给能跑的代码和参数说明。适合正在赶大作业的本科生也适合想用一个小项目把 Python 和数据库串起来练手的人。整套方案用 SQLite pandas matplotlib不需要装 MySQL 服务一台笔记本就能跑完。2. 电影数据库的表结构设计与 SQLite 落地2.1 为什么选 SQLite 而不是 MySQL大作业场景下数据库选型的第一原则是“别人拿到你的压缩包能直接跑”。MySQL 需要装服务、配账号密码、导 SQL 文件换台电脑就可能连不上SQLite 是单文件数据库整个库就是一个.db文件跟着代码走sqlite3还是 Python 标准库零依赖。常见做法是本地开发用 SQLite报告里说明“生产环境可迁移至 MySQL/PostgreSQL”既省事又显得有工程意识。电影数据系统的核心实体其实就四个电影、导演、演员、评分。很多同学一上来画七八个表结果外键关系理不清查询写到一半自己都绕晕。我一般会先画 ER 图再按“一对多拆外键、多对多建中间表”的规则转成物理表。下面这张表结构是我用过最稳的版本字段不多但够用表名字段类型说明moviesmovie_idINTEGER PK电影主键自增titleTEXT NOT NULL电影名release_yearINTEGER上映年份director_idINTEGER FK关联 directorsgenreTEXT类型如剧情/科幻box_officeREAL票房单位万元directorsdirector_idINTEGER PK导演主键nameTEXT NOT NULL导演姓名countryTEXT国籍ratingsrating_idINTEGER PK评分主键movie_idINTEGER FK关联 moviesscoreREAL评分0-10votesINTEGER评价人数rating_dateTEXT评分日期注意release_year用 INTEGER 而不是 TEXT后面按年份分组、算年代区间会方便很多rating_date存YYYY-MM-DD字符串SQLite 没有原生日期类型这样存排序和比较都不会出错。2.2 建库建表的完整脚本下面这段代码直接跑会在当前目录生成movie.db并建好三张表。注意PRAGMA foreign_keys ON这行SQLite 默认不开启外键约束不写的话删数据时关联表不会报错数据一致性就废了。import sqlite3 # 连接数据库文件不存在会自动创建 conn sqlite3.connect(movie.db) # 开启外键约束SQLite 默认关闭必须手动打开 conn.execute(PRAGMA foreign_keys ON) cursor conn.cursor() # 导演表先建因为 movies 要引用它 cursor.execute( CREATE TABLE IF NOT EXISTS directors ( director_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, country TEXT ) ) # 电影表director_id 作为外键指向 directors cursor.execute( CREATE TABLE IF NOT EXISTS movies ( movie_id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, release_year INTEGER, director_id INTEGER, genre TEXT, box_office REAL, FOREIGN KEY (director_id) REFERENCES directors(director_id) ) ) # 评分表一部电影可以有多条评分记录 cursor.execute( CREATE TABLE IF NOT EXISTS ratings ( rating_id INTEGER PRIMARY KEY AUTOINCREMENT, movie_id INTEGER, score REAL CHECK(score 0 AND score 10), votes INTEGER, rating_date TEXT, FOREIGN KEY (movie_id) REFERENCES movies(movie_id) ) ) conn.commit() conn.close() print(数据库与表结构创建完成)逻辑说明三张表的创建顺序不能乱movies依赖directors所以导演表必须先建。CHECK(score 0 AND score 10)是给评分加约束防止脏数据混进来报告里写一句“通过 CHECK 约束保证评分合法性”就是加分项。参数方面AUTOINCREMENT保证主键不重复IF NOT EXISTS让脚本可以重复执行不报错——这点很重要调试时你会反复跑。2.3 插入测试数据与批量导入建完表是空的得先塞点数据才能验证查询。手工写 INSERT 太慢我一般用executemany批量插配合一个数据列表。下面这段插 3 个导演、5 部电影、若干评分够你跑通所有查询。import sqlite3 conn sqlite3.connect(movie.db) conn.execute(PRAGMA foreign_keys ON) cursor conn.cursor() # 批量插入导演 directors [ (克里斯托弗·诺兰, 英国), (宫崎骏, 日本), (张艺谋, 中国) ] cursor.executemany(INSERT INTO directors (name, country) VALUES (?, ?), directors) # 批量插入电影director_id 对应上面插入的顺序 1/2/3 movies [ (盗梦空间, 2010, 1, 科幻, 83000), (星际穿越, 2014, 1, 科幻, 55000), (千与千寻, 2001, 2, 动画, 25000), (活着, 1994, 3, 剧情, 3000), (影, 2018, 3, 剧情, 6000) ] cursor.executemany( INSERT INTO movies (title, release_year, director_id, genre, box_office) VALUES (?, ?, ?, ?, ?), movies ) # 插入评分数据 ratings [ (1, 9.3, 1800000, 2024-01-15), (2, 9.4, 1500000, 2024-01-16), (3, 9.4, 2000000, 2024-01-17), (4, 9.2, 800000, 2024-01-18), (5, 7.2, 300000, 2024-01-19) ] cursor.executemany( INSERT INTO ratings (movie_id, score, votes, rating_date) VALUES (?, ?, ?, ?), ratings ) conn.commit() conn.close() print(测试数据导入完成)逻辑说明executemany比循环单条execute快得多数据量大时差距明显。参数用?占位符而不是字符串拼接这是防 SQL 注入的基本功报告里可以专门提一句。注意director_id是硬编码的 1/2/3因为AUTOINCREMENT从 1 开始递增插入顺序决定 ID实际项目里应该先查再插但作业数据量小这样写最省事。3. 增删改查与数据分析从 SQL 到 pandas 可视化3.1 四个必写的增删改查操作大作业要求里“增删改查”是硬指标但很多同学只写查询增删改一笔带过。其实这四类操作各写一个函数报告里贴出来结构清晰还显得完整。下面用参数化查询实现每个函数都能单独调用。import sqlite3 def get_conn(): conn sqlite3.connect(movie.db) conn.execute(PRAGMA foreign_keys ON) return conn def add_movie(title, year, director_id, genre, box_office): 新增电影 conn get_conn() conn.execute( INSERT INTO movies (title, release_year, director_id, genre, box_office) VALUES (?, ?, ?, ?, ?), (title, year, director_id, genre, box_office) ) conn.commit() conn.close() def delete_movie(movie_id): 删除电影先删评分再删电影避免外键冲突 conn get_conn() conn.execute(DELETE FROM ratings WHERE movie_id ?, (movie_id,)) conn.execute(DELETE FROM movies WHERE movie_id ?, (movie_id,)) conn.commit() conn.close() def update_score(movie_id, new_score): 修改某部电影的评分 conn get_conn() conn.execute(UPDATE ratings SET score ? WHERE movie_id ?, (new_score, movie_id)) conn.commit() conn.close() def query_top_movies(limit5): 查询评分最高的 N 部电影联表取导演名 conn get_conn() cursor conn.execute( SELECT m.title, d.name, m.release_year, r.score, r.votes FROM movies m JOIN directors d ON m.director_id d.director_id JOIN ratings r ON m.movie_id r.movie_id ORDER BY r.score DESC LIMIT ? , (limit,)) result cursor.fetchall() conn.close() return result # 测试 add_movie(奥本海默, 2023, 1, 传记, 45000) print(query_top_movies(3))逻辑说明delete_movie里先删ratings再删movies因为ratings.movie_id是外键直接删电影会触发约束报错——这是新手最常翻车的地方。query_top_movies用了两次 JOIN把电影、导演、评分三张表串起来ORDER BY r.score DESC降序排列LIMIT ?控制返回条数。参数limit默认 5调用时可以传任意整数。3.2 用 pandas 做评分与票房分析数据库查询结果直接喂给 pandas就能做分组统计和可视化。下面这段代码从movie.db读数据算每个导演的平均评分和总票房再画一张柱状图。中文显示需要设置字体Windows 用SimHeiMac 用Arial Unicode MS否则图表上全是方框。import sqlite3 import pandas as pd import matplotlib.pyplot as plt # 设置中文字体Windows 用 SimHeiMac 改成 Arial Unicode MS plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False conn sqlite3.connect(movie.db) # 一条 SQL 把电影、导演、评分全部拉出来 df pd.read_sql_query( SELECT m.title, m.genre, m.box_office, m.release_year, d.name AS director, r.score, r.votes FROM movies m JOIN directors d ON m.director_id d.director_id JOIN ratings r ON m.movie_id r.movie_id , conn) conn.close() # 按导演分组算平均评分和总票房 grouped df.groupby(director).agg( 平均评分(score, mean), 总票房(box_office, sum), 作品数(title, count) ).round(2).sort_values(平均评分, ascendingFalse) print(grouped) # 画图导演平均评分 grouped[平均评分].plot(kindbar, colorsteelblue, figsize(8, 5)) plt.title(各导演平均评分对比) plt.ylabel(评分) plt.xticks(rotation0) plt.tight_layout() plt.savefig(director_score.png, dpi150) plt.show()逻辑说明pd.read_sql_query直接把 SQL 结果转 DataFrame比手动fetchall再拼列表省事得多。groupby(director).agg(...)是 pandas 的核心分组聚合round(2)保留两位小数sort_values按评分降序。画图部分figsize控制图片尺寸dpi150保证报告里插图清晰savefig存成 PNG 方便贴进 Word。参数ascendingFalse表示降序改成True就是升序。3.3 评分与评价人数的 ER 关系验证热搜里有人问“画出电影评分与评价的 ER 图”其实 ER 图的核心是理清实体和关系电影和评分是一对多一部电影多条评分记录电影和导演是多对一一个导演多部电影。转成表就是ratings表里放movie_id外键movies表里放director_id外键。验证关系是否建对可以跑一个查询统计每部电影的评分条数如果某部电影有多条评分说明一对多关系生效了。import sqlite3 conn sqlite3.connect(movie.db) cursor conn.execute( SELECT m.title, COUNT(r.rating_id) AS rating_count FROM movies m LEFT JOIN ratings r ON m.movie_id r.movie_id GROUP BY m.movie_id ) for row in cursor.fetchall(): print(f{row[0]}: {row[1]} 条评分) conn.close()逻辑说明用LEFT JOIN而不是INNER JOIN这样即使某部电影没有评分也会显示出来条数为 0。GROUP BY m.movie_id按电影分组COUNT(r.rating_id)统计评分条数。这个查询在报告里可以作为“关系验证”的证据证明你的表结构设计是自洽的。4. 避坑与排查大作业里最容易翻车的五个地方4.1 中文乱码pandas 读出来全是问号现象pd.read_sql_query读出来的电影名显示为???或乱码。原因通常是 SQLite 数据库文件编码和 Python 默认编码不一致或者终端不支持 UTF-8。解决办法建库时确保 Python 脚本文件本身是 UTF-8 编码连接时加conn.text_factory strWindows 终端跑脚本前执行chcp 65001切到 UTF-8 代码页。如果还不行检查数据插入时是不是用了错误的编码写入。4.2 外键约束报错FOREIGN KEY constraint failed现象删除电影时报FOREIGN KEY constraint failed。原因是ratings表里还有引用该电影的记录SQLite 外键约束阻止了删除。解决办法按delete_movie函数的写法先删子表ratings再删父表movies。另一个常见原因是建表时没写PRAGMA foreign_keys ON导致约束根本没生效删数据时看似成功但留下孤儿记录查询时 JOIN 不出来。4.3 图表中文显示方框现象matplotlib 画出来的图标题和坐标轴全是方框。原因是默认字体不支持中文。解决办法plt.rcParams[font.sans-serif] [SimHei]Windows或[Arial Unicode MS]Mac同时加plt.rcParams[axes.unicode_minus] False解决负号显示问题。如果系统没有 SimHei可以下载字体文件放到项目目录用font_manager手动注册。4.4 数据库文件路径错误no such table现象代码在 PyCharm 里跑得好好的换台电脑就报no such table: movies。原因是sqlite3.connect(movie.db)用的是相对路径换目录后连到了一个新创建的空库。解决办法用os.path拼绝对路径或者把.db文件和脚本放同一目录并在报告里注明“运行前请确保 movie.db 与脚本在同一文件夹”。4.5 评分数据重复插入导致统计翻倍现象平均评分算出来偏高或偏低和实际不符。原因是脚本重复执行时INSERT又插了一遍数据翻倍。解决办法建表时给ratings表加唯一约束UNIQUE(movie_id, rating_date)或者插入前先DELETE FROM ratings清空再插。更稳妥的做法是把建表和插数据分开成两个脚本插数据脚本开头先清表。5. 报告组织与进阶技巧让大作业从及格到优秀操作报告是很多人忽略的得分点。老师看代码的时间远少于看报告的时间报告结构清晰、图表规范、结论有数据支撑分数直接上一个档次。我一般按这个顺序组织需求分析为什么做电影数据库→ ER 图与表结构贴图 字段说明表→ 核心功能实现增删改查代码 截图→ 数据分析至少两张图表 结论→ 遇到的问题与解决就是上一章的内容。数据分析部分不要只贴图每张图下面写两三句结论比如“诺兰的平均评分最高但作品数最少说明样本量小结论需谨慎”——这种带边界的分析老师最喜欢。进阶技巧方面如果想让项目更有亮点可以加两个东西。一是用argparse做一个命令行入口支持python main.py --action top --limit 5这样的调用显得有工程化思维。二是把分析结果导出成 Excel用df.to_excel(report.xlsx, indexFalse)报告里附上 Excel 文件老师可以直接打开看数据。注意to_excel需要装openpyxlpip install openpyxl即可。import argparse import sqlite3 import pandas as pd def main(): parser argparse.ArgumentParser(description电影数据库查询工具) parser.add_argument(--action, choices[top, group], defaulttop, helptop: 评分排行; group: 按导演分组) parser.add_argument(--limit, typeint, default5, help返回条数) args parser.parse_args() conn sqlite3.connect(movie.db) if args.action top: df pd.read_sql_query( SELECT m.title, r.score FROM movies m JOIN ratings r ON m.movie_id r.movie_id ORDER BY r.score DESC LIMIT ? , conn, params(args.limit,)) print(df) else: df pd.read_sql_query( SELECT d.name, AVG(r.score) AS avg_score FROM movies m JOIN directors d ON m.director_id d.director_id JOIN ratings r ON m.movie_id r.movie_id GROUP BY d.name , conn) print(df) conn.close() if __name__ __main__: main()逻辑说明argparse定义了两个参数--action控制查询类型--limit控制返回条数。pd.read_sql_query的params参数用来传占位符的值注意这里 SQL 里用的是?params传元组。这个脚本可以直接在命令行跑报告里截图展示比单纯贴函数调用有说服力。最后说个血泪经验大作业提交前一定要在另一台电脑上完整跑一遍从建库到出图确认没有路径依赖和缺失的包。我见过太多人本地跑通就交结果老师那边ModuleNotFoundError直接扣分。把依赖写进requirements.txtpip install -r requirements.txt一行搞定这个习惯从大作业开始养以后做项目会省很多后悔药。希望帮到你。本文还有配套的精品资源点击获取