猫眼数据+SVR回归器实现电影票房预测的完整流程

发布时间:2026/10/9 4:11:20
猫眼数据+SVR回归器实现电影票房预测的完整流程 简介基于猫眼电影数据与SVR回归器的票房预测系统是一套覆盖数据采集、清洗、特征分析与票房预测全流程的Python项目融合人工智能回归方法适合计算机、人工智能、自动化等专业的在校学生用于毕业设计、课程设计或入门数据挖掘实践。压缩包共18个文件包含7个Python脚本分别负责猫眼数据爬取、字体反爬解析、IP定位、特征工程与SVR建模6个pyc编译文件便于对照调试4个woff字体映射文件用于破解页面加密字体1个xls文件保存中间数据整体仅186KB目录清晰、代码精简易读。目前已有158人学习下载代码事先经测试运行成功答辩平均分达到96分具备较高的可靠性。读者可以获得从爬虫到特征分析再到票房预测的完整实现思路借助文档说明快速上手也方便在此基础上扩展功能或直接作为课程项目演示。1. 电影票房预测不是玄学猫眼数据 SVR 回归器的完整落地路径新片上映前三周发行方最想算的不是口碑而是首周末票房能落在哪个区间。拍脑袋有风险线性回归扛不住非线性关系纯深度学习又缺样本量。常见的做法是抓猫眼电影上的公开数据——评分、评论数、想看人数、类型、档期这些字段整理成结构化数据集再交给 SVR 回归器去拟合票房曲线。这套方案训练成本低、参数可解释后续想叠加新特征也方便适合学生做毕设练手也适合小型团队先跑通一版票房预估。下面把数据爬取、特征分析到 SVR 调参的完整路径过一遍。2. 从猫眼电影抓数据请求头伪装、字段补齐与增量入库2.1 请求头伪装与榜单页请求先解决能不能拿到动手之前先把环境准备好。我一般用python -m venv .venv建一个独立虚拟环境再执行pip install requests beautifulsoup4 pandas lxml把依赖装齐依赖列表顺手写进requirements.txt。Windows 上装 Python 时如果没勾选 Add to PATH后面 pip 命令会找不到解释器要去系统设置里手动把安装目录加进环境变量用 vscode 的话在项目里按 CtrlShiftP 选 Python 解释器指向.venv下的 python终端里的 python 和 pip 才会跟着走。真正写爬虫的第一步不是解析 HTML而是先让服务器认为你是一个正常访问浏览器的用户。直接用 requests 打开榜单页通常会收到非 200 状态码原因多半是请求头里缺少 User-Agent或者请求频率太高被反爬识别。import requests import time import random def build_headers(): # 关键字段是 User-Agent 和 Referer数值可以从浏览器开发者工具里复制 return { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36, Referer: https://maoyan.com/, Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9 } def fetch_one_page(url): # 0.5 到 1.5 秒的随机延时避免固定间隔暴露程序行为 time.sleep(random.uniform(0.5, 1.5)) resp requests.get(url, headersbuild_headers(), timeout10) if resp.status_code ! 200: print(f非 200 状态码{resp.status_code}先停一下) return None return resp.textbuild_headers 里三个字段各管一件事User-Agent 告诉服务器你是哪款浏览器Referer 表示你是从猫眼首页跳转过来的Accept 声明你接受 HTML 响应。前两个字段缺一个都可能被当成异常程序请求timeout10 是网络超时兜底防止脚本卡死。请求失败时先不重试把状态码打出来判断是请求头问题还是频率问题。2.2 榜单页解析与详情页补字段把 HTML 变成结构化数据拿到 HTML 之后用 BeautifulSoup 解析页面。这一步要做的是“先观察页面结构再写选择器”不是上来就靠猜。打开开发者工具找到电影条目所在的标签层级再写对应的 select 语句。经典的榜单页结构里每部电影在dl.board-wrapper下的一个dd标签里片名、主演、上映时间和评分都有固定节点。from bs4 import BeautifulSoup def parse_board(html): soup BeautifulSoup(html, html.parser) movie_items soup.select(dl.board-wrapper dd) rows [] for item in movie_items: name_node item.select_one(p.name a) if not name_node: continue name name_node.get_text(stripTrue) star item.select_one(p.star).get_text(stripTrue) release item.select_one(p.releasetime).get_text(stripTrue) score_node item.select_one(p.score i) score score_node.get_text(stripTrue) if score_node else # film_id 用于后续拼接详情页 URL film_id name_node[href].split(/)[-1] rows.append({ film_id: film_id, name: name, star: star, releasetime: release, score: score }) return rowsfilm_id 要单独存下来后面补字段要用。榜单页缺少类型、想看人数这类训练特征需要再请求一次详情页 URL形式通常是https://maoyan.com/films/{film_id}。详情页在不同版本的页面结构差异很大我的习惯是先抓某一部电影的详情页 HTML 存成.html文件在编辑器里搜“想看”或“影片类型”确认字段位置后再写选择器而不是在公网上反复请求调试页面结构。这一个动作能把调页面选择器的时间压缩大半。2.3 写入 CSV 和 SQLite增量去重是这样做的预测系统的爬虫一般会跑很多轮核心要求是数据不重复、不丢失。常见做法是每次抓回的数据追加到同一个 CSV用“片名 上映时间”做唯一键去重数据量上来之后换成 SQLite 更便于查询。import os import pandas as pd csv_path maoyan_raw.csv df_old pd.read_csv(csv_path, encodingutf-8-sig) if os.path.exists(csv_path) else pd.DataFrame() df_new pd.DataFrame(new_rows) # concat 后按唯一键去重保留最后一次抓到的记录 df_all pd.concat([df_old, df_new], ignore_indexTrue) df_all df_all.drop_duplicates(subset[name, releasetime], keeplast) df_all.to_csv(csv_path, indexFalse, encodingutf-8-sig)CSV 写出去的时候要指定encodingutf-8-sig否则用 Excel 打开中文会乱码。SQLite 的追加模式不会自动去重需要先查出库里已有的唯一键再过滤新数据这一步很多人会漏import sqlite3 conn sqlite3.connect(maoyan.db) df_all.to_sql(movie_raw, conn, if_existsreplace, indexFalse) existing pd.read_sql(SELECT name, releasetime FROM movie_raw, conn) df_filtered df_new.merge(existing, on[name, releasetime], howleft, indicatorTrue) df_filtered df_filtered[df_filtered[_merge] left_only].drop(columns[_merge]) df_filtered.to_sql(movie_raw, conn, if_existsappend, indexFalse)to_sql的if_exists有两个常用值replace 适合第一轮建表append 适合持续追加。merge 时indicatorTrue会生成_merge列值为left_only的就是库里还没有的新片把它留下写入就完成了增量入库。这一版数据只有榜单字段后面做特征工程时还要和详情页数据合并所以film_id此时别丢。3. 特征分析从原始字段到 SVR 能吃下的结构化数据3.1 特征构造类型、档期与主创影响力爬下来的原始字段适合给人看不适合直接喂给 SVR。票房预测常见的做法是把文本字段转成数值、把日期字段转成档期和星期属性、把主创人员的历史票房表现转成影响力分。先清理字符串再做类型展开。import pandas as pd df pd.read_csv(maoyan_raw.csv, encodingutf-8-sig) df[releasetime] df[releasetime].str.replace(上映时间, , regexFalse) df[release_dt] pd.to_datetime(df[releasetime], errorscoerce) df[release_weekday] df[release_dt].dt.weekday # “主演沈腾, 马丽”这类字符串按逗号切出第一位主演 df[actor0] df[star].str.replace(主演, , regexFalse).str.split(,).str[0].str.strip() df[actor0] df[actor0].fillna(未知) # 类型字段是多值字符串用 get_dummies 按逗号展开成多列 type_cols df[type].str.get_dummies(sep,) df pd.concat([df, type_cols], axis1)releasetime字段往往带“上映时间”前缀必须先清理再转日期否则 pandas 解析会报错。release_weekday用 0 到 6 表示星期几首周效应和周内/周末的票房走势差异都能被模型捕捉到。type字段经常是“动作,冒险”这种逗号分隔的多值用str.get_dummies(sep,)展开后每个类型变成一列 0/1 特征如果类型值太多我会只保留出现次数超过 5 次的热门类型避免特征矩阵过度稀疏。档期标记是票房预测里信号最强的特征之一。春节档、暑期档、国庆档的票房天花板和普通档期差很多判断档期不能只按自然月因为每年春节日期不同要维护一张当年的档期起止表或者直接用“上映日期是否落在法定节假日区间”生成一个布尔特征。简单场景下用一个df[has_holiday]列先顶上也能立竿见影。3.2 特征量纲与类型转换为什么 SVR 前必须标准化SVR 对特征尺度极其敏感。累计票房以万元为单位数值可能到几万甚至几十万评分只有 0 到 10想看人数可能是几万。这些列直接拼在一起SVR 算核函数距离时票房列会完全主导结果其他特征等于没有参与计算。from sklearn.preprocessing import StandardScaler feature_cols [release_weekday, score, comment_count, wish_count] list(type_cols) scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train[feature_cols]) X_test_scaled scaler.transform(X_test[feature_cols])这里有两个容易遗漏的细节。第一scaler 只能用训练集 fit再对测试集 transform如果测试集也单独 fit两个数据集的标准差就不一致相当于在验证阶段换了一套预处理规则后面的误差分析全部失真。第二把 StandardScaler 放进 sklearn Pipeline 和 SVR 一起做网格搜索比手动先 fit 再喂模型更安全能避免切错顺序导致的数据泄漏。特征里有缺失值时先补缺失再标准化不能带着空值做 fit。注意SVR 之前不做标准化的后果往往不是训练报错而是模型收敛到一个奇怪结果。如果你的预测值全是同一个数优先检查这一步。3.3 标签与时间分割预测哪个票房就用哪个时点的特征票房预测的标签通常是累计票房或首周公映票房比如“总票房万元”。这里更关键的是特征和标签的时间对齐你想在上映前预测只能用上映前就能拿到的数据比如类型、主创、档期、想看人数想在上映首日预测首周票房才可以加入评分、评论数这些上映后才产生的字段。把“上映后第 7 天的评论数”拿来做“首周票房”的特征属于典型的未来信息泄漏。df_sorted df.sort_values(release_dt).reset_index(dropTrue) cut int(len(df_sorted) * 0.8) train_df df_sorted.iloc[:cut] test_df df_sorted.iloc[cut:] # 这是时间序列切片不是随机抽样 print(f训练集时间范围{train_df[release_dt].min()} ~ {train_df[release_dt].max()}) print(f测试集时间范围{test_df[release_dt].min()} ~ {test_df[release_dt].max()})用iloc按排序后的行号切片是处理时间序列分割最简单的方式。训练集只包含早于测试集的影片这样的评估结果才接近真实上线场景。如果把train_test_split默认的随机切分用到票房数据上模型等于提前看到了未来影片的分布验证分数虚高实际预测一塌糊涂。主创影响力这列统计特征也一样只能在训练集上算均值再映射到测试集不能拿全量数据统计完再切分。4. SVR 回归器训练RBF 核的三个必调参数与网格搜索4.1 为什么选 SVR小样本非线性场景下的回归器选型票房预测的样本量通常只有几百到上千部影片这种规模下深度学习很难发挥。树模型可以用但随机森林的输出是阶跃式的票房预测很难给出平滑的估计而且它对超参数不敏感的特性容易让人忽略特征质量问题。SVR 的优势是支持核函数处理非线性关系自带正则项约束过拟合样本量要求低输出曲线平滑。缺点也很明显特征量纲不一致时结果直接崩超参数要调对。模型特征量纲敏感度小样本表现主要调参对象线性回归高容易欠拟合基本不用调SVRRBF 核极高参数合理时表现可靠C、epsilon、gamma随机森林低能用但输出不平滑树深度、树数量我通常先用线性回归跑一遍基线再上 SVR。SVR 的误差没有比线性回归低太多时先怀疑特征信息量不足而不是急着调参。4.2 C、epsilon、gamma三个容易看晕却决定成败的参数RBF 核的 SVR 有三个关键参数。C 是误分类惩罚系数C 越大模型越努力拟合训练集容易过拟合C 越小模型越宽容拟合不足。epsilon 是回归误差的管道宽度预测值和真实值差距落在 epsilon 之内就不计误差epsilon 越大模型越佛系预测曲线越平。gamma 控制 RBF 核函数的半径gamma 越大只有离样本点很近的点才互相影响拟合更细碎gamma 越小影响范围越大曲线更平滑。sklearn 里 SVR 的默认 gamma 是scale模式会按特征数量自动放缩初始阶段可以先用这个默认值网格搜索时再把scale放进候选组一起比较。4.3 用 TimeSeriesSplit 网格搜索完成训练训练环节我会把 StandardScaler 和 SVR 包在一个 Pipeline 里避免预处理步骤和模型分开造成的数据泄漏。搜索参数时用TimeSeriesSplit代替默认 KFold保证每一次交叉验证里训练集都早于验证集。from sklearn.pipeline import Pipeline from sklearn.svm import SVR from sklearn.model_selection import GridSearchCV, TimeSeriesSplit X_train train_df[feature_cols].copy() y_train train_df[box_office].copy() X_test test_df[feature_cols].copy() y_test test_df[box_office].copy() pipeline Pipeline([ (scaler, StandardScaler()), (svr, SVR()) ]) param_grid { svr__C: [0.1, 1, 10, 100], svr__epsilon: [0.01, 0.1, 0.5], svr__gamma: [0.001, 0.01, 0.1, scale] } tscv TimeSeriesSplit(n_splits3) search GridSearchCV( pipeline, param_grid, cvtscv, scoringneg_mean_absolute_error, return_train_scoreTrue ) search.fit(X_train, y_train) print(best params:, search.best_params_) print(best negative_mae:, search.best_score_)GridSearchCV的 scoring 用neg_mean_absolute_errorsklearn 的约定是“负的越小越好”所以打印出来的best_score_是负数判断好坏要看绝对值。param_grid 里每个键都带svr__前缀这是 Pipeline 的命名规则步骤名后面跟两个下划线再接参数名。C 和 gamma 的搜索范围按数量级递增epsilon 一般集中在 0.01 到 0.5 之间范围太大没意义太大会让整段预测变成一条直线。注意网格搜索只看验证集分数不够还要在独立测试集上算一次误差两个分数的差值能反映过拟合程度。模型训练完不能在交叉验证里就打住要在没参与调参的测试集上做最终评估用 MAE 单位是万元业务上能直接知道预测误差大概是多少。from sklearn.metrics import mean_absolute_error y_pred search.predict(X_test) mae mean_absolute_error(y_test, y_pred) print(f测试集 MAE{mae:.1f} 万元)如果测试集 MAE 明显高于交叉验证得分说明模型过拟合了训练时间段优先降低 C或者删掉与时间绑定过紧的特征再重跑。5. 票房预测避坑指南5 个容易让模型翻车的真实案例5.1 请求被识别返回 403 或拿到验证页现象requests 请求返回状态码 403或者 HTML 内容里找不到任何电影条目。原因请求头缺少浏览器特征或请求频率过高服务器返回反爬验证页面。解决先补全 User-Agent、Referer、Accept 等请求头字段再把请求间隔从固定 1 秒改成随机 0.5 到 1.5 秒。仍然被拒时把一次响应的 HTML 保存到本地在编辑器里看返回的到底是什么页面再决定是调整请求头还是降低频率不要盲目重试。5.2 预测值全是同一个常数现象训练完成后测试集里所有影片的预测票房都等于同一个数预测曲线是一条水平直线。原因特征尺度差异太大票房列数值上万评分列只有 0 到 10SVR 的核距离计算被大数值列主导模型学不到有效区分度。解决对所有数值特征做 StandardScaler检查特征方差是否出现极端值。标准化之后仍然收敛到常数就把 C 调小、epsilon 调大看训练集和测试集的误差变化判断是特征问题还是参数问题。5.3 数据泄漏把未发生的数据当成特征现象模型在测试集上 MAE 很低但换到真正要预测的新片时误差翻了 3 倍。原因特征里混入了预测时点之后才产生的数据。最常见的是用“影片上映第 7 天的评论数”预测“首周票房”或者主创影响力分是用包含目标影片在内的全量数据统计出来的。解决按时间窗口给特征打标记严格区分“预测日之前可见”和“预测日之后可见”。主创影响力、评分均值这类统计特征只允许使用训练集里早于预测时间的数据计算。5.4 网格搜索的分数和真实表现背离太远现象GridSearchCV 的best_score_看起来很好放到最新上映的 20 部影片上验证时误差明显偏大。原因默认的 KFold 交叉验证会随机打乱样本时间序列被拆乱后训练集里的影片可能比验证集还晚模型等于提前看到了未来分布分数虚高。解决换用TimeSeriesSplit或者把样本按上映日期排序后手动用前 80% 训练、后 20% 验证让评估流程接近真实的上线时间线。5.5 缺失值 dropna 把样本量削掉一半现象数据清洗时直接dropna原本 800 部影片只剩 400 部SVR 没有足够样本训练。原因许多字段的缺失不是随机的比如小成本文艺片往往没有明星主创、没有想看人数。直接删除样本等于把所有低预算影片清除了样本分布被改变模型学到的规律严重偏差。解决数值字段用中位数填充类别字段新增“未知”类别再加一列is_missing标记是否缺失。对缺失严重的列先看缺失率是否超过 60%再决定剔除还是保留不能一刀切。6. 预测完成之后误差拆解与模型迭代的一个实战习惯6.1 用散点图和残差直方图定位系统性偏差算出 MAE 只完成了三成工作剩下的是搞清楚误差从哪来。我会把测试集的真实票房和预测票房画成散点图对角线代表完美预测点明显偏离对角线的区间就是模型失灵的区域再画一张残差直方图看误差是否近似正态分布。残差整体偏正或偏负说明存在系统性偏差不是随机噪声多半是档期或类型特征没构造到位。import matplotlib.pyplot as plt fig, axes plt.subplots(1, 2, figsize(12, 4)) axes[0].scatter(y_test, y_pred, alpha0.5, s30) axes[0].plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], r--, lw1) axes[0].set_xlabel(actual box office (万元)) axes[0].set_ylabel(predicted box office (万元)) residual y_test - y_pred axes[1].hist(residual, bins30) axes[1].set_xlabel(residual (万元)) axes[1].set_title(residual distribution) # 画时间序列预测对比图时日期太多会挤成一团隔 5 个点标一个 fig2, ax2 plt.subplots(figsize(12, 4)) ax2.plot(range(len(y_test)), y_test.to_numpy(), labelactual) ax2.plot(range(len(y_test)), y_pred, labelpredicted, alpha0.7) ticks range(0, len(y_test), 5) ax2.set_xticks(ticks) plt.show()横坐标把所有影片日期逐个排列时几十个标签堆在一起就是一团黑这是 matplotlib 最常见的观感问题之一解决办法是只挑一部分下标设置刻度。散点图如果整体偏在对角线下方说明系统性地低估票房优先检查特征里是不是漏掉了档期残差直方图如果是双峰分布大概率样本里混着商业大片和小众文艺片两种规律差异很大的群体可以考虑按类型拆成两个模型分别预测。6.2 先跑线性回归基线再上 SVR判断问题出在特征还是模型我自己的一个习惯是每轮迭代先跑线性回归再跑 SVR记录两张误差表做对比。SVR 的 MAE 只比线性回归低不到 10%说明特征信息量不够线性模型已经把能学的东西学完了此时调 SVR 参数没有意义回去加特征、修缺失值才是正路SVR 明显优于线性回归时说明非线性关系确实存在再在 C 和 gamma 附近做细粒度搜索。验证特征是否有效可以算特征与票房之间的相关矩阵筛掉与票房几乎无关的列这个动作对降低特征噪音很有帮助。另外模型文件最好带上日期、特征版本号和 MAE 值后再存档。有一次我调了两天参数没提升回查记录发现换了一版特征构造函数之后参数组合全变了不保存当时的环境根本没法复现。所以别嫌麻烦每次训练完都留一条可追溯的记录这个习惯能帮你在做特征迭代时少走一次回头路。希望这套流程对你跑通自己的票房预测系统有帮助。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询