ARTICLE DETAIL

资讯详情

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

NBA比赛数据抓取与机器学习预测:Python课程设计实战指南

NBA比赛数据抓取与机器学习预测:Python课程设计实战指南 简介这份资源面向计算机相关专业学生与Python初学者提供一套可直接参考的NBA比赛结果预测课程设计完整方案解决从数据采集到模型预测的全流程实现问题。包内共11个文件以6个CSV数据文件、4个Python脚本和1份docx说明文档为主压缩包约311KBCSV涵盖球队赛季统计、赛程与历史比赛结果等原始数据Python脚本分别负责爬虫抓取与机器学习建模预测文档则说明项目结构与使用方式。目前已有1462人学习下载说明该方案在同类课设中具有较高参考价值。读者可据此获得一套已获学期优秀项目评选的完整代码与数据资料直接用于课程设计答辩或大作业提交同时理解爬虫采集、特征处理与结果预测的衔接思路并借助现成数据快速复现实验、对照调试节省从零搜集数据与搭建框架的时间。1. 从一份课程设计说起NBA 比赛数据抓取与机器学习预测到底怎么做每年一到期末计算机相关专业的课程设计就会集中爆发其中「获取 NBA 比赛数据并进行机器学习智能预测」是出现频率极高的选题。原因很直接数据公开、业务好懂、模型可解释、答辩时老师一听就明白你在干什么。但真正动手的人会发现卡点根本不在模型而在数据从哪来、字段怎么对齐、特征怎么构造、预测结果为什么总是接近五五开。这篇笔记面向正在做 Python 课程设计大作业、或者想用 python 机器学习入门实战练手的同学。我会按一条真实可复现的路径讲先解决数据获取再讲特征工程和模型选型然后落到代码和参数最后把我在这个方向上踩过的坑摊开说。整套流程用到的库都是 python 安装 sklearn 库、pandas、requests 这类常规依赖vscode python 环境配置或 pycharm 配置 python 环境都能跑。读完你应该能自己搭出一条从原始比赛记录到胜负预测的完整链路而不是停在「调了个模型准确率 0.5」的阶段。需要先建立一个认知NBA 胜负预测本质上是一个二分类问题但它的信噪比很低。常规赛 82 场主客场、背靠背、伤病、轮休都会让单场结果带很大随机性。所以课程设计里追求的不是 90% 准确率而是把一套完整的数据处理和机器学习应用流程跑通并能解释清楚每个环节为什么这么做。这才是老师真正想看的东西。2. 数据获取从公开数据源拿到可用的比赛记录2.1 为什么数据源选择决定了后面一半的工作量做 NBA 数据获取常见做法有三类一是调用公开的统计接口二是抓取体育数据网站的页面三是用现成的 CSV 数据集。课程设计里我一般推荐前两种结合因为老师会看你有没有真实的「获取」动作直接下载一个 CSV 交上去工作量说服力不够。公开统计接口的好处是返回结构化 JSON字段干净省去大量解析工作。缺点是部分接口有访问频率限制连续请求会被限流。抓取网页的好处是数据全缺点是页面结构会变今天能跑的解析逻辑下个月可能就翻车。所以稳妥的策略是优先用接口拿核心比赛数据用网页抓取补充球队和球员的辅助信息并且把抓下来的原始数据先落盘存成 CSV后续所有处理都基于本地文件避免每次跑代码都重新请求。这里有个血泪经验一定要做本地缓存。我见过太多同学调试模型时反复请求数据源结果 IP 被临时限制答辩前一天数据拉不下来只能干瞪眼。落盘之后数据处理和模型训练就完全离线了稳定得多。2.2 用 requests 拉取赛季比赛数据的完整代码下面这段代码演示从公开接口按赛季拉取比赛记录并保存为 CSV 的最小实现。接口地址和字段名以你实际使用的数据源为准这里给出的是通用结构。import requests import pandas as pd import time import os # 请求头模拟正常浏览器访问避免被直接拒绝 HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 } def fetch_season_games(season: str, save_dir: str data) - pd.DataFrame: 按赛季拉取比赛数据 season: 例如 2023-24 os.makedirs(save_dir, exist_okTrue) cache_file os.path.join(save_dir, fgames_{season}.csv) # 本地已有缓存直接读取避免重复请求 if os.path.exists(cache_file): print(f命中缓存: {cache_file}) return pd.read_csv(cache_file) # 这里替换成你实际使用的数据接口 url https://example-sports-api.com/games params {season: season, league: NBA} all_records [] page 1 while True: params[page] page resp requests.get(url, headersHEADERS, paramsparams, timeout10) if resp.status_code ! 200: print(f请求失败状态码 {resp.status_code}第 {page} 页) break data resp.json() records data.get(games, []) if not records: break all_records.extend(records) page 1 time.sleep(1.5) # 控制频率别把人家接口打挂 df pd.DataFrame(all_records) df.to_csv(cache_file, indexFalse, encodingutf-8-sig) print(f共获取 {len(df)} 条比赛记录已保存到 {cache_file}) return df if __name__ __main__: df fetch_season_games(2023-24) print(df.head())逻辑说明函数先检查本地缓存有就直接读没有才发请求。分页循环里每请求一页 sleep 1.5 秒这是控制访问频率的关键参数设太小容易触发限流设太大拉一个赛季的数据会很慢1 到 2 秒是比较平衡的值。timeout 设 10 秒避免网络卡住时程序一直挂着。保存时用 utf-8-sig 编码这样用 Excel 打开不会中文乱码答辩演示时省事。参数说明season 决定拉哪个赛季课程设计建议至少拉 3 个赛季样本量才够模型学。save_dir 是缓存目录建议单独建一个 data 文件夹和代码分开。如果你用的接口需要 API Key把它放到环境变量里别硬编码在代码里提交这是基本习惯。2.3 抓取网页数据时的解析要点如果接口拿不到你想要的字段就得抓网页。用 requests 拿到 HTML 后用 BeautifulSoup 或 lxml 解析表格。核心是定位到表格元素逐行提取单元格文本再转成结构化数据。from bs4 import BeautifulSoup import requests import pandas as pd def parse_standings(url: str) - pd.DataFrame: resp requests.get(url, headersHEADERS, timeout10) resp.encoding resp.apparent_encoding # 防止中文乱码 soup BeautifulSoup(resp.text, lxml) table soup.find(table, class_standings-table) if table is None: raise ValueError(未找到目标表格页面结构可能已变化) rows [] for tr in table.find_all(tr)[1:]: # 跳过表头 cells [td.get_text(stripTrue) for td in tr.find_all(td)] if cells: rows.append(cells) columns [team, wins, losses, win_rate, points_for, points_against] df pd.DataFrame(rows, columnscolumns[:len(rows[0])]) return df逻辑说明resp.apparent_encoding 会自动推断编码比手动指定 gbk 或 utf-8 更省心。find 表格时用 class 定位如果页面改版导致找不到会抛异常而不是静默返回空数据这样你能第一时间发现解析失效。跳过第一行是因为那是表头单独定义列名更可控。参数说明columns 列表要和实际表格列数对齐列数不匹配时用切片兜底。抓取频率同样要控制网页抓取比接口更容易被封建议每次请求间隔 2 秒以上并且只抓必要的页面。3. 特征工程把原始比赛记录变成模型能吃的输入3.1 哪些特征真正影响胜负拿到原始数据后直接扔给模型是不行的。原始记录里通常有比赛日期、主客队、比分这些要转成模型能理解的数值特征。我一般会构造这几类第一类是球队近期状态比如最近 5 场、10 场的胜率这是强特征。第二类是主客场因素NBA 主场胜率长期在 55% 到 60% 之间这个信号必须保留。第三类是休息天数背靠背比赛对球队影响明显用比赛间隔天数表示。第四类是历史交手记录两队本赛季之前的交锋结果。第五类是进攻防守效率用场均得分、场均失分、净胜分来刻画。这里要提醒一个常见误区不要用比赛当天的比分去构造特征那叫数据泄露。比如你想预测 A 队对 B 队的胜负就不能把这场比赛的得分放进特征里。所有特征必须只用这场比赛之前的信息计算。这个坑非常隐蔽很多同学模型准确率虚高到 0.9一查就是泄露了。3.2 构造滚动胜率和休息天数的代码实现import pandas as pd def build_features(df: pd.DataFrame) - pd.DataFrame: 输入: 含 date, home_team, away_team, home_score, away_score 的比赛记录 输出: 带特征和标签的数据集 df df.copy() df[date] pd.to_datetime(df[date]) df df.sort_values(date).reset_index(dropTrue) # 标签: 主队是否获胜 df[home_win] (df[home_score] df[away_score]).astype(int) # 记录每支球队上一场比赛日期用于算休息天数 last_game_date {} home_rest, away_rest [], [] for _, row in df.iterrows(): h, a, d row[home_team], row[away_team], row[date] home_rest.append((d - last_game_date[h]).days if h in last_game_date else 3) away_rest.append((d - last_game_date[a]).days if a in last_game_date else 3) last_game_date[h] d last_game_date[a] d df[home_rest] home_rest df[away_rest] away_rest # 滚动胜率: 每支球队最近 10 场的胜率 team_history {} home_winrate, away_winrate [], [] for _, row in df.iterrows(): h, a row[home_team], row[away_team] home_winrate.append(_recent_winrate(team_history.get(h, []), 10)) away_winrate.append(_recent_winrate(team_history.get(a, []), 10)) # 更新历史注意用本场结果更新供后续比赛使用 team_history.setdefault(h, []).append(row[home_win]) team_history.setdefault(a, []).append(1 - row[home_win]) df[home_winrate_10] home_winrate df[away_winrate_10] away_winrate df[winrate_diff] df[home_winrate_10] - df[away_winrate_10] df[rest_diff] df[home_rest] - df[away_rest] return df def _recent_winrate(history: list, n: int) - float: recent history[-n:] if not recent: return 0.5 # 无历史时给中性值 return sum(recent) / len(recent)逻辑说明整个函数按时间顺序遍历比赛每场比赛只使用此前累积的历史信息来构造特征这样就不会泄露未来数据。滚动胜率用列表保存每队逐场的胜负序列取最近 n 场求平均。休息天数用字典记录每队上一场比赛日期相减得到间隔。参数说明滚动窗口 n 取 10 是经验值取太小噪声大取太大反应迟钝5 到 15 之间都可以试。无历史时胜率给 0.5、休息天数给 3这是中性填充避免赛季初样本全是缺失值。winrate_diff 和 rest_diff 是差值特征比单独两个特征更能让模型捕捉相对优势。3.3 特征标准化和数据集划分构造完特征后把数值特征和标签分开用 train_test_split 划分训练集和测试集。注意时间序列数据不能随机打乱要按时间切分否则又是变相泄露。from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler feature_cols [home_winrate_10, away_winrate_10, winrate_diff, home_rest, away_rest, rest_diff] X df[feature_cols].values y df[home_win].values # 按时间顺序切分前 80% 训练后 20% 测试 split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:] scaler StandardScaler() X_train scaler.fit_transform(X_train) X_test scaler.transform(X_test) # 注意用训练集的参数逻辑说明StandardScaler 在训练集上 fit在测试集上只 transform这是标准做法。如果对全体数据 fit 再切分测试集的均值方差信息就泄露到训练里了。按时间切分而不是随机切分是因为预测未来比赛才是真实场景。参数说明split 比例 0.8 是常见选择样本少时可以调到 0.7。feature_cols 要和前面构造的列名严格一致多一个少一个都会报错。4. 模型训练与评估从逻辑回归到梯度提升的选型对比4.1 为什么先跑逻辑回归再上复杂模型很多同学一上来就用 XGBoost 或神经网络结果调参调到崩溃还不一定比逻辑回归好。我的建议是先跑逻辑回归作为基线它训练快、可解释、不容易过拟合能让你快速判断特征到底有没有用。如果逻辑回归准确率只有 0.5说明特征有问题换再复杂的模型也救不回来。基线跑通后再试随机森林和梯度提升。这两个在表格数据上表现通常最好而且 sklearn 里都有现成实现不需要额外装库。课程设计里对比三到四个模型画个对比表工作量和技术深度都够了。4.2 多模型训练与对比的完整代码from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier from sklearn.metrics import accuracy_score, roc_auc_score, classification_report models { LogisticRegression: LogisticRegression(max_iter1000, C1.0), RandomForest: RandomForestClassifier(n_estimators200, max_depth6, min_samples_leaf10, random_state42), GradientBoosting: GradientBoostingClassifier(n_estimators150, learning_rate0.05, max_depth3, random_state42), } results [] for name, model in models.items(): model.fit(X_train, y_train) pred model.predict(X_test) prob model.predict_proba(X_test)[:, 1] acc accuracy_score(y_test, pred) auc roc_auc_score(y_test, prob) results.append({model: name, accuracy: round(acc, 4), auc: round(auc, 4)}) print(f\n {name} ) print(classification_report(y_test, pred, target_names[客胜, 主胜])) import pandas as pd print(pd.DataFrame(results))逻辑说明循环训练三个模型统一用准确率和 AUC 评估。AUC 比准确率更能反映模型区分能力尤其当主客胜样本不均衡时。classification_report 输出精确率、召回率、F1答辩时能讲出更多细节。参数说明逻辑回归的 C 是正则强度越小正则越强1.0 是默认起点。随机森林 n_estimators 是树的数量200 棵通常够用max_depth 限制深度防过拟合min_samples_leaf 设 10 让叶子节点至少 10 个样本避免学噪声。梯度提升 learning_rate 设 0.05 配合 150 棵树是慢学习率的稳健组合调大学习率虽然收敛快但容易过拟合。4.3 评估指标怎么看才不被误导准确率 0.6 在 NBA 预测里已经算不错了因为主场优势本身就贡献了约 0.58 的基线。也就是说如果你无脑预测主队全胜准确率就有 0.58 左右。所以你的模型必须明显超过这个基线才有意义。我一般会先算一个「全预测主胜」的基线准确率作为对照。AUC 在 0.6 到 0.65 之间说明模型有一定区分能力超过 0.7 就要警惕是不是数据泄露了。如果 AUC 高得离谱先回去检查特征构造有没有用到未来信息。这个自查习惯能帮你避开答辩时被老师一句话问倒的尴尬。5. 避坑与排查课程设计里最容易翻车的五个地方5.1 现象模型准确率异常高超过 0.85原因几乎可以确定是数据泄露。最常见的是特征里混入了比赛结果相关的字段比如用本场得分算了净胜分或者标准化时对全体数据 fit。解决逐列检查特征确认每个特征都只用比赛之前的信息。把特征构造函数按时间顺序重写确保更新历史发生在特征提取之后。标准化严格在训练集 fit。5.2 现象抓取数据时程序中途报错退出只拿到一部分数据原因网络请求没有异常处理某一次请求超时或返回非 200 就整个崩掉。或者分页逻辑没写终止条件死循环。解决给每个请求包 try except失败时重试最多 3 次仍失败就记录页码跳过。分页循环要有明确的终止条件比如返回空列表或达到最大页数。已经拿到的数据先存盘别等全部拉完才保存。5.3 现象训练时报错「Input contains NaN」或「could not convert string to float」原因原始数据里有缺失值或字符串列没处理干净。比如某场比赛日期缺失或者球队名里混了空格。解决在特征工程前先做一轮数据清洗用 df.isnull().sum() 看每列缺失情况数值列用中位数或 0 填充字符串列 strip 去空格。特征列确保全是数值类型用 df[feature_cols].dtypes 检查。5.4 现象换了赛季数据后模型效果大幅下降原因不同赛季的球队、赛制、数据字段可能有变化特征分布漂移。或者新赛季数据里有些球队是新增的历史胜率全是默认值。解决跨赛季训练时把赛季作为特征之一或者至少保证训练集覆盖多个赛季。新球队的历史用联盟平均值填充而不是 0.5 这种拍脑袋的值。评估时按赛季分别看效果别只看总体。5.5 现象predict_proba 输出的概率全是 0.5 附近原因特征区分度不够或者模型欠拟合。也可能是特征标准化后数值范围被压缩模型学不到东西。解决先看特征和标签的相关性用 df.corr() 检查。如果所有特征相关性都接近 0说明特征构造有问题回去重新设计。模型方面可以适当增加树的数量或深度但优先解决特征问题。6. 进阶技巧用时间序列交叉验证和特征重要性把模型调到位走到这一步你已经有一条能跑的链路了。但课程设计想拿高分还得在验证方法和调优上多走一步。我一般会用时间序列交叉验证替代单次切分因为单次切分的结果波动很大换个随机种子准确率可能差好几个点答辩时被问到「你这个结果稳定吗」就不好回答。时间序列交叉验证的思路是把数据按时间分成 K 折每次用前 k 折训练、第 k1 折验证逐步向前滚动。sklearn 里用 TimeSeriesSplit 就能做。from sklearn.model_selection import TimeSeriesSplit from sklearn.ensemble import GradientBoostingClassifier from sklearn.metrics import accuracy_score import numpy as np tscv TimeSeriesSplit(n_splits5) model GradientBoostingClassifier(n_estimators150, learning_rate0.05, max_depth3, random_state42) scores [] for train_idx, val_idx in tscv.split(X): X_tr, X_val X[train_idx], X[val_idx] y_tr, y_val y[train_idx], y[val_idx] model.fit(X_tr, y_tr) pred model.predict(X_val) scores.append(accuracy_score(y_val, pred)) print(f各折准确率: {[round(s, 4) for s in scores]}) print(f平均准确率: {np.mean(scores):.4f}标准差: {np.std(scores):.4f})逻辑说明TimeSeriesSplit 保证每次验证集都在训练集之后符合真实预测场景。n_splits5 表示切 5 折数据量少时可以降到 3。输出各折分数和标准差标准差小说明模型稳定这比单次的高准确率更有说服力。参数说明n_splits 越大每折验证集越小评估越不稳定5 是常用值。如果数据只有一两个赛季建议用 3 折。另一个进阶点是看特征重要性搞清楚模型到底靠什么做判断。随机森林和梯度提升都有 feature_importances_ 属性。import pandas as pd model.fit(X_train, y_train) importance pd.DataFrame({ feature: feature_cols, importance: model.feature_importances_ }).sort_values(importance, ascendingFalse) print(importance)如果 winrate_diff 重要性最高说明近期状态差异是主要信号这符合直觉答辩时能讲出业务含义。如果某个你没想到的特征排第一就要警惕是不是泄露了。特征重要性既是调优工具也是自查工具。最后说个我自己的习惯每次改完特征或参数都把结果记到一个表格里包括特征组合、模型、参数、准确率、AUC。课程设计报告里把这表格一放老师能清楚看到你的迭代过程比只放一个最终结果有说服力得多。这个方向不难难的是把每个环节做扎实、讲清楚希望帮到你。本文还有配套的精品资源点击获取
返回列表