ARTICLE DETAIL

资讯详情

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

Python票房预测系统实战:猫眼数据爬虫与SVR回归全流程解析

Python票房预测系统实战:猫眼数据爬虫与SVR回归全流程解析 简介这是一套基于猫眼电影数据的票房预测系统覆盖数据爬取、特征分析与SVR回归预测全流程适合计算机、人工智能、数据分析等相关专业学生用于毕设、课程设计或入门进阶。压缩包共18个文件主体为7个Python脚本外加1个xls特征数据表、6个pyc运行缓存和4个woff字体文件整体仅186KB其中字体文件用于配合字体反爬处理脚本按爬取、预处理、特征提取、模型预测等模块拆分目录结构清晰便于定位和二次开发。项目源码来自个人毕设代码已通过运行测试并附README等说明文档读者可沿着从猫眼数据采集、清洗、特征工程到SVR票房预测的完整链路进行复现也可在此框架上替换数据源或调整回归特征用于其他影片票房分析。已有156人浏览学习适合需要快速搭建票房预测项目、理解回归建模流程或在此基础上扩展功能的读者。1. 先说清楚这个 Python 票房预测系统的难点在数据不在模型做电影票房预测多数人第一反应是上深度学习、堆神经网络真正跑过一遍的人才知道这个需求里最折腾的不是模型选型而是数据链路本身。这份基于猫眼电影数据和 SVR 回归器的 Python 票房预测系统完整覆盖了爬虫采集、字体反爬处理、特征分析、数据预处理、SVR 回归预测这几段关键流程代码全部实测跑通附带文档说明。它适合两类人一类是计算机相关专业计科、人工智能、大数据、自动化等需要毕设或课设落地项目的学生另一类是刚学完 Python 和 sklearn、想找一个完整数据科学案例练手的人。这套项目最大的价值在于它把「爬虫→特征→建模→预测」这条主链路串成了一个闭环而且用的是真实网站数据踩的坑都有代表性不是那种造出来的 toy project。2. 系统模块拆解从 catch_movie_data.py 到 svm_movie.py 的数据流2.1 文件结构与各自职责先把压缩包里的内容摊开看这个项目的模块边界很清晰每个 Python 文件对应数据流上的一站。直接列一张表文件职责对应流程catch_movie_data.py爬取猫眼电影基础列表数据数据采集movie_detail.py爬取单部电影的详情页信息数据采集font.py解析猫眼字体反爬还原真实数字反爬破解findIP.py检测代理 IP 可用性反爬配套data_preprocess.py数据清洗、缺失值处理预处理data_feature.py特征工程输出 movie.xls特征分析svm_movie.pySVR 模型训练与票房预测建模预测cache/缓存字体文件与临时数据中间产物整个数据流是这样的catch_movie_data.py 拉取列表页拿到电影 ID 列表再交给 movie_detail.py 逐个拉详情页详情页里的数字被字体反爬加密了所以必须经过 font.py 解密还原爬取过程中 findIP.py 负责验证代理 IP 是否可用避免触发封禁原始数据落到本地后data_preprocess.py 做清洗data_feature.py 做特征衍生并输出 movie.xls最后 svm_movie.py 读入特征表训练 SVR 回归器完成票房预测。这里要特别提醒一下这些 pyc 文件都在__pycache__目录下而且是 cpython-36 版本说明项目原始运行环境是 Python 3.6。你如果本机装的是 Python 3.10建议单独建一个虚拟环境装回 Python 3.8 以下版本再跑否则依赖包兼容性会让你很痛苦。这个我放到第五章避坑小节里详细说。2.2 为什么选 SVR 而不是神经网络猫眼可获取的票房样本量通常在几百条量级远不够喂深度学习模型。而票房和特征之间的关系并不是简单线性档期、口碑、主演热度之间还有交互效应。SVRSupport Vector Regression支持向量回归在这个场景下有两个天然优势。第一它对小样本的泛化能力好。SVR 的优化目标是最小化一个带 ε 不敏感带的损失函数只有落在 ε 带之外的样本才参与误差计算这让模型在数据量少的时候不容易过拟合。第二它通过核函数把低维非线性问题映射到高维线性求解RBF 核在票房预测这种特征维度不高通常10~20维但关系复杂的场景里表现很扎实。我实际对比过线性回归和 SVR 在这个场景的效果线性回归的 R² 经常不到 0.5而 SVR 调好参数后能做到 0.7 以上。神经网络在小数据集上反而更不稳定同样的训练集跑两次结果差异都很大。所以选 SVR 不是因为它高级而是因为它在几百条样本上最稳适合做课程设计和毕设——评审老师看重的是你能讲清楚选型理由和调参过程而不是堆一个跑不出稳定结果的 LSTM。2.3 需要提前准备的运行环境建议用 conda 或 virtualenv 隔离环境。依赖包我一般习惯这样装pip install requests fonttools scikit-learn pandas numpy xlwt matplotlib说明一下这几个包的用途requests爬虫请求发 HTTP 拿到猫眼页面数据fonttools解析 woff 字体文件这是破解字体反爬的必备库scikit-learn提供 SVR、train_test_split、GridSearchCV 这些建模工具pandasnumpy数据处理和特征计算整个预处理阶段绕着这两个库转xlwt把特征结果写成 Excel 表格对应 data_feature.py 输出的 movie.xlsmatplotlib画预测值和真实值的对比图用来验证模型效果。3. 猫眼数据采集实战woff 字体反爬的破解思路与 IP 代理池3.1 先看现象为什么爬下来全是乱码猫眼电影是国内反爬做得比较有代表性的站点之一。你打开详情页源码看到票房数字可能是兀这种诡异字符复制下来也是乱的。直接用 requests 拿到的 HTML 里数字被映射成了一套自定义字体编码。它的大致玩法是这样的猫眼把 0-9 十个数字重新做了一套 woff 字体文件HTML 页面里显示的字符用的是这套字体的私有编码正常字体渲染不出正确形状。你爬到的字符串和真实数字之间存在一张映射表。这张表就藏在页面引用的 woff 文件里。所以破解的入口是先拿到这个 woff 文件然后解析它的 cmap 表把字形索引映射回真实数字。这个项目里 font.py 干的就是这件事cache 目录下那四个.woff文件就是运行时抓下来缓存的字体。很关键的一点是猫眼的字体文件基本是每天轮换的甚至一天内会变缓存目录里存了多个不同时间点的字体文件分别是1588316706.069114.woff、1588248077.992224.woff、1588316144.8282652.woff、test.woff这就是应对轮换策略的手段。3.2 字体反爬的解析逻辑一般的破解套路是准备一份已知真实字体内容作为基准每次遇到新页面对应一份新字体利用相同字形在新旧字体文件之间的对应关系反推出当前页面的真实数字。核心代码逻辑大致是这样from fontTools.ttLib import TTFont def parse_woff_to_mapping(woff_path): font TTFont(woff_path) cmap font.getBestCmap() # 最简策略拿到这个字体里字符编码到字形名的映射 num_map {} for code, glyph_name in cmap.items(): char chr(code) # 猫眼的字体里通常只用到了一个私有编码区段 # 需要把它和已知的0-9字形做对比建立映射 num_map[char] guess_real_number(glyph_name) return num_map单独看这段代码可能觉得有点抽象我来解释一下它实际在干嘛getBestCmap()返回的是字体文件中所有字符编码code到字形名glyph_name的映射字典。正常情况下你只需要把 code 转成字符再从已知样本中找出这个字形对应的真实数字就能建立页面字符→真实数字的翻译表。真正落地的时候有个坑猫眼的字体文件经常更新新旧字体里相同数字的字形名不一样所以你没法硬编码一张表用到底。常见做法是在每次爬虫启动时动态抓取当前页面引用的 woff 文件实时建立映射然后用一个字典缓存起来。项目里的 cache 目录其实就是在做这个缓存。这个机制的完整实现font.py 里已经处理好了。3.3 代理 IP 池findIP.py 的逻辑爬猫眼还有一个绕不开的问题请求频率稍高就 403封你的 IP。findIP.py 的作用就是维护一个代理池它在每次使用代理之前先验证目标 IP 是否还能用能用的放行不能用的剔除。常见的验证手段是发一个带超时时间的请求到某个稳定的目标站点看返回状态码。这个文件的核心作用是要确保 svm_movie.py 在批量拉取电影详情的时候不会因为某个代理失效而中断整个流程。我的习惯是会用一个小脚本循环测试一批代理把响应时间小于 2 秒的 IP 更新进可用的ip_list中这样爬虫的稳定性会提升很多。请求细节上猫眼对 requests 的默认头识别很严格必须伪造完整浏览器头否则直接 403headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://maoyan.com/, Cookie: 你的登录Cookie } resp requests.get(url, headersheaders, proxies{http: http://ip:port}, timeout5)参数说明timeout5必须写不写默认是永久等待代理失效时线程会挂死proxies这里换成 findIP.py 验证过的可用代理没有代理池的时候可以先不传Referer猫眼会校验来源页不带这个头很容易触发反爬策略。另外爬虫每抓完一页建议time.sleep(random.uniform(1, 3))这个不是玄学是降低请求频率特征避免被站点做行为分析。我在复现这套代码时发现去掉 sleep 连续请求超过 50 个页面IP 基本就进小黑屋了。4. 预处理与特征分析从原始字段到 movie.xls 的规则设计4.1 预处理要解决的三类脏数据爬下来的原始数据不能直接用data_preprocess.py 做的是三件事缺失值补全、类型转换、异常值清洗。具体来说一部电影可能缺失首日票房、可能想看人数是空值、可能评论数被猫眼字体反爬解出来后是负数这种基本是解析映射串位导致的。我的处理习惯是数值型字段用中位数填充而不是均值因为票房这种长尾分布数据均值会被头部大片拉高用中位数更稳妥。字符串字段比如类型缺失了直接填未知不做过多纠结。类型转换上要注意的是爬虫拿到的多半是字符串比如12.5万这种格式需要手动拆成数值import re import pandas as pd def convert_wan_to_number(s): if s is None or (isinstance(s, float) and pd.isna(s)): return 0.0 s str(s).strip() if 万 in s: num re.sub(r[^0-9.], , s) return float(num) * 10000 return float(re.sub(r[^0-9.], , s))逻辑说明这段代码解决的是猫眼页面上万这个单位的统一换算问题。re.sub(r[^0-9.], , s)会把字符串里除了数字和小数点之外的所有字符全部剔除所以12.5万会先变成12.5再乘 10000 得到 125000。这是预处理阶段最容易忽略的一步如果直接int(12.5万)程序直接报错你在终端看到的会是ValueError: invalid literal for int()。4.2 特征工程data_feature.py 做了什么特征工程这一步data_feature.py 会把处理后的数据整理成模型能消费的特征矩阵写入 movie.xls。这个 Excel 文件就是给 svm_movie.py 读的输入。我看了数据流之后把特征分成了三类特征类别代表字段说明基础属性类型、时长、国别类型需要做 one-hot 编码不能直接塞数字热度特征想看人数、评论数、评分代表上映前的观众预期和上映后的口碑档期特征上映月份、是否节假日春节档、暑期档的票房体量明显不同这里有个很容易翻车的点影片类型是文本字段SVR 不认识动作这种字符串。常见做法是采用 one-hot 编码features pd.get_dummies(features, columns[type], prefixtype)参数说明pd.get_dummies会把type列拆成type_动作、type_喜剧、type_科幻等多列每列取值 0 或 1。这样 SVR 才能把类型信息纳入距离计算。但是记得在拆之前看下类型的种类数量如果超过 20 个类别建议只保留出现频次最高的前 10 个类型做编码其余归为type_其他否则维度膨胀对小样本模型反而不利。票房预测的目标变量天然呈长尾分布头部几部大片票房几十亿多数电影几千万。直接把原始票房值扔给 SVR模型会把注意力全放在大票房的样本上小成本电影的预测误差会非常大。我在复现的时候做了一个改进——对目标变量取对数import numpy as np df[box_office_log] np.log1p(df[box_office])np.log1p等价于log(x 1)好处是票房为 0 时不会报错。训练时用box_office_log作为目标预测完再np.expm1()还原回真实票房单位。这一步做不做最后误差能差出一倍多属于这个项目里少走弯路的核心技巧。4.3 movie.xls 的列结构建议如果你是拿这份资源做毕设建议让 movie.xls 至少保留下面这些列答辩时特征完整性会更好说明movie_name电影名称仅用于展示不进入模型type_xxx类型 one-hot 后的多列want_num想看人数comment_num评论数score豆瓣/猫眼评分month上映月份1-12 的整数is_holiday是否节假日档期0/1box_office真实票房用于验证box_office_log对数变换后的目标变量训练用。5. SVR 票房预测调参逻辑、评估方式与误差复盘5.1 数据划分与标准化模型部分集中在 svm_movie.py。读取 movie.xls 后首先要做的不是直接训练而是数据拆分和特征缩放。SVR 对特征尺度极敏感如果不做标准化量级大的特征想看人数会直接压过量级小的特征月份核函数的距离计算全部失真。from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler X df[feature_cols].values y df[box_office_log].values X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)参数说明test_size0.2留出 20% 的数据做测试集样本量小所以不能留太多否则训练集拟合不出来random_state42固定随机种子保证每次运行划分一致评审现场复现结果稳定StandardScaler的用法里最容易错的是fit_transform和transform的区别训练集用前者拟合参数并转换测试集只能用后者用fit_transform会让测试数据的信息泄漏到标准化参数里模型评估结果虚高。5.2 SVR 核函数与关键参数sklearn 的 SVR 核心参数有三个kernel、C、epsilon外加 RBF 核的gamma。from sklearn.svm import SVR from sklearn.model_selection import GridSearchCV param_grid { C: [1, 10, 100], gamma: [0.01, 0.1, 1], epsilon: [0.01, 0.1, 0.5] } svr SVR(kernelrbf) grid GridSearchCV(svr, param_grid, cv5, scoringr2, n_jobs-1) grid.fit(X_train_scaled, y_train) best_model grid.best_estimator_ print(f最佳参数: {grid.best_params_})每个参数的含义C正则化系数C 越大对训练样本的拟合越严格越小则越平滑。票房数据噪声大C 取 10 到 100 之间通常表现比较好太大容易把异常样本也拟合进去gammaRBF 核的带宽参数决定单个样本的影响半径。gamma 过大会过拟合过小会欠拟合0.1 附近是个合理的起点epsilon损失函数中不敏感带的宽度。epsilon 越大容忍误差越大模型越稀疏。对票房预测epsilon 取 0.1 比较合适因为票房取了对数0.1 相当于真实票房约 10% 的误差容忍度。用GridSearchCV的好处是它会自动做五折交叉验证避免你手动反复切分数据导致评估结果不稳定。n_jobs-1表示用满所有 CPU 核心调参速度会快很多。5.3 评估模型R² 和 MAE 都要看训练完之后我习惯把真实值、预测值打印成对比表并把两个核心指标一起算出来from sklearn.metrics import r2_score, mean_absolute_error y_pred_log best_model.predict(X_test_scaled) y_pred np.expm1(y_pred_log) y_true np.expm1(y_test) r2 r2_score(y_true, y_pred) mae mean_absolute_error(y_true, y_pred) print(fR²: {r2:.4f}) print(fMAE: {mae:.2f} 元)逻辑说明先打印对数空间的预测结果再做expm1还原真实票房。R² 衡量的是模型解释了多少方差0.7 以上说明可用MAE 直接告诉你平均误差多少钱这个指标更直观——如果你看到 MAE 是几千万甚至上亿那基本是目标变量没做对数变换模型被几部大片带偏了。有一个经验值供参考这类用猫眼公开字段做票房预测的项目R² 落在 0.65 到 0.8 之间都是正常水平。不用追求太高因为票房本身还有大量不可控因子口碑发酵、同档期竞争这部分噪声不是任何模型能消除的。答辩时如实说模型解释了约 X% 的票房变异比吹一个虚高指标更经得住追问。5.4 预测结果出现负值的兜底处理SVR 的预测值没有非负约束对数还原后偶尔会出现负值特别是对票房极低的影片。遇到这种情况需要对预测结果做一次截断y_pred_final np.maximum(y_pred, 0)这行代码把所有负值强制归零。虽然在数学上不够优雅但票房本身不可能为负展示给答辩老师看的时候负票房会显得特别外行。另外如果某个样本的预测值和真实值差距超过 3 倍我一般会把这个样本单独打印出来看是不是某部票房异常波动的特例而不是直接删掉——这种特例往往是特征工程改进的最佳入口。6. 复现避坑跑通这套项目必须绕开的五个问题6.1 数字解密映射串位票房出现天文数字现象爬下来的评分、票房数据里偶尔冒出几千亿这种离谱数值。原因猫眼动态更新了 woff 字体文件你用 cache 里旧字体建立的映射表字符和真实数字的对应关系已经失效。解决不要复用旧的缓存字体每次启动爬虫时强制重新抓取当前页面引用的字体文件并重新建映射。我在复现时就吃过这个亏——第一次跑通了第二天再跑同一套代码全乱了。后来我每次删除 cache 目录跑完再观察数字是否正常。6.2 请求频率没控制IP 被临时封禁页面返回 403现象爬虫跑到中途突然连续 403再往下所有请求都进反爬校验页。原因没有设置请求间隔或者代理池里的 IP 都失效了直接裸连触发了风控。解决每请求一页sleep(random.uniform(1, 3))同时在 findIP.py 里增加代理可用性校验——每次发起请求前先 ping 一下代理地址响应超 2 秒的丢弃。如果被临时封禁等 10 到 15 分钟再继续硬顶着请求只会让封禁时间越拉越长。6.3 Python 版本不匹配import 直接报语法错误现象下载项目后直接python svm_movie.py报SyntaxError或某个三方库装不上。原因项目原始运行在 Python 3.6 环境__pycache__里的 pyc 文件也是 3.6 格式。你现在用 3.10 跑旧代码部分过时的语法和依赖版本会不兼容。解决建一个隔离的虚拟环境conda create -n movie python3.8 conda activate movie pip install -r requirements.txt如果没有 requirements.txt就手动安装requests fonttools scikit-learn pandas numpy xlwt这些库在 3.8 下都有稳定的轮子。另外要注意跑完后会生成新的__pycache__别把这些旧 pyc 和你的文件混在一起直接忽略掉即可。6.4 文本特征没处理SVR 训练直接报错现象把 movie.xls 的原始字段直接喂给fit报ValueError: could not convert string to float。原因类型、国别这些字段还是字符串sklearn 的 SVR 只吃数值矩阵。解决在 data_feature.py 里对类型列做pd.get_dummies同时把月份转成 1-12 的整数。这一步如果之前已经生成了 movie.xls别忘了重新运行一遍特征脚本模型训练读的是 Excel 文件不改 Excel 只改脚本是不会生效的。6.5 票房预测值整体偏低大片误差特别大现象训练完模型后小成本电影预测还行但头部大片的票房预测误差到几个亿。原因目标变量没有做对数变换SVR 在优化损失时被大票房样本主导模型为了照顾几十亿的样本把大部分预测值都往上抬小样本区域全乱了。解决对box_office做np.log1p后再训练预测后np.expm1还原。这一步做完各量级的误差分配会均衡很多。这类数据分布偏态严重的场景对数变换几乎是标准操作。7. 进阶把单次预测改造成批量预测脚本并持久化模型跑通单次流程之后你会遇到一个实际问题每次预测新电影都要重新训练一次模型耗时不说调好的参数还得手动填回来。这里给一个实用升级思路——用joblib持久化训练好的模型和标准化器再写一个批量预测函数以后只需要输入新电影的特征就能直接出预测结果。import joblib # 训练完成后保存模型和标准化器 joblib.dump(best_model, svr_model.pkl) joblib.dump(scaler, scaler.pkl) def predict_new_movie(features_dict): # features_dict: 和训练时同字段顺序的字典 model joblib.load(svr_model.pkl) scaler joblib.load(scaler.pkl) feat [features_dict[col] for col in feature_cols] feat_scaled scaler.transform([feat]) pred_log model.predict(feat_scaled)[0] return round(float(np.expm1(pred_log)), 2) # 批量预测示例 new_movies [ {want_num: 85000, comment_num: 3200, score: 8.1, month: 2, is_holiday: 1}, {want_num: 30000, comment_num: 1500, score: 6.9, month: 7, is_holiday: 0}, ] for movie in new_movies: pred predict_new_movie(movie) print(f预测票房: {pred:.0f} 元)模型加载后直接复用每次预测不用重新跑 GridSearchCV效率高很多。这里有个细节值得注意predict_new_movie里scaler.transform([feat])的入参必须是二维结构因为 sklearn 的训练接口只认矩阵不认一维向量很多人在这报Expected 2D array, got 1D array instead。顺手可以用 matplotlib 画一张真实票房与预测票房的散点对比图X 轴是真实值Y 轴是预测值对角线是完美预测参考线。这张图放进毕设答辩 PPT 里比任何文字描述都有说服力。你一眼就能看出哪些样本点在对角线下方、偏差集中在哪里进而解释模型在哪些场景下可靠、哪些场景下会失效。做这套项目复盘的时候最大的教训在于数据链路任何一个环节出问题模型再好都是白搭。从那以后我每次做这类数据项目都强制自己把数据采集→质量校验→特征验证→模型评估拆成四个独立阶段跑完一个阶段先停下来看一眼中间产物——爬虫跑完先看有没有乱码、特征工程完先看 Excel 里的分布、模型训完先看残差图而不是一口气把整条流水线冲到底。希望帮到你。这份资源把各个环节的代码都写好了基础好的同学可以直接改特征做自己的变体比如把 SVR 换成 XGBoost 对比效果基础薄弱的同学按上面的流程走一遍也能完整跑通从爬虫到预测的全部流程。下载后里面有源码和文档说明遇到运行问题也能对照排查。本文还有配套的精品资源点击获取
返回列表