GetFootballData 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- GetFootballData/__init__.py +0 -0
- GetFootballData/fotmob_scraper.py +317 -0
- GetFootballData/scraper_utilities/__init__.py +0 -0
- GetFootballData/scraper_utilities/league_to_fotmob_map.py +11 -0
- GetFootballData/scraper_utilities/league_to_sofascore_map.py +393 -0
- GetFootballData/scraper_utilities/league_to_tm_map.py +6 -0
- GetFootballData/scraper_utilities/year_maps.py +89 -0
- GetFootballData/sofascore_scraper.py +472 -0
- GetFootballData/transfermarkt_scraper.py +288 -0
- getfootballdata-0.1.0.dist-info/METADATA +110 -0
- getfootballdata-0.1.0.dist-info/RECORD +14 -0
- getfootballdata-0.1.0.dist-info/WHEEL +5 -0
- getfootballdata-0.1.0.dist-info/licenses/LICENSE.md +665 -0
- getfootballdata-0.1.0.dist-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
import time
|
|
4
|
+
import random
|
|
5
|
+
from curl_cffi import requests as cffi_requests
|
|
6
|
+
from GetFootballData.scraper_utilities.league_to_fotmob_map import league_to_fotmob_dict
|
|
7
|
+
from GetFootballData.scraper_utilities.year_maps import year_to_fotmob_season
|
|
8
|
+
|
|
9
|
+
pd.set_option('display.max_columns', None)
|
|
10
|
+
pd.set_option('display.max_rows', None)
|
|
11
|
+
|
|
12
|
+
root_site = 'www.fotmob.com'
|
|
13
|
+
|
|
14
|
+
class FotmobScraper:
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
|
|
18
|
+
self.session = cffi_requests.Session(impersonate='chrome124')
|
|
19
|
+
|
|
20
|
+
self.session.headers.update({"accept": "application/json", "referer": f"https://{root_site}/"})
|
|
21
|
+
|
|
22
|
+
self.warm_up()
|
|
23
|
+
|
|
24
|
+
def warm_up(self):
|
|
25
|
+
"""Request fotmob page to familiarise session with fotmob."""
|
|
26
|
+
try:
|
|
27
|
+
self.session.get(f"https://{root_site}/", timeout=5)
|
|
28
|
+
time.sleep(random.uniform(1.5, 3.0))
|
|
29
|
+
except Exception as e:
|
|
30
|
+
print(f"Warm-up handshake warning: {e}")
|
|
31
|
+
|
|
32
|
+
def __get_team_id_dict(self, year=int, league_name=str):
|
|
33
|
+
|
|
34
|
+
'''
|
|
35
|
+
:param year: the year that the season begun
|
|
36
|
+
:param league_name: The name of the league in the standardised format of the package
|
|
37
|
+
:return: A dictionary containing {'id': 'team_name', ...} pairs for the given league in the given season starting year
|
|
38
|
+
'''
|
|
39
|
+
|
|
40
|
+
league_country_to_find = league_to_fotmob_dict[league_name]
|
|
41
|
+
|
|
42
|
+
url = f'https://{root_site}/api/data/allLeagues?locale=en&country=GBR'
|
|
43
|
+
print(url)
|
|
44
|
+
|
|
45
|
+
r = self.session.get(url)
|
|
46
|
+
league_list_data = r.json()['countries']
|
|
47
|
+
|
|
48
|
+
league_page_url = ''
|
|
49
|
+
league_id = ''
|
|
50
|
+
|
|
51
|
+
for country in league_list_data:
|
|
52
|
+
if country['name'] == league_country_to_find[1]:
|
|
53
|
+
league_list = country['leagues']
|
|
54
|
+
for league in league_list:
|
|
55
|
+
if league['name'] == league_country_to_find[0]:
|
|
56
|
+
league_page_url = f'https://{root_site}/api/data/leagues?id={league["id"]}&ccode3=GBR&season={year}%2F{int(year)+1}'
|
|
57
|
+
league_id = league['id']
|
|
58
|
+
break
|
|
59
|
+
|
|
60
|
+
if len(league_page_url) == 0:
|
|
61
|
+
raise Exception('No leagues found')
|
|
62
|
+
if league_id == '':
|
|
63
|
+
raise Exception('No league_id found')
|
|
64
|
+
|
|
65
|
+
r = self.session.get(league_page_url)
|
|
66
|
+
league_data = r.json()
|
|
67
|
+
|
|
68
|
+
#Get tournament id for given year
|
|
69
|
+
tournament_id_list = league_data.get('stats').get('seasonStatLinks')
|
|
70
|
+
tournament_id = ''
|
|
71
|
+
for season in tournament_id_list:
|
|
72
|
+
if season.get('Name') == year_to_fotmob_season[year]:
|
|
73
|
+
tournament_id = season.get('TournamentId')
|
|
74
|
+
if tournament_id == '':
|
|
75
|
+
raise Exception('No tournament ID found for that year')
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
#Get dictionary of {id, team_name}
|
|
79
|
+
team_dict = {}
|
|
80
|
+
|
|
81
|
+
team_list = league_data['table'][0]['data']['table']['all']
|
|
82
|
+
no_of_teams = len(team_list)
|
|
83
|
+
for index, team in enumerate(team_list):
|
|
84
|
+
team_dict.update({team['id']: {
|
|
85
|
+
'name': team['name'],
|
|
86
|
+
'deduction': team['deduction'],
|
|
87
|
+
'ongoing': team['ongoing'],
|
|
88
|
+
'played': team['played'],
|
|
89
|
+
'wins': team['wins'],
|
|
90
|
+
'draws': team['draws'],
|
|
91
|
+
'losses': team['losses'],
|
|
92
|
+
'scored-conceded': team['scoresStr'],
|
|
93
|
+
'goal_dif': team['goalConDiff'],
|
|
94
|
+
'points': team['pts'],
|
|
95
|
+
}})
|
|
96
|
+
print(f'{index + 1}/{no_of_teams} teams read')
|
|
97
|
+
print('all teams read successfully')
|
|
98
|
+
|
|
99
|
+
return team_dict, league_id, tournament_id
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def __get_player_id_dict(self, year, league_name):
|
|
104
|
+
|
|
105
|
+
'''
|
|
106
|
+
:param year: the year that the season begun
|
|
107
|
+
:param league_name: The name of the league in the standardised format of the package
|
|
108
|
+
:return: A dictionary containing {'id': ('player_name', 'player_mins'), ...} pairs for the given league in the given year
|
|
109
|
+
'''
|
|
110
|
+
|
|
111
|
+
team_dict, league_id, tournament_id = self.__get_team_id_dict(year, league_name)
|
|
112
|
+
|
|
113
|
+
player_dict = {}
|
|
114
|
+
|
|
115
|
+
for team_id, team_name in team_dict.items():
|
|
116
|
+
|
|
117
|
+
team_url = f'https://{root_site}/api/data/leagueseasondeepstats?lng=en-GB&id={league_id}&season={tournament_id}&type=players&stat=mins_played&teamId={team_id}'
|
|
118
|
+
r = self.session.get(team_url)
|
|
119
|
+
minutes_played_data = r.json()
|
|
120
|
+
|
|
121
|
+
played_players = minutes_played_data['statsData']
|
|
122
|
+
|
|
123
|
+
for player in played_players:
|
|
124
|
+
player_dict.update({player['id']: (player['name'], player['statValue']['value'])})
|
|
125
|
+
|
|
126
|
+
return player_dict
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def get_player_data_df(self, year, league_name) -> pd.DataFrame:
|
|
131
|
+
|
|
132
|
+
'''
|
|
133
|
+
This method scrapes the fotmob player data, for every player in the given league
|
|
134
|
+
|
|
135
|
+
:param year: starting year of the league (ex. 2025)
|
|
136
|
+
:param league_name: league name in the package's standardised format: (ex. 'ENG-Premier League')
|
|
137
|
+
:return: a pd.Dataframe instance containing the player data.
|
|
138
|
+
'''
|
|
139
|
+
|
|
140
|
+
league_country_to_find = league_to_fotmob_dict[league_name]
|
|
141
|
+
year_to_find = year_to_fotmob_season[year]
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
player_dict = self.__get_player_id_dict(year, league_name)
|
|
145
|
+
|
|
146
|
+
# All player rows will be added to this list before being assembled into a dataframe
|
|
147
|
+
player_data_list = []
|
|
148
|
+
|
|
149
|
+
for player_id, player_name_mins_array in player_dict.items():
|
|
150
|
+
|
|
151
|
+
player_name = player_name_mins_array[0]
|
|
152
|
+
player_mins_played = player_name_mins_array[1]
|
|
153
|
+
|
|
154
|
+
player_url = f'https://www.fotmob.com/api/data/playerData?id={player_id}'
|
|
155
|
+
r = self.session.get(player_url)
|
|
156
|
+
player_data = r.json()
|
|
157
|
+
stat_seasons = player_data.get('statSeasons')
|
|
158
|
+
if stat_seasons is None:
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
entry_id = '' #Find the entryId to put in the URL to get data for the correct league
|
|
162
|
+
for season in stat_seasons:
|
|
163
|
+
leagues = season['tournaments']
|
|
164
|
+
for league in leagues:
|
|
165
|
+
if league['name'] == league_country_to_find[0] and season['seasonName'] == year_to_find:
|
|
166
|
+
entry_id = league['entryId']
|
|
167
|
+
break
|
|
168
|
+
|
|
169
|
+
if entry_id == '':
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
player_stats_url = f'https://www.fotmob.com/api/data/playerStats?playerId={player_id}&seasonId={entry_id}&isFirstSeason=false'
|
|
173
|
+
r = self.session.get(player_stats_url)
|
|
174
|
+
player_stats_data = r.json()
|
|
175
|
+
|
|
176
|
+
print(player_name)
|
|
177
|
+
print(player_id)
|
|
178
|
+
print(player_stats_data.keys())
|
|
179
|
+
print(player_stats_url)
|
|
180
|
+
print(' ')
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
player_data_dicts = player_stats_data.get('statsSection', {}).get('items', {})
|
|
185
|
+
player_data_metadata_dict = player_stats_data.get('topStatCard', {}).get('items', {})
|
|
186
|
+
combined_stat_dict = {}
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
for stat_dict in player_data_metadata_dict:
|
|
190
|
+
combined_stat_dict.update({stat_dict['title']: stat_dict['statValue']})
|
|
191
|
+
|
|
192
|
+
for player_stat_category_dict in player_data_dicts:
|
|
193
|
+
player_stat_dict = player_stat_category_dict['items']
|
|
194
|
+
for stat_dict in player_stat_dict:
|
|
195
|
+
combined_stat_dict.update({stat_dict['title']: stat_dict['statValue']})
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
player_info_list = player_data.get('playerInformation', [])
|
|
199
|
+
player_info_dict = {}
|
|
200
|
+
for info in player_info_list:
|
|
201
|
+
title = info.get('title')
|
|
202
|
+
if title:
|
|
203
|
+
player_info_dict[title] = info.get('value', {})
|
|
204
|
+
|
|
205
|
+
#Get correct club
|
|
206
|
+
club_name = ''
|
|
207
|
+
for season in player_data.get('careerHistory', {}).get('careerItems', {}).get('senior', {}).get('seasonEntries', []):
|
|
208
|
+
if season.get('seasonName', '') == year_to_fotmob_season[year] and season.get('tournamentStats', [{}])[0].get('leagueName') == league_country_to_find[0]:
|
|
209
|
+
club_name = season.get('team', '')
|
|
210
|
+
break
|
|
211
|
+
|
|
212
|
+
#Get main position
|
|
213
|
+
main_position = ''
|
|
214
|
+
for position in player_data.get('positionDescription', {}).get('positions', []):
|
|
215
|
+
if position.get('isMainPosition', False):
|
|
216
|
+
main_position = position.get('strPosShort', {}).get('label', '')
|
|
217
|
+
break
|
|
218
|
+
if main_position == '':
|
|
219
|
+
main_position = player_data.get('positionDescription', {}).get('positions', [{}])[0].get('strPosShort', {}).get('label', None)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
#Ensure primaryTeam exists
|
|
223
|
+
if player_data['primaryTeam'] is None:
|
|
224
|
+
player_data['primaryTeam'] = {}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
player_data_dict = {
|
|
229
|
+
#Metadata
|
|
230
|
+
'id': player_data.get('id', None),
|
|
231
|
+
'name': player_data.get('name', None),
|
|
232
|
+
'club': club_name,
|
|
233
|
+
'isCaptain': player_data.get('isCaptain', None),
|
|
234
|
+
'teamColour': player_data.get('primaryTeam', {}).get('teamColors', {}).get('color', None),
|
|
235
|
+
'position': main_position,
|
|
236
|
+
'height': player_info_dict.get('Height', {}).get('numberValue', None),
|
|
237
|
+
'shirtNumber': player_info_dict.get('Shirt', {}).get('numberValue', None),
|
|
238
|
+
'Age': player_info_dict.get('Age', {}).get('numberValue', None),
|
|
239
|
+
'preferredFoot': player_info_dict.get('Preferred foot', {}).get('key', None),
|
|
240
|
+
'nation': player_info_dict.get('Country', {}).get('fallback', None),
|
|
241
|
+
'marketValue': player_info_dict.get('Market value', {}).get('numberValue', None),
|
|
242
|
+
'contractEnd': player_info_dict.get('Contract end', {}).get('dateValue', None),
|
|
243
|
+
'league': league_to_fotmob_dict[league_name][0],
|
|
244
|
+
'minutesPlayed': player_mins_played,
|
|
245
|
+
'appearances': combined_stat_dict.get('Matches', None),
|
|
246
|
+
'started': combined_stat_dict.get('Started', None),
|
|
247
|
+
#Goalkeeping
|
|
248
|
+
'saves': combined_stat_dict.get('Saves', None),
|
|
249
|
+
'savePercentage': combined_stat_dict.get('Save percentage', None),
|
|
250
|
+
'goalsConceded': combined_stat_dict.get('Goals conceded', None),
|
|
251
|
+
'goalsPrevented': combined_stat_dict.get('Goals prevented', None),
|
|
252
|
+
'penaltySaves': combined_stat_dict.get('Penalty saves', None),
|
|
253
|
+
'penaltySavePercentage': combined_stat_dict.get('Penalty save %', None),
|
|
254
|
+
'errorLedToGoal': combined_stat_dict.get('Error led to goal', None),
|
|
255
|
+
'actedAsSweeper': combined_stat_dict.get('Acted as sweeper', None),
|
|
256
|
+
'highClaims': combined_stat_dict.get('High claims', None),
|
|
257
|
+
'goals': combined_stat_dict.get('Goals', None),
|
|
258
|
+
'expectedGoals': combined_stat_dict.get('xG', None),
|
|
259
|
+
'expectedGoalsOnTarget': combined_stat_dict.get('xGOT', None),
|
|
260
|
+
'nonPenaltyxG': combined_stat_dict.get('xG excl. penalty', None),
|
|
261
|
+
'shots': combined_stat_dict.get('Shots', None),
|
|
262
|
+
'shotsOnTarget': combined_stat_dict.get('Shots on target', None),
|
|
263
|
+
'shotsHeaded': combined_stat_dict.get('Headed shots', None),
|
|
264
|
+
#Passing
|
|
265
|
+
'assists': combined_stat_dict.get('Assists', None),
|
|
266
|
+
'xA': combined_stat_dict.get('xA', None),
|
|
267
|
+
'accuratePasses': combined_stat_dict.get('Accurate passes', None),
|
|
268
|
+
'passAccuracy': combined_stat_dict.get('Pass accuracy', None),
|
|
269
|
+
'accurateLongBalls': combined_stat_dict.get('Accurate long balls', None),
|
|
270
|
+
'accurateLongBallsPercentage': combined_stat_dict.get('Long ball accuracy', None),
|
|
271
|
+
'lineBreakingPasses': combined_stat_dict.get('Line-breaking passes', None),
|
|
272
|
+
'chancesCreated': combined_stat_dict.get('Chances created', None),
|
|
273
|
+
'bigChancesCreated': combined_stat_dict.get('Big chances created', None),
|
|
274
|
+
'successfulCrosses': combined_stat_dict.get('Successful crosses', None),
|
|
275
|
+
'crossAccuracy': combined_stat_dict.get('Cross accuracy', None),
|
|
276
|
+
#Possession
|
|
277
|
+
'dribbles': combined_stat_dict.get('Dribbles', None),
|
|
278
|
+
'dribblesSuccessRate': combined_stat_dict.get('Dribbles success rate', None),
|
|
279
|
+
'duelsWon': combined_stat_dict.get('Duels won', None),
|
|
280
|
+
'duelsWonPercentage': combined_stat_dict.get('Duels won %', None),
|
|
281
|
+
'aerialsWon': combined_stat_dict.get('Aerials won', None),
|
|
282
|
+
'aerialsWonPercentage': combined_stat_dict.get('Aerials won %', None),
|
|
283
|
+
'touches': combined_stat_dict.get('Touches', None),
|
|
284
|
+
'touchesInOppositionBox': combined_stat_dict.get('Touches in opposition box', None),
|
|
285
|
+
'dispossessed': combined_stat_dict.get('Dispossessed', None),
|
|
286
|
+
'foulsWon': combined_stat_dict.get('Fouls won', None),
|
|
287
|
+
#Defensive
|
|
288
|
+
'defensiveActions': combined_stat_dict.get('Defensive actions', None),
|
|
289
|
+
'tackles': combined_stat_dict.get('Tackles', None),
|
|
290
|
+
'interceptions': combined_stat_dict.get('Interceptions', None),
|
|
291
|
+
'blockedScoringAttempts': combined_stat_dict.get('Blocked scoring attempt', None),
|
|
292
|
+
'foulsCommitted': combined_stat_dict.get('Fouls committed', None),
|
|
293
|
+
'penaltiesConceded': combined_stat_dict.get('Penalties conceded', None),
|
|
294
|
+
'recoveries': combined_stat_dict.get('Recoveries', None),
|
|
295
|
+
'dribbledPast': combined_stat_dict.get('Dribbled past', None),
|
|
296
|
+
'clearances': combined_stat_dict.get('Clearances', None),
|
|
297
|
+
'cleanSheets': combined_stat_dict.get('Clean sheets', None),
|
|
298
|
+
'goalsConcededWhileOnPitch': combined_stat_dict.get('Goals conceded while on pitch', None),
|
|
299
|
+
'xGAgainstWhileOnPitch': combined_stat_dict.get('xG against while on pitch', None),
|
|
300
|
+
#Physical
|
|
301
|
+
'topSpeed': combined_stat_dict.get('Top Speed', None),
|
|
302
|
+
'totalDistanceCovered': combined_stat_dict.get('Total Distance Covered', None),
|
|
303
|
+
'distanceRun': combined_stat_dict.get('Running', None),
|
|
304
|
+
'distanceSprinted': combined_stat_dict.get('Sprinting', None),
|
|
305
|
+
'totalSprints': combined_stat_dict.get('Number of Sprints', None),
|
|
306
|
+
#Misc
|
|
307
|
+
'yellowCards': combined_stat_dict.get('Yellow cards', None),
|
|
308
|
+
'redCards': combined_stat_dict.get('Red cards', None),
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
player_data_list.append(player_data_dict)
|
|
312
|
+
|
|
313
|
+
#time.sleep(0.5)
|
|
314
|
+
|
|
315
|
+
player_data_dataframe = pd.DataFrame(data=player_data_list)
|
|
316
|
+
|
|
317
|
+
return player_data_dataframe
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
league_to_fotmob_dict = {'ENG-Premier League': ['Premier League', 'England'],
|
|
2
|
+
'ESP-La Liga': ['LaLiga', 'Spain'],
|
|
3
|
+
'FRA-Ligue 1': ['Ligue 1', 'France'],
|
|
4
|
+
'ITA-Serie A': ['Serie A', 'Italy'],
|
|
5
|
+
'GER-Bundesliga': ['Bundesliga', 'Germany'],
|
|
6
|
+
'EUR-UEFA Champions League': ['Champions League', 'International'],
|
|
7
|
+
'EUR-UEFA Europa League': ['Europa League', 'International'],
|
|
8
|
+
'EUR-UEFA Conference League': ['Conference League', 'International'],
|
|
9
|
+
'INT-EURO': ['EURO', 'International'],
|
|
10
|
+
'INT-FIFA World Cup': ['FIFA World Cup', 'International'],
|
|
11
|
+
}
|