
简介基于Python的电影数据可视化分析系统毕业设计资源包面向计算机相关专业毕业生及需要项目实战练习的学习者也可用于课程设计、期末大作业。项目综合运用爬虫技术从豆瓣TOP250和猫眼票房排行榜抓取电影评分、票房、评论等数据并通过pandas DataFrame导出CSV和写入MySQL数据库两条路径完成持久化随后基于所存数据开展可视化分析探索评分、票房、年份、地区等因素之间的关系并借助统计模型构建票房预测算法形成从数据获取、存储、分析到预测的完整闭环。资源共38个文件主要内容包括6个Python功能脚本、3个Jupyter Notebook分析文档、1个SQL数据库脚本、24张可视化结果图表含预测效果图、数据库结构图等另附Markdown说明文档与PDF版说明压缩包约5.18MB目录结构清晰便于按源码、分析文档和结果图模块对照学习。项目已经严格调试下载即可运行已有4332人学习下载特别适合希望通过真实电影数据案例快速上手完整数据科学流程的读者。1. 电影数据可视化分析系统一份能跑通全流程的毕设源码这套基于Python的电影数据可视化分析系统源码里跑通的是从豆瓣TOP250、猫眼票房榜单爬取数据到CSV与MySQL双通道持久化再到可视化分析和票房预测的完整链路。它把爬虫、数据库、分析、预测四个环节都装进一个src目录解压后不需要补核心逻辑配好环境就能从main.py一路跑到result目录出图。适合两类人正在做毕设的计算机专业学生需要在答辩现场讲清“数据怎么来、存在哪、怎么分析、怎么预测”以及课程设计、期末大作业阶段想练全流程的学习者。毕设翻车的重灾区从来不是模型不够高级而是爬虫断在反爬、中文乱码、MySQL连不上这类细节。这套源码把这些坑预先踩过一遍改自己的数据源时能省一个量级的调试时间。2. 项目骨架与数据链路src目录划分与双通道持久化方案2.1 src 下文件角色谁负责爬、谁负责存、谁负责画图先把源码包里的文件按角色拆开看不要一上来就双击main.py。下面这张表是我根据项目文件结构和常见实现整理的职责对照跑代码前先对着它确认每个文件的定位。文件角色主要产出main.py主流程入口按顺序调度爬取、清洗、入库database.py数据库操作封装MySQL连接、建表、INSERT语句movie_basic.pyTOP250基础信息爬虫片名、评分、导演等基础DataFramemovie_detail.py电影详情页爬虫年份、类型、时长、票房等补全字段attachfile.py附加数据处理合并附加文件、生成中间结果unit.py公共工具函数UA生成、延时、路径拼接、时间格式化predict.ipynb票房预测建模特征筛选、模型训练、预测结果图visualization_pandas.ipynbpandas可视化分析趋势图、分布图、散点图visualization_sql.ipynbSQL聚合分析基于MySQL查询结果的可视化douban.sql数据库初始化脚本建库建表语句调用顺序上常见做法是让main.py扮演接线员先调movie_basic和movie_detail拿原始数据把两份数据合并清洗后再交给database.py执行入库unit.py被前面两个模块共用负责UA头和延时控制。三个ipynb不在主链路里它们读取的是已经持久化的数据属于分析阶段的产物答辩时逐个打开展示过程比只贴一张结果图更有说服力。源码包还附带README.pdf和README.md里面有运行步骤和环境要求。我建议拿到压缩包后先花十分钟把README.md过一遍再对照后面第5章的避坑清单顺序反了容易在环境问题上耗掉一个晚上。2.2 双通道持久化CSV与MySQL互补的选型理由这个项目在持久化上做了两条路一条是pandas的DataFrame直接to_csv落盘另一条是走MySQL关系型数据库。很多学习者会问“为什么要存两遍”其实这是刻意的设计选择。CSV路线的核心优势是零依赖、好调试。爬虫跑完中间结果直接落成一个文件用Excel或pandas随时能看排查字段问题时不用先启动数据库服务。MySQL路线的优势在于能讲SQL聚合、表关系、主外键答辩问答环节老师大概率会问“为什么不用SQLite”“表结构怎么设计的”有真实MySQL库在回答就有了支撑。我一般会把CSV路线当成“中间产物”把MySQL当成“最终成果”。落CSV时有一个参数值得单独说import pandas as pd df pd.read_csv(result/movie_basic.csv) df.to_csv(result/movie_basic_standard.csv, indexFalse, encodingutf-8-sig)indexFalse表示不写入行索引否则读回来会多一列Unnamedencodingutf-8-sig是为了兼容Windows下Excel直接打开不乱码纯UTF-8在Excel里经常变成乱码这一点在给导师交过程文件时尤其重要。MySQL这边database.py里一般封装了连接、建表、插入三件事。连接参数里host、user、password、database四个缺一不可charset建议显式写成utf8mb4。常见做法是main.py跑完爬虫后把清洗好的DataFrame逐行INSERT或者用pymysql的executemany做批量插入批量插入速度比逐行快一个数量级数据量大时能明显感觉到差别。2.3 douban.sql 的表结构字段、主键与查询思路douban.sql是项目的数据库初始化脚本运行前先在里面确认表字段和你代码里INSERT的列名对得上。常见的表结构设计大概是这样的CREATE DATABASE IF NOT EXISTS douban DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE douban; CREATE TABLE movie ( movie_id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(128) NOT NULL, rating DECIMAL(3,1), year SMALLINT, director VARCHAR(64), comment_count INT, box_office BIGINT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB;几个字段选择的思考rating用DECIMAL(3,1)而不是FLOAT因为豆瓣评分固定一位小数DECIMAL能避免浮点误差box_office用BIGINT票房数字有时会上亿INT容易溢出create_time加DEFAULT CURRENT_TIMESTAMP数据入库时间自动记录答辩展示时能直接看到“什么时间点抓了多少条”。如果项目里把详情拆成了独立表通常会以movie_id做外键关联。查询时用JOIN把基础信息和详情合并和爬虫阶段用pandas merge的结果做交叉验证。这一段逻辑跑通等于把“DataFrame合并”和“SQL JOIN”两个知识点都演示了一遍工作量上很划算。3. 爬虫模块拆解从豆瓣TOP250到干净的数据表3.1 数据源选型与反爬的基本礼节项目选豆瓣TOP250和猫眼票房排行榜作数据源原因很实际榜单页结构固定、分页规则清晰、字段稳定不需要处理复杂的搜索接口登录态。TOP250是评分类数据票房榜是商业类数据两者合并后正好支撑“评分与票房是否相关”这类分析问题。反爬这块没有玄学就是UA、超时、延时、重试四个基本功。常见的封装方式是这样import time import requests from requests.adapters import HTTPAdapter HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Accept-Language: zh-CN,zh;q0.9, } session requests.Session() session.headers.update(HEADERS) session.mount(https://, HTTPAdapter(max_retries3)) def fetch_url(url, paramsNone): resp session.get(url, paramsparams, timeout10) resp.raise_for_status() time.sleep(2) return respmax_retries3让请求在连接失败时自动重试timeout10防止某个请求卡住拖死整个脚本time.sleep(2)是抓取间隔。很多新手栽在“裸请求”上不带UA直接requests.get豆瓣很快返回418或403连着高频抓取触发限制后整个IP段被临时挡掉。血泪经验是爬取类毕设宁可慢不要快两秒间隔看着浪费实则是给后续分析省时间。3.2 movie_basic.py抓片名、评分这些基础字段TOP250的列表页结构比较规整每个电影条目都在ol.grid_view下面的li里。用BeautifulSoup做选择器提取是常见做法核心逻辑如下from bs4 import BeautifulSoup def parse_top250(html): soup BeautifulSoup(html, html.parser) rows [] for item in soup.select(ol.grid_view li): title item.select_one(.title).text.strip() rating item.select_one(.rating_num).text.strip() quote item.select_one(.quote span) rows.append({ title: title, rating: float(rating), quote: quote.text.strip() if quote else , }) return rowssoup.select(ol.grid_view li)选中整个榜单列表每一个li就是一部电影.title取的是主标题因为豆瓣条目里可能有副标题select_one只取第一个匹配项rating_num取到的评分是字符串必须float转换后才能做数值计算。quote字段不一定每部电影都有先判空再取值否则直接.text会抛AttributeError。把分页参数接上TOP250一共10页每页25条start偏移量从0递增到225。main.py里循环调用fetch_url和parse_top250最终拼出一个包含250条记录的列表转成DataFrame落盘。3.3 movie_detail.py详情页补全与字段合并基础字段拿完后评分、片名有了但年份、时长、类型这些还缺。movie_detail.py负责进详情页补全字段。详情页信息密度高用正则提取比逐层解析更快import re def parse_detail(html): soup BeautifulSoup(html, html.parser) text soup.get_text( , stripTrue) year re.search(r(19|20)\d{2}, text) duration re.search(r(\d)分钟, text) return { year: int(year.group()) if year else None, duration_min: int(duration.group(1)) if duration else None, }正则里(19|20)\d{2}把年份限定在1900到2099之间避免误匹配到“第20届”这类文字(\d)分钟匹配时长数字。两个字段都先判断是否匹配到再int转换处理缺字段的页面时不会中断整个流程。详情字段准备好后和基础表合并import pandas as pd basic pd.DataFrame(basic_rows) detail pd.DataFrame(detail_rows) full basic.merge(detail, left_ontitle, right_ontitle, howleft)合并键用title而不是自造ID因为豆瓣条目名称在该榜单内唯一howleft表示以基础表为准详情缺失的电影保留记录只是字段为空不会因详情页抓取失败导致整行蒸发。合并后打印一下full.shape250条基础记录加详情字段后应该是250行行数变多说明title里有重复变少说明左连接被误用成内连接。3.4 数据清洗与落库前自检爬虫拿到的是“能用”的数据但离“能分析”还有一步清洗。常见的问题有三个重复行、评分列类型不对、可选字段全是NaN。清洗代码一般长这样full full.drop_duplicates(subset[title]) full full[full[rating].notna()] full[duration_min] full[duration_min].fillna(0).astype(int)drop_duplicates(subset[title])去掉片名重复的记录rating.notna()筛掉没有评分的行这类记录进分析环节只会干扰均值计算duration_min用fillna(0)补缺再astype(int)统一成整数。清洗完做一次dtypes检查rating应该是float64year是int64如果rating是object说明前面float转换漏了某条脏数据回爬虫解析函数排查。清洗后的DataFrame一式两份一份to_csv存中间结果一份executemany进MySQL。两边的字段顺序要完全对齐INSERT语句里列的个数和值列表的个数对不上是database.py最常见的报错来源。4. 数据可视化与SQL分析复现答辩用图的完整套路4.1 visualization_pandas从DataFrame到趋势图可视化分析的第一个版本在visualization_pandas.ipynb里基于pandas处理好的CSV做图表。核心是“分组聚合后画图”比如分析历年TOP250电影的平均评分走势import matplotlib.pyplot as plt import pandas as pd plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False df pd.read_csv(result/movie_basic.csv, encodingutf-8-sig) group df.groupby(year)[rating].agg([mean, count]).reset_index() fig, ax plt.subplots(figsize(10, 5)) ax.plot(group[year], group[mean], markero, linewidth2) ax.set_title(豆瓣TOP250各年平均评分) ax.set_xlabel(年份) ax.set_ylabel(平均评分) fig.savefig(result/output.png, dpi150, bbox_inchestight)plt.rcParams两行是中文显示的关键不设字体图里的标题和坐标轴全是方块即使系统有中文字体axes.unicode_minus不设False的话负号也会显示成乱码。groupby(year)[rating].agg([mean, count])同时算均值和数量一次聚合拿两个指标比分开groupby两次更高效。savefig里dpi150保证PPT投影不糊bbox_inchestight裁掉多余白边。这部分对应result目录里的output.png。答辩时讲这张图可以顺带说出“某几年上榜电影数量少均值波动大”这类数据细节比干讲代码效果好得多。4.2 visualization_sql用SQL聚合替代内存计算visualization_sql.ipynb换了一条路不读CSV直连MySQL把聚合逻辑写在SQL里。同样的问题SQL写法是SELECT year, COUNT(*) AS movie_count, ROUND(AVG(rating), 2) AS avg_rating, MAX(box_office) AS top_box_office FROM movie GROUP BY year ORDER BY year DESC LIMIT 20;这段SQL的核心是GROUP BY year按年份分组后COUNT统计电影数量AVG算平均分并用ROUND保留两位小数MAX取当年最高票房。LIMIT 20只留最近20年避免早期数据稀疏造成图表尾部毛刺。SQL方案的意义不在于比pandas快而在于验证两条持久化链路的数据一致性。同一个问题pandas算一遍、SQL算一遍结果一致说明爬虫入库过程没有丢字段。这个验证逻辑在答辩里很加分直接回应了“你怎么保证数据没错”的追问。pandas读SQL结果用pymysql即可import pymysql conn pymysql.connect( hostlocalhost, userroot, password123456, databasedouban, charsetutf8mb4 ) df_sql pd.read_sql(sql, conn)conn里的charset必须和douban.sql建库时的字符集一致一个用utf8mb4一个用utf8中文读取时会出现编码错位。读回DataFrame后再用matplotlib画一遍同样的趋势图两张图对照展示pandas路线和SQL路线的结果就都可视化了。4.3 result 目录里每张图的验收标准源码包result目录下带了一组png它们既是项目运行结果的快照也是你重跑后的对照物。我按文件名整理了验收要点重跑时逐张核对能快速定位链路哪一段出了问题。图片内容验收检查点output.png整体可视化结果图中文字体正常、趋势清晰、坐标轴有标签db.png数据库入库状态表名正确、行数与爬取记录数一致db_struct.png表结构截图字段类型、主键、字符集设置可见预测1.png、预测2.png票房预测结果有真实点与预测线对比、轴标签完整p (1) 到 p (19)分步过程图与README步骤编号对应顺序不乱验收的操作方式很简单清空result目录重跑main.py和两个ipynb同名文件自动生成。如果某张图缺失就顺着它的来源找——output.png缺失说明可视化环节没执行db.png缺失说明数据库连接或查询有问题。这套“清空重跑”的验证习惯值得在交毕设前强制走一遍。5. 避坑指南不是你的代码问题是环境的五个坑5.1 现象一一跑 main.py 就 ModuleNotFoundError现象命令行执行python main.py立刻报ModuleNotFoundError: No module named requests或pymysql。原因项目源码本身没有缺失但当前Python环境没装依赖。源码包没有提供requirements.txt或提供了但从未执行过pip安装。解决先建虚拟环境再统一装依赖。python -m venv venv venv\Scripts\activate # Windows pip install pandas requests beautifulsoup4 pymysql matplotlib scikit-learn jupyterpandas建议装1.5到2.0之间的大版本Python建议用3.8到3.10。用3.12跑pymysql有时会出现字符集校验的兼容警告不致命但会干扰排查。装完后执行python -c import pandas, requests, pymysql确认全部可导入再跑main.py。5.2 现象二MySQL 连不上报 Access denied 或 2003现象main.py执行到入库环节抛出pymysql.err.OperationalError错误码2003表示连不上服务1045表示用户名或密码被拒绝。原因两个常见来源。一是MySQL 8默认认证插件是caching_sha2_password老版本pymysql不识别二是database.py里的密码和本地MySQL实际密码不一致源码包带的是开发者本机的配置。解决把数据库用户认证方式改回兼容模式或新建专用用户。ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;mysql_native_password是老牌认证插件兼容性最好FLUSH PRIVILEGES让修改立即生效。改完用mysql -u root -p手动登录一次确认能进命令行再回跑main.py。注意database.py里的port默认3306如果本机MySQL改过端口连接参数也要同步。5.3 现象三豆瓣返回 418 或抓回来是验证页现象爬虫代码没报错但解析结果为空列表或者请求直接抛出异常状态码是418或403。原因豆瓣对无UA请求和过密请求有限制。不带浏览器标识的请求容易被识别为脚本连续高频率抓取会触发临时限制。解决给请求补全浏览器头部并降低抓取频率。session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Accept-Language: zh-CN,zh;q0.9, Referer: https://movie.douban.com/top250, })Referer字段模拟从榜单页跳转过来的浏览行为能降低被识别为直接访问的风险time.sleep(2)的间隔不要省更不要用多线程并发抓。爬虫是给毕业设计准备数据不是压测慢就是快。5.4 现象四图和 CSV 的中文全变成方块或乱码现象可视化图表标题变成一个个方框CSV文件用Excel打开后中文全乱。原因matplotlib默认字体里没有中文字体需要用rcParams指定CSV用UTF-8保存但Excel默认按ANSI解析。解决画图前设置字体写CSV时用utf-8-sig编码。plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False df.to_csv(result/movie_full.csv, indexFalse, encodingutf-8-sig)utf-8-sig会在文件开头写入BOM标记Excel识别到标记后自动按UTF-8解析这是和普通utf-8唯一的区别但直接影响文件能不能直接交给导师看。读取时保持对称pd.read_csv(xxx.csv, encodingutf-8-sig)防止BOM混进第一列字段名。5.5 现象五douban.sql 导入报错、查询中文异常现象在Navicat或命令行导入douban.sql时报语法错误或者导入成功后SELECT查询中文显示为问号。原因建库语句没有指定字符集或者库的默认排序规则和代码连接参数不一致。MySQL 5.7默认是utf8mb4MySQL 8部分环境下默认排序规则不同导致中文字段异常。解决重建数据库显式指定字符集。CREATE DATABASE douban DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE douban; SOURCE douban.sql;SOURCE是MySQL命令行导入SQL文件的关键字相当于执行文件内全部语句。建库时把CHARACTER SET和COLLATE都写全后面代码连接也用charsetutf8mb4三处统一中文问题基本绝迹。如果已经建过错库先DROP DATABASE douban再重建不要在原库上打补丁。6. 票房预测模型把可视化结论变成可解释的回归结果票房预测模块在predict.ipynb里它不是让你跑一个多高级的模型而是把可视化阶段的结论变成可解释的回归结果。我重跑时发现最影响答辩观感的不是R²多高而是能不能讲清楚“为什么选这几个特征”。所以第一步我会先打印相关系数矩阵import pandas as pd df pd.read_csv(result/movie_full.csv) print(df.corr(numeric_onlyTrue)[box_office].sort_values(ascendingFalse))先看box_office和谁正相关再决定特征进不进模型。常见做法是选comment_count、rating、year做特征box_office做目标值。如果发现两个特征之间相关系数超过0.8比如comment_count和rating明显互相影响就只留一个避免多重共线性让回归系数失真。from sklearn.linear_model import LinearRegression from sklearn.model_selection import train_test_split X df[[comment_count, rating, year]].fillna(0) y df[box_office] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) model LinearRegression().fit(X_train, y_train) print(R^2:, model.score(X_test, y_test))random_state42保证每次运行划分结果一致答辩现场重跑不会因为随机划分变化而出现不同分数test_size0.2留出20%样本做验证model.score返回的是R²决定系数。我不追求R²贴近10.6上下已经能说明趋势预测图里同时画真实点与预测线比堆指标更有说服力。我重跑这套流程时踩过的教训是把强相关的特征全部塞进去回归系数正负号变得很不稳定后来强制先打corr矩阵再选特征才正常。从那以后我每次做预测前都会先跑一遍print(df.corr())确认特征没有强共线性再进模型这个习惯也帮我把答辩问答环节的风险降了一半。希望帮到你。本文还有配套的精品资源点击获取