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.
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
+ }