irpas技术客

Python课程设计大作业:利用爬虫获取NBA比赛数据并进行机器学习预测NBA比赛结果_逍遥

未知 8039

前言

该篇是之前遗漏的大三上的Python课程设计。刚好今天有空就补发了一篇文章。全部的代码在最后附录中。爬虫类的代码直接全部放到一起了,读者可以自行研究。

百度网盘链接链接:https://pan.baidu.com/s/1sQPMbO-sy2_QqQHyMtTmUg 提取码:mx10

一、课程设计项目说明

该课程设计项目“利用爬虫获取NBA数据信息并进行机器学习预测NBA比赛结果”的原型是基于网上一个教程网站“实验楼”中的项目“使用 Python 进行 NBA 比赛数据分析”。原项目是利用Python进行对NBA的数据机器学习进行预测。在原项目之上,我对项目做出了如下更新:

1、使用Python爬虫爬取NBA官网中的每一年的季后赛常规赛等部分项目中需要的比赛统计数据并输出成csv格式的文件,避免像原项目一样,全部比赛数据需要自己从NBA网站中复制黏贴到txt文本上然后更改后缀名为csv得到数据,能够减轻获取数据的繁琐工作量。 (注:原项目网站链接:https://·/courses/782/learning/?id=2647)

2、对原项目代码结合了网上资料进行自主学习,并对原项目代码进行了改进优化。

二、课程设计项目功能

首先可通过Python爬虫获取来自NBA官网的任意年度的球队数据,保存在本地文件夹后,更改名为“NBA-nwz”的代码中的路径folder为数据文件路径,即可导入球队各类数据而后进行特征向量、逻辑回归、球队的EloScore计算等机器学习,最终将预测的比赛结果输出到特定路径下的格式为.csv的文件查看比赛预测结果。

三、项目所需数据文件

本项目中一共需要5张数据表,分别是Team Per Ganme Stats(各球队每场比赛数据统计)、Opponent Per Game Stats(对手平均平常比赛的数据统计)、Miscellaneous Stats(各球队综合统计数据表)、2015-2016 NBA Schedule and Results(2015-16赛季比赛安排与结果)、2016-2017 NBA Schedule and Results(2016-2015赛季比赛安排)。

四、项目原理介绍 1、比赛数据介绍

本项目中,采用来自与NBA网站的数据。在该网站中,可以获取到任意球队、任意球员的各类比赛统计数据,如得分、投篮次数、犯规次数等等。 (注:NBA网站链接https://·/anaconda/. 应该是最开始自己安装python环境的时候使用的anaconda没有配置好,或者说这个源不起作用了,于是首先尝试了第一种方法找到.condarc文件,更改里面的channels通道地址。但是当我根据网上的指导教程换国科大、阿里等信号源后依然出现错误。

后面找到了一篇文章,说是需要将https://改为 http即可,刚看到的时候以为不是这个问题,后面实在是没办法了,被这个问题搞得头大,一个多小时了卡着,只好死马当活马医,更改了一下https为http,并配入了清华源的最新配置channels,没想到解决了!

可以通过cmd命令 conda info查看自己的channels路径配置。

也可以通过在.condarc文件直接更改即可。进行如下配置即可轻松拥有速度较快的安装包速度了!

2、项目展望

总的来说,项目还是有一些小不足和继续优化的,例如在5张表中,爬下来就可以立即使用的只有三张表,分别是 Team Per Game Stats、Opponent Per Game Stats 和 Miscellaneous Stats。另外爬下来的表格需要进行字段处理,去掉不需要的字段,并且更改字段名等才能使用。而Python中是可以做到自动化处理数据字段的。这一点没有较好的实现。

除此之外,还可以使用Python可视化来做到更好的展示出比赛中两个队哪个胜率更高, 这一点我曾尝试过,但是由于效果并不是很完美,就没有放到设计项目中来。

以及在10折交叉验证中,可以看出正确率接近70%左右,感觉还可以在机器学习及数据处理(选用数据)方面再下一些功夫,达到更高的正确率。

因为机器学习是我自己课余时间学习过一点点的小教程,所以了解接触并不是很深,做的并不是特别完善,有机会可以多更改,进一步完善优化。

附录:全部代码

进行预测的代码:

import pandas as pd import math import numpy as np import csv from sklearn import linear_model from sklearn.model_selection import cross_val_score init_elo = 1600 # 初始化elo值 team_elos = {} folder = 'D:\pydzy\py-n' # 文件路径 def PruneData(M_stat, O_stat, T_stat): #这个函数要完成的任务在于将原始读入的诸多队伍的数据经过修剪,使其变为一个以team为索引的排列的特征数据 #丢弃与球队实力无关的统计量 pruneM = M_stat.drop(['Rk', 'Arena'],axis = 1) pruneO = O_stat.drop(['Rk','G','MP'],axis = 1) pruneT = T_stat.drop(['Rk','G','MP'],axis = 1) #将多个数据通过相同的index:team合并为一个数据 mergeMO = pd.merge(pruneM, pruneO, how = 'left', on = 'Team') newstat = pd.merge(mergeMO, pruneT, how = 'left', on = 'Team') #将team作为index的数据返回 return newstat.set_index('Team', drop = True, append = False) def GetElo(team): # 初始化每个球队的elo等级分 try: return team_elos[team] except: team_elos[team] = init_elo return team_elos[team] def CalcElo(winteam, loseteam): # winteam, loseteam的输入应为字符串 # 给出当前两个队伍的elo分数 R1 = GetElo(winteam) R2 = GetElo(loseteam) # 计算比赛后的等级分,参考elo计算公式 E1 = 1/(1 + math.pow(10,(R2 - R1)/400)) E2 = 1/(1 + math.pow(10,(R1 - R2)/400)) if R1>=2400: K=16 elif R1<=2100: K=32 else: K=24 R1new = round(R1 + K*(1 - E1)) R2new = round(R2 + K*(0 - E2)) return R1new, R2new def GenerateTrainData(stat, trainresult): #将输入构造为[[team1特征,team2特征],...[]...] X = [] y = [] for index, rows in trainresult.iterrows(): winteam = rows['WTeam'] loseteam = rows['LTeam'] #获取最初的elo或是每个队伍最初的elo值 winelo = GetElo(winteam) loseelo = GetElo(loseteam) # 给主场比赛的队伍加上100的elo值 if rows['WLoc'] == 'H': winelo = winelo+100 else: loseelo = loseelo+100 # 把elo当为评价每个队伍的第一个特征值 fea_win = [winelo] fea_lose = [loseelo] # 添加我们从basketball reference.com获得的每个队伍的统计信息 for key, value in stat.loc[winteam].iteritems(): fea_win.append(value) for key, value in stat.loc[loseteam].iteritems(): fea_lose.append(value) # 将两支队伍的特征值随机的分配在每场比赛数据的左右两侧 # 并将对应的0/1赋给y值 if np.random.random() > 0.5: X.append(fea_win+fea_lose) y.append(0) else: X.append(fea_lose+fea_win) y.append(1) # 更新team elo分数 win_new_score, lose_new_score = CalcElo(winteam, loseteam) team_elos[winteam] = win_new_score team_elos[loseteam] = lose_new_score # nan_to_num(x)是使用0代替数组x中的nan元素,使用有限的数字代替inf元素 return np.nan_to_num(X),y def GeneratePredictData(stat,info): X=[] #遍历所有的待预测数据,将数据变换为特征形式 for index, rows in stat.iterrows(): #首先将elo作为第一个特征 team1 = rows['Vteam'] team2 = rows['Hteam'] elo_team1 = GetElo(team1) elo_team2 = GetElo(team2) fea1 = [elo_team1] fea2 = [elo_team2+100] #球队统计信息作为剩余特征 for key, value in info.loc[team1].iteritems(): fea1.append(value) for key, value in info.loc[team2].iteritems(): fea2.append(value) #两队特征拼接 X.append(fea1 + fea2) #nan_to_num的作用:1将列表变换为array,2.去除X中的非数字,保证训练器读入不出问题 return np.nan_to_num(X) if __name__ == '__main__': # 设置导入数据表格文件的地址并读入数据 M_stat = pd.read_csv(folder + '/15-16Miscellaneous_Stat.csv') O_stat = pd.read_csv(folder + '/15-16Opponent_Per_Game_Stat.csv') T_stat = pd.read_csv(folder + '/15-16Team_Per_Game_Stat.csv') team_result = pd.read_csv(folder + '/2015-2016_result.csv') teamstat = PruneData(M_stat, O_stat, T_stat) X,y = GenerateTrainData(teamstat, team_result) # 训练网格模型 limodel = linear_model.LogisticRegression() limodel.fit(X,y) # 10折交叉验证 print(cross_val_score(model, X, y, cv=10, scoring='accuracy', n_jobs=-1).mean()) # 预测 pre_data = pd.read_csv(folder + '/16-17Schedule.csv') pre_X = GeneratePredictData(pre_data, teamstat) pre_y = limodel.predict_proba(pre_X) predictlist = [] for index, rows in pre_data.iterrows(): reslt = [rows['Vteam'], pre_y[index][0], rows['Hteam'], pre_y[index][1]] predictlist.append(reslt) # 将预测结果输出保存为csv文件 with open(folder+'/prediction of 2016-2017.csv', 'w',newline='') as f: writers = csv.writer(f) writers.writerow(['Visit Team', 'corresponding probability of winning', 'Home Team', 'corresponding probability of winning']) writers.writerows(predictlist)

爬虫代码:

import requests import re import csv from parsel import Selector class NBASpider: def __init__(self): self.url = "https://·/leagues/NBA_2021.html" self.schedule_url = "https://·/leagues/NBA_2016_games-{}.html" self.advanced_team_url = "https://·/leagues/NBA_2016.html" self.headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 " "Safari/537.36" } # 发送请求,获取数据 def send(self, url): response = requests.get(url, headers=self.headers, timeout=30) response.encoding = 'utf-8' return response.text # 解析html def parse(self, html): team_heads, team_datas = self.get_team_info(html) opponent_heads, opponent_datas = self.get_opponent_info(html) return team_heads, team_datas, opponent_heads, opponent_datas def get_team_info(self, html): """ 通过正则从获取到的html页面数据中team表的表头和各行数据 :param html 爬取到的页面数据 :return: team_heads表头 team_datas 列表内容 """ # 1. 正则匹配数据所在的table team_table = re.search('<table.*?id="per_game-team".*?>(.*?)</table>', html, re.S).group(1) # 2. 正则从table中匹配出表头 team_head = re.search('<thead>(.*?)</thead>', team_table, re.S).group(1) team_heads = re.findall('<th.*?>(.*?)</th>', team_head, re.S) # 3. 正则从table中匹配出表的各行数据 team_datas = self.get_datas(team_table) return team_heads, team_datas # 解析opponent数据 def get_opponent_info(self, html): """ 通过正则从获取到的html页面数据中opponent表的表头和各行数据 :param html 爬取到的页面数据 :return: """ # 1. 正则匹配数据所在的table opponent_table = re.search('<table.*?id="per_game-opponent".*?>(.*?)</table>', html, re.S).group(1) # 2. 正则从table中匹配出表头 opponent_head = re.search('<thead>(.*?)</thead>', opponent_table, re.S).group(1) opponent_heads = re.findall('<th.*?>(.*?)</th>', opponent_head, re.S) # 3. 正则从table中匹配出表的各行数据 opponent_datas = self.get_datas(opponent_table) return opponent_heads, opponent_datas # 获取表格body数据 def get_datas(self, table_html): """ 从tboday数据中解析出实际数据(去掉页面标签) :param table_html 解析出来的table数据 :return: """ tboday = re.search('<tbody>(.*?)</tbody>', table_html, re.S).group(1) contents = re.findall('<tr.*?>(.*?)</tr>', tboday, re.S) for oc in contents: rk = re.findall('<th.*?>(.*?)</th>', oc) datas = re.findall('<td.*?>(.*?)</td>', oc, re.S) datas[0] = re.search('<a.*?>(.*?)</a>', datas[0]).group(1) datas.insert(0, rk[0]) # yield 声明这个方法是一个生成器, 返回的值是datas yield datas def get_schedule_datas(self, table_html): """ 从tboday数据中解析出实际数据(去掉页面标签) :param table_html 解析出来的table数据 :return: """ tboday = re.search('<tbody>(.*?)</tbody>', table_html, re.S).group(1) contents = re.findall('<tr.*?>(.*?)</tr>', tboday, re.S) for oc in contents: rk = re.findall('<th.*?><a.*?>(.*?)</a></th>', oc) datas = re.findall('<td.*?>(.*?)</td>', oc, re.S) if datas and len(datas) > 0: datas[1] = re.search('<a.*?>(.*?)</a>', datas[1]).group(1) datas[3] = re.search('<a.*?>(.*?)</a>', datas[3]).group(1) datas[5] = re.search('<a.*?>(.*?)</a>', datas[5]).group(1) datas.insert(0, rk[0]) # yield 声明这个方法是一个生成器, 返回的值是datas yield datas def get_advanced_team_datas(self, table): trs = table.xpath('./tbody/tr') for tr in trs: rk = tr.xpath('./th/text()').get() datas = tr.xpath('./td[@data-stat!="DUMMY"]/text()').getall() datas[0] = tr.xpath('./td/a/text()').get() datas.insert(0, rk) yield datas def parse_schedule_info(self, html): """ 通过正则从获取到的html页面数据中的表头和各行数据 :param html 爬取到的页面数据 :return: heads表头 datas 列表内容 """ # 1. 正则匹配数据所在的table table = re.search('<table.*?id="schedule" data-cols-to-freeze=",1">(.*?)</table>', html, re.S).group(1) table = table + "</tbody>" # 2. 正则从table中匹配出表头 head = re.search('<thead>(.*?)</thead>', table, re.S).group(1) heads = re.findall('<th.*?>(.*?)</th>', head, re.S) # 3. 正则从table中匹配出表的各行数据 datas = self.get_schedule_datas(table) return heads, datas def parse_advanced_team(self, html): """ 通过xpath从获取到的html页面数据中表头和各行数据 :param html 爬取到的页面数据 :return: heads表头 datas 列表内容 """ selector = Selector(text=html) # 1. 获取对应的table table = selector.xpath('//table[@id="advanced-team"]') # 2. 从table中匹配出表头 res = table.xpath('./thead/tr')[1].xpath('./th/text()').getall() heads = [] for i, head in enumerate(res): if '\xa0' in head: continue heads.append(head) # 3. 匹配出表的各行数据 table_data = self.get_advanced_team_datas(table) return heads, table_data # 存储成csv文件 def save_csv(self, title, heads, rows): f = open(title + '.csv', mode='w', encoding='utf-8', newline='') csv_writer = csv.writer(f) csv_writer.writerow(heads) for row in rows: csv_writer.writerow(row) f.close() def crawl_team_opponent(self): # 1. 发送请求 res = self.send(self.url) # 2. 解析数据 team_heads, team_datas, opponent_heads, opponent_datas = self.parse(res) # 3. 保存数据为csv self.save_csv("team", team_heads, team_datas) self.save_csv("opponent", opponent_heads, opponent_datas) def crawl_schedule(self): months = ["october", "november", "december", "january", "february", "march", "april", "may", "june"] for month in months: html = self.send(self.schedule_url.format(month)) # print(html) heads, datas = self.parse_schedule_info(html) # 3. 保存数据为csv self.save_csv("schedule_"+month, heads, datas) def crawl_advanced_team(self): # 1. 发送请求 res = self.send(self.advanced_team_url) # 2. 解析数据 heads, datas = self.parse_advanced_team(res) # 3. 保存数据为csv self.save_csv("advanced_team", heads, datas) def crawl(self): # 1. 爬取各队伍信息 # self.crawl_team_opponent() # 2. 爬取计划表 # self.crawl_schedule() # 3. 爬取Advanced Team表 self.crawl_advanced_team() if __name__ == '__main__': # 运行爬虫 spider = NBASpider() spider.crawl()


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #Python #进行 #NBA #比赛数据分析