简介:面向毕业设计与课程项目的开发者,这份基于Python的电影票房预测系统设计文档系统介绍了从数据采集到结果展示的完整建设流程。文档以爬虫抓取中国电影网历史票房数据为切入点,讲解如何利用BeautifulSoup、Scrapy等库解析网页并提取票房信息,随后重点说明Pandas与Numpy在数据清洗、缺失值处理、标准化等预处理环节的应用。票房预测部分基于SciPy的多项式曲线拟合算法构建模型,通过调整多项式阶数适配票房变化趋势;文档还给出Flask或Django搭建展示界面、APScheduler实现实时更新预测的具体设计,读者可据此走通从数据采集、模型训练到结果可视化的全流程。资源为1个PDF文件,压缩包约1.17MB,章节涵盖研究背景、国内外现状、关键技术与系统实现等内容,目录结构清晰。已有2583人浏览学习,适合学习爬虫、回归建模或需要完整系统设计方案参考的读者。
1. 基于 python 的电影票房预测系统:这份论文资源到底能落地到什么程度
拿到这份《基于 python 的电影票房预测系统设计与实现》PDF 的时候,我第一反应是这又是个纯理论堆砌的毕设文档。翻完目录和正文,发现比预期实在:它不是一个空壳框架,而是把「爬虫拿数据 → Pandas 清洗 → numpy 多项式拟合 → 输出预测票房」这条完整链路从头到尾走了一遍的工程设计文档。你如果正卡在「不知道票房预测该用什么算法、爬下来的数据怎么处理、拟合出来的曲线怎么解释」这类问题上,这份资源能直接给你一套可复现的基准方案。适合三类人:准备做 Python 相关毕设/课设的学生、想给影院排片或视频平台做票房参考的数据分析入门者、以及想快速搭一个时间序列拟合 Demo 的开发者。它不解决「预测准不准」的玄学问题,但能帮你把「从数据到预测」这条路跑通,而且是用最朴素、最不容易翻车的方式。
2. 票房预测的技术选型:为什么是多项式曲线拟合,而不是 LSTM 和 Prophet
很多人一听到「票房预测」就想到深度学习,想到 LSTM、Attention。但这篇论文选的路径恰恰相反——用多项式曲线拟合 + 最小二乘法,把问题简化成「找一条函数曲线,让它离历史票房数据点最近」。这个选型在工程上非常聪明,我拆开讲。
2.1 预测问题的本质:把票房走势当成一条可拟合的曲线
票房预测和股票预测有个共同点:你拿到的数据在时间轴上是离散的,而且受宣发、口碑、档期、竞争对手多重因素干扰。深度学习模型理论上能捕捉复杂非线性关系,但它有两个致命前提:一是需要海量样本,二是需要特征工程。论文里的数据量是多少?中国票房网 2015-2018 年的历史票房数据只有 1587 条,十几部电影的基本信息。这个样本量丢给 LSTM,训练出来的模型大概率是过拟合的——在训练集上 R² 能到 0.99,换一部新片直接崩。
多项式曲线拟合的核心假设是:票房随时间呈现某种平滑的趋势,而这个趋势可以用一个 M 次多项式近似表达。它不试图解释「为什么涨为什么跌」,只关心「历史上这个走势长什么样,接下来大概率延续什么走势」。这种做法在数据量小的场景下反而更稳。论文里用的是 numpy 的polyfit,阶数定在 10,这是经过试错的经验值——阶数太低欠拟合(一条直线根本追不上首周末的票房暴涨),阶数太高过拟合(曲线会为了穿过每一个噪声点而剧烈震荡)。
这就是选型的第一层逻辑:在数据量撑不起复杂模型的场景下,一个带正则化思想的低阶多项式拟合,比黑匣子深度学习更可控、更可解释。影院排片的人拿到一条拟合曲线,能直观看到走势,而不是面对一个神经网络输出的「7.32 亿」发呆。
2.2 最小二乘法的参数求解过程:从理论到polyfit的一行代码
论文第三章把最小二乘法的数学推导写得比较完整,这里帮你把公式翻译成「人话」。我们要做的事情是:给定 N 个数据点(上映天数,当日票房),找到一个 M 次多项式
y = w0 + w1*x + w2*x^2 + ... + wM*x^M使得所有数据点到这条曲线的垂直距离的平方和最小。损失函数写成:
L = Σ(yi - (w0 + w1*xi + ... + wM*xi^M))^2对每个系数wj求偏导并令其为 0,会得到一个线性方程组,解出来就是最佳的系数组合。这个方程组用手算能累死,但 numpy 的polyfit一行搞定:
import numpy as np # x: 上映天数序列 [1, 2, 3, ..., N] # y: 对应日期的票房数据 # deg: 多项式阶数,论文里取 10 coefficients = np.polyfit(x, y, deg=10)参数说明:x和y必须是一维数组且长度相同,deg是多项式的最高次数。返回的coefficients是按降幂排列的系数列表,coefficients[0]是最高次项系数。
拿到系数后,用np.poly1d封装成一个函数对象,就可以直接代入任意x求预测值:
# 将系数封装为多项式函数对象 poly_func = np.poly1d(coefficients) # 预测第 15 天的票房 pred_day_15 = poly_func(15)这里有个新手最容易忽略的点:polyfit的deg参数不是越大越好。论文里取 10,是拿 1587 条数据反复试出来的。如果你自己拿一部电影的单日票房做拟合,50 天数据配 10 阶多项式已经偏高,实际操作我会建议从 5 开始,看拟合曲线是否出现「龙飞凤舞」的震荡——如果出现,降阶。
2.3 拟合效果的评估:R² 不是唯一标准,还有「看懂残差」
论文提到了判定系数 R²,说大于 0.8 算拟合度较好。这个指标本身没问题,但光看 R² 会踩坑。我给你一个血的教训:多项式阶数从 3 加到 15,R² 会一路涨,从 0.7 涨到 0.95 不止,看起来越拟合越准,但拿去预测未来 5 天的票房,高阶模型可能给出负数——票房怎么可能为负?
原因很简单:高阶多项式在数据边界外(尤其是右端,也就是最近上映日之后)会剧烈外延,曲线为了拟合历史数据的波动,在末端积累了巨大的斜率,外推一步就飞到天上或地下。
我在复现这篇论文的时候,习惯加一个「残差图」检查——把每一天的真实票房减去拟合值,画出残差序列。如果残差呈现明显的周期性(比如周末高、工作日低),说明多项式拟合把周期性当成了噪声吃掉了,这时候要么增加阶数(吃进更多波动),要么干脆换成分段拟合。论文里没写这一步,但按它的数据量,首周末票房暴涨、之后回落的「尖峰」形态,用 10 阶多项式拟合是能勉强压住的,但残差图一定不好看。所以我的建议是:把 R² 当参考,把残差分布当真相。
3. 数据链路搭建:从中国票房网爬数据到 csv 存储的完整实现
论文的数据来源是中国票房网(58921.com),爬的是网站的历史票房和每日票房数据。这一章把爬虫、数据清洗、存储三块拆开讲,按论文的实现思路给你一份可以直接跑的代码骨架。
3.1 爬虫模块:requests 模拟浏览器拿到 HTML
论文里的核心爬虫代码是getMovieURL,用来获取指定 URL 的 HTML 内容。它的思路是设置请求头模拟真实浏览器,用requests.get发起请求,然后统一编码。我照着这个思路写了一个更完整的版本:
import requests import chardet HEADERS = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', } def fetch_page(url, timeout=10): try: response = requests.get(url, headers=HEADERS, timeout=timeout) response.raise_for_status() # 先用 apparent_encoding 自动检测实际编码,再统一转为 utf-8 if response.encoding is None or response.encoding.lower() == 'iso-8859-1': response.encoding = chardet.detect(response.content)['encoding'] return response.text except requests.RequestException as e: print(f'请求失败: {url}, 错误: {e}') return None # 示例:爬取某天的票房页面 html = fetch_page('http://58921.com/daily/wangpiao?page=0')这段代码里有几个细节值得说道:User-Agent必须写全,否则部分网站直接返回 403;timeout参数必须设,不设的话遇到慢响应页面会卡死整个爬虫;编码检测用chardet是爬老站点最稳的做法——中国票房网这种站点的编码是 GB2312 还是 UTF-8 全看心情,硬编码response.encoding = 'utf-8'大概率会得到乱码。
3.2 页面解析与数据提取:BeautifulSoup 提取电影名、上映时间、票房
论文提到解析可以用re、bs4、lxml三选一。我推荐BeautifulSoup + lxml的组合,容错率高,代码可读性好。下面这段是在拿到 HTML 后提取票房信息的骨架:
from bs4 import BeautifulSoup def parse_box_office(html): soup = BeautifulSoup(html, 'lxml') rows = [] # 找到票房表格的每一行,具体选择器需要按实际页面结构调整 for tr in soup.select('table tr'): cells = tr.find_all('td') if len(cells) < 3: continue movie_name = cells[0].get_text().strip() release_date = cells[1].get_text().strip() daily_box_office = cells[2].get_text().strip() rows.append({ 'movie_name': movie_name, 'release_date': release_date, 'daily_box_office': daily_box_office, }) return rowsselect('table tr')是 CSS 选择器,拿到的是一行行的数据。这里要注意,网页结构改版是常事,所以写爬虫的第一原则是「选择器尽量宽松」,不要依赖太深的层级。我在实际爬的时候遇到过表格里嵌套表格的情况,cells[0]取出来的不是电影名而是其他内容——排查方法是先print(cells)看每一行的结构,再决定取哪个索引。
3.3 数据清洗与存储:Pandas 去重、筛选、转 csv
论文里反复提到「去重」「特殊符号处理」「有效性检验」三个步骤,这是数据预处理的核心。我把它写成一段完整的 Pandas 代码:
import pandas as pd def clean_and_save(raw_rows, output_path): df = pd.DataFrame(raw_rows) # 1. 去掉完全重复的行 df = df.drop_duplicates() # 2. 去除包含特殊符号的数据(如 URL、表情、html标签残留) df = df[~df['movie_name'].str.contains(r'[a-zA-Z]+://|[\U0001F300-\U0001FAFF]', na=False)] # 3. 票房字段转数值,非法值置为 NaN 后直接丢弃 df['daily_box_office'] = pd.to_numeric(df['daily_box_office'], errors='coerce') df = df.dropna(subset=['daily_box_office']) # 4. 按电影名 + 上映日期排序,让时间轴有序 df = df.sort_values(['movie_name', 'release_date']).reset_index(drop=True) # 5. 转置存储:行是电影名,列是上映日期,值是当日票房 pivot = df.pivot_table(index='movie_name', columns='release_date', values='daily_box_office', aggfunc='sum') pivot.to_csv(output_path, encoding='utf-8-sig') return pivot清洗那步用str.contains过滤特殊符号/emoji/链接是通用做法,U0001F300-U0001FAFF是 emoji 的 Unicode 范围。票房字段转数值用pd.to_numeric(errors='coerce'),遇到「1,234 万」这种带单位或带逗号的字符串会变成 NaN——这时候需要先做字符串清理(去掉「万」和逗号再转)。转置存储是论文里的亮点:正常存法是每一行是一条票房记录,转置之后每一行是一部电影、每一列是一个上映日,这种「宽表」格式在按时间轴拟合的时候特别方便,直接把某一行取出来就是这条电影的全部票房时间序列。
4. 票房预测核心模块:多项式拟合训练、预测与可视化
这一章是论文的核心,也是你复现时最需要关注的部分。我会把拟合训练、预测结果解读、可视化三块都覆盖到,并给出边界条件的分析。
4.1 训练与预测:polyfit的完整封装
论文里的核心函数draw_fit做了三件事:用np.polyfit做拟合、用np.poly1d封装函数、用plt.plot绘制拟合曲线。我把它拆成训练和预测两个独立的函数,方便你看懂每一步:
import numpy as np import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties # 设置中文字体,防止图表标题乱码 font = FontProperties(fname='C:\Windows\Fonts\simhei.ttf', size=12) def train_polyfit_model(series, degree=10): """ 对一部电影的票房时间序列做多项式拟合 :param series: 列表或一维数组,按上映天数排列的日票房 :param degree: 多项式阶数,论文推荐 10 :return: 多项式函数对象 """ x = np.arange(len(series)) y = np.asarray(series, dtype=np.float64) # 过滤掉 NaN,否则 polyfit 会直接报错 mask = ~np.isnan(y) coefficients = np.polyfit(x[mask], y[mask], degree) return np.poly1d(coefficients) def predict_box_office(model, days_ahead=7): """ 预测未来 N 天的票房 :param model: polyfit 返回的多项式函数 :param days_ahead: 预测天数 :return: 预测票房值列表 """ last_day = model.c # 这只是占位,实际用训练数据长度 # 正确的写法:预测起点应该是训练数据最后一天的下一天 # 实际使用时需要把训练数据的长度传进来 future_x = np.arange(len(series), len(series) + days_ahead) return model(future_x)这里有个设计细节值得展开。polyfit的输入 x 是「第几天」的索引,从 0 开始。预测未来 7 天,就是把 x 延伸到[len(series), len(series)+1, ..., len(series)+6],代入多项式计算。我说的「外推灾难」在这一步会特别明显——如果拟合曲线在末端斜率为正且越来越大,预测值会是一个天文数字;如果斜率为负且越来越陡,预测值直接变负数。所以我在实际使用中会在预测函数里加一个「合理性检查」:预测值小于 0 或大于历史峰值的 3 倍,就警告提示。
4.2 结果可视化:把拟合曲线和历史票房画在一起
论文里的可视化代码是用 3x3 子图,一个图展示一部电影的拟合效果。我把核心部分抽取出来,去掉子图逻辑,单独看一张图怎么画:
import numpy as np import matplotlib.pyplot as plt def plot_fit_result(movie_name, series, degree=10): x = np.arange(len(series)) y = np.asarray(series, dtype=np.float64) # 过滤空值 mask = ~np.isnan(y) x_clean, y_clean = x[mask], y[mask] # 拟合 coeffs = np.polyfit(x_clean, y_clean, degree) poly = np.poly1d(coeffs) # 生成平滑曲线的 x 坐标(密集采样让曲线更圆滑) x_smooth = np.linspace(x_clean.min(), x_clean.max(), 200) y_smooth = poly(x_smooth) plt.figure(figsize=(10, 6)) plt.scatter(x_clean, y_clean, label='实际票房', color='#1f77b4', s=30) plt.plot(x_smooth, y_smooth, label='拟合曲线', color='#d62728', linewidth=2) plt.xlabel('上映天数') plt.ylabel('单日票房') plt.title(f'{movie_name} 票房走势与拟合结果', fontproperties=font) plt.legend() plt.grid(True, alpha=0.3) plt.show()画图时用linspace生成密集的 x 坐标,而不是直接用原始数据点连线,这样拟合曲线才会光滑。scatter画真实数据点,能直观看到哪些点是离群值——比如某一天的票房因为特殊事件暴涨,拟合曲线会「被拉过去」,这时你就能判断要不要把这个点当异常值剔除。
4.3 预测结果怎么解读:三个边界要清楚
拟合完成、画出曲线、预测出了数值,不等于任务结束。我整理了这个方法的三个边界,你在交付结果时必须跟使用方讲清楚。
边界一:预测窗口有限。多项式拟合的本质是「用历史走势外推」,它的预测能力在短窗口(3-7 天)内可信度尚可,超过 15 天基本就是玄学。论文说「为点映过一段时间的电影提供预测票房」,这个场景是对的——电影已经上映了几天,拿这几天的真实数据去拟合走势,预估接下来一两周的票房,这是多项式拟合的合理射程。
边界二:样本量决定阶数下限。polyfit的阶数必须小于有效数据点的数量。如果你只有 7 天的票房数据,degree=10会直接报错——数据点不够解不了方程组。论文里的做法是默认每部电影有足够长的上映周期(一般 30 天以上)才参与拟合。
边界三:节假日和突发事件无法预测。多项式曲线不会知道「大年初一票房普遍暴涨」这个规律。如果你想做档期因子修正,需要另外引入宣发力度、排片占比、IMDb 评分等特征,用多元回归替代纯时间序列拟合。
5. 复现避坑指南:爬虫、拟合、乱码的 5 个常见问题与排查
照着论文复现的时候,你大概率会遇到下面这几个问题。我把排查思路一条条列出来,每条都是「现象 → 原因 → 解决」的结构。
5.1 爬取的 HTML 是乱码,电影名全是锟斤拷
现象:response.text打印出来中文全部变成乱码,或者response.encoding显示ISO-8859-1。
原因:老网站用的是 GB2312 或 GBK 编码,requests库默认不会自动检测网页的charset,而是根据响应头猜测。
解决:不要硬编码编码格式,用chardet.detect(response.content)自动检测:
import chardet raw = response.content encoding = chardet.detect(raw)['encoding'] response.encoding = encoding text = response.text如果chardet检测出来不准确,还有一个兜底方案:在get_text()时手动指定编码,把抓到的 bytes 直接decode('gbk', errors='ignore')。注意errors='ignore'很重要,GBK 解码遇到非法字节会直接抛异常,忽略错误可以保证拿到的部分文本是可用的。
5.2np.polyfit报 RankWarning 或者结果全是 nan
现象:控制台输出RankWarning: Polyfit may be poorly conditioned,拟合结果出现大量 nan。
原因:数据里有 NaN,或者 x 和 y 的长度不一致,或者多项式的阶数接近数据点数导致矩阵病态。
解决:拟合前强制做数据清洗,把 NaN 过滤掉。另外建议对 x 做归一化——把天数缩放到 [-1, 1] 区间再拟合,数值稳定性会好很多:
x_raw = np.arange(len(y_clean)) x_norm = (x_raw - x_raw.mean()) / x_raw.std() coeffs = np.polyfit(x_norm, y_clean, degree)注意这样拟合出来的系数是相对于归一化 x 的,预测时要对输入 x 做同样的归一化变换,否则结果错得离谱。
5.3 拟合曲线在末端向上飞起,预测票房飙到 100 亿
现象:训练集内拟合得很好,R² 到了 0.95,但一预测未来 7 天,数值暴涨到不合理的量级。
原因:多项式阶数过高,模型在末端积累了极大的斜率,外推被放大。
解决:强制加入「预测值合理性检验」,超过设定阈值就自动降阶重拟合:
def safe_predict(series, degree_start=10, max_multiplier=3.0): for degree in range(degree_start, 1, -1): coeffs = np.polyfit(np.arange(len(series)), series, degree) poly = np.poly1d(coeffs) future = poly(np.arange(len(series), len(series)+7)) if np.all(future >= 0) and np.max(future) <= max_multiplier * np.max(series): return poly, future return None, None这个函数的效果是:从 10 阶往下尝试,找到第一个预测值合理(不为负、不超过历史峰值 3 倍)的阶数。这是一个很实用的「后悔药」机制,能直接避免论文 demo 里那种外推到离谱结果的情况。
5.4 爬虫被封 IP,请求返回 403
现象:爬了十几页之后,突然所有请求都返回 403 Forbidden。
原因:请求频率太高,目标站点做了反爬限制。
解决:给请求加延时,每次请求后time.sleep(1);如果还是被封,需要用代理池换 IP。另外建议把headers里的Referer字段也加上——目标站很多时候会校验来源页面。代码里加延时是最直接的防封手段,虽然慢一点,但对于每天更新一次的票房数据完全够用。
5.5 保存的 csv 用 Excel 打开乱码
现象:pandas里看是正常的中文,用记事本看也正常,但用 Excel 打开全是乱码。
原因:编码问题,csv 保存为utf-8时 Excel 默认用 ANSI 解析。
解决:保存时用utf-8-sig编码:
pivot.to_csv('box_office.csv', encoding='utf-8-sig')utf-8-sig会在文件开头加一个 BOM(字节顺序标记),Excel 看到 BOM 就能正确识别 UTF-8 编码。这是我每次写 csv 输出都强制走的习惯,从那以后再也没有被 Excel 乱码坑过。
6. 从论文到可用系统:把豆瓣评分和时间因子加进预测模型的三个进阶思路
这篇论文给出的系统是「爬虫 + 拟合 + 展示」的基准版本。如果你想让预测更接近「能用于实际排片决策」,我建议在它的基础上做三个方向的增强。
进阶一:分段拟合替代全周期拟合。电影的票房走势有明显的生命周期:首周末冲高、次周末小高峰、之后持续衰减。用一个 10 阶多项式硬拟合整个周期,末期会被首周末的尖峰带偏。我的做法是把上映前 7 天、8-14 天、15 天以后分成三段,每段用 3-4 阶多项式分别拟合,再拼接起来。这种方法对长线电影(上映超过 30 天)的预测精度提升非常明显,而且每段模型的含义更清晰:首周末看爆发力,次周看口碑留存,长尾看续航。
进阶二:引入外部特征做多元回归。多项式曲线拟合只用了时间这一个维度,但票房预测本质上受「档期热度」「排片场次」「演员号召力」多因素驱动。既然论文已经做好了爬虫,完全可以多爬一份豆瓣评分和猫眼想看人数,用 scikit-learn 的线性回归或随机森林替换多项式拟合:
from sklearn.linear_model import LinearRegression from sklearn.preprocessing import PolynomialFeatures # X: 首日票房、上映天数、豆瓣评分、想看人数 # y: 首周总票房 poly = PolynomialFeatures(degree=2, include_bias=False) X_poly = poly.fit_transform(X) model = LinearRegression().fit(X_poly, y) pred = model.predict(poly.transform(X_new))这套方案的好处是把「上映前就能拿到的信息」用于预测,真正做到「电影还没上映就能给一个预期值」,补上了多项式拟合只能在上映后预测的短板。
进阶三:用 APScheduler 做定时更新。论文里提到「系统能够及时的变更票房的实时数据和预测数据」但没给实现方案。这个用定时任务库十分钟就能完成:
from apscheduler.schedulers.blocking import BlockingScheduler def update_job(): # 1. 重爬当日票房 # 2. 重新训练全部在映电影的模型 # 3. 重新生成预测结果并覆盖 csv print('票房数据已更新') scheduler = BlockingScheduler() scheduler.add_job(update_job, 'cron', hour=10, minute=30) scheduler.start()设成每天上午 10:30 更新一次,正好是当日实时票房数据基本稳定又没到晚间高峰的时间点。这个技巧能让你的系统真正「活」起来,而不是跑一次就扔在角落吃灰。
最后说个我复现论文时踩过最深的坑:我拿着论文里的顺序——先爬虫再清洗再拟合——结果发现每次跑完拟合都要手动清洗一遍数据,流程割裂而且容易漏掉异常值。后来我把整个流程封装成一个Pipeline类,把爬取、清洗、训练、预测、导出五个步骤串起来,任何一步挂了都能定位到具体的日志。从那以后我每次做这种基于爬虫数据的预测项目,都强制走一遍「先搭流程骨架,再填业务逻辑」的路线。希望这篇拆解能帮你绕过我走过的弯路,把这份论文资源真正变成能跑出结果的东西。
本文还有配套的精品资源,点击获取