☰
春节档电影数据爬虫与可视化分析毕业设计全攻略
2026/9/25 7:59:09 网站建设 项目流程

简介:这份毕业设计项目包基于Python实现春节档电影信息的爬取与数据可视化分析,面向计算机、数学、电子信息等专业的本科及高职学生,适用于课程设计、期末大作业或毕业设计参考。项目覆盖从数据采集、清洗入库、统计整理到可视化展示的完整闭环,核心代码、数据库及前端页面均已齐备,并附有答辩PPT和说明文档,便于理解整体设计思路后进行二次开发与功能扩展。压缩包共39个文件,主要含Python源码、HTML/CSS/JavaScript页面资源、数据库文件、配置文件、启动脚本、图片与PDF文档等,整体大小20.86MB,目录结构简洁,按功能分模块存放,可快速定位所需材料。目前已有1094人学习下载,无论是完成课程任务还是准备毕业答辩,都能从中获得从技术选型到结果呈现的完整参考。

1. 春节电影爬虫:为什么这个毕业设计年年有人做

每年春节档结束,猫眼、豆瓣、灯塔上会沉淀出一批类型丰富、时间跨度集中的电影数据,票房、评分、想看人数、排片占比、上映天数全都有,而且字段结构还算规整——这正是练手 Python 爬虫和数据可视化最顺手的数据集。这个课题的完整形态是一套“数据采集 + 清洗落库 + 可视化分析 + 答辩 PPT”的全链路项目,适合正处于毕业设计选型期的计算机相关专业学生,也适合想快速验证爬虫工程化能力的一线开发。很多人以为难点在爬虫,实际做过就知道,反爬策略、数据口径统一和图表叙事才是真正拉开差距的地方。本文按这套源码常见的技术路线,把从数据源选型到答辩演示的完整方案讲透。

2. 春节档电影数据源选型与反爬策略

2.1 猫眼、豆瓣、灯塔三选一,数据侧重点完全不同

做春节档电影数据可视化,第一步不是写代码,而是先明确要分析什么问题。不同数据源可以回答的问题不同,后端存储结构也就不同。常见做法是三选一为主、另一个为辅,避免一来就并行爬三个站,导致字段口径对不上。

数据源核心字段适合分析方向爬取难度
猫眼专业版实时票房、排片占比、上座率档期大盘趋势、单片票房走势中,有 JS 动态渲染接口
豆瓣电影评分、评价人数、短评内容口碑分析、观众情绪倾向中低,静态页面为主
灯塔专业版预售票房、想看人数、受众画像映前热度预测、宣发效果高,字段加密较多

对于毕业设计来说,最稳妥的组合是豆瓣电影做评分和短评分析,猫眼做票房和排片趋势分析。两者都在春节档这个时间窗口内保持每日更新,数据连续性有保障。如果你所在学校对数据来源有明确要求,优先选豆瓣,它的页面结构相对稳定,反爬强度可控,爬下来的短评还能顺带做词云,可视化素材更多。

2.2 动态页面与接口优先策略,把 BS4 用在正确位置

热词里频繁出现“bs4 爬取动态页面”,很多同学会拿 BS4 直接解析整个 HTML 页面,遇到内容是 JS 动态加载的就抓瞎。实际上,BS4 适合解析服务端渲染后的静态 HTML,而猫眼的票房数据是通过 XHR 异步请求返回的 JSON 数据,浏览器里看到的标签是渲染后的结果。正确做法是先打开浏览器开发者工具的 Network 面板,找返回 JSON 的接口地址,直接请求这个接口,再用json模块解析,性能远高于渲染完整页面。

如果遇到接口参数加密,退路才是 Selenium 模拟浏览器操作。但 Selenium 启动慢、容易被识别,不建议作为主力方案。我一般这样排序:先找公开 JSON 接口,其次用 requests 请求静态页面配合 BS4 解析,最后才用 Selenium 兜底。顺序对了,爬虫代码的量级会差出好几倍。

3. 用 Python 把春节档电影信息稳定落库

3.1 定义数据表结构与字段规范

爬虫代码写得好不好,看表结构就能判断。春节档电影信息至少要分成两张表:电影基础信息表和每日票房快照表。基础表存电影 ID、片名、导演、主演、类型、上映日期、片长;快照表存电影 ID、统计日期、票房、排片场次、上座率。快照表按天追加,这样后期才能画出时间序列曲线。

CREATE TABLE movie_info ( movie_id TEXT PRIMARY KEY, title TEXT NOT NULL, directors TEXT, actors TEXT, genres TEXT, release_date DATE, duration INTEGER ); CREATE TABLE daily_box_office ( id INTEGER PRIMARY KEY AUTOINCREMENT, movie_id TEXT NOT NULL, stat_date DATE NOT NULL, box_office REAL, show_count INTEGER, seat_count INTEGER, avg_seat_rate REAL, UNIQUE(movie_id, stat_date) );

这里把movie_id设计成字符串而不是自增整数,是因为外部数据源有自己的影片 ID,直接用源 ID 做主键可以方便后续增量更新和跨表关联。快照表加了联合唯一约束,movie_id + stat_date重复写入时会报错,正好用来做去重。

3.2 手写一个最小可用的动态接口爬虫脚本

以猫眼专业版春节档榜单为例,核心步骤是构造请求头、拿 JSON、抽取字段、写入 SQLite。下面给一个能直接跑通流程的最小脚本骨架:

import json import sqlite3 import requests from datetime import date headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": "https://piaofang.maoyan.com/" } def fetch_spring_festival_data(target_date: str) -> list: """拉取指定日期的春节档票房榜单,返回电影字段列表""" url = "https://piaofang.maoyan.com/boxoffice/rank.json" params = { "date": target_date, "type": "day" } resp = requests.get(url, params=params, headers=headers, timeout=10) resp.raise_for_status() payload = resp.json() movies = [] for item in payload.get("data", {}).get("list", []): movies.append({ "movie_id": item["movieId"], "title": item["movieName"], "box_office": float(item["boxInfo"].replace("万", "")), "show_count": int(item["showInfo"].split("场")[0]), "stat_date": target_date }) return movies def save_to_db(movies: list) -> None: conn = sqlite3.connect("spring_festival.db") cursor = conn.cursor() for m in movies: cursor.execute( "INSERT OR IGNORE INTO daily_box_office " "(movie_id, stat_date, box_office, show_count) " "VALUES (?, ?, ?, ?)", (m["movie_id"], m["stat_date"], m["box_office"], m["show_count"]) ) conn.commit() conn.close() if __name__ == "__main__": data = fetch_spring_festival_data(date.today().isoformat()) save_to_db(data) print(f"今日写入 {len(data)} 条记录")

逻辑说明:fetch_spring_festival_data里先拼请求参数,timeout=10防止网络抖动时进程卡死;resp.raise_for_status()在 HTTP 状态码异常时直接抛错,方便日志定位。save_to_db里用了INSERT OR IGNORE,利用表上的联合唯一约束跳过重复数据。参数说明:type=day表示按日维度取数,如果改成type=week可以得到周榜;boxInfo返回的是带“万”的字符串,必须转 float 再存,否则后续聚合没法算。

3.3 断点续爬与异常重试的工程化处理

上面是单日抓取,毕业设计要求的是一整个春节档周期,跨 7 到 15 天,中间必须考虑失败重试和断点续爬。常见做法是维护一个待爬日期队列,已经成功的日期从队列里移除,失败的任务最多重试三次,重试间隔指数退避。

import time from datetime import timedelta def crawl_date_range(start: date, end: date) -> None: current = start while current <= end: for attempt in range(3): try: movies = fetch_spring_festival_data(current.isoformat()) save_to_db(movies) break except Exception as exc: print(f"{current} 第 {attempt + 1} 次失败: {exc}") time.sleep(2 ** attempt) else: print(f"{current} 重试 3 次仍失败,跳过,稍后可手动补采") current += timedelta(days=1)

这里用for...else的写法,循环内break代表成功,else块只有在三次尝试都异常时才会执行,正好标记需要补采的日期。重试间隔依次是 1 秒、2 秒、4 秒,比固定间隔更不容易触发服务端的访问频率限制。实际采集时建议每次请求后额外time.sleep(1)左右,把 QPS 控制在个位数,这是业界默认的礼貌爬取规范。

4. 数据清洗与指标体系:从票房数字到可分析维度

4.1 清洗脏数据的三个高频场景

爬虫拿到的数据不能直接用。我整理过一份春节档电影数据,脏数据主要集中在三种情况:票房单位不统一,有的返回“万”,有的返“亿”;同一部影片在不同日期被 ID 不同地录入;演员和类型字段夹带空白字符或 HTML 标签残留。清洗代码里需要重点处理这三件事。

import pandas as pd def clean_box_office(value) -> float: """将 '1.2亿' / '3456万' / 纯数字 统一转成以 万 为单位的浮点数""" if isinstance(value, (int, float)): return float(value) / 10000 text = str(value).replace(",", "").strip() if text.endswith("亿"): return float(text[:-1]) * 10000 if text.endswith("万"): return float(text[:-1]) return float(text) def clean_movie_frame(raw_df: pd.DataFrame) -> pd.DataFrame: df = raw_df.copy() df["box_office"] = df["box_office"].apply(clean_box_office) df["title"] = df["title"].str.strip() df["genres"] = df["genres"].str.replace(r"<[^>]+>", "", regex=True) df["stat_date"] = pd.to_datetime(df["stat_date"]) df = df.drop_duplicates(subset=["movie_id", "stat_date"], keep="last") return df

代码逻辑:clean_box_office先判断类型,再按字符串结尾字符选择换算逻辑,所有金额统一成“万”才能做数值运算。clean_movie_frame里先去掉片名首尾空格,再正则剔除 HTML 标签残留,最后按影片加日期去重。参数说明:keep="last"表示同一天同一部影片重复录入时保留最后一次写入的版本,适用于接口重试导致的数据覆盖场景。

4.2 春节档专有的指标体系

通用的电影分析指标有票房、排片比、上座率,但春节档还有两个特殊维度:一个是档期累计票房,另一个是“大年初一开画成绩”。这两个指标的代码实现很简单,但承载的分析价值完全不同。

指标计算方式分析用途
单片档期累计票房groupby(movie_id).box_office.sum()排名与头部集中度
单片每日票房环比groupby(movie_id).box_office.pct_change()口碑扩散趋势
排片占比show_count / 当日总场次影院资源调配方向
上座率avg_seat_rate直接取接口字段供需关系判断
档期票房集中度前 3 名票房合计 / 档期总票房市场垄断程度

特别要注意的是“排片占比”必须按日计算,因为春节档每天总场次不同,直接用绝对场次跨日对比没有意义。上座率字段在部分接口里是缺省的,缺失率超过 30% 就要考虑放弃整列,或者用“票房除以场次”的场均产出替代。

4.3 用 pandas 做一版含金量足够的分析

清洗之后的分析不需要太花哨,能覆盖趋势、对比、结构三个维度就足以上答辩台面。趋势看每日累计票房曲线,对比看同档期几部头部影片的票房走势,结构看各类型影片的票房占比。下面这段代码一次完成三类计算:

# 假设 df 已经过 clean_movie_frame 清洗 daily_total = df.groupby("stat_date")["box_office"].sum() movie_daily = df.pivot_table( index="stat_date", columns="title", values="box_office", aggfunc="sum" ) daily_total = daily_total.sort_index() movie_daily = movie_daily.fillna(0) # 档期票房集中度 top3 = movie_daily.sum().nlargest(3).sum() total = movie_daily.sum().sum() concentration = top3 / total if total > 0 else 0 print(f"档期前3集中度: {concentration:.1%}")

pivot_table的关键是把“长表”转成“宽表”,行是日期、列是影片名,这样后续画折线图时每部影片就是一条完整的序列。nlargest(3)取累计票房最高的三部影片。集中度超过 60% 说明今年春节档头部效应明显,这个数可以作为答辩 PPT 上的核心论点。

5. 用 pyecharts 做春节档电影可视化分析

5.1 为什么选择 pyecharts 而非原生 Matplotlib

毕业设计答辩时,图表的美观程度直接影响评委的第一印象。Matplotlib 默认样式太理工科,需要额外配字体和主题才好看;pyecharts 生成的是基于 ECharts 的 HTML 文件,交互式图表自带鼠标悬浮、缩放、图例切换功能,Python 代码写完直接输出 HTML,放 PPT 里演示时观感接近商业产品。对于春节档电影数据,最常见的组合是折线图展示每日票房趋势、柱状图展示总票房排行、饼图展示类型占比、词云展示短评关键词。

5.2 票房趋势与排片对比的核心图表代码

按照前面清洗好的数据,直接喂给 pyecharts。下面代码生成一个双系列折线图,同时展示档期总票房和当日票房环比:

from pyecharts.charts import Line, Bar from pyecharts import options as opts def make_trend_chart(daily_total: pd.Series) -> Line: line = ( Line(init_opts=opts.InitOpts(width="1200px", height="600px")) .add_xaxis(daily_total.index.strftime("%m-%d").tolist()) .add_yaxis( "每日票房(万)", daily_total.round(0).tolist(), is_smooth=True, label_opts=opts.LabelOpts(is_show=False) ) .set_global_opts( title_opts=opts.TitleOpts(title="春节档每日票房趋势"), tooltip_opts=opts.TooltipOpts(trigger="axis"), yaxis_opts=opts.AxisOpts(name="票房(万)") ) ) return line line = make_trend_chart(daily_total) line.render("box_office_trend.html")

add_yaxis里is_smooth=True让折线更圆滑,label_opts关掉数值标签防止密集数据点文字重叠。trigger="axis"保证鼠标悬浮时同一天的所有系列数值一起展示。render输出一个独立 HTML 文件,双击就能在浏览器里看效果,不需要启动任何服务。

5.3 短评词云与评分分布的组合呈现

票房之外,短评词云是春节档项目里最容易出效果、也最容易翻车的部分。翻车点在于中文分词。直接用英文空格分词会把“流浪地球”切成“流浪”、“地球”两个词,必须引入 jieba 先做分词,再过滤停用词。一个能用的词云绘制流程是这样:

import jieba from wordcloud import WordCloud def build_wordcloud(comments: list, output_path: str) -> None: text = " ".join(comments) words = jieba.cut(text) filtered = [w for w in words if len(w) > 1 and w not in STOPWORDS] wc = WordCloud( font_path="C:/Windows/Fonts/msyh.ttc", width=1000, height=700, background_color="white" ).generate(" ".join(filtered)) wc.to_file(output_path)

font_path必须指定中文字体,否则词云图片里全是乱码方块,Windows 上一般用微软雅黑msyh.ttc,Mac 上换成/System/Library/Fonts/PingFang.ttc。len(w) > 1过滤单字,STOPWORDS里装“电影”“一部”“真的”这类无信息量词。词云图片生成后再拿 PIL 或 matplotlib 贴到画布上,配到 PPT 里就是一张能讲故事的热度图谱。评分分布则是直方图,直接用 pyecharts 的 Bar 组件,把“9 分以上 / 8 到 9 分 / 7 到 8 分”分桶统计,比散点图直观得多。

5.4 可视化页面的布局技巧

毕业设计的可视化部分不应只是一堆孤立图表,要按分析逻辑排顺序。常见布局是先放档期总览(总票房数字卡片 + 每日票房折线),再放单片对比(头部影片票房走势),接着放结构分析(类型饼图 + 评分分布),最后放词云收尾。每个 HTML 图表之间保持统一的配色主题,pyecharts 里可以通过InitOpts(theme="dark")或"light"全局统一,不要让一份 PPT 里出现三套风格。图表文件名按照01_box_office_trend.html的顺序命名,答辩演示时直接按文件名顺序打开。

6. 答辩演示前必调的 3 个细节

6.1 先验证数据完整性,再开 PPT

答辩演示最常见的事故是评委问“这个数据是全的吗”,答不上来。提前跑一段自检脚本,把爬取日期范围、每天影片数量、字段缺失率一次算清:

conn = sqlite3.connect("spring_festival.db") df = pd.read_sql_query("SELECT * FROM daily_box_office", conn) missing_rate = df["box_office"].isna().mean() date_range = f"{df['stat_date'].min()} 至 {df['stat_date'].max()}" print(f"覆盖日期: {date_range}") print(f"总记录数: {len(df)}") print(f"票房缺失率: {missing_rate:.1%}")

这组数字建议直接放进答辩 PPT 的附录页,证明数据采集过程是可靠的。缺失率超过 10% 就说明某个数据源在春节档期间有过连续失败,需要先补采再答辩。

6.2 图表里的数量单位统一标在轴名称里

猫眼接口给的值有“万”有“亿”,清洗时统一成万之后,图表标题和 y 轴名称也要同步写清楚,否则评委看到几千这个数会当场质疑。pyecharts 里AxisOpts(name="票房(万)")就是干这个的,做好了能避免一半的口头解释。同一份 PPT 里,所有涉及金额的图表必须用同一单位。上座率用百分比,评分用 0 到 10 分,口径统一,图表之间才能互相参照。

6.3 把分析结论做成图表旁的注释文字

图表自己不会说话,评委看的是你能否从图里提炼结论。比较稳妥的做法是每张图上加一句注释文字,比如“前 3 名影片贡献了档期 60% 以上的票房,头部效应显著”,这句话无论是放在 PPT 的图表下方还是代码里的TitleOptssubtitle 位置,都能让人一眼看到分析重点。

答辩 PPT 的结构建议按“数据采集说明 → 清洗过程 → 指标体系 → 可视化图表 → 分析结论 → 不足与展望”排,每一页只讲一件事,图表旁边必须有结论句,不要放高密度表格。源码目录里爬虫、清洗、可视化三个模块拆开建文件夹,main.py 放顶层,能让评委快速定位结构。

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

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

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

立即咨询