maxpreps-scraper 0.1.0__tar.gz

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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Raghav Dhir
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: maxpreps-scraper
3
+ Version: 0.1.0
4
+ Summary: A Python scraper for MaxPreps high school sports data
5
+ Home-page: https://github.com/raghavdhir03/maxpreps_scraper
6
+ Author: Raghav Dhir
7
+ Author-email: dhir.raghav@gmail.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.7
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: requests
15
+ Requires-Dist: beautifulsoup4
16
+ Requires-Dist: tqdm
17
+ Requires-Dist: pandas
18
+ Requires-Dist: lxml
19
+ Requires-Dist: html5lib
20
+ Dynamic: author
21
+ Dynamic: author-email
22
+ Dynamic: classifier
23
+ Dynamic: description
24
+ Dynamic: description-content-type
25
+ Dynamic: home-page
26
+ Dynamic: license-file
27
+ Dynamic: requires-dist
28
+ Dynamic: requires-python
29
+ Dynamic: summary
30
+
31
+ # MaxPreps Web Scraper
32
+
33
+ This Python module scrapes high school sports data from [MaxPreps.com](https://www.maxpreps.com), enabling efficient extraction of team rankings and game results across states and sports.
34
+
35
+ ## Features
36
+
37
+ - Retrieve team rankings with strength of schedule, rating, and team links.
38
+ - Scrape team schedules, game outcomes, rankings
39
+ - Includes address/location data for each team.
40
+ - Multi-threaded scraping for speed and efficiency.
41
+ - Cleans and structures game metadata (venue, game type, outcome, etc.).
42
+
43
+ ## File Structure
44
+
45
+ - `scraper.py`: Contains the `MaxPrepsScraper` class
46
+
47
+ ## ️ Installation
48
+
49
+ ```bash
50
+ pip install requests beautifulsoup4 pandas tqdm
51
+ ```
52
+ ## Functions
53
+
54
+ ##### `get_rankings(state: str, sport: str, year: str, boys: bool = True)`
55
+
56
+ Retrieves state rankings for a given sport and academic year.
57
+ **Parameters:**
58
+ - `state` (`str`): Two-letter state abbreviation (e.g., `'tx'` for Texas)
59
+ - `sport` (`str`): Sport name (e.g., `'basketball'`)
60
+ - `year` (`str`): Academic year of the season (e.g., `'21-22'`)
61
+ - `boys` (`bool`, optional): Set to `True` for boys' sports and `False` for girls'. Defaults to `True`.
62
+
63
+ **Returns:**
64
+ `pandas.DataFrame` containing:
65
+ - School Name
66
+ - State Rank
67
+ - Strength of Schedule (SOS)
68
+ - Team Rating
69
+ - Team URL
70
+
71
+ ---
72
+
73
+ ##### `get_contests(state: str, sport: str, year: str, boys: bool = True, cities: list = None)`
74
+
75
+ Scrapes all contests (games) for selected schools by state, sport, and year.
76
+ **Parameters:**
77
+ - `state` (`str`): Two-letter state abbreviation (e.g., `'tx'`)
78
+ - `sport` (`str`): Sport name (e.g., `'basketball'`)
79
+ - `year` (`str`): Academic year of the season (e.g., `'21-22'`)
80
+ - `boys` (`bool`, optional): Set to `True` for boys' sports and `False` for girls'. Defaults to `True`.
81
+ - `cities` (`list`, optional): A list of cities to filter for, e.g., `['austin', 'el paso']` (if omitted, full state will be scraped)
82
+
83
+ **Returns:**
84
+ `pandas.DataFrame` containing game-level contest data, including:
85
+ - Date, opponent, venue, scores
86
+ - Team location details (address, city, state, zipcode)
87
+ - URLs to the MaxPreps pages of both teams
88
+
89
+ ## Usage and Example Output
90
+ ```
91
+ from scraper import MaxPrepsScraper
92
+ scraper = MaxPrepsScraper()
93
+
94
+ # Get Team Rankings
95
+ rankings_df = scraper.get_rankings(state = 'de', sport = 'football', year = '23-24')
96
+
97
+ # Get Contest Data
98
+ contests_df = scraper.get_contests(state='tx', sport='basketball', year='21-22', boys=False, cities = ['san antonio'])
99
+
100
+ #output datframes
101
+ rankings_df.head(10)
102
+ contests_df.head(10)
103
+ ```
104
+ ![Get Rankings](images/rankings_demo.png)
105
+ ![Get Contests](images/contests_demo.png)
106
+
107
+
108
+
@@ -0,0 +1,78 @@
1
+ # MaxPreps Web Scraper
2
+
3
+ This Python module scrapes high school sports data from [MaxPreps.com](https://www.maxpreps.com), enabling efficient extraction of team rankings and game results across states and sports.
4
+
5
+ ## Features
6
+
7
+ - Retrieve team rankings with strength of schedule, rating, and team links.
8
+ - Scrape team schedules, game outcomes, rankings
9
+ - Includes address/location data for each team.
10
+ - Multi-threaded scraping for speed and efficiency.
11
+ - Cleans and structures game metadata (venue, game type, outcome, etc.).
12
+
13
+ ## File Structure
14
+
15
+ - `scraper.py`: Contains the `MaxPrepsScraper` class
16
+
17
+ ## ️ Installation
18
+
19
+ ```bash
20
+ pip install requests beautifulsoup4 pandas tqdm
21
+ ```
22
+ ## Functions
23
+
24
+ ##### `get_rankings(state: str, sport: str, year: str, boys: bool = True)`
25
+
26
+ Retrieves state rankings for a given sport and academic year.
27
+ **Parameters:**
28
+ - `state` (`str`): Two-letter state abbreviation (e.g., `'tx'` for Texas)
29
+ - `sport` (`str`): Sport name (e.g., `'basketball'`)
30
+ - `year` (`str`): Academic year of the season (e.g., `'21-22'`)
31
+ - `boys` (`bool`, optional): Set to `True` for boys' sports and `False` for girls'. Defaults to `True`.
32
+
33
+ **Returns:**
34
+ `pandas.DataFrame` containing:
35
+ - School Name
36
+ - State Rank
37
+ - Strength of Schedule (SOS)
38
+ - Team Rating
39
+ - Team URL
40
+
41
+ ---
42
+
43
+ ##### `get_contests(state: str, sport: str, year: str, boys: bool = True, cities: list = None)`
44
+
45
+ Scrapes all contests (games) for selected schools by state, sport, and year.
46
+ **Parameters:**
47
+ - `state` (`str`): Two-letter state abbreviation (e.g., `'tx'`)
48
+ - `sport` (`str`): Sport name (e.g., `'basketball'`)
49
+ - `year` (`str`): Academic year of the season (e.g., `'21-22'`)
50
+ - `boys` (`bool`, optional): Set to `True` for boys' sports and `False` for girls'. Defaults to `True`.
51
+ - `cities` (`list`, optional): A list of cities to filter for, e.g., `['austin', 'el paso']` (if omitted, full state will be scraped)
52
+
53
+ **Returns:**
54
+ `pandas.DataFrame` containing game-level contest data, including:
55
+ - Date, opponent, venue, scores
56
+ - Team location details (address, city, state, zipcode)
57
+ - URLs to the MaxPreps pages of both teams
58
+
59
+ ## Usage and Example Output
60
+ ```
61
+ from scraper import MaxPrepsScraper
62
+ scraper = MaxPrepsScraper()
63
+
64
+ # Get Team Rankings
65
+ rankings_df = scraper.get_rankings(state = 'de', sport = 'football', year = '23-24')
66
+
67
+ # Get Contest Data
68
+ contests_df = scraper.get_contests(state='tx', sport='basketball', year='21-22', boys=False, cities = ['san antonio'])
69
+
70
+ #output datframes
71
+ rankings_df.head(10)
72
+ contests_df.head(10)
73
+ ```
74
+ ![Get Rankings](images/rankings_demo.png)
75
+ ![Get Contests](images/contests_demo.png)
76
+
77
+
78
+
@@ -0,0 +1 @@
1
+ from .scraper import MaxPrepsScraper
@@ -0,0 +1,286 @@
1
+ import requests
2
+ from bs4 import BeautifulSoup
3
+ from tqdm import tqdm
4
+ import pandas as pd
5
+ from io import StringIO
6
+ import concurrent.futures
7
+ from concurrent.futures import ThreadPoolExecutor, as_completed
8
+ import re
9
+
10
+ class MaxPrepsScraper():
11
+
12
+ BASE_URL = 'https://www.maxpreps.com'
13
+
14
+ def __init__(self):
15
+ """Initialize scraper settings (headers, session, etc.)."""
16
+ self.session = requests.Session()
17
+
18
+ def get_rankings(self, state: str, sport: str, year: str, boys=True):
19
+ """
20
+ Get rankings for a given sport, state, and year.
21
+ Returns a Pandas DataFrame with:
22
+ - School Name
23
+ - State Rank
24
+ - Strength of Schedule (SOS)
25
+ - Team Rating
26
+ - Team URL
27
+ """
28
+
29
+ if sport not in ['basketball', 'football', 'baseball', 'soccer', 'volleyball', 'lacrosse', 'softball']:
30
+ raise ValueError(f"Sport '{sport}' is not supported. Please choose from: basketball, football, baseball, soccer, volleyball, lacrosse, softball")
31
+
32
+ if (boys == False and sport in ['football', 'baseball']) or (boys and sport in ['softball', 'volleyball']):
33
+ raise ValueError(f"{'boys' if boys else 'girls'} {sport} is not supported")
34
+
35
+ if sport == 'soccer' and state not in ['tx', 'la', 'ms', 'hi', 'ca', 'fl', 'az']:
36
+ raise ValueError(f"Soccer is not supported in {state}")
37
+
38
+ state_url = f"{self.BASE_URL}/{state}/{sport}/{'' if (boys or sport in ['softball', 'volleyball']) else 'girls/'}{'winter/' if sport == 'soccer' else ''}{year}/rankings"
39
+
40
+ page = 1
41
+ full_df = pd.DataFrame()
42
+
43
+ while True:
44
+ page_url = f'{state_url}/{page}/'
45
+
46
+ response = requests.get(page_url)
47
+
48
+ # Stop if the page does not exist
49
+ if response.status_code != 200:
50
+ break
51
+
52
+ soup = BeautifulSoup(response.text, 'html.parser')
53
+
54
+ # You can add extra checks here to stop if there's no data
55
+ table = soup.find('table')
56
+ if not table:
57
+ break
58
+
59
+ # Process your table here
60
+ df = self._scrape_table(soup)
61
+
62
+ full_df = pd.concat([full_df, df]).reset_index(drop=True)
63
+
64
+ page += 1
65
+
66
+ full_df['Team'] = full_df['Team'].str.replace(r'^([A-Z])\1', r'\1', regex=True) #take care of schools with no mascot. Turns AAustin into Austin
67
+
68
+ return full_df
69
+
70
+ #sports that this function suppoprts: basketball, football, baseball, soccer, volleyball, lacrosse, softball,
71
+ def get_contests(self, state: str, sport: str, year: str, boys: bool = True, cities=None):
72
+
73
+ if sport not in ['basketball', 'football', 'baseball', 'soccer', 'volleyball', 'lacrosse', 'softball']:
74
+ raise ValueError(f"Sport '{sport}' is not supported. Please choose from: basketball, football, baseball, soccer, volleyball, lacrosse, softball")
75
+
76
+ if (boys == False and sport in ['football', 'baseball']) or (boys and sport in ['softball', 'volleyball']):
77
+ raise ValueError(f"{'boys' if boys else 'girls'} {sport} is not supported")
78
+
79
+ if sport == 'soccer' and state not in ['tx', 'la', 'ms', 'hi', 'ca', 'fl', 'az']:
80
+ raise ValueError(f"Soccer is not supported in {state}")
81
+
82
+
83
+ state_url = f"{self.BASE_URL}/{state}/{sport}/{'' if (boys or sport in ['softball', 'volleyball']) else 'girls/'}{'winter/' if sport == 'soccer' else ''}{year}/rankings"
84
+ page = 1
85
+ school_list = []
86
+
87
+ # Step 1: Collect all school links
88
+ while True:
89
+ page_url = f'{state_url}/{page}/'
90
+ #print(page_url)
91
+ response = requests.get(page_url)
92
+
93
+ if response.status_code != 200:
94
+ break
95
+
96
+ soup = BeautifulSoup(response.text, 'html.parser')
97
+ page_list = self._get_school_list(soup, cities)
98
+ #print(page_list)
99
+ school_list += page_list
100
+ #print(len(school_list))
101
+ page += 1
102
+
103
+
104
+
105
+ # Step 2: Thread-safe scrape function that still uses self._scrape_table()
106
+ def _fetch_and_scrape(school, url, base_url='https://www.maxpreps.com'):
107
+ try:
108
+ response = requests.get(base_url + url, timeout=10)
109
+ if response.status_code == 200:
110
+ soup = BeautifulSoup(response.text, 'html.parser')
111
+ table = self._scrape_table(soup)
112
+ table['Team 2 URL'] = self._extract_opponent_urls(soup)
113
+ location_info = self._extract_location_info(soup)
114
+ table['Team 1'] = school
115
+ table['Team 1 Address'] = location_info['address']
116
+ table['Team 1 City'] = location_info['city']
117
+ table['Team 1 State'] = location_info['state']
118
+ table['Team 1 Zipcode'] = location_info['zipcode']
119
+ table['Team 1 URL'] = url
120
+ return table
121
+ else:
122
+ print(f"Failed to fetch {url} (status code: {response.status_code})")
123
+ except Exception as e:
124
+ print(f"Error scraping {school} at {url}: {e}")
125
+ return None
126
+
127
+ # Step 3: Run threads
128
+
129
+ full_df = pd.DataFrame()
130
+ with ThreadPoolExecutor(max_workers=10) as executor:
131
+ futures = [executor.submit(_fetch_and_scrape, school, url) for school, url in school_list]
132
+
133
+ with tqdm(total=len(futures), desc=f"Scraping Schools for {', '.join(city for city in cities) if cities else ''} {state}", unit=" schools") as pbar:
134
+ for future in as_completed(futures):
135
+ result_df = future.result()
136
+ if result_df is not None and not result_df.empty:
137
+ full_df = pd.concat([full_df, result_df], ignore_index=True)
138
+ pbar.update(1)
139
+
140
+
141
+ full_df = self._clean_contest_data(full_df)
142
+
143
+ return full_df
144
+
145
+
146
+ def _scrape_table(self, soup):
147
+ table = soup.find('table')
148
+ html = str(table)
149
+ df = pd.read_html(StringIO(html))[0]
150
+ return df
151
+
152
+ def _get_school_list(self, soup, cities=None, base_url='https://www.maxpreps.com'):
153
+ """
154
+ Extracts school names and links to schedule pages based on new MaxPreps HTML structure.
155
+ """
156
+ school_data = []
157
+
158
+ for td in soup.find_all('td'):
159
+ a_tag = td.find('a', href=True)
160
+ if a_tag and 'schedule' in a_tag['href']:
161
+ href = a_tag['href']
162
+
163
+ # Check if cities is None (no filter), or if any city is in the href
164
+ if cities is None or any(city.lower().replace(" ", "-") == href.lower().split('/')[2] for city in cities):
165
+ name = a_tag.get_text(strip=True)
166
+ link = href
167
+ school_data.append((name, link))
168
+
169
+
170
+ return school_data
171
+
172
+ def _extract_location_info(self, soup):
173
+
174
+ address_element = soup.select_one('address')
175
+ if not address_element:
176
+ return {
177
+ "address": None,
178
+ "city": None,
179
+ "state": None,
180
+ "zipcode": None
181
+ }
182
+
183
+ # Get city/state/zip from <span>
184
+ city_state_span = address_element.find('span')
185
+ if city_state_span:
186
+ city_state_text = city_state_span.get_text(strip=True)
187
+ city_state_span.decompose() # Remove span from the address block
188
+ else:
189
+ city_state_text = ""
190
+
191
+ # Now get street address (without the span)
192
+ street_address = address_element.get_text(strip=True)
193
+
194
+ # Parse city, state, and zip using regex
195
+ city, state, zipcode = None, None, None
196
+ match = re.match(r'^(.*?),\s*([A-Z]{2})\s*(\d{5})(?:-\d{4})?$', city_state_text)
197
+ if match:
198
+ city, state, zipcode = match.groups()
199
+
200
+ return {
201
+ "address": street_address or None,
202
+ "city": city,
203
+ "state": state,
204
+ "zipcode": zipcode
205
+ }
206
+
207
+ def _extract_opponent_urls(self, soup): #Extracts a list of opponent URLs (or None) from the 'Opponent' column in the schedule table.
208
+
209
+ opponent_urls = []
210
+
211
+ # Find the schedule table
212
+ table = soup.find('table')
213
+ if not table:
214
+ return []
215
+
216
+ # Step 1: Get header row and find the column index for 'Opponent'
217
+ header_row = table.find('tr')
218
+ headers = [th.get_text(strip=True).lower() for th in header_row.find_all('th')]
219
+
220
+ try:
221
+ opponent_idx = headers.index('opponent')
222
+ except ValueError:
223
+ return []
224
+
225
+ # Step 2: Go through all remaining rows and get href from the opponent column
226
+ for row in table.find_all('tr')[1:]: # skip header row
227
+ cells = row.find_all('td')
228
+ if len(cells) > opponent_idx:
229
+ opponent_cell = cells[opponent_idx]
230
+ a_tag = opponent_cell.find('a', href=True)
231
+
232
+ if a_tag and a_tag['href'].endswith('/schedule/'):
233
+ opponent_urls.append(a_tag['href'])
234
+ else:
235
+ opponent_urls.append(None)
236
+
237
+ return opponent_urls
238
+
239
+ def _clean_contest_data(self, df):
240
+ # --- 1. Clean 'Opponent' column ---
241
+ opponent_pattern = r'(?P<VenueRaw>vs\.?|@)?\s*(?P<Team2>.+?)(?P<Star>\*{0,3})$'
242
+
243
+ opponent_info = df['Opponent'].str.extract(opponent_pattern)
244
+
245
+ # Venue
246
+ opponent_info['Venue'] = opponent_info['VenueRaw'].map({
247
+ 'vs': 'Home',
248
+ 'vs.': 'Home',
249
+ '@': 'Away'
250
+ }).fillna('Neutral')
251
+
252
+ # Game Type
253
+ opponent_info['Game Type'] = opponent_info['Star'].map({
254
+ '': 'Regular Season',
255
+ '*': 'District',
256
+ '**': 'Playoff',
257
+ '***': 'Tournament'
258
+ }).fillna('Regular Season')
259
+
260
+ df['Team 2'] = opponent_info['Team2'].str.strip()
261
+ df['Venue'] = opponent_info['Venue']
262
+ df['Game Type'] = opponent_info['Game Type']
263
+
264
+ # --- 2. Clean 'Result' column ---
265
+ result_pattern = r'(?P<Outcome>[WL])(?: (?P<Team1_Score>\d+)-(?P<Team2_Score>\d+)|\((?P<Forfeit>FF)\))'
266
+
267
+ result_info = df['Result'].str.extract(result_pattern)
268
+
269
+ df['Outcome'] = result_info['Outcome']
270
+ df['Team 1 Score'] = pd.to_numeric(result_info['Team1_Score'], errors='coerce')
271
+ df['Team 2 Score'] = pd.to_numeric(result_info['Team2_Score'], errors='coerce')
272
+ df['Forfeit'] = result_info['Forfeit'].notna()
273
+
274
+ cols_to_drop = ['Opponent', 'Result', 'Game Info', 'Match Info']
275
+ df.drop(columns=[col for col in cols_to_drop if col in df.columns], inplace=True)
276
+
277
+ df.loc[df['Outcome'] == 'L', ['Team 1 Score', 'Team 2 Score']] = df.loc[df['Outcome'] == 'L', ['Team 2 Score', 'Team 1 Score']].values # Swap scores if team 1 lost
278
+
279
+ df['Team 1'] = df['Team 1'].str.replace(r'(^[A-Z])\1', '', regex=True) #take care of schools with no mascot. Turns AAustin into Austin
280
+
281
+ # Reorder columns
282
+ new_order = ['Date', 'Team 1', 'Team 2', 'Team 1 Score', 'Team 2 Score', 'Outcome', 'Forfeit', 'Venue', 'Game Type', 'Team 1 Address', 'Team 1 City', 'Team 1 State', 'Team 1 Zipcode', 'Team 1 URL', 'Team 2 URL']
283
+ df = df[new_order]
284
+
285
+ return df
286
+
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: maxpreps-scraper
3
+ Version: 0.1.0
4
+ Summary: A Python scraper for MaxPreps high school sports data
5
+ Home-page: https://github.com/raghavdhir03/maxpreps_scraper
6
+ Author: Raghav Dhir
7
+ Author-email: dhir.raghav@gmail.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.7
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: requests
15
+ Requires-Dist: beautifulsoup4
16
+ Requires-Dist: tqdm
17
+ Requires-Dist: pandas
18
+ Requires-Dist: lxml
19
+ Requires-Dist: html5lib
20
+ Dynamic: author
21
+ Dynamic: author-email
22
+ Dynamic: classifier
23
+ Dynamic: description
24
+ Dynamic: description-content-type
25
+ Dynamic: home-page
26
+ Dynamic: license-file
27
+ Dynamic: requires-dist
28
+ Dynamic: requires-python
29
+ Dynamic: summary
30
+
31
+ # MaxPreps Web Scraper
32
+
33
+ This Python module scrapes high school sports data from [MaxPreps.com](https://www.maxpreps.com), enabling efficient extraction of team rankings and game results across states and sports.
34
+
35
+ ## Features
36
+
37
+ - Retrieve team rankings with strength of schedule, rating, and team links.
38
+ - Scrape team schedules, game outcomes, rankings
39
+ - Includes address/location data for each team.
40
+ - Multi-threaded scraping for speed and efficiency.
41
+ - Cleans and structures game metadata (venue, game type, outcome, etc.).
42
+
43
+ ## File Structure
44
+
45
+ - `scraper.py`: Contains the `MaxPrepsScraper` class
46
+
47
+ ## ️ Installation
48
+
49
+ ```bash
50
+ pip install requests beautifulsoup4 pandas tqdm
51
+ ```
52
+ ## Functions
53
+
54
+ ##### `get_rankings(state: str, sport: str, year: str, boys: bool = True)`
55
+
56
+ Retrieves state rankings for a given sport and academic year.
57
+ **Parameters:**
58
+ - `state` (`str`): Two-letter state abbreviation (e.g., `'tx'` for Texas)
59
+ - `sport` (`str`): Sport name (e.g., `'basketball'`)
60
+ - `year` (`str`): Academic year of the season (e.g., `'21-22'`)
61
+ - `boys` (`bool`, optional): Set to `True` for boys' sports and `False` for girls'. Defaults to `True`.
62
+
63
+ **Returns:**
64
+ `pandas.DataFrame` containing:
65
+ - School Name
66
+ - State Rank
67
+ - Strength of Schedule (SOS)
68
+ - Team Rating
69
+ - Team URL
70
+
71
+ ---
72
+
73
+ ##### `get_contests(state: str, sport: str, year: str, boys: bool = True, cities: list = None)`
74
+
75
+ Scrapes all contests (games) for selected schools by state, sport, and year.
76
+ **Parameters:**
77
+ - `state` (`str`): Two-letter state abbreviation (e.g., `'tx'`)
78
+ - `sport` (`str`): Sport name (e.g., `'basketball'`)
79
+ - `year` (`str`): Academic year of the season (e.g., `'21-22'`)
80
+ - `boys` (`bool`, optional): Set to `True` for boys' sports and `False` for girls'. Defaults to `True`.
81
+ - `cities` (`list`, optional): A list of cities to filter for, e.g., `['austin', 'el paso']` (if omitted, full state will be scraped)
82
+
83
+ **Returns:**
84
+ `pandas.DataFrame` containing game-level contest data, including:
85
+ - Date, opponent, venue, scores
86
+ - Team location details (address, city, state, zipcode)
87
+ - URLs to the MaxPreps pages of both teams
88
+
89
+ ## Usage and Example Output
90
+ ```
91
+ from scraper import MaxPrepsScraper
92
+ scraper = MaxPrepsScraper()
93
+
94
+ # Get Team Rankings
95
+ rankings_df = scraper.get_rankings(state = 'de', sport = 'football', year = '23-24')
96
+
97
+ # Get Contest Data
98
+ contests_df = scraper.get_contests(state='tx', sport='basketball', year='21-22', boys=False, cities = ['san antonio'])
99
+
100
+ #output datframes
101
+ rankings_df.head(10)
102
+ contests_df.head(10)
103
+ ```
104
+ ![Get Rankings](images/rankings_demo.png)
105
+ ![Get Contests](images/contests_demo.png)
106
+
107
+
108
+
@@ -0,0 +1,11 @@
1
+ LICENSE
2
+ README.md
3
+ setup.py
4
+ maxpreps_scraper/__init__.py
5
+ maxpreps_scraper/scraper.py
6
+ maxpreps_scraper.egg-info/PKG-INFO
7
+ maxpreps_scraper.egg-info/SOURCES.txt
8
+ maxpreps_scraper.egg-info/dependency_links.txt
9
+ maxpreps_scraper.egg-info/requires.txt
10
+ maxpreps_scraper.egg-info/top_level.txt
11
+ tests/test_scraper.py
@@ -0,0 +1,6 @@
1
+ requests
2
+ beautifulsoup4
3
+ tqdm
4
+ pandas
5
+ lxml
6
+ html5lib
@@ -0,0 +1 @@
1
+ maxpreps_scraper
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,27 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name='maxpreps-scraper', # Package name (pip install maxpreps-scraper)
5
+ version='0.1.0', # Initial version
6
+ packages=find_packages(), # Automatically find all modules inside your package folder
7
+ install_requires=[
8
+ 'requests',
9
+ 'beautifulsoup4',
10
+ 'tqdm',
11
+ 'pandas',
12
+ 'lxml',
13
+ 'html5lib',
14
+ ],
15
+ author='Raghav Dhir',
16
+ author_email='dhir.raghav@gmail.com',
17
+ description='A Python scraper for MaxPreps high school sports data',
18
+ long_description=open('README.md').read(),
19
+ long_description_content_type='text/markdown', # So Markdown renders on PyPI
20
+ url='https://github.com/raghavdhir03/maxpreps_scraper', # Your GitHub repo
21
+ classifiers=[
22
+ 'Programming Language :: Python :: 3',
23
+ 'License :: OSI Approved :: MIT License',
24
+ 'Operating System :: OS Independent',
25
+ ],
26
+ python_requires='>=3.7',
27
+ )
@@ -0,0 +1,42 @@
1
+ import requests
2
+ import pandas as pd
3
+ from bs4 import BeautifulSoup
4
+ from io import StringIO
5
+ import sys
6
+ import os
7
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
8
+ from maxpreps_scraper.scraper import MaxPrepsScraper
9
+ from concurrent.futures import ThreadPoolExecutor
10
+ import concurrent.futures
11
+ import re
12
+
13
+
14
+ ## testing scraping state rankings
15
+ '''scraper = MaxPrepsScraper()
16
+ table = scraper.get_rankings('de', 'basketball', '21-22', boys=False)
17
+ print(table.head())'''
18
+
19
+ ## testing scraping contests
20
+ scraper = MaxPrepsScraper()
21
+ #football_data = scraper.get_contests('de', 'football', '21-22')
22
+ #basketball_data = scraper.get_contests('tx', 'basketball', '21-22', cities=['austin', 'san antonio'])
23
+ baseball_data = scraper.get_contests('hi', 'baseball', '21-22')
24
+ #lacrosse_data = scraper.get_contests('tx', 'lacrosse', '21-22', boys=False)
25
+ #soccer_data = scraper.get_contests('la', 'soccer', '21-22')
26
+ #softball_data = scraper.get_contests('de', 'softball', '21-22')
27
+ #print(football_data.head())
28
+ #print(basketball_data.head())
29
+ print(baseball_data.head())
30
+ #print(softball_data.head())
31
+ #print(lacrosse_data.head())
32
+ #print(soccer_data.head())
33
+
34
+ """baseball_rankings = scraper.get_rankings('de', 'baseball', '21-22')
35
+ print(baseball_rankings.head())"""
36
+
37
+
38
+ ##testing roster function
39
+ """scraper = MaxPrepsScraper()
40
+ roster = scraper.get_roster('https://www.maxpreps.com/tx/austin/bowie-bulldogs/basketball/21-22/roster/')
41
+ print(roster.head())"""
42
+