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.
@@ -0,0 +1,288 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ import requests
4
+ import time
5
+ import random
6
+ from bs4 import BeautifulSoup
7
+ from curl_cffi import requests as cffi_requests
8
+ from GetFootballData.scraper_utilities.league_to_tm_map import league_to_tm_dict
9
+
10
+ pd.set_option('display.max_columns', None)
11
+ pd.set_option('display.max_rows', None)
12
+
13
+ root_site = 'www.transfermarkt.com'
14
+
15
+ class TransfermarktScraper:
16
+
17
+ def __init__(self):
18
+
19
+ self.session = cffi_requests.Session(impersonate='chrome124')
20
+
21
+ self.session.headers.update({"accept": "application/json", "referer": f"https://{root_site}/"})
22
+
23
+ self.warm_up()
24
+
25
+ def warm_up(self):
26
+ """Request fotmob page to familiarise session with fotmob."""
27
+ try:
28
+ self.session.get(f"https://{root_site}/", timeout=5)
29
+ time.sleep(random.uniform(1.5, 3.0))
30
+ except Exception as e:
31
+ print(f"Warm-up handshake warning: {e}")
32
+
33
+ def __get_player_id_df(self, year: int, league: str) -> pd.DataFrame:
34
+
35
+ '''
36
+ This method scrapes the transfermarkt ids, nationality and club for every player in the given league
37
+
38
+ :param year: starting year of the league (ex. 2025)
39
+ :param league: league name according to the package's naming conventions (ex. 'ENG-Premier League', 'FRA-Ligue 1')
40
+ :return: a dataframe containing: player_name, player_id, club_name, nation. If data can't be found, an empty dataframe is returned.
41
+ '''
42
+
43
+ league_dict = league_to_tm_dict[league]
44
+ league_name = league_dict[0]
45
+ league_code = league_dict[1]
46
+
47
+ url = f'https://{root_site}/{league_name}/tabelle/wettbewerb/{league_code}/saison_id/{str(year)}'
48
+
49
+ r = self.session.get(url)
50
+
51
+ if not r:
52
+ return pd.DataFrame()
53
+
54
+ soup = BeautifulSoup(r.text, "html.parser")
55
+
56
+ club_acronym_list = []
57
+
58
+ table = soup.find('table', {'class': 'items'})
59
+
60
+ for row in table.find_all('tr'):
61
+ link = row.find('a', href=True)
62
+ if not link:
63
+ continue
64
+ href = link['href']
65
+ club_acronym = href.split('/')[1]
66
+ club_id = href.split('/')[4]
67
+ club_name = link['title']
68
+ club_acronym_list.append([club_acronym, club_id, club_name])
69
+
70
+ name_id_list = []
71
+
72
+ club_counter = 0
73
+
74
+ for club_acronym in club_acronym_list:
75
+
76
+ club_name = club_acronym[2]
77
+ #squad_url = f'https://www.transfermarkt.com/{club_acronym[0]}/kader/verein/{club_acronym[1]}/plus/0/galerie/0?saison_id={year}'
78
+ squad_url = f'https://{root_site}/{club_acronym[0]}/kader/verein/{club_acronym[1]}/saison_id/{year}'
79
+ r = self.session.get(squad_url)
80
+ soup = BeautifulSoup(r.text, "html.parser")
81
+ table = soup.find('table', {'class': 'items'})
82
+
83
+ for row in table.find_all('table', {'class': 'inline-table'}):
84
+ link_area = row.find('td', {'class': 'hauptlink'})
85
+ if not link_area:
86
+ continue
87
+ link = link_area.find('a', href=True)
88
+ href = link['href']
89
+ player_id = href.split('/')[4]
90
+ player_name = link_area.get_text(strip=True)
91
+
92
+ parent_row = row.find_parent('tr')
93
+
94
+ nation = None
95
+ for area in parent_row.find_all('td', {'class': 'zentriert'}):
96
+ nation_image = area.find('img', class_='flaggenrahmen')
97
+ if not nation_image:
98
+ continue
99
+ nation = nation_image['title']
100
+
101
+ name_id_list.append((player_name, player_id, club_name, nation))
102
+
103
+ club_counter += 1
104
+ print(f'{club_counter}/{len(club_acronym_list)} teams complete.')
105
+
106
+ name_id_df = pd.DataFrame(name_id_list, columns=['player_name', 'player_id', 'club_name', 'nation']).set_index('player_name')
107
+
108
+ return name_id_df
109
+
110
+
111
+
112
+ def get_player_overview_df(self, year: int, league: str) -> pd.DataFrame:
113
+
114
+ dataframe = self.__get_player_id_df(year, league)
115
+
116
+ base_players_url = f'https://tmapi.transfermarkt.technology/players?'
117
+
118
+ team_list = dataframe['club_name'].unique().tolist()
119
+
120
+ final_player_dataframe = pd.DataFrame()
121
+
122
+ for team in team_list:
123
+
124
+ team_player_ids = dataframe[dataframe['club_name'] == team]['player_id'].tolist()
125
+
126
+ team_players_url = base_players_url
127
+
128
+ for player_id in team_player_ids:
129
+
130
+ team_players_url = team_players_url + f'ids[]={player_id}&'
131
+
132
+ team_players_url = team_players_url[:-1]
133
+
134
+ if team_players_url == base_players_url:
135
+ raise Exception(f'No team player ids found for {team}')
136
+
137
+ r = self.session.get(team_players_url)
138
+
139
+ player_data = r.json()['data']
140
+
141
+ team_data_list = []
142
+
143
+ for player in player_data:
144
+
145
+ club_assignment_list = [{}, {}]
146
+ for index, club_dict in enumerate(player['clubAssignments'][:2]): # Only go through first two assignments
147
+ club_assignment_list[index] = club_dict
148
+
149
+ data_dict = {
150
+ 'player_name': player['name'],
151
+ 'player_id': player['id'],
152
+ 'age': player['lifeDates'].get('age', None),
153
+ 'date_of_birth': player['lifeDates'].get('dateOfBirth', None),
154
+ 'birth_place': player['birthPlaceDetails'].get('placeOfBirth', None),
155
+ 'position': player['attributes'].get('position', {}).get('name', None),
156
+ 'team': team,
157
+ 'general_position': player['attributes'].get('position', {}).get('category', None),
158
+ 'preferred_foot': player['attributes'].get('preferredFoot', {}).get('name', None),
159
+ 'height': player['attributes'].get('height', None),
160
+ 'contractEnd':player['attributes'].get('contractUntil', None),
161
+ 'sponsor': player['attributes'].get('outfitter', {}).get('name', None),
162
+ 'agent': player['attributes'].get('consultantAgency', {}).get('name', None),
163
+ 'market_value': player.get('marketValueDetails', {}).get('current', {}).get('value', None),
164
+ 'previous_market_value': player.get('marketValueDetails', {}).get('previous', {}).get('value', None),
165
+ 'highest_market_value': player.get('marketValueDetails', {}).get('highest', {}).get('value', None),
166
+ 'shirt_number': club_assignment_list[0].get('shirtNumber', None),
167
+ 'debut': club_assignment_list[0].get('debut', None),
168
+ 'is_captain': club_assignment_list[0].get('isCaptain', None),
169
+ 'nt_shirt_number': club_assignment_list[1].get('shirtNumber', None),
170
+ 'nt_debut': club_assignment_list[1].get('debut', None),
171
+ 'is_nt_captain': club_assignment_list[1].get('isCaptain', None),
172
+ }
173
+
174
+ team_data_list.append(data_dict)
175
+
176
+ team_stats_df = pd.DataFrame(team_data_list)
177
+ final_player_dataframe = pd.concat([final_player_dataframe, team_stats_df])
178
+
179
+ final_player_dataframe.set_index('player_id', inplace=True)
180
+
181
+ #Set Nationality
182
+ final_player_dataframe = final_player_dataframe.merge(dataframe.reset_index()[['player_id', 'nation']], on='player_id', how='left')
183
+
184
+ return final_player_dataframe
185
+
186
+
187
+ def get_player_transfer_history(self, player_id: str) -> pd.DataFrame:
188
+
189
+ '''
190
+ :param player_id: transfermarkt's player id (as seen in the url of a player's profile)
191
+ :return: A pandas dataframe containing a row for every recorded transfer a given player has had according to transfermarkt. If there is no history, an empty dataframe is returned. Rows include;
192
+
193
+
194
+ Columns:
195
+ - source_name: source club
196
+ - destination_name: destination club
197
+ - source_club_id: source club transfermarkt id
198
+ - destination_club_id: destination club transfermarkt id
199
+ - source_competition: league code of source team
200
+ - destination_competition: league code of destination team
201
+ - contract_start_date: start of contract date
202
+ - contract_end_date: end of contract date
203
+ - season: year of the beginning of the season
204
+ - market_value_at_time: market value at the time of the transfer
205
+ - age: age of the player at the time of the transfer
206
+ - fee: fee of the transfer in euros (€)
207
+ - transfer_type: description of the nature of the transfer ex. loan. Column will not contain data if it is a normal transfer.
208
+ - fee_description: Extra description of the nature of the attached fee, ex. loan fee. Column will not contain data if it is a normal transfer.
209
+ '''
210
+
211
+ url = f"https://tmapi.transfermarkt.technology/transfer/history/player/{player_id}"
212
+
213
+ r = self.session.get(url) #Fetch transfer history page via URL
214
+
215
+ print(player_id)
216
+
217
+ try:
218
+ transfer_history = r.json()["data"]['history']['terminated']
219
+ except:
220
+ return pd.DataFrame()
221
+
222
+
223
+
224
+ club_id_set = set()
225
+
226
+ source_data = transfer_history[0]["transferSource"]
227
+ club_id_set.add(source_data["clubId"])
228
+
229
+ for row in transfer_history:
230
+ source_data = row["transferSource"]
231
+ destination_data = row["transferDestination"]
232
+ club_id_set.add(source_data["clubId"])
233
+ club_id_set.add(destination_data["clubId"])
234
+
235
+ club_url = f'https://tmapi.transfermarkt.technology/clubs?'
236
+ for club_id in club_id_set:
237
+ club_url = club_url + f'ids[]={club_id}&'
238
+ club_url = club_url[:-1]
239
+
240
+
241
+ for i in range(1,5):
242
+ try:
243
+ club_r = self.session.get(club_url) # Fetch club name data from transfermarkt api
244
+ club_data = club_r.json()["data"]
245
+ break
246
+ except requests.exceptions.RequestException as e:
247
+ print(e)
248
+ if i == 5:
249
+ print(f'Could not access transfermarkt club data from api.')
250
+ exit()
251
+ print(f'Request failed, retrying {i+1}/5 after 10 seconds')
252
+ time.sleep(10)
253
+
254
+
255
+
256
+ id_to_club_dict = {}
257
+ for row in club_data:
258
+ id_to_club_dict.update({row['id']:row['name']})
259
+
260
+ transfer_history_list = []
261
+
262
+ for row in transfer_history:
263
+ source_data = row["transferSource"]
264
+ destination_data = row["transferDestination"]
265
+ details_data = row["details"]
266
+ transfer_type_data = row['typeDetails']
267
+ entry = {'player_id': player_id,
268
+ 'source_name': id_to_club_dict[source_data['clubId']],
269
+ 'destination_name': id_to_club_dict[destination_data['clubId']],
270
+ 'source_club_id': source_data['clubId'],
271
+ 'destination_club_id': destination_data['clubId'],
272
+ 'source_competition': source_data['competitionId'],
273
+ 'destination_competition': destination_data['competitionId'],
274
+ 'contract_start_date': details_data["date"],
275
+ 'contract_end_date': details_data["contractUntilDate"],
276
+ 'season': details_data.get("season").get('id'),
277
+ 'market_value_at_time': details_data.get('marketValue', {}).get('value', np.nan),
278
+ 'age': details_data["age"],
279
+ 'fee': details_data.get('fee', {}).get('value', np.nan),
280
+ 'transfer_type': transfer_type_data.get('name', np.nan),
281
+ 'fee_description': transfer_type_data.get('feeDescription', np.nan),
282
+ }
283
+
284
+ transfer_history_list.append(entry)
285
+
286
+ transfer_history_df = pd.DataFrame(transfer_history_list).set_index('player_id')
287
+
288
+ return transfer_history_df
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: GetFootballData
3
+ Version: 0.1.0
4
+ Summary: Scrapers for FotMob, SofaScore and Transfermarkt
5
+ Author: Max Newby
6
+ License-Expression: AGPL-3.0-or-later
7
+ Project-URL: Homepage, https://github.com/MaxNewby/GetFootballData
8
+ Keywords: football,soccer,scraper,analytics,datasets
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE.md
12
+ Requires-Dist: beautifulsoup4>=4.15
13
+ Requires-Dist: curl_cffi>=0.16
14
+ Requires-Dist: numpy>=2.4
15
+ Requires-Dist: pandas>=3.0
16
+ Requires-Dist: requests>=2.34
17
+ Dynamic: license-file
18
+
19
+ # GetFootballData
20
+
21
+ This package contains classes to read data from Sofascore, FotMob and Transfermarkt.
22
+
23
+ To make it easier league naming conventions are derived from other packages such as ScraperFC to make this package more compatible with others.
24
+
25
+ Created by Max Newby
26
+
27
+ >[NOTE] about using the 'league_name' parameter
28
+ >
29
+ >
30
+ >To see the format in which the league_name parameter should be entered in, check the dictionary keys in any _map.py file inside scraper_utilities.
31
+ > Examples include; 'ENG-Premier League', 'ESP-La Liga', 'GER-Bundesliga'.
32
+
33
+ ## <u>Using the Package:</u>
34
+
35
+ ### Sofascore_scraper.py:
36
+
37
+ A SofascoreScraper object comes with the following methods:
38
+
39
+ `get_player_id_dicts(self, year: int, league_name: str) -> dict[int, dict[str, str | dict[int, str]]]`
40
+
41
+ This method returns a dictionary containing {team_id: team_data} pairs. Inside team_data, is the 'team_name' and another dictionary 'player_id_dict' that contains the {player_id, player_name} pairs.
42
+
43
+ year: Takes the starting year of the given season.
44
+ league_name: Takes the name of the given league in the given format.
45
+
46
+ 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, ...}}, ...}.
47
+
48
+
49
+ `get_player_metadata_df(self, year: int, league_name: str) -> pd.Dataframe`
50
+
51
+ This method returns a dataframe containing general data about each player in the league for a given season.
52
+
53
+ :param year: starting year of the given season.
54
+ :param league_name: name of the league according to the given format.
55
+
56
+ :return: a pd.DataFrame containing general data about each player in a given league (height, preferred foot, etc.).
57
+
58
+ `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:`
59
+
60
+ This method will return a dataframe containing statistical data for every player in the league for that given season. Responses can be filtered using the parameters.
61
+
62
+ year: starting year of the league in the given format
63
+ league_name: name of the league in the wanted format
64
+ stat_type: 'total', 'per90', 'perGame'
65
+ specific_stats: accepts a list of specific stats to return. Stats can be listed as seen on sofascore, in a league->stats->detailed tab
66
+ position_list: accepts a list of positions to return. options ['G', 'D', 'M', 'F']
67
+ nationality_code_list: accepts a list of nationality codes to return.
68
+ team_id_list: enter a list of sofascore's team IDs to return data only from those teams.
69
+ min_appearances: min appearances of a player in the given league/year
70
+ age: age of the player in the given league/year
71
+ age_bound: options: 'LT', 'EQ', 'GT'
72
+ home_or_away_only: 'home', 'away'
73
+ preferred_foot: 'left', 'right'. Returns only players with the given preferred foot.
74
+
75
+ return: a dataframe containing all records of stat data of given filters, for players in a given season and league
76
+
77
+ ### fotmob_scraper.py:
78
+
79
+ A FotmobScraper object comes with the following methods:
80
+
81
+ `get_player_data_df(self, year: int, league_name: str) -> pd.DataFrame`
82
+
83
+ This method scrapes statistical player data, for every player in the league for a given season
84
+
85
+ year: starting year of the league (ex. 2025)
86
+ league_name: league name in the package's standardised format: (ex. 'ENG-Premier League')
87
+ return: a pd.Dataframe instance containing the player data.
88
+
89
+
90
+
91
+ ### transfermarkt_scraper.py:
92
+
93
+ A TransfermarktScraper object comes with the following methods:
94
+
95
+ `transfermarkt_scraper.get_player_transfer_history(self, player_id: str) -> pd.DataFrame:`
96
+
97
+ This method returns a dataframe containing a player's transfer history, and data such as fees and contract durations for each transfer.
98
+
99
+ player_id: transfermarkt's player id (as seen in the url of a player's profile)
100
+ return: A pandas dataframe containing a row for every recorded transfer a given player has had according to transfermarkt. If there is no history, an empty dataframe is returned. Rows include;
101
+
102
+
103
+ `transfermarkt_scraper.get_player_overview_df(self, year: int, league: str)`
104
+
105
+ This method scrapes general player data such as transfermarkt ids, nationality and club for every player in the given league
106
+
107
+ year: starting year of the league (ex. 2025)
108
+ league: league name according to the package's naming conventions (ex. 'ENG-Premier League', 'FRA-Ligue 1')
109
+ return: a dataframe containing: player_name, player_id, club_name, nation. If data can't be found, an empty dataframe is returned.
110
+
@@ -0,0 +1,14 @@
1
+ GetFootballData/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ GetFootballData/fotmob_scraper.py,sha256=J6WCSooIHvjpM4WXrlYY6wx0yJxYZIXYXfbqY0vxfLA,14925
3
+ GetFootballData/sofascore_scraper.py,sha256=HGc6-k-crftIXVfkreoORsABdMtMsGVKiOXsC8DOJg0,20556
4
+ GetFootballData/transfermarkt_scraper.py,sha256=VRJtkGSWj9qQ3vQm_ehPVSUvbhv7Qkc1XCDO5Vq4yXk,12067
5
+ GetFootballData/scraper_utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ GetFootballData/scraper_utilities/league_to_fotmob_map.py,sha256=HK6vs_raRvRGYAxgBvFlnWbnKVyqGvigR93VWC66APc,783
7
+ GetFootballData/scraper_utilities/league_to_sofascore_map.py,sha256=_t2EHCprmMfCJ9hbf1swm9OQSDY4_LNfdgklgZIZ5Y4,32945
8
+ GetFootballData/scraper_utilities/league_to_tm_map.py,sha256=HKSChPmTmbc3NTloI3SQW_x7tcxOGBKlWOCoD9fiFKk,320
9
+ GetFootballData/scraper_utilities/year_maps.py,sha256=Ia33YqPyokMlRq51vhijFKq7ab7eZiCxGY1YdcqSxUM,1768
10
+ getfootballdata-0.1.0.dist-info/licenses/LICENSE.md,sha256=sxfZgBWf-IozZGoJkRPevqw7flHraKK9PtjvoMiEs0w,37188
11
+ getfootballdata-0.1.0.dist-info/METADATA,sha256=EZS9J-ApYwJv2gwse_buqmHZxwxjrvU_iPRYPJvbK5w,5260
12
+ getfootballdata-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
13
+ getfootballdata-0.1.0.dist-info/top_level.txt,sha256=vH3LQZS30zltBF8geUokep7WtnAV-ZDZDPPhQQHy0Wg,16
14
+ getfootballdata-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+