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
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import numpy as np
|
|
4
|
+
import random
|
|
5
|
+
import json
|
|
6
|
+
from bs4 import BeautifulSoup
|
|
7
|
+
from curl_cffi import requests as cffi_requests
|
|
8
|
+
from GetFootballData.scraper_utilities.league_to_sofascore_map import league_to_sofascore_dict
|
|
9
|
+
from GetFootballData.scraper_utilities.year_maps import year_to_sofascore_season
|
|
10
|
+
|
|
11
|
+
pd.set_option('display.max_columns', None)
|
|
12
|
+
pd.set_option('display.max_rows', None)
|
|
13
|
+
|
|
14
|
+
root_site = 'www.sofascore.com'
|
|
15
|
+
|
|
16
|
+
class SofascoreScraper:
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self.session = cffi_requests.Session()
|
|
20
|
+
|
|
21
|
+
self.session.headers.update({
|
|
22
|
+
"Accept": "*/*",
|
|
23
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
24
|
+
"Origin": "https://sofascore.com",
|
|
25
|
+
"Referer": "https://sofascore.com/",
|
|
26
|
+
"Sec-Fetch-Dest": "empty",
|
|
27
|
+
"Sec-Fetch-Mode": "cors",
|
|
28
|
+
"Sec-Fetch-Site": "same-site"
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
self.warm_up()
|
|
32
|
+
|
|
33
|
+
self.stat_name_dict = {
|
|
34
|
+
"Accurate crosses": "accurateCrosses",
|
|
35
|
+
"Accurate crosses %": "accurateCrossesPercentage",
|
|
36
|
+
"Accurate final third passes": "accurateFinalThirdPasses",
|
|
37
|
+
"Long balls (accurate)": "accurateLongBalls",
|
|
38
|
+
"Accurate long balls %": "accurateLongBallsPercentage",
|
|
39
|
+
"Passes in opposition half (acc.)": "accurateOppositionHalfPasses",
|
|
40
|
+
"Passes in own half (acc.)": "accurateOwnHalfPasses",
|
|
41
|
+
"Accurate passes": "accuratePasses",
|
|
42
|
+
"Accurate passes %": "accuratePassesPercentage",
|
|
43
|
+
"Aerial duels won": "aerialDuelsWon",
|
|
44
|
+
"Aerial duels won %": "aerialDuelsWonPercentage",
|
|
45
|
+
"Appearances": "appearances",
|
|
46
|
+
"Assists": "assists",
|
|
47
|
+
"Big chances created": "bigChancesCreated",
|
|
48
|
+
"Big chances missed": "bigChancesMissed",
|
|
49
|
+
"Blocked shots": "blockedShots",
|
|
50
|
+
"Clean sheets": "cleanSheet",
|
|
51
|
+
"Clearances": "clearances",
|
|
52
|
+
"Crosses not claimed": "crossesNotClaimed",
|
|
53
|
+
"Dispossessed": "dispossessed",
|
|
54
|
+
"Dribbled past": "dribbledPast",
|
|
55
|
+
"Errors leading to goal": "errorLeadToGoal",
|
|
56
|
+
"Errors leading to shot": "errorLeadToShot",
|
|
57
|
+
"Expected goals (xG)": "expectedGoals",
|
|
58
|
+
"Fouls": "fouls",
|
|
59
|
+
"Free kick goals": "freeKickGoal",
|
|
60
|
+
"Goal conversion %": "goalConversionPercentage",
|
|
61
|
+
"Goals": "goals",
|
|
62
|
+
"Goals conceded inside the box": "goalsConcededInsideTheBox",
|
|
63
|
+
"Goals conceded outside the box": "goalsConcededOutsideTheBox",
|
|
64
|
+
"Goals from inside the box": "goalsFromInsideTheBox",
|
|
65
|
+
"Goals from outside the box": "goalsFromOutsideTheBox",
|
|
66
|
+
"Ground duels (won)": "groundDuelsWon",
|
|
67
|
+
"Ground duels won %": "groundDuelsWonPercentage",
|
|
68
|
+
"Headed goals": "headedGoals",
|
|
69
|
+
"High claims": "highClaims",
|
|
70
|
+
"Hit woodwork": "hitWoodwork",
|
|
71
|
+
"Inaccurate passes": "inaccuratePasses",
|
|
72
|
+
"Interceptions": "interceptions",
|
|
73
|
+
"Key passes": "keyPasses",
|
|
74
|
+
"Left-footed goals": "leftFootGoals",
|
|
75
|
+
"Started": "matchesStarted",
|
|
76
|
+
"Minutes played": "minutesPlayed",
|
|
77
|
+
"Offsides": "offsides",
|
|
78
|
+
"Own goals": "ownGoals",
|
|
79
|
+
"Passes to assist": "passToAssist",
|
|
80
|
+
"Penalties taken": "penaltiesTaken",
|
|
81
|
+
"Penalties committed": "penaltyConceded",
|
|
82
|
+
"Penalty conversion": "penaltyConversion",
|
|
83
|
+
"Penalties faced (saved)": "penaltyFaced",
|
|
84
|
+
"Penalty goals": "penaltyGoals",
|
|
85
|
+
"Penalties saved": "penaltySave",
|
|
86
|
+
"Penalties won": "penaltyWon",
|
|
87
|
+
"Possession lost": "possessionLost",
|
|
88
|
+
"Punches": "punches",
|
|
89
|
+
"Sofascore Rating": "rating",
|
|
90
|
+
"Red cards": "redCards",
|
|
91
|
+
"Right-footed goals": "rightFootGoals",
|
|
92
|
+
"Runs out": "runsOut",
|
|
93
|
+
"Saves from inside box": "savedShotsFromInsideTheBox",
|
|
94
|
+
"Saved shots from outside the box": "savedShotsFromOutsideTheBox",
|
|
95
|
+
"Total saves": "saves",
|
|
96
|
+
"Set piece conversion %": "setPieceConversion",
|
|
97
|
+
"Shots from set piece": "shotFromSetPiece",
|
|
98
|
+
"Shots off target": "shotsOffTarget",
|
|
99
|
+
"Shots on target": "shotsOnTarget",
|
|
100
|
+
"Succ. dribbles": "successfulDribbles",
|
|
101
|
+
"Successful dribbles %": "successfulDribblesPercentage",
|
|
102
|
+
"Successful runs out": "successfulRunsOut",
|
|
103
|
+
"Tackles": "tackles",
|
|
104
|
+
"Total duels won": "totalDuelsWon",
|
|
105
|
+
"Total duels won %": "totalDuelsWonPercentage",
|
|
106
|
+
"Total passes": "totalPasses",
|
|
107
|
+
"Total shots": "totalShots",
|
|
108
|
+
"Was fouled": "wasFouled",
|
|
109
|
+
"Yellow cards": "yellowCards",
|
|
110
|
+
'Accurate chip passes': 'accurateChippedPasses',
|
|
111
|
+
'Aerial duels lost': 'aerialLost',
|
|
112
|
+
'Penalty misses': 'attemptPenaltyMiss',
|
|
113
|
+
'Penalty post hits': 'attemptPenaltyPost',
|
|
114
|
+
'Penalties on target': 'attemptPenaltyTarget',
|
|
115
|
+
'Balls recovered': 'ballRecovery',
|
|
116
|
+
'Matches rated': 'countRating',
|
|
117
|
+
'Red': 'directRedCards',
|
|
118
|
+
'Duels lost': 'duelLost',
|
|
119
|
+
'Expected assists (xA)': 'expectedAssists',
|
|
120
|
+
'Goal kicks': 'goalKicks',
|
|
121
|
+
'Goal involvements': 'goalsAssistsSum',
|
|
122
|
+
'Goals conceded': 'goalsConceded',
|
|
123
|
+
'Goals prevented': 'goalsPrevented',
|
|
124
|
+
'Outfield blocks': 'outfielderBlocks',
|
|
125
|
+
'Possession won (final third)': 'possessionWonAttThird',
|
|
126
|
+
'Saves caught': 'savesCaught',
|
|
127
|
+
'Saves parried': 'savesParried',
|
|
128
|
+
'Scoring frequency': 'scoringFrequency',
|
|
129
|
+
'Shots from inside the box': 'shotsFromInsideTheBox',
|
|
130
|
+
'Shots from outside the box': 'shotsFromOutsideTheBox',
|
|
131
|
+
'Tackles won': 'tacklesWon',
|
|
132
|
+
'Tackles won percentage': 'tacklesWonPercentage',
|
|
133
|
+
'Top speed': 'topSpeed',
|
|
134
|
+
'Total attempted assists': 'totalAttemptAssist',
|
|
135
|
+
'Total chipped passes': 'totalChippedPasses',
|
|
136
|
+
'Total contest': 'totalContest',
|
|
137
|
+
'Total cross': 'totalCross',
|
|
138
|
+
'Total long balls': 'totalLongBalls',
|
|
139
|
+
'Total opposition half passes': 'totalOppositionHalfPasses',
|
|
140
|
+
'Total own half passes': 'totalOwnHalfPasses',
|
|
141
|
+
'Total rating': 'totalRating',
|
|
142
|
+
'TOTW Appearances': 'totwAppearances',
|
|
143
|
+
'Touches': 'touches'
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
self.concatenated_stat_names = "%2C".join(self.stat_name_dict.values())
|
|
147
|
+
|
|
148
|
+
def warm_up(self):
|
|
149
|
+
"""Request sofascore page to familiarise session with sofascore."""
|
|
150
|
+
try:
|
|
151
|
+
self.session.get("https://sofascore.com/", timeout=5)
|
|
152
|
+
time.sleep(random.uniform(1.5, 3.0))
|
|
153
|
+
except Exception as e:
|
|
154
|
+
print(f"Warm-up handshake warning: {e}")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def __get_team_id_dict(self, year: int, league_name: str):
|
|
158
|
+
|
|
159
|
+
'''
|
|
160
|
+
:param year: Takes the starting year of the given season.
|
|
161
|
+
:param league_name: Takes the name of the given league in the given format.
|
|
162
|
+
:return *team_id_dict: a dict containing {player_id: player_name, ...} combinations that sofascore uses.
|
|
163
|
+
*league_id: The league id of the given league.
|
|
164
|
+
*season_id: The season id of the given season in that league. Used to find the specific season in the given league.
|
|
165
|
+
'''
|
|
166
|
+
|
|
167
|
+
league_info_list = league_to_sofascore_dict[league_name]
|
|
168
|
+
country = league_info_list[0]
|
|
169
|
+
league = league_info_list[1]
|
|
170
|
+
league_id = league_info_list[2]
|
|
171
|
+
|
|
172
|
+
url = f'https://{root_site}/football/tournament/{country}/{league}/{league_id}?page=1'
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
r = self.session.get(url, timeout=5)
|
|
176
|
+
soup = BeautifulSoup(r.text, "html.parser")
|
|
177
|
+
next_data = soup.find("script", id="__NEXT_DATA__")
|
|
178
|
+
data = json.loads(next_data.string)
|
|
179
|
+
seasons = data["props"]["pageProps"]['seasons']
|
|
180
|
+
|
|
181
|
+
season_id = ''
|
|
182
|
+
for season in seasons:
|
|
183
|
+
if season['year'] == year_to_sofascore_season[year]:
|
|
184
|
+
season_id = season['id']
|
|
185
|
+
|
|
186
|
+
if season_id == '':
|
|
187
|
+
raise Exception('No season ID found for given year')
|
|
188
|
+
|
|
189
|
+
url = f'https://{root_site}/api/v1/unique-tournament/{league_id}/season/{season_id}/statistics/info'
|
|
190
|
+
|
|
191
|
+
r = self.session.get(url, timeout=5)
|
|
192
|
+
|
|
193
|
+
team_id_dict = {}
|
|
194
|
+
|
|
195
|
+
team_data = r.json()
|
|
196
|
+
team_data = team_data['teams']
|
|
197
|
+
|
|
198
|
+
for team in team_data:
|
|
199
|
+
team_id_dict.update({team['id']: team['name']})
|
|
200
|
+
|
|
201
|
+
return team_id_dict, league_id, season_id
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def get_player_id_dicts(self, year: int, league_name: str) -> dict[int, dict[str, str | dict[int, str]]]:
|
|
206
|
+
|
|
207
|
+
'''
|
|
208
|
+
:param year: Takes the starting year of the given season.
|
|
209
|
+
:param league_name: Takes the name of the given league in the given format.
|
|
210
|
+
:return: a dictionary of team names mapping to dictionaries containing player ids and names in the format {team_id: {name: team_name, player_dict: {player_id: player_name, ...}}, ...}.
|
|
211
|
+
'''
|
|
212
|
+
|
|
213
|
+
team_id_dict, league_id, season_id = self.__get_team_id_dict(year=year, league_name=league_name)
|
|
214
|
+
|
|
215
|
+
player_id_dicts = {}
|
|
216
|
+
|
|
217
|
+
for team_id, team_name in team_id_dict.items():
|
|
218
|
+
|
|
219
|
+
player_stats_url = f'https://{root_site}/api/v1/team/{team_id}/unique-tournament/{league_id}/season/{season_id}/top-players/overall'
|
|
220
|
+
|
|
221
|
+
r = self.session.get(player_stats_url, timeout=5)
|
|
222
|
+
|
|
223
|
+
data = r.json()
|
|
224
|
+
data = data['topPlayers']['rating']
|
|
225
|
+
|
|
226
|
+
team_player_dict = {}
|
|
227
|
+
|
|
228
|
+
for player_dict in data:
|
|
229
|
+
player_name = player_dict['player']['name']
|
|
230
|
+
player_id = player_dict['player']['id']
|
|
231
|
+
|
|
232
|
+
team_player_dict.update({player_id: player_name})
|
|
233
|
+
player_id_dicts[team_id] = {'team_name': team_name, 'player_id_dict': team_player_dict}
|
|
234
|
+
|
|
235
|
+
return player_id_dicts
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def get_player_metadata_df(self, year: int, league_name: str) -> pd.DataFrame:
|
|
239
|
+
|
|
240
|
+
'''
|
|
241
|
+
:param year: starting year of the given season.
|
|
242
|
+
:param league_name: name of the league according to the given format.
|
|
243
|
+
:return: a pd.DataFrame containing general data about each player in a given league (height, preferred foot, etc.).
|
|
244
|
+
'''
|
|
245
|
+
|
|
246
|
+
player_id_dicts = self.get_player_id_dicts(year=year, league_name=league_name)
|
|
247
|
+
|
|
248
|
+
player_data_list = []
|
|
249
|
+
|
|
250
|
+
for team_id, player_dict in player_id_dicts.items():
|
|
251
|
+
|
|
252
|
+
team_name = player_dict['team_name']
|
|
253
|
+
player_id_dict = player_dict['player_id_dict']
|
|
254
|
+
|
|
255
|
+
for player_id, player_name in player_id_dict.items():
|
|
256
|
+
|
|
257
|
+
player_metadata_url = f'https://{root_site}/api/v1/player/{player_id}'
|
|
258
|
+
|
|
259
|
+
r = self.session.get(player_metadata_url, timeout=5)
|
|
260
|
+
metadata = r.json()
|
|
261
|
+
|
|
262
|
+
player = metadata.get('player', {})
|
|
263
|
+
country = player.get('country', {})
|
|
264
|
+
|
|
265
|
+
player_info = {
|
|
266
|
+
'player_name': player.get('name', None),
|
|
267
|
+
'sofascore_id': player.get('id', None),
|
|
268
|
+
'dateOfBirth': player.get('dateOfBirth', None),
|
|
269
|
+
'country_name': country.get('name', None),
|
|
270
|
+
'league': league_name,
|
|
271
|
+
'team_name': team_name,
|
|
272
|
+
'team_id': team_id,
|
|
273
|
+
'shirtNumber': player.get('jerseyNumber', None),
|
|
274
|
+
'height': player.get('height', None),
|
|
275
|
+
'weight': player.get('weight', None),
|
|
276
|
+
'general_position': player.get('position', None),
|
|
277
|
+
'position': player.get('positionsDetailed', [None])[0],
|
|
278
|
+
'preferredFoot': player.get('preferredFoot', None),
|
|
279
|
+
'marketValue': player.get('proposedMarketValue', None)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
player_data_list.append(player_info)
|
|
283
|
+
|
|
284
|
+
player_metadata_df = pd.DataFrame(player_data_list)
|
|
285
|
+
|
|
286
|
+
return player_metadata_df
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def get_player_stat_df(self, year: int, league_name: str, stat_type='total', specific_stats=None, position_list=None, nationality_code_list=None, team_id_list=None, min_appearances=None, age=None, age_bound='EQ', home_or_away_only=None, preferred_foot=None) -> pd.DataFrame:
|
|
290
|
+
|
|
291
|
+
'''
|
|
292
|
+
:param year: starting year of the league in the given format
|
|
293
|
+
:param league_name: name of the league in the wanted format
|
|
294
|
+
:param stat_type: 'total', 'per90', 'perGame'
|
|
295
|
+
:param specific_stats: accepts a list of specific stats to return. Stats can be listed as seen on sofascore, in a league->stats->detailed tab
|
|
296
|
+
:param position_list: accepts a list of positions to return. options ['G', 'D', 'M', 'F']
|
|
297
|
+
:param nationality_code_list: accepts a list of nationality codes to return.
|
|
298
|
+
:param team_id_list: enter a list of sofascore's team IDs to return data only from those teams.
|
|
299
|
+
:param min_appearances: min appearances of a player in the given league/year
|
|
300
|
+
:param age: age of the player in the given league/year
|
|
301
|
+
:param age_bound: options: 'LT', 'EQ', 'GT'
|
|
302
|
+
:param home_or_away_only: 'home', 'away'
|
|
303
|
+
:param preferred_foot: 'left', 'right'. Returns only players with the given preferred foot.
|
|
304
|
+
:return: a dataframe containing all records of stat data for players in a given season and league
|
|
305
|
+
'''
|
|
306
|
+
|
|
307
|
+
try:
|
|
308
|
+
league_data = league_to_sofascore_dict[league_name]
|
|
309
|
+
except:
|
|
310
|
+
print(f'league {league_name} not found in sofascore dict.')
|
|
311
|
+
league_data = None
|
|
312
|
+
|
|
313
|
+
if league_data is None:
|
|
314
|
+
return pd.DataFrame()
|
|
315
|
+
|
|
316
|
+
league_id = league_data[2]
|
|
317
|
+
|
|
318
|
+
player_data_list = []
|
|
319
|
+
|
|
320
|
+
#f'https://www.sofascore.com/api/v1/unique-tournament/17/season/96668/statistics?limit=20&order=-rating&accumulation=per90&group=summary&minApps=yes'
|
|
321
|
+
#f'https://www.sofascore.com/api/v1/unique-tournament/17/season/96668/statistics?limit=20&order=-rating&accumulation=per90&fields=goals%2CsuccessfulDribbles%2Ctackles%2Cassists%2CaccuratePassesPercentage%2Crating&filters=position.in.G~D~M~F'
|
|
322
|
+
#f'https://www.sofascore.com/api/v1/unique-tournament/17/season/96668/statistics?limit=20&order=-rating&accumulation=per90&fields=goals%2CsuccessfulDribbles%2Ctackles%2Cassists%2CaccuratePassesPercentage%2Crating&filters=type.EQ.home%2Cappearances.GT.5%2Cage.GT.10%2Cposition.in.G~D~F~M%2CpreferredFoot.EQ.Right%2Cteam.in.35~42~40~33%2Cnationality.in.EN~AR~BR~DE~FR~US~UY~SX~PT~IT'
|
|
323
|
+
|
|
324
|
+
#Get season_id
|
|
325
|
+
|
|
326
|
+
season_url = f'https://www.sofascore.com/api/v1/unique-tournament/{league_id}/seasons'
|
|
327
|
+
|
|
328
|
+
r = self.session.get(season_url, timeout=5)
|
|
329
|
+
|
|
330
|
+
season_data = r.json()
|
|
331
|
+
|
|
332
|
+
season_id = ''
|
|
333
|
+
|
|
334
|
+
# need to get in format '26\/27'
|
|
335
|
+
shortened_year = str(year)[-2:]
|
|
336
|
+
season_in_sofascore_format = f'{shortened_year}/{int(shortened_year)+1}'
|
|
337
|
+
|
|
338
|
+
for season in season_data['seasons']:
|
|
339
|
+
if season_in_sofascore_format == season['year'] or str(year) == season['year']:
|
|
340
|
+
season_id = season['id']
|
|
341
|
+
break
|
|
342
|
+
|
|
343
|
+
if season_id == '':
|
|
344
|
+
print(f'No season ID found for {league_name} in {year}')
|
|
345
|
+
return pd.DataFrame()
|
|
346
|
+
|
|
347
|
+
limit = 100
|
|
348
|
+
offset = 0
|
|
349
|
+
|
|
350
|
+
player_counter = 0
|
|
351
|
+
|
|
352
|
+
last_page_complete = False
|
|
353
|
+
|
|
354
|
+
while not last_page_complete:
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
base_player_stat_url = f"https://www.sofascore.com/api/v1/unique-tournament/{league_id}/season/{season_id}/statistics?limit={limit}&offset={offset}&order=-rating"
|
|
358
|
+
|
|
359
|
+
if stat_type in ['total', 'per90', 'perGame']:
|
|
360
|
+
stat_type_string = f'&accumulation={stat_type}'
|
|
361
|
+
else:
|
|
362
|
+
print(f'stat type {stat_type} not in the list. Options include: "total", "per90", "perGame" Defaulting to "total"')
|
|
363
|
+
stat_type_string = f'&accumulation=total'
|
|
364
|
+
base_player_stat_url += stat_type_string
|
|
365
|
+
|
|
366
|
+
if specific_stats is not None:
|
|
367
|
+
stat_list = [self.stat_name_dict[x] + '%2C' for x in specific_stats]
|
|
368
|
+
stat_string = ''.join(stat_list)
|
|
369
|
+
stat_string = stat_string[:-3]
|
|
370
|
+
base_player_stat_url += f'&fields={stat_string}'
|
|
371
|
+
else:
|
|
372
|
+
stat_list = [x + '%2C' for x in self.stat_name_dict.values()]
|
|
373
|
+
stat_string = ''.join(stat_list)
|
|
374
|
+
stat_string = stat_string[:-3]
|
|
375
|
+
base_player_stat_url += f'&fields={stat_string}'
|
|
376
|
+
|
|
377
|
+
#FILTERS
|
|
378
|
+
|
|
379
|
+
#positions
|
|
380
|
+
|
|
381
|
+
base_player_stat_url += f'&filters=' #Filters is needed by default for the position_string, so the guaranteed %2C needs removing at the end
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
if home_or_away_only is not None:
|
|
385
|
+
if home_or_away_only in ['home', 'away']:
|
|
386
|
+
home_or_away_only_string = f'type.EQ.{home_or_away_only}%2C'
|
|
387
|
+
base_player_stat_url += home_or_away_only_string
|
|
388
|
+
|
|
389
|
+
if min_appearances is not None:
|
|
390
|
+
min_appearances_string = f'appearances.GT.{min_appearances}%2C'
|
|
391
|
+
base_player_stat_url += min_appearances_string
|
|
392
|
+
|
|
393
|
+
if age is not None:
|
|
394
|
+
if age_bound in ['LT', 'EQ', 'GT']:
|
|
395
|
+
age_string = f'age.{age_bound}.{age}%2C'
|
|
396
|
+
base_player_stat_url += age_string
|
|
397
|
+
else:
|
|
398
|
+
raise Exception(f'Invalid age bound {age_bound}. Must be "LT" or "EQ" or "GT"')
|
|
399
|
+
|
|
400
|
+
if position_list is None:
|
|
401
|
+
position_string = f'position.in.G~D~F~M%2C'
|
|
402
|
+
else:
|
|
403
|
+
position_string = [pos + '~' for pos in position_list]
|
|
404
|
+
position_string = position_string[:-1]
|
|
405
|
+
position_string = f'position.in.{position_string}%2C'
|
|
406
|
+
|
|
407
|
+
base_player_stat_url += position_string
|
|
408
|
+
|
|
409
|
+
if preferred_foot is not None:
|
|
410
|
+
if preferred_foot in ['left', 'right']:
|
|
411
|
+
preferred_foot_string = f'type.EQ.{preferred_foot}%2C'
|
|
412
|
+
base_player_stat_url += preferred_foot_string
|
|
413
|
+
|
|
414
|
+
#nationalities
|
|
415
|
+
|
|
416
|
+
if nationality_code_list is not None:
|
|
417
|
+
nationality_string = [nat + '~' for nat in nationality_code_list]
|
|
418
|
+
nationality_string = nationality_string[:-1]
|
|
419
|
+
nationality_string = f'nationality.in.{nationality_string}%2C'
|
|
420
|
+
base_player_stat_url += nationality_string
|
|
421
|
+
|
|
422
|
+
#teams
|
|
423
|
+
|
|
424
|
+
if team_id_list is not None:
|
|
425
|
+
team_string = [nat + '~' for nat in team_id_list]
|
|
426
|
+
team_string = team_string[:-1]
|
|
427
|
+
team_string = f'team.in.{team_string}%2C'
|
|
428
|
+
base_player_stat_url += team_string
|
|
429
|
+
|
|
430
|
+
base_player_stat_url = base_player_stat_url[:-3]
|
|
431
|
+
|
|
432
|
+
'''
|
|
433
|
+
player_stats_url = "https://api.sofascore.com/api/v1" + \
|
|
434
|
+
f"/unique-tournament/{league_id}/season/{'96668'}/statistics" + \
|
|
435
|
+
f"?limit=100&offset='0'" + \
|
|
436
|
+
f"&accumulation=total" + \
|
|
437
|
+
f"&fields={self.concatenated_stat_names}" + \
|
|
438
|
+
f"&filters=position.in.G~D~M~F"
|
|
439
|
+
|
|
440
|
+
#print(player_stats_url)
|
|
441
|
+
'''
|
|
442
|
+
|
|
443
|
+
r = self.session.get(base_player_stat_url, timeout=5)
|
|
444
|
+
|
|
445
|
+
stat_data = r.json()
|
|
446
|
+
stat_data = stat_data['results']
|
|
447
|
+
|
|
448
|
+
stat_names = self.stat_name_dict.values()
|
|
449
|
+
|
|
450
|
+
for player in stat_data:
|
|
451
|
+
|
|
452
|
+
player_dict = {field: player.get(field, None) for field in stat_names}
|
|
453
|
+
player_dict['player_name'] = player.get('player', {}).get('name', None)
|
|
454
|
+
player_dict['player_id'] = player.get('player', {}).get('id', None)
|
|
455
|
+
player_dict['team_name'] = player.get('team', {}).get('name', None)
|
|
456
|
+
player_dict['team_id'] = player.get('team', {}).get('id', None)
|
|
457
|
+
|
|
458
|
+
player_data_list.append(player_dict)
|
|
459
|
+
|
|
460
|
+
time.sleep(5)
|
|
461
|
+
|
|
462
|
+
player_counter += len(stat_data)
|
|
463
|
+
print(f'Total {player_counter} players read')
|
|
464
|
+
|
|
465
|
+
if len(stat_data) < limit: #Break the while loop when the last page has been read, which will happen when less results are returned than the maximum
|
|
466
|
+
last_page_complete=True
|
|
467
|
+
|
|
468
|
+
offset += limit
|
|
469
|
+
|
|
470
|
+
player_data_dataframe = pd.DataFrame(player_data_list)
|
|
471
|
+
|
|
472
|
+
return player_data_dataframe
|