发现一个好玩的数据分析项目《数据告诉你,中超16家球队谁是老大》,
原文链接:https://mp.weixin.qq.com/s/-csfuvlb8xwTsD8p1VQxXg
采集懂球帝网站上的中超16家球队球员的能力信息,分析出,各球队之间的纸面数据差距,依据数据预测出夺冠希望最大的No.1。
整个项目,思路清晰,数据简单,是入门数据分析的不二法门。
中超足球,也是大家期望值比较大的实用项目。
因为朋友是个资深的足球迷,资深到能够半夜三四点定闹钟起来看世界杯的比赛,所以对足球也有一丝丝的兴趣。
去年刷爆的C罗,以及经常听的梅西,还有各个球队间的恩怨情仇,都是有意思的故事。
原文中的项目实现分为两部分:数据采集,数据分析。
本次项目的数据来源是懂球帝的官方网站。特此说明,本项目仅供学习研究,不用做任何商业用途。
分析html标签知道每支球队的详情页链接位于class='team’的td标签下的a超链接的href属性。
def ChineseSuper_team(self):
"""
中超球队列表
:return:
"""
url = "https://www.dongqiudi.com/data?competition=51"
res = self.parse_url(url)
element = Selector(res)
# 中超球队列表
teams = element.xpath("//td[@class='team']/a/@href").extract()
return teams
parse_url()函数是封装的发送请求获取响应的功能,传入请求url返回得到的响应,后续项目需要请求响应功能时,可以避免重复代码。
通过requests+scrapy.Selector,获取网页的element对象,确定了中超球队的标签位置,使用xpath获取中超球队的具体链接列表。
球员的详情链接获取同球队的获取方式相同。
打开浏览器的调试模式发现了一个意料之外的问题。html文件中不存在球员的详情链接,对应球员姓名的是球员照片的jpg链接。
原文中使用selenium开发者调试工具,模拟打开网页,模拟点击获取到的球员链接。selenium的效率以及资源使用率消耗过大了,我一般是不推荐使用的。(特殊情况除外,当然也根据个人喜欢,青菜萝卜,各有所爱嘛。)
不用调试工具,html中又没有球员链接。可以肯定的是,球员的链接是在网页点击时,通过某个方法生成。去哪里找到这个方法呢?
这里我们需要使用到逆推的思想。打开球员的详情链接,通过比较,发现不同的球员链接变化的只有最后的一串数字。
可以推测出,球员的详情链接组成方式为:
https://www.dongqiudi.com/player/ + 球员id,例如:广州恒大淘宝的前锋韦世豪的详情链接为:https://www.dongqiudi.com/player/50222265,最后的50222265就是韦世豪的唯一标识符。
在球队源码中搜索韦世豪的球员id:
发现球员信息存在于一个类似于字典的字符串中,其中person_id就是我们需要的球员id。
接下来的思路很简单,获取球队中的所有person_id,拼接成具体的球员详情页url。
def footballer(self, team_url):
"""
足球运动员id列表url
:return:
"""
res = self.parse_url(team_url)
# 俱乐部信息
team_content = self.team_content(res)
club_name = team_content[0]
print(f"俱乐部名字是:{club_name}")
# 球员id列表
r = re.compile("teamMemberData:(.*),relatedNewsList:", re.S)
info = r.search(res.text)
footballer_info = info.group(1)
r2 = re.compile('person_id:"(.*?)",', re.S)
footballer_list = r2.findall(footballer_info)
# 球员信息链接
qiuyuan_url = "https://www.dongqiudi.com/player/{}"
footballer_url = [qiuyuan_url.format(i) for i in footballer_list]
# 获取球员信息
# print(footballer_url)
content_list = (self.footballer_content(footballer) for footballer in footballer_url)
content_list = [content for content in content_list if content is not None]
# print(content_list)
# 信息写入excel表格
file_path = pathlib.Path.cwd().joinpath('file', f"中超球队信息.xlsx")
print(file_path)
self.write_excel(file_path, content_list)
通过parse_url()函数获取球队信息页的网页response,获取源码,使用re双重正则过滤出当前球队的所有球员person_id。
使用字符串拼接生成球员详情链接的列表。
调用self.footballer_content()函数,以球员详情链接url作为参数,获取球员的具体数据值。
并存入excel表格。
def footballer_content(self, footballer_url):
"""
球员信息
:return:
"""
# footballer_url = "https://www.dongqiudi.com/player/50006963"
res = self.parse_url(footballer_url)
element = Selector(res)
# 球员信息
content_list = self.get_content(element)
# 球员能力评分
ability_list = self.footballer_ability(element)
# print(content_list, 11)
# print(ability_list, 22)
if content_list and ability_list:
content_list.extend(ability_list)
# print(content_list)
# print(footballer_url, 111)
return content_list
get_content()函数获取球员信息,football_ability()函数获取球员能力评分。
def get_content(self, element):
"""
球员信息列表
:return:
"""
# 姓名
name = element.xpath("//p[@class='china-name']/text()").extract_first()
# 俱乐部
club = element.xpath("//*[contains(text(),'俱乐部:')]/parent::*/text()").extract()
club = "".join(club).strip()
# print(club)
# 位置
position = element.xpath("//*[contains(text(),'位 置:')]/parent::*/text()").extract_first()
if position is None or "教练" in position:
return []
# 号码
number = element.xpath("//*[contains(text(),'号 码:')]/parent::*/text()").extract_first()
# 国籍
nationnalty = element.xpath("//*[contains(text(),'国 籍:')]/parent::*/text()").extract_first()
# 年龄
age = element.xpath("//*[contains(text(),'年 龄:')]/parent::*/text()").extract_first()
# 生日
birthday = element.xpath("//*[contains(text(),'生 日:')]/parent::*/text()").extract_first()
# 身高
height = element.xpath("//*[contains(text(),'身 高:')]/parent::*/text()").extract_first()
# 体重
weight = element.xpath("//*[contains(text(),'体 重:')]/parent::*/text()").extract_first()
# 惯用脚
foot = element.xpath("//*[contains(text(),'惯用脚:')]/parent::*/text()").extract_first()
return [name, club, position, number, nationnalty, age, birthday, height, weight, foot]
def footballer_ability(self, element):
"""
球员能力列表
:return:
"""
# 综合能力
synthetic = element.xpath("//*[contains(text(),'综合能力')]/*/text()").extract_first()
if synthetic is None:
return []
# 速度
speed = element.xpath("//*[contains(text(),'速度')]/*/text()").extract_first()
# 射门
shoot = element.xpath("//*[contains(text(),'射门')]/*/text()").extract_first()
if shoot is None:
# 门将能力
# 扑救
put_ball = element.xpath("//*[contains(text(),'扑救')]/*/text()").extract_first()
# 手型
hand_shape = element.xpath("//*[contains(text(),'手型')]/*/text()").extract_first()
# 开球
open_ball = element.xpath("//*[contains(text(),'开球')]/*/text()").extract_first()
# 反应
reaction = element.xpath("//*[contains(text(),'反应')]/*/text()").extract_first()
# 位置
location = element.xpath("//*[contains(text(),'位置')]/*/text()").extract_first()
return [synthetic, speed, put_ball, hand_shape, open_ball, reaction, location]
# 传球
passing = element.xpath("//*[contains(text(),'传球')]/*/text()").extract_first()
# 盘带
dribbling = element.xpath("//*[contains(text(),'盘带')]/*/text()").extract_first()
# 防守
defense = element.xpath("//*[contains(text(),'防守')]/*/text()").extract_first()
# 力量
power = element.xpath("//*[contains(text(),'力量')]/*/text()").extract_first()
return [synthetic, speed, shoot, passing, dribbling, defense, power]
需要注意的问题有三个:
1.我们只需要球员的信息,需要将位置为教练的过滤掉。
2.位置不同,能力评分的标准不同。最明显的问题是 其他位置例如前锋,中场的能力评分和门将的能力评分信息不一样。需要单独判断。
3.没有能力评分的球员,无法为数据分析提供帮助,需要过滤掉。
没有能力评分的球员,例如:广州恒大淘宝的门将张建智。球员信息页如下:
def write_excel(self, file_path, content_list):
"""
数据写入excel表格
:return:
"""
# 数据写入excel表格
try:
df = pd.read_excel(file_path, header=None)
ds = pd.DataFrame(content_list)
print(ds)
df = df.append(ds, ignore_index=True)
df.to_excel(file_path, index=False, header=False)
except Exception as e:
# 创建excel表格
print("表格不存在,创建表格")
columns = ['姓名', '俱乐部', '位置', '号码', '国籍', '年龄', '生日', '身高', '体重', '惯用脚', '综合能力', '速度', '射门(扑救)', '传球(手型)',
'盘带(开球)', '防守(反应)',
'力量(位置)']
df = pd.DataFrame(columns=columns)
df.to_excel(file_path, index=False)
self.write_excel(file_path, content_list)
使用Pandas,将数据写入excel表格,捕获写入表格异常,表格不存在,先创建excel表格,再重新写入数据。
这里涉及到pandas对excel表格的数据追加问题。
前面写过一篇pandas对excel表格的数据追加问题。有兴趣的可以研究一下。https://blog.csdn.net/mygodit/article/details/97640770
16支球队的总数据量是346条。这里筛选了教练,以及没有能力评分的球员
def read_excel(self):
"""
读取excel表格数据
:return:返回俱乐部集合,各球队综合能力的平均值
"""
# 俱乐部综合能力平均值
ability_mean_dict = {ds: int(self.df.get(self.df['俱乐部'] == ds)['综合能力'].mean()) for ds in self.team_list}
# 依据平均值排序
a_order = sorted(ability_mean_dict.items(), key=lambda x: x[1], reverse=True)
# 获取对应的值
team_list = [team[0] for team in a_order]
ability_mean = [team[1] for team in a_order]
# 各球队前锋的值
# ability_mean_dict = {ds: int(df.get(df['俱乐部'] == ds)['前锋'].mean()) for ds in set(team_set)}
# print(a_order)
print(team_list)
# print(ability_mean)
return team_list, ability_mean
read_excel()函数,返回俱乐部集合,各球队综合能力的平均值。
使用pandas的Dataframe格式数据操作方法,依据球队名获取综合能力值。Dataframe的mean()方法获取数值的平均值。
根据综合能力值进行排序。reverse=True,从大到小。
获取到排序后的队名列表和综合能力列表。
def create_zhuzhuang(self, team_list, ability_mean):
"""
创建柱状图,记录各球队综合能力
:return:
"""
v1 = ability_mean
x1 = team_list
print(x1)
print(v1)
bar = (
Bar()
.add_xaxis(x1)
.add_yaxis("中超球队综合得分", v1, category_gap="50%", color='green')
.set_global_opts(title_opts=opts.TitleOpts(title="懂球帝球队信息分析")))
bar.render("中超球队综合得分柱状图.html")
create_zhuzhuang()函数传入x轴参数球队列表和y轴参数ability_mean综合能力平均值。
生成柱状图使用pyecharts第三方库的Bar,具体实现原理,有兴趣的请面向百度编程。
生成的柱状图如下:
def get_full_back(self):
"""
获取球队后卫球员的平均防守,平均传球
:return:
"""
full_back_list = [
list(map(lambda x: int(self.df.get(self.df['俱乐部'] == team).get(self.df['位置'] == '后卫')[x].mean()),
["防守(反应)", '传球(手型)','速度 '])) for
team in self.team_list]
return full_back_list
使用Pandas读取excel表格。依据俱乐部名称,位置==后卫,从excel表格获取到各球队后卫球员的平均盘带,平均传球,平均速度数据。
生成列表返回。
def create_full_back_sandian(self, full_back_list):
"""
创建后卫球员平均防守,平均传球,平均速度,综合能力的散点图
:param full_back_list:
:return:
"""
# print(full_back_list)
# 加入后卫球员综合能力
full_back_ability = self.get_ability(full_back_list)
# 球队,能力对应
full_back_dict = dict(zip(self.team_list, full_back_ability))
new_dict = sorted(full_back_dict.items(), key=lambda x: x[1][3], reverse=True)
# print(new_dict)
print(full_back_ability)
# x轴队名
x_data = [team[0] for team in new_dict]
# y轴 平均防守
defense_list = [team[1][0] for team in new_dict]
# y轴 平均传球
passing_list = [team[1][1] for team in new_dict]
# y轴 平均速度
speed_list = [team[1][1] for team in new_dict]
# y轴 后卫能力
full_back_ability_list = [team[1][3] for team in new_dict]
(
Scatter(init_opts=opts.InitOpts(width='1900px', height='500px', page_title="中超后卫球员能力散点图"))
.add_xaxis(x_data)
.add_yaxis(
series_name="后卫球员平均防守能力",
y_axis=defense_list,
symbol_size=20,
symbol=None,
label_opts=opts.LabelOpts(is_show=False),
)
.add_yaxis(
series_name="后卫球员平均传球能力",
y_axis=passing_list,
symbol=None,
symbol_size=20,
label_opts=opts.LabelOpts(is_show=False),
)
.add_yaxis(
series_name="后卫球员平均速度能力",
y_axis=speed_list,
symbol_size=20,
symbol=None,
label_opts=opts.LabelOpts(is_show=False)
)
.add_yaxis(
series_name="后卫球员综合能力",
y_axis=full_back_ability_list,
symbol=None,
symbol_size=20,
label_opts=opts.LabelOpts(is_show=False),
)
.set_global_opts(
title_opts=opts.TitleOpts(title='中超后卫球员能力散点图'),
xaxis_opts=opts.AxisOpts(splitline_opts=opts.SplitLineOpts(is_show=True)),
yaxis_opts=opts.AxisOpts(splitline_opts=opts.SplitLineOpts(is_show=True))
)
.render('中超后卫球员能力散点图.html')
)
create_full_back_sandian()函数接收后卫球员能力列表作为参数,生成后卫球员能力的散点图。
由于散点图生成的比较混乱,看不出来具体球队后卫球员的能力,因此,调用self.get_ability()函数,生成后卫球员能力的综合值。作为散点图排序的参数。
x轴为排序后的队名,y轴分别为平均防守能力散点图,平均传球能力散点图,综合能力散点图。
散点图使用pyecharts的Scatter():
球队前锋,球队中场,球队门将的能力散点图生成同后卫球员的生成方式相似。
至此,我们做完了中超16只球队的数据分析。
至于是不是如原文所说,广州恒大淘宝的夺冠概率最大,咱也不知道,咱也不敢问啊。