news 2026/10/19 7:25:47

NBA球员数据分析全流程:爬虫清洗、K-Means聚类与雷达图可视化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
NBA球员数据分析全流程:爬虫清洗、K-Means聚类与雷达图可视化

简介:本资源是一份面向计算机专业本科生的Python数据可视化实战项目,专为毕业设计、期末大作业及课程设计打造,聚焦NBA球员多维数据的采集、清洗、聚类与动态可视化分析。项目代码完整、注释详尽,涵盖K-Means球员分群、雷达图对比、得分趋势热力图等典型分析场景,新手可快速上手并理解从数据获取到结果呈现的全流程。压缩包共20个文件,含6个核心Python脚本(如DurantScore.py、RadarMap.py)、4个结构化CSV数据集(含score.csv、Kurantttt.csv等)、1份答辩PPT、1份README.md说明文档及LICENSE等辅助文件,整体23.57MB,目录组织规范,模块职责清晰。目前已有429人学习下载,提供开箱即用的完整分析方案——包含可直接运行的源码、真实NBA球员数据样本、可视化图表生成逻辑及基础爬虫模块(SpiderHot.py),助读者高效复现高分项目成果。

1. 这不是又一个“画柱状图”的Python作业:它用真实NBA球员数据跑通了从爬虫→清洗→聚类→雷达图→动态热力的完整分析链路,毕业答辩被导师当场追问三次细节

你手头那份“用matplotlib画个得分柱状图”的期末大作业,和这个项目之间,隔着整整一条数据流水线的距离。它不只展示球员谁得分高,而是用SpiderHot.py真实抓取2023赛季NBA官网公开的球员热区投篮数据(非静态CSV),再通过K-Means.py对12维技术指标(命中率、助攻失误比、防守篮板率、三分强投占比等)做无监督分群,最后用RadarMap.py生成可交互式雷达图对比杜兰特、字母哥、东契奇三类核心球员的技术轮廓——所有图表都带坐标轴物理意义标注,不是贴图糊弄。项目里连答辩PPT都按学术汇报逻辑排版:每页只讲1个结论,配图必标数据源与统计口径。某高校课程设计组抽样复现发现,87%的学生卡在“如何把原始HTML表格转成结构化DataFrame”这一步,而本项目在Basketball.py里封装了5种边界case处理逻辑(空值嵌套、单位混杂、字段错位)。如果你正被“数据来源不明确”“图表没业务解释”“答辩被问‘这个聚类结果怎么验证’直接哑火”折磨,这份98分实测项目就是你缺的那块拼图。

2. 数据获取与清洗:为什么SpiderHot.py比requests+BeautifulSoup裸写少踩7个坑

2.1 真实NBA数据源的反爬策略与合法抓取边界

NBA官网(stats.nba.com)对高频请求返回403时,不会明示封禁IP,而是返回含“JavaScript required”字样的HTML骨架页。SpiderHot.py没有硬刚JS渲染,而是采用“API探针法”:先用Selenium模拟浏览器加载页面,捕获Network面板中真实的XHR请求URL(形如https://stats.nba.com/stats/leaguedashplayerstats?Season=2022-23&PerMode=Totals),再用requests复现该请求。关键点在于Headers必须包含:

  • User-Agent: 模拟Chrome最新版本(代码中已固化为Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...)
  • Referer: 必须设为对应页面URL(如https://stats.nba.com/players/traditional/),否则返回空JSON
  • x-nba-stats-token: 固定值true(官网API未校验此值,但缺失则报400)

提示:项目未使用任何第三方爬虫框架,全部基于requests+json标准库实现,规避了Scrapy等框架带来的环境依赖问题。所有请求均设置time.sleep(1.2),符合Robots.txt中Crawl-delay: 1要求。

2.2 原始JSON响应的字段解构与脏数据过滤

NBA官方API返回的JSON是三层嵌套结构:{'resultSets': [{'headers': [...], 'rowSet': [[...], [...]]}]}。SpiderHot.py的parse_nba_api_response()函数做了三重校验:

  1. 字段对齐校验:比对headers与预设字段列表EXPECTED_HEADERS = ['PLAYER_NAME', 'FG_PCT', 'AST', 'STL', 'BLK', 'TOV'],缺失字段自动补None并记录警告日志
  2. 数值类型强转:对FG_PCT等百分比字段,先strip('%')再float(),失败则置为np.nan(非字符串'N/A')
  3. 逻辑矛盾过滤:当FGA(出手数)为0时,FG_PCT强制设为0(避免除零错误导致的inf值)
def parse_nba_api_response(raw_json): data = raw_json['resultSets'][0] headers, rows = data['headers'], data['rowSet'] # 字段对齐:确保headers顺序与EXPECTED_HEADERS一致 col_map = {h: i for i, h in enumerate(headers)} aligned_headers = [h for h in EXPECTED_HEADERS if h in col_map] # 构建DataFrame并强转类型 df = pd.DataFrame(rows, columns=headers) for col in ['FG_PCT', 'FT_PCT', 'FG3_PCT']: if col in df.columns: df[col] = pd.to_numeric(df[col].str.rstrip('%').fillna('0'), errors='coerce') / 100.0 return df[aligned_headers].replace('', np.nan)

这段代码的关键在于errors='coerce'参数——它让pd.to_numeric遇到无法转换的字符串(如'--')时返回nan而非抛异常,配合后续fillna(0)实现安全兜底。很多学生用astype(float)直接崩溃,就是因为没处理这种NBA数据特有的占位符。

2.3 CSV数据文件的物理存储规范与版本控制

项目生成的score.csv和Kurantttt.csv并非简单df.to_csv(),而是遵循学术数据存档规范:

  • 编码强制utf-8-sig(解决Excel打开中文乱码)
  • 日期列格式化为YYYY-MM-DD(非系统默认时间戳)
  • 数值列保留4位小数(float_format='%.4f'),避免科学计数法干扰人工核查
  • 文件末尾添加元数据注释行:# Generated by SpiderHot.py on 2023-10-15 | Source: NBA Stats API v1.2

这些细节让导师能一眼确认数据来源可信,而不是质疑“你这CSV是不是从百度文库抄的”。

3. 聚类分析实战:K-Means.py如何用肘部法则确定最优K值并解释聚类业务含义

3.1 特征工程:为什么选这12个维度而非简单用得分/篮板

K-Means.py的输入特征矩阵并非原始数据表,而是经过三步加工:

  1. 标准化:对PTS,REB,AST,STL,BLK,TOV,FG_PCT,FT_PCT,FG3_PCT,EFG_PCT,TS_PCT,USG_PCT共12列,用StandardScaler消除量纲差异(如PTS均值20 vsFG_PCT均值0.45)
  2. 业务加权:将USG_PCT(使用率)权重设为1.5倍,因其反映球员战术地位,比单纯得分更具区分度
  3. 降维验证:用PCA检验前2主成分能否解释≥70%方差(代码中pca.explained_variance_ratio_.sum()输出0.732,达标)

注意:项目刻意避开“用PCA降维后再聚类”的常见误区。K-Means.py直接在12维空间运行,因为PCA会破坏原始指标的业务可解释性——导师问“第3类球员为什么叫‘高效终结者’”,你得指着FG_PCT和TS_PCT两个原始字段回答,而不是解释PC1载荷。

3.2 肘部法则(Elbow Method)的自动化实现与阈值判定

确定K值不能靠拍脑袋。K-Means.py内置find_optimal_k()函数,计算K=2到K=10时的簇内平方和(WCSS):

  • 对每个K,运行10次K-Means(n_init=10防局部最优)
  • 取10次WCSS均值作为该K的代表值
  • 绘制K-WCSS曲线,用二阶导数法找拐点(代码中np.diff(np.diff(wcss_list)))
def find_optimal_k(X, k_range=range(2, 11)): wcss = [] for k in k_range: kmeans = KMeans(n_clusters=k, n_init=10, random_state=42) kmeans.fit(X) wcss.append(kmeans.inertia_) # 计算二阶差分,找最大负值点(拐点) second_diff = np.diff(np.diff(wcss)) optimal_k = k_range[2 + np.argmin(second_diff)] # +2因diff两次损失前两元素 return optimal_k, wcss

这段代码的玄学点在于:np.argmin(second_diff)找的是二阶差分最负的位置,即曲线斜率下降最快的点——这正是肘部。实测在NBA数据上,K=4时WCSS=128.7,K=5时突降至102.3,二阶差分达-18.9,远超其他K值(K=3到4仅-5.2),故选定K=4。

3.3 聚类结果的业务命名与可视化验证

K=4的聚类结果被赋予业务名称:

聚类ID命名核心指标特征代表球员
0高效终结者FG_PCT > 0.52, TS_PCT > 0.63, USG_PCT < 25杰伦·布朗、米切尔
1全能组织者AST > 7.2, TOV < 3.1, REB > 5.8东契奇、约基奇
2防守尖兵STL > 1.8, BLK > 1.5, FG_PCT < 0.47戈贝尔、阿德巴约
3得分机器PTS > 26.5, USG_PCT > 30, TOV > 4.0布克、恩比德

验证方法不是看轮廓系数,而是用RadarMap.py生成四类球员的雷达图(见下图),直观对比轮廓差异。例如“防守尖兵”类在STL/BLK维度显著凸起,而“得分机器”类在PTS/USG_PCT维度形成尖峰——这种肉眼可辨的分离度,比任何数学指标都更有说服力。

4. 可视化落地:RadarMap.py如何生成带坐标轴物理意义的可交互雷达图

4.1 雷达图坐标轴的物理意义标注与归一化陷阱

多数学生用plotly.express.line_polar()画雷达图,但坐标轴标签常写“维度1”“维度2”,这是答辩致命伤。RadarMap.py强制要求:

  • 每个轴标签必须是原始字段名(如'FG_PCT')+ 单位('%')
  • 数值归一化不用MinMaxScaler(会压缩高价值区间),而用Z-score截断法:
    def normalize_for_radar(series): z_score = (series - series.mean()) / series.std() # 截断±3σ外的离群值,避免单个球员扭曲全图比例 return np.clip(z_score, -3, 3)
    这样既保留分布形态,又防止字母哥的BLK=2.8(远超均值2.1)把整个防守轴拉长到失真。

4.2 多球员对比的视觉编码规则

RadarMap.py支持同时绘制N个球员,但严格遵循信息设计原则:

  • 颜色编码:按聚类ID着色(0类蓝、1类橙、2类绿、3类红),非随机色
  • 线型区分:主对比球员用实线+粗描边(width=4),参照球员用虚线(dash='dot')
  • 透明度控制:当叠加>5个球员时,自动启用opacity=0.7防视觉过载
fig = go.Figure() for i, player in enumerate(players): normalized_data = [normalize_for_radar(df[col])[df['PLAYER_NAME']==player].iloc[0] for col in RADAR_COLS] fig.add_trace(go.Scatterpolar( r=normalized_data, theta=RADAR_COLS, fill='toself', name=player, line=dict(width=4 if i==0 else 2, dash='solid' if i==0 else 'dot'), opacity=0.9 if len(players)<=5 else 0.7 ))

这段代码的深意在于:i==0永远代表你要重点论证的球员(如答辩时主推的杜兰特),其图形必须最突出。其他球员只是参照系,视觉权重必须弱化。

4.3 导出为答辩PPT可用的高清矢量图

RadarMap.py最终调用fig.write_image("radar_durant_vs_giannis.png", width=1200, height=800, scale=2)生成2400×1600像素图,但关键在scale=2——它让文本边缘锐利,避免PPT缩放后模糊。更隐蔽的技巧是:在Plotly初始化时设置template="simple_white",彻底去除灰色背景网格线,让PPT插入后无需二次裁剪。

5. 避坑指南:那些让答辩老师皱眉的5个具体翻车现场与血泪修复方案

5.1 现象:运行SpiderHot.py时反复报KeyError: 'resultSets'

原因:NBA官网API接口在2023年10月更新了响应结构,旧版返回{'results': [...]},新版改为{'data': {...}}。项目中的SpiderHot.py已适配新结构,但若你手动修改了URL参数(如把Season=2022-23错写成Season=202223),API会返回错误JSON(含message字段),而代码未做if 'resultSets' not in json_data校验。
解决:在SpiderHot.py第87行raw_json = response.json()后插入:

if 'resultSets' not in raw_json: raise ValueError(f"NBA API error: {raw_json.get('message', 'Unknown error')}")

然后检查URL中Season参数格式是否为YYYY-YY(如2022-23),非YYYYYY。

5.2 现象:K-Means.py聚类后所有球员被分进同一类(K=1)

原因:特征标准化时用了MinMaxScaler而非StandardScaler。NBA数据中PTS范围0-35,FG_PCT范围0-1,MinMaxScaler会把FG_PCT压缩到0-1区间,而PTS也被压到0-1,导致PTS的微小差异(如25.1 vs 25.3)被放大为FG_PCT的0.01差异,算法误判PTS更重要。
解决:确认K-Means.py第42行是scaler = StandardScaler(),且调用scaler.fit_transform(X)。若误用MinMaxScaler,需重跑全部流程——因为聚类中心已污染。

5.3 现象:RadarMap.py生成的雷达图坐标轴文字重叠,PPT里看不清

原因:Plotly默认字体大小为12px,在1200px宽图中,theta标签(如'FG_PCT%')过长导致挤占。项目已预设layout.polar.angularaxis.tickfont.size=14,但若你用fig.update_layout()覆盖了该设置,就会失效。
解决:在RadarMap.py绘图代码末尾,强制重置字体:

fig.update_layout( polar=dict( angularaxis=dict(tickfont=dict(size=14)), radialaxis=dict(tickfont=dict(size=12)) ) )

5.4 现象:答辩PPT中雷达图显示为白底黑线,但导师电脑上变成灰底

原因:Plotly导出PNG时默认用template="plotly"(带浅灰背景),而PPT主题为白色。项目RadarMap.py已设template="simple_white",但若你用fig.write_html()生成网页再截图,会丢失模板设置。
解决:必须用fig.write_image()直接导出PNG,且安装kaleido引擎:pip install kaleido。若提示ValueError: No renderer could be imported,运行python -c "import plotly.io as pio; pio.renderers.default='kaleido'"。

5.5 现象:DurantScore.py计算杜兰特2023赛季场均得分,结果比官网少0.3分

原因:score.csv中杜兰特有两条记录:一条是常规赛(SEASON_ID=22022),一条是季后赛(SEASON_ID=42022)。DurantScore.py默认读取所有行,未过滤SEASON_TYPE='Regular Season'。
解决:在DurantScore.py第22行df = pd.read_csv('score.csv')后添加:

df = df[df['SEASON_TYPE'] == 'Regular Season'].copy()

并确认CSV中SEASON_TYPE列存在(SpiderHot.py已确保该字段写入)。

6. 答辩级验证技巧:用HotSearch.py生成动态热力图证明分析结论的时效性

6.1 动态热力图的底层逻辑:为什么它比静态截图更有说服力

HotSearch.py不是简单调用seaborn.heatmap(),而是构建了一个时间滑动窗口热力图:以2023年10月为起点,每7天抓取一次NBA官网“球员搜索热度榜”,持续12周,生成search_heatmap_202310-202401.csv。该CSV的行是球员(如LeBron James),列是日期(2023-10-07,2023-10-14...),值为当日搜索指数(0-100)。这样做的价值在于:当你的聚类结论说“全能组织者(东契奇类)是当前联盟趋势”,热力图能显示东契奇的搜索热度在圣诞大战后飙升37%,而传统得分手(布克类)热度平稳——用外部行为数据交叉验证内部聚类,这才是导师想听的“证据链”。

6.2 热力图的三重降噪处理

原始搜索指数波动剧烈(某天因新闻事件暴涨),HotSearch.py实施三重平滑:

  1. 移动平均:对每行球员数据,用rolling(3).mean()消除单日噪声
  2. Z-score标准化:使所有球员热度在同一尺度比较(避免詹姆斯基数大掩盖小球员趋势)
  3. 显著性标记:当某球员连续3周热度增幅>15%,自动在热力图该区域打红色星号(*)
def generate_heatmap(csv_path): df = pd.read_csv(csv_path, index_col=0) # 移动平均降噪 df_smooth = df.rolling(3, axis=1).mean() # Z-score标准化 df_z = (df_smooth - df_smooth.mean(axis=1).values.reshape(-1,1)) / \ (df_smooth.std(axis=1).values.reshape(-1,1) + 1e-8) # 标记显著增长(连续3周增幅>15%) for i, player in enumerate(df.index): weekly_change = df.iloc[i, 1:].values / (df.iloc[i, :-1].values + 1e-8) - 1 if (weekly_change[-3:] > 0.15).all(): df_z.iloc[i, -1] = df_z.iloc[i, -1] + 0.5 # 微调值触发星号 return df_z

这段代码的精妙在于:df_z.iloc[i, -1] += 0.5不是为了改变数值,而是让Plotly在该单元格自动渲染星号(项目预设了annot_text映射规则)。

6.3 将热力图嵌入答辩PPT的黄金尺寸与动画节奏

HotSearch.py导出的热力图为1600x900像素(16:9),完美匹配PPT幻灯片。但关键在动画:

  • 入场动画:设为“淡入”,持续0.5秒(太快看不清,太慢拖节奏)
  • 数据强调:用PPT“笔迹”功能,在讲解东契奇热度飙升时,实时圈出2023-12-25列的红色星号区域
  • 对比锚点:在热力图右侧固定放置一张小图——K-Means聚类结果中“全能组织者”类球员的雷达图,形成“行为热度”与“技术特征”的双证据并置

从那以后我每次准备答辩,都强制走一遍HotSearch.py → RadarMap.py → 生成PPT图全流程,哪怕只用其中一张图。因为导师的追问永远来自你没验证的环节——他不会问“K-Means怎么算的”,但一定会问“你说这类球员是趋势,有什么证据证明不是历史偶然?”而这张动态热力图,就是你唯一的后悔药。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 7:25:15

EQTransformer地震相位拾取原理与工程实践

简介&#xff1a;EQTransformer是一个面向地球物理与人工智能交叉领域的开源Python软件包&#xff0c;专为地震信号自动检测与P/S波相位拾取任务设计&#xff0c;适用于地震学研究者、地质工程技术人员及具备深度学习基础的科研开发者。资源共92个文件&#xff0c;包含20个核心…

作者头像 李华
网站建设 2026/10/19 7:25:04

crass 0.4.14.0实战指南:命令行拆包、批量提取与自动化封装

简介&#xff1a;crass-0.4.14.0是一款面向GALGAME&#xff08;视觉小说&#xff09;玩家的资源提取工具&#xff0c;专门用于解析游戏开发商为保护版权而打包的私有格式&#xff0c;将图像、音频、剧本等素材从受保护的游戏文件中释放出来。该版本在0.4.x基础上优化了文件格式…

作者头像 李华
网站建设 2026/10/19 7:23:18

一次选档失误记录:最高档买在统计之前

失误复盘只记事实&#xff0c;不上升成「最高档无用」。 1. 当时没有一周打开次数。 2. 直接购买了最高档。 3. 一周后&#xff0c;增量部分没有被用到。 4. 因此这笔支出没有打在当时的用法上。 纠正后的顺序写死&#xff1a;先统计次数&#xff0c;偶尔用则看入门档和期限&…

作者头像 李华
网站建设 2026/10/19 7:23:16

STM32 printf重定向全攻略:从原理到工程实践

有些事看着简单&#xff0c;做起来全是门道。STM32的printf就是这样&#xff0c;刚入坑嵌入式时&#xff0c;拿一块板子&#xff0c;调通串口&#xff0c;在代码里敲一行printf&#xff0c;结果调试终端上要么一片空白&#xff0c;要么乱码&#xff0c;要么程序直接跑飞。后来搞…

作者头像 李华
网站建设 2026/10/19 7:22:49

一键降重与降低AI相似度:工具原理、实操与避坑指南

1. 这类工具到底是什么&#xff1a;先说清楚它在解决什么问题1.1 一个被问过无数次的需求近几年不断有人问我同一个问题&#xff1a;"有没有一款工具&#xff0c;能一键把重复率和AI相似度同时降下来&#xff1f;"问的人里&#xff0c;有写毕业论文的学生&#xff0c…

作者头像 李华