ARTICLE DETAIL

资讯详情

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

用Python构建电影票房数据分析系统:从采集到预测的完整指南

用Python构建电影票房数据分析系统:从采集到预测的完整指南 简介基于Python的电影票房数据分析系统设计与实现源码包面向课程作业、毕业设计及数据分析入门等场景适合需要快速搭建完整项目的学习者。系统围绕票房数据采集、导入、统计展示与对比分析等环节实现用户注册登录与权限管理、实时票房统计、折线图柱状图饼图等趋势展示、按省市或影院查看地区分布并支持同档期多片对比等实用功能。压缩包共578个文件、约26.75MB核心代码涵盖Python后端脚本py/pyc、Vue前端组件vue/js、HTML页面与CSS样式同时包含SVG图表素材、SQL数据库初始化脚本、运行安装批处理工具以及Word版说明文档。整体目录结构清晰便于直接导入开发环境运行调试和二次开发已有166人学习下载。资源附带完整项目源码与部署脚本可帮助理解从数据获取、清洗、存储到前端可视化展示的全链路设计也可作为课程设计、毕业设计的项目基础方便在此基础上扩展算法或改进界面为论文撰写或答辩提供工程参考。1. 先立框架一部电影的票房是怎么被拆开看的如果你准备开发一个“基于 Python 的电影票房数据分析系统”第一件事不是急着写爬虫或者画图而是先定义清楚这个系统到底输出什么。票房不是一个孤零零的数字而是一组可以被拆解、排序、关联的字段上映日期、档期、类型、评分、首周票房、总票房。系统设计的目标就是把这些原始字段变成两类结论——某类电影为什么卖得好、某部新片大概能卖多少。这个方向适合三类人刚学完 pandas 想找完整练手项目的开发者需要做数据分析项目交作业的学生以及想看数据找发行感觉的从业者。本文给出的落地路径包含 6 个环节从工程骨架一直走到预测验证每段都能照着复现。2. 系统骨架先行技术选型与工程目录设计2.1 为什么用 Python 当主引擎而不是 Excel 和 BI 工具票房数据看起来是整齐的表格真拿到手你会发现原始数据里同时混着“3.2亿”“8645万”“上映日期没写全”的脏值。Excel 处理万行级别就开始卡Power BI 这类工具做交互图和报表很顺手但你要在里面做特征工程和回归预测就得绕回 Python。pandas 负责框选、清洗、聚合matplotlib 和 seaborn 负责出图scikit-learn 负责建模。用 Python 的另一个原因是整个数据链路都在同一个语言环境里从 requests 抓页面到 sklearn 出模型中间不需要把数据导出成 CSV 再倒腾进另一个工具类型和空值的语义不会在传递中丢失。选择 Python 还有一个现实原因招聘市场里“Python数据分析”是高频词这套系统的代码在求职或课程设计展示时可以直接讲成完整项目。用 Python 做电影票房数据分析系统既要能分析也要能设计——这里的“设计”更多指工程上把模块切干净而不是画一堆类图。很多项目死在代码全揉在一个 notebook 里后面想改一处要翻几百行改完又不知道会影响哪一段。模块化不是为了好看是为了让每条数据从采集到预测只走一条固定路径出错时能定位到具体环节。2.2 系统模块划分采集、清洗、分析、预测四段式整个项目按“数据单向流动”的原则组织。scraper 只负责把公开页面变成原始 CSVcleaner 只读 CSV输出结构化 parquet 或新 CSVanalysis 读干净数据做统计predict 在特征表之上做模型训练和推理。模块之间不互相调用只通过中间文件交换。这样设计的好处是任何一个环节出错只需要重跑那一段不影响已经生成的数据。比如清洗规则改了analysis 和 predict 可以继续用上一次的干净数据对比差异不用从头抓一遍网。movie_boxoffice_system/ ├── data/ │ ├── raw/ # 原始下载数据只读不写 │ └── processed/ # 清洗后的结构化数据 ├── src/ │ ├── scraper.py # 采集公开票房榜单页 │ ├── cleaner.py # 清洗与特征构造 │ ├── analysis.py # 多维统计与可视化 │ ├── predict.py # 模型训练与推理 │ └── utils.py # 公共工具如单位转换 ├── notebooks/ # 探索用脚本不进主流程 ├── output/ │ ├── figures/ # 输出图表 │ └── report/ # 汇总报告 └── requirements.txt这个目录结构是从实际项目里收敛出来的。data/raw 必须保持原始文件不改动因为采集脚本跑出来的数据是“证据”清洗错了还有机会回滚notebooks 只承担探索功能摸索阶段画图没问题但正式运行以 src 下的脚本为准。常见项目把这两者混在一起结果就是模型引用了一个手工改过的 CSV复现时找不出数据是谁改的。如果你拿到的已经是整理好的票房数据集不需要自己抓取那就把 scraper.py 删掉数据直接放 data/raw从 cleaner.py 开始接入。2.3 环境准备虚拟环境与依赖清单我一般会把依赖隔离进虚拟环境尤其当电脑里同时开着多个数据分析项目时pandas 版本冲突是真实会出现的问题。创建环境并安装依赖的命令在项目根目录执行python -m venv venv source venv/bin/activate # Windows 上改用 venv\Scripts\activate pip install -r requirements.txtrequirements.txt 我这里不锁小版本安装时以当前最新稳定版为准。原因是指定太老的版本会和新的 Python 解释器冲突指定太新的版本又可能在半年后装不上保持主版本最新是维护成本最低的做法。pandas numpy requests beautifulsoup4 matplotlib seaborn scikit-learn依赖为什么要这几样requests 和 beautifulsoup4 用来抓公开票房榜单页pandas 负责清洗和透视seaborn 基于 matplotlib 画分类型对比图scikit-learn 提供回归模型和交叉验证。numpy 是 pandas 的底层依赖虽然不直接调用但必须显式声明避免环境安装顺序导致 numpy 版本被隐式抬高或压低。装完后可以用一行命令验证环境python -c import pandas, sklearn; print(pandas.__version__)能正常输出就说明环境没问题。3. 数据从哪来采集与清洗做成一张能用的票房总表3.1 公开榜单页采集用 requests 拿到表格这里按公开票房榜单页的场景说明。目标页面通常是一张表格包含片名、总票房、上映日期、类型、评分等字段。用 requests 带参数请求再用 BeautifulSoup 解析表格是稳定的做法不需要模拟浏览器也不依赖重型渲染框架。如果你所在地区无法访问某些站点不要绕路直接换一个可访问的公开数据源国内也有整理好的票房榜单页可供采集。爬取公开页面时控制请求频率间隔 12 秒遵守站点条款这是做数据采集的基本红线。import requests from bs4 import BeautifulSoup import pandas as pd from time import sleep def fetch_public_pages(url, pages1, per_page30, delay1.5): 抓公开榜单表格返回 DataFrame。delay 是请求间隔秒数。 rows [] for page in range(1, pages 1): params {page: page, limit: per_page} resp requests.get(url, paramsparams, timeout10) resp.raise_for_status() soup BeautifulSoup(resp.text, html.parser) for tr in soup.select(table tbody tr): cells [td.get_text(stripTrue) for td in tr.find_all(td)] if cells: rows.append(cells) sleep(delay) return pd.DataFrame(rows)逻辑上每页请求之间睡 delay 秒是给目标站点留出间隔避免短时间高频请求被封或给服务器添压。timeout10 表示连接和读取超时都按 10 秒算网络抖动时会抛出异常而不是让进程卡死。resp.raise_for_status() 在状态码 4xx/5xx 时直接中断防止把错误页当成正常数据解析。返回值用 DataFrame 接着往下传表格缺失字段时会得到 None交给 cleaner 处理。这里有一个常见误用对选中的 HTML 写死“第 3 列是片名第 5 列是票房”。一旦目标页结构调整解析就全盘翻车。更稳的做法是把表头也抓下来用列名定位字段这样列位置变了也能对上。如果采集过程中被断网或者页面改版不要立刻重跑——先把已经抓到的部分存成 CSV再考虑增量补抓这是数据采集里最基础的后悔药。3.2 清洗票房单位统一、日期解析、空值处理拿到表格后问题就开始暴露了“3.21亿”“9458万”“1,470元”混在一列里上映日期有“2023-12-01”也有“2023.12.01”。清洗的目标是把这堆字符串变成数值和日期否则后续所有聚合计算全是错的。票房单位不统一是这类数据最典型的脏点直接画折线图会出现断崖式跳动看起来像某个月的电影全部扑街实际只是单位换算漏了。import pandas as pd def normalize_boxoffice(df): df df.copy() df.columns [rank, title, boxoffice, release_date, genre, score] def to_yuan(value): value str(value).strip().replace(,, ) if value.endswith(亿): return float(value[:-1]) * 1e8 if value.endswith(万): return float(value[:-1]) * 1e4 return float(value) df[boxoffice_yuan] df[boxoffice].map(to_yuan) df[release_date] pd.to_datetime(df[release_date], errorscoerce) df df.dropna(subset[release_date, boxoffice_yuan]) return dfto_yuan 函数先去掉千分位逗号再按后缀决定乘数。亿和万在票房数据里是最常见的单位处理时要连“1,470元”这种带逗号的裸数值一起考虑。release_date 用 to_datetime 解析errorscoerce 表示无法解析的日期变成 NaT而不是抛异常终止整个流程NaT 在 dropna 时被一并去掉。这里的取舍是宁可丢弃没有日期的少量记录也不让脏日期污染档期分析。清洗完要做一次质量校验清洗后的行数应该和清洗前相差不大如果 dropna 把超过 10% 的数据洗没了说明采集环节有字段缺失应该回头检查 scraper 而不是直接往下跑。我习惯在 cleaner 里加一句print(f保留 {len(df)} / {len(raw)} 行)每次跑完都确认这个比例数据流水线的第一道门槛就从这里算起。3.3 构造口径字段年度、档期、类型分组原始表里一般没有“档期”这一列只有发布日期。做分析时需要把从日期派生的字段固定下来这样后面的分析代码不用重复写同样的逻辑。档期的定义需要先想明白春节档是农历日期不是固定公历粗略方案用月份近似精细方案要维护一张节假日公历日期表。def build_features(df): df df.copy() df[year] df[release_date].dt.year df[month] df[release_date].dt.month df[is_holiday] df[month].isin([1, 2, 5, 10]) df[score_group] pd.cut(df[score], [0, 6, 8, 10], labels[low, mid, high]) return dfis_holiday 用月份近似判断档期1 月和 2 月含元旦和春节5 月有五一10 月有国庆。严格口径需要考虑农历春节的公历日期漂移这套系统如果只做整体趋势分析月份近似够用如果要精细分析春节档就得额外维护一个“公历节假日表”把精确日期传进来替换 is_holiday。score_group 把评分切成低、中、高三档方便后面做分组聚合pd.cut 的 bins 边界按业务认知定为 0-6、6-8、8-10实际分布如果偏移可以再调。建完特征建议把结果落盘成 parquet 格式压缩率高而且能保留 categorical 和 datetime 类型不会像 CSV 那样把日期读回字符串。落盘这一步看似多余却能省掉后续每次分析都从头跑一遍 cleanfeature 的时间。4. 让票房说话多维拆解与可视化找出影响票房的因子4.1 分布规律票房市场是二八分化不是正态分布很多新人拿到票房数值第一件事是画一张直方图看分布。画完会发现右拖尾极其严重少数爆款把均值拉得很高均值并不代表“常见水平”。更稳的拆解是先排序看头部占比这个数字直接决定了后面所有分析的视角——如果你关注平均票房你会被几部超级大片骗得以为所有电影都能卖钱。share df.sort_values(boxoffice_yuan, ascendingFalse) top_20_count int(len(share) * 0.2) top_20_share share.head(top_20_count)[boxoffice_yuan].sum() / share[boxoffice_yuan].sum() print(f前20%的影片贡献了约{top_20_share:.1%}的总票房)这个比例在不同年份的票房市场里通常都很高常年在 70%~85% 之间。打印出来之后结论就出来了这个系统如果用于商业分析重点观察的是头部影片而不是平均票房。后续所有分组对比建议都先做头部和长尾分开再看否则类型均值会被几部超级大片带偏。长尾部分的电影再分析才有价值因为那里藏着被埋没但口碑不错的中小成本片。4.2 类型与档期对比箱线图比柱状图更诚实比较不同类型票房的平均水平柱状图加均值是最常见的做法但会掩盖离散程度。箱线图能同时看到中位数、四分位和离群点更适合右上角有异常大票房的场景。类型字段通常是字符串的枚举比如“剧情”“喜剧”“动作”用 seaborn 的 boxplot 可以一行画出来关键是要处理中文显示。import seaborn as sns import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False fig, axes plt.subplots(1, 2, figsize(14, 5)) sns.boxplot(datadf, xgenre, yboxoffice_yuan, axaxes[0]) axes[0].set_yscale(log) axes[0].set_xticklabels(axes[0].get_xticklabels(), rotation45) sns.boxplot(datadf, xis_holiday, yboxoffice_yuan, axaxes[1]) axes[1].set_yscale(log) axes[1].set_xticklabels([非档期, 档期]) plt.tight_layout() plt.savefig(output/figures/genre_holiday_box.png, dpi150)这里有两个参数值得解释。set_yscale(log) 是因为票房跨了几个数量级线性坐标上非头部影片几乎挤成一条线对数尺度下箱体和须的对比才看得清。dpi150 是给图片定分辨率报告中要打印或缩放展示时不至于糊。如果不用对数尺度你会看到几个触须无限长的箱体剩下大部分样本的差异被压成看不见的短线。数据可视化里尺度选择直接影响读图人的判断输出图表前一定要先问自己我是在展示分布还是在展示个别极值类型和档期两幅图并列作用不只是看单张图而是对照着读——2023 年春节档的动画片票房中位数和国庆档的剧情片可能差出几倍。箱线图把群组差异和组内离散一起呈现这是柱状图做不到的。如果发现某类影片样本量太少图上的箱体会很扁解读时要标注样本数避免拿 3 部片子代表整个类型。4.3 相关性矩阵数值字段间的线性关系把票房、评分、年份这些数值字段放一起算相关系数是检查“什么因素和票房走得近”最直接的做法。pandas 一行就能出矩阵但解读要小心。相关系数高不代表因果相关系数低也不代表没关联这两句话放在票房这种噪声很大的数据上尤其适用。num_cols [boxoffice_yuan, score, year, month] corr df[num_cols].corr() print(corr[boxoffice_yuan].sort_values(ascendingFalse))结果里票房和评分的相关系数通常是正的但不会高到足以单独解释票房。这说明评分的方差只能解释票房变化的一部分单抓一个特征去预测是不成立的。有些入门项目看到相关系数 0.3 就下结论“评分越高票房越高”这是过度解读。相关系数只衡量线性相关年度、档期这类离散变量还要回到分组对比里看。把相关矩阵当成线索而不是证据这条准则在建模时同样适用。相关性分析的意义在于筛特征不在结论。如果两个特征之间的相关系数超过 0.8建模时它们就是冗余的比如“票房”和“分账票房”高度共线同时放进模型反而会把回归系数搅乱。相关性矩阵在这里的价值是给第 5 章建模前做一轮变量初筛把明显冗余的字段排除在特征列表之外。5. 避坑与排查跑通数据流程时最常遇到的5个问题数据项目里跑通了才是起点不出错是不可能的。这一章写我调整这套电影票房数据分析系统时反复踩过的 5 个坑每条按“现象 - 原因 - 解决”列出来后面做类似项目可以直接对照排查。前四个坑集中在数据准备阶段最后一个坑在建模阶段都是真实改代码时踩过的泥。5.1 CSV 用 Excel 打开后乱码现象采集到的 CSV 中文列名用 pandas 读没问题但用 Excel 打开全是乱码或者转给别人后变成问号。原因pandas 写文件默认编码是 utf-8而 Excel 老版本默认按 gbk 解析编码对不上就花屏。解决写 CSV 时统一编码为 utf_8_sig。这个编码会带上 BOM 头Excel 识别 BOM 后就不会按 gbk 猜了。df.to_csv(data/raw/boxoffice.csv, indexFalse, encodingutf_8_sig)另外读取时也用明确编码不要依赖默认值。如果已经乱码用pd.read_csv(path, encodinggbk)可能救回来前提是数据当初确实以 gbk 落盘。这套系统如果想发给同伴协作编码统一是第一步否则对方打开花的直接过来问“你这数据是不是坏了”解释成本比写代码还高。5.2 票房单位混用导致折线图出现诡异跳水现象按月份聚合票房后画折线某几个月突然低得离谱像数据缺失。原因原始字段里有的票房是“1.2亿”有的是“8000万”清洗时只按字符串转 float单位换算漏了数值直接差了一万倍。解决用第 3 章里的 to_yuan 函数强制统一单位。额外建议在清洗后加一条断言检查是否还有字段以“万”“亿”结尾防止新增数据绕过规则。assert not df[boxoffice].astype(str).str.endswith((万, 亿)).any(), 存在未换算的票房这条断言成本低效果明显任何清洗函数跑完都能立刻自检。数据流水线最怕的是“看起来没问题”断言就是给流程上的一道保险。如果你在排查时发现票房数值比合理区间离谱先查单位再查精度。我因为这个坑重新跑过两遍全流程从那以后清洗模块里必定带单位断言。5.3 日期解析失败导致档期分析为空现象pivot_table 按 is_holiday 分组时档期组全是空或者报 ValueError。原因上映日期里有“2023/12/01”和“2023.12.01”混排pd.to_datetime 默认格式解析不了其中一种errorscoerce 把整批转成 NaTdropna 后档期分析数据被抽干。解决to_datetime 先指定主格式再兜底解析或者用 infer_datetime_format 让它自动推断。df[release_date] pd.to_datetime(df[release_date], errorscoerce, infer_datetime_formatTrue)实际中命中的是第二种写法。infer_datetime_format 遇到格式不一致时会尝试常见分隔符速度稍慢但更稳妥。排错时先看df[release_date].isna().mean()如果缺失比例异常再用df[df[release_date].isna()][release_date].head(20)看看没解析出来的是什么格式别整文件重来。日期格式问题在国产数据源里极常见因为很多人手工录入时没按统一格式。5.4 内存占用过高处理大量数据直接死机现象加了几列衍生特征后脚本跑到一半内存占满电脑卡死。原因pandas 的 object 类型列每格都压着 Python 字符串对象几千部电影感觉不大但如果按日粒度去采集数据量会到百万行object 和 float64 的差别就放大了。解决读取时先指定 dtypes把重复的文本字段降成 category 类型日期解析成 datetime64数值字段用 float32 而不是默认 float64。df pd.read_csv(boxoffice.csv, low_memoryFalse) df[genre] df[genre].astype(category) for col in [score, boxoffice_yuan]: df[col] df[col].astype(float32)category 类型在“类型”“档期”这种取值有限的字段上能省不少内存。换 dtype 之后同样的数据量内存占用能降一半以上而且不会改变分析结果的语义。排查时如果脚本突然变慢先看df.info(memory_usagedeep)里哪列占得最多通常就是 object 列。特别注意 pandas 2.x 里读 CSV 默认对字符串列做字符串推断不指定 dtype 就会变成 object。5.5 用默认参数跑回归预测结果几乎不变现象训练完回归模型预测值集中在一个窄区间仿佛模型在“躺平”。原因票房数值横跨数量级评分、年份和票房量纲差异巨大线性类模型在量纲差异下会偏向取值大的特征更常见的是没处理极端值少数爆款主导了损失模型整体变成了“预测均值”。解决先做特征缩放再用对离群点更稳健的模型或变换目标变量。例如对票房取对数或者用 Huber 回归损失。import numpy as np from sklearn.preprocessing import StandardScaler y np.log1p(df[boxoffice_yuan]) # 对票房取对数 X df[[score, year, month]].fillna(0) X_scaled StandardScaler().fit_transform(X)取对数之后爆款和普通片的差距从几百倍压缩到几倍模型不再被少数样本牵着走。这个坑在票房预测里尤其明显因为票房天然是长尾分布谁的模型能感知到这一层预测效果才会有明显差别。排查时先看预测值的 std如果比训练集 y 的 std 小一个数量级基本就是目标变量没处理。模型“躺平”不是模型坏了是数据形态没配合好。6. 把系统再往前推一步验证预测结果与落地使用技巧6.1 时间序列划分验证不要随机打散票房预测和一般表格分类不同电影的上映日期有时间顺序随机把样本打散会让模型用“未来”的数据去预测“过去”的票房看起来评估分数很高上线后就露馅。验证时用 TimeSeriesSplit 按时间顺序切训练集和测试集既保持时序又复用了样本。from sklearn.model_selection import TimeSeriesSplit from sklearn.ensemble import RandomForestRegressor tscv TimeSeriesSplit(n_splits5) for fold, (train_idx, val_idx) in enumerate(tscv.split(X)): model RandomForestRegressor(random_state42, n_estimators200) model.fit(X.iloc[train_idx], y.iloc[train_idx]) print(ffold{fold}: R2 {model.score(X.iloc[val_idx], y.iloc[val_idx]):.3f})n_estimators200 是随机森林的树数量这里取 200 是效率与稳定性的平衡调成 500 可能只有 1% 以内的收益却要让每折训练时间翻倍。输出每一折的 R2如果前几折高后几折低说明模型的规律在新样本上漂移需要回去看特征有没有过期。这个验证方式不适合调参调过头每一折的测试集严格在训练集之后才能模拟“用它预测未来”的真实场景。6.2 让预测结果回到业务语言模型输出的是对数票房报告里要对数换回“亿元”并给出预测区间而不是单一值。我习惯用分位数表示“合理波动范围”再配合“预期中性/乐观/悲观”的描述直接交给运营同事判断。pred_test np.expm1(model.predict(X.iloc[val_idx])) print(f单部预测{np.mean(pred_test)/1e8:.2f} 亿)我踩过最深的教训是把 R2 做得好看就满怀信心把预测结果贴到报告里结果被问“区间呢依据呢”时哑口无言。业内的票房看的是档期和类型的组合预期不是一个单点。从那以后我习惯了对所有预测附上样本量和区间项目结论才真正站得住脚。希望帮到你。本文还有配套的精品资源点击获取
返回列表