From 601e320ae073d767649811a5a9cc1b3dec015f45 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Mon, 8 May 2023 19:00:55 -0500 Subject: [PATCH 01/17] new salary model; --- nba_data/migrations/0012_playersalarydata.py | 21 ++++++++++++++++++++ nba_data/models.py | 8 ++++++++ nba_data/nba_scraper_salary.py | 0 3 files changed, 29 insertions(+) create mode 100644 nba_data/migrations/0012_playersalarydata.py create mode 100644 nba_data/nba_scraper_salary.py diff --git a/nba_data/migrations/0012_playersalarydata.py b/nba_data/migrations/0012_playersalarydata.py new file mode 100644 index 0000000..e5b188f --- /dev/null +++ b/nba_data/migrations/0012_playersalarydata.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2 on 2023-05-08 23:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nba_data', '0011_alter_playerplayoffadvanceddata_bpm_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='PlayerSalaryData', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('player_name', models.CharField(max_length=255, null=True)), + ('salary', models.IntegerField(null=True)), + ], + ), + ] diff --git a/nba_data/models.py b/nba_data/models.py index f7c39ca..6f0f639 100644 --- a/nba_data/models.py +++ b/nba_data/models.py @@ -199,6 +199,14 @@ class PlayerPlayoffAdvancedData(models.Model): def __str__(self): return self.player_name + +class PlayerSalaryData(models.Model): + player_name = models.CharField(max_length=255, null=True) + salary = models.IntegerField(null=True) + + def __str__(self): + return self.player_name + diff --git a/nba_data/nba_scraper_salary.py b/nba_data/nba_scraper_salary.py new file mode 100644 index 0000000..e69de29 From 966111d71897aecc1b440d8c959cb68bec10ab3a Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Mon, 8 May 2023 19:02:15 -0500 Subject: [PATCH 02/17] serializer --- nba_data/serializers.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/nba_data/serializers.py b/nba_data/serializers.py index 9f03285..98ba998 100644 --- a/nba_data/serializers.py +++ b/nba_data/serializers.py @@ -1,6 +1,6 @@ # Serializers are defined that will convert model instances to JSON from rest_framework import serializers -from .models import PlayerData, PlayerTotalsData, PlayerAdvancedData, PlayerPlayoffTotalsData, PlayerPlayoffAdvancedData +from .models import PlayerData, PlayerTotalsData, PlayerAdvancedData, PlayerPlayoffTotalsData, PlayerPlayoffAdvancedData, PlayerSalaryData class PlayerDataSerializer(serializers.ModelSerializer): class Meta: @@ -30,5 +30,10 @@ class PlayerPlayoffAdvancedDataSerializer(serializers.ModelSerializer): class Meta: model = PlayerPlayoffAdvancedData fields = '__all__' - + +class PlayerSalaryDataSerializer(serializers.ModelSerializer): + class Meta: + model = PlayerSalaryData + fields = '__all__' + From dcab61a7fce58515ef55b4585819cd047e5455d6 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Mon, 8 May 2023 19:49:11 -0500 Subject: [PATCH 03/17] salary scraper success; --- ...salarydata_season_playersalarydata_team.py | 23 ++++ nba_data/models.py | 2 + nba_data/nba_scraper_playoffs_totals.py | 1 + nba_data/nba_scraper_salary.py | 104 ++++++++++++++++++ 4 files changed, 130 insertions(+) create mode 100644 nba_data/migrations/0013_playersalarydata_season_playersalarydata_team.py diff --git a/nba_data/migrations/0013_playersalarydata_season_playersalarydata_team.py b/nba_data/migrations/0013_playersalarydata_season_playersalarydata_team.py new file mode 100644 index 0000000..0f600b2 --- /dev/null +++ b/nba_data/migrations/0013_playersalarydata_season_playersalarydata_team.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2 on 2023-05-09 00:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nba_data', '0012_playersalarydata'), + ] + + operations = [ + migrations.AddField( + model_name='playersalarydata', + name='season', + field=models.IntegerField(null=True), + ), + migrations.AddField( + model_name='playersalarydata', + name='team', + field=models.CharField(default='', max_length=30), + ), + ] diff --git a/nba_data/models.py b/nba_data/models.py index 6f0f639..0bc5fda 100644 --- a/nba_data/models.py +++ b/nba_data/models.py @@ -203,6 +203,8 @@ class PlayerPlayoffAdvancedData(models.Model): class PlayerSalaryData(models.Model): player_name = models.CharField(max_length=255, null=True) salary = models.IntegerField(null=True) + team = models.CharField(max_length=30, default='') + season = models.IntegerField(null=True) def __str__(self): return self.player_name diff --git a/nba_data/nba_scraper_playoffs_totals.py b/nba_data/nba_scraper_playoffs_totals.py index 202fe41..47853ff 100644 --- a/nba_data/nba_scraper_playoffs_totals.py +++ b/nba_data/nba_scraper_playoffs_totals.py @@ -29,6 +29,7 @@ from nba_data.models import PlayerPlayoffTotalsData teams = ['HOU' ,'PHI', 'BOS', 'NYK', 'NJN', 'TOR', 'MEM', 'NOH', 'DAL', 'SAS', 'DEN', 'MIN', 'OKC', 'UTA', 'POR', 'MIL', 'CLE', 'CHI', 'IND', 'DET', 'SAC', 'PHO', 'GSW', 'LAC', 'LAL', 'MIA', 'ATL', 'WAS', 'ORL', 'CHA'] + team_abbreviations = ['ATL', 'BOS', 'BRK', 'CHI', 'CHO', 'CLE', 'DAL', 'DEN', 'DET', 'GSW', 'HOU', 'IND', 'LAC', 'LAL', 'MEM', 'MIA', 'MIL', 'MIN', 'NOP', 'NYK', 'OKC', 'ORL', 'PHI', 'PHO', 'POR', 'SAC', 'SAS', 'TOR', 'UTA', 'WAS'] season = input('What season? ') diff --git a/nba_data/nba_scraper_salary.py b/nba_data/nba_scraper_salary.py index e69de29..031a428 100644 --- a/nba_data/nba_scraper_salary.py +++ b/nba_data/nba_scraper_salary.py @@ -0,0 +1,104 @@ +import os +import sys +import django +import requests +from bs4 import BeautifulSoup, Comment +import csv +import json +import time +import re + +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'nba_stats.settings') +django.setup() + +from nba_data.models import PlayerSalaryData + +# Change CHO to CHA 2014 and prior. +# Change NOP to NOH for 2013 and prior. +# Change OKC to SEA for 2008 and prior. +# Change NOH to NOK for 2007 and 2006 +# Change NOK to NOH for 2005 and prior. +# Change NOH to CHH for 2002 and prior. NOLA did not have a team before 2003. +# Change WAS to WSB for 1997 and prior. +# Change BRK to NJN for 2012 and prior. +# Change MEM to VAN for 2001 and prior. +# Change LAC to SDC for 1984 and prior. + + +teams = ['HOU' ,'PHI', 'BOS', 'NYK', 'BRK', 'TOR', 'MEM', 'NOP', 'DAL', 'SAS', 'DEN', 'MIN', 'OKC', 'UTA', 'POR', 'MIL', 'CLE', 'CHI', 'IND', 'DET', 'SAC', 'PHO', 'GSW', 'LAC', 'LAL', 'MIA', 'ATL', 'WAS', 'ORL', 'CHO'] +teams_test = ['HOU', 'PHI', 'BOS'] + +season = input('What season? ') + +for team in teams_test: + url_make = 'https://www.basketball-reference.com/teams/' + team + '/' + season + '.html' + response = requests.get(url_make) + + soup = BeautifulSoup(response.text, "html.parser") + + comment = soup.find(string=lambda text: isinstance(text, Comment) and "salaries2" in text) + table_soup = BeautifulSoup(comment, "html.parser") + table = table_soup.find("table", {'class': 'suppress_all stats_table', 'id': 'salaries2'}) + + if table is None: + print(f"Table not found for team {team} and season {season}.") + # sys.exit(1) + continue + + header_row = table.find("thead").find("tr") + rows = table.find("tbody").find_all("tr") + + + # Extract coloumn headers + headers = ['player_name'] + headers.extend([col.text.strip() for col in header_row.find_all("th")][2:]) + headers.extend(['team', 'season']) # Add 'team' and 'season' fields to headers + + # Extract rows as dictionaries + data = [] + for row in rows: + cols = row.find_all("td") + cols = [col.text.strip() for col in cols] + row_dict = {header: (None if col == "" else col) for header, col in zip(headers, cols)} + row_dict['team'] = team + row_dict['season'] = season + data.append(row_dict) + + + # locate current directory + script_dir = os.path.dirname(os.path.abspath(__file__)) + + # save as CSV file + csv_output_path = os.path.join(script_dir, '..', 'data', f'{team}_{season}_player_salary_output.csv') + with open(csv_output_path, "w", newline="") as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=headers) + writer.writeheader() + for row in data: + writer.writerow(row) + + # Save as JSON + json_output_path = os.path.join(script_dir, '..', 'data', f'{team}_{season}_player_salary_output.json') + with open(json_output_path, 'w') as jsonfile: + json.dump(data, jsonfile) + + # save data to database + + # for row in data: + + # row['Salary'] = int(row['Salary'].replace(',', '').replace('$', '')) if row['Salary'] else None + + # player_data = PlayerSalaryData( + # player_name = row['player_name'], + # salary = row['Salary'], + # team = row['team'], + # season = row['season'], + # ) + # player_data.save() + +print('success') + + + \ No newline at end of file From 60accec6ccaf31b94bb6c80e342e6676d1a1d85f Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Mon, 8 May 2023 20:03:35 -0500 Subject: [PATCH 04/17] salary scrape to db works --- nba_data/nba_scraper_salary.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/nba_data/nba_scraper_salary.py b/nba_data/nba_scraper_salary.py index 031a428..ad2e1dc 100644 --- a/nba_data/nba_scraper_salary.py +++ b/nba_data/nba_scraper_salary.py @@ -33,7 +33,7 @@ teams_test = ['HOU', 'PHI', 'BOS'] season = input('What season? ') -for team in teams_test: +for team in teams: url_make = 'https://www.basketball-reference.com/teams/' + team + '/' + season + '.html' response = requests.get(url_make) @@ -86,17 +86,17 @@ for team in teams_test: # save data to database - # for row in data: + for row in data: - # row['Salary'] = int(row['Salary'].replace(',', '').replace('$', '')) if row['Salary'] else None + row['Salary'] = int(row['Salary'].replace(',', '').replace('$', '')) if row['Salary'] else None - # player_data = PlayerSalaryData( - # player_name = row['player_name'], - # salary = row['Salary'], - # team = row['team'], - # season = row['season'], - # ) - # player_data.save() + player_data = PlayerSalaryData( + player_name = row['player_name'], + salary = row['Salary'], + team = row['team'], + season = row['season'], + ) + player_data.save() print('success') From 6762f310e7ce9446fb711bf8b28c79f8915bbc75 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Mon, 8 May 2023 20:28:40 -0500 Subject: [PATCH 05/17] totals scraped to 2010 --- nba_data/nba_scraper_totals.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/nba_data/nba_scraper_totals.py b/nba_data/nba_scraper_totals.py index 8ca132f..600e30a 100644 --- a/nba_data/nba_scraper_totals.py +++ b/nba_data/nba_scraper_totals.py @@ -15,8 +15,19 @@ django.setup() from nba_data.models import PlayerTotalsData -teams = ['HOU' ,'PHI', 'BOS', 'NYK', 'BRK', 'TOR', 'MEM', 'NOP', 'DAL', 'SAS', 'DEN', 'MIN', 'OKC', 'UTA', 'POR', 'MIL', 'CLE', 'CHI', 'IND', 'DET', 'SAC', 'PHO', 'GSW', 'LAC', 'LAL', 'MIA', 'ATL', 'WAS', 'ORL', 'CHO'] -team_abbreviations = ['ATL', 'BOS', 'BRK', 'CHI', 'CHO', 'CLE', 'DAL', 'DEN', 'DET', 'GSW', 'HOU', 'IND', 'LAC', 'LAL', 'MEM', 'MIA', 'MIL', 'MIN', 'NOP', 'NYK', 'OKC', 'ORL', 'PHI', 'PHO', 'POR', 'SAC', 'SAS', 'TOR', 'UTA', 'WAS'] +# Change CHO to CHA 2014 and prior. +# Change NOP to NOH for 2013 and prior. +# Change OKC to SEA for 2008 and prior. +# Change NOH to NOK for 2007 and 2006 +# Change NOK to NOH for 2005 and prior. +# Change NOH to CHH for 2002 and prior. NOLA did not have a team before 2003. +# Change WAS to WSB for 1997 and prior. +# Change BRK to NJN for 2012 and prior. +# Change MEM to VAN for 2001 and prior. +# Change LAC to SDC for 1984 and prior. + +teams = ['HOU' ,'PHI', 'BOS', 'NYK', 'NJN', 'TOR', 'MEM', 'NOH', 'DAL', 'SAS', 'DEN', 'MIN', 'OKC', 'UTA', 'POR', 'MIL', 'CLE', 'CHI', 'IND', 'DET', 'SAC', 'PHO', 'GSW', 'LAC', 'LAL', 'MIA', 'ATL', 'WAS', 'ORL', 'CHA'] + # team = input('What team? ') # OMIT above. Will now only ask for season and iterate thru teams list. From 7a2392ea5e65b1dac18883924d23b98c383c763f Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Mon, 8 May 2023 21:29:16 -0500 Subject: [PATCH 06/17] season advanced scraped --- nba_data/nba_scraper_advanced.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/nba_data/nba_scraper_advanced.py b/nba_data/nba_scraper_advanced.py index e8e4808..2e7ed4b 100644 --- a/nba_data/nba_scraper_advanced.py +++ b/nba_data/nba_scraper_advanced.py @@ -15,10 +15,19 @@ django.setup() from nba_data.models import PlayerAdvancedData -# If season is 2014, then change CHO to CHA -teams = ['HOU' ,'PHI', 'BOS', 'NYK', 'BRK', 'TOR', 'MEM', 'NOP', 'DAL', 'SAS', 'DEN', 'MIN', 'OKC', 'UTA', 'POR', 'MIL', 'CLE', 'CHI', 'IND', 'DET', 'SAC', 'PHO', 'GSW', 'LAC', 'LAL', 'MIA', 'ATL', 'WAS', 'ORL', 'CHO'] -team_abbreviations = ['ATL', 'BOS', 'BRK', 'CHI', 'CHO', 'CLE', 'DAL', 'DEN', 'DET', 'GSW', 'HOU', 'IND', 'LAC', 'LAL', 'MEM', 'MIA', 'MIL', 'MIN', 'NOP', 'NYK', 'OKC', 'ORL', 'PHI', 'PHO', 'POR', 'SAC', 'SAS', 'TOR', 'UTA', 'WAS'] +# Change CHO to CHA 2014 and prior. +# Change NOP to NOH for 2013 and prior. +# Change BRK to NJN for 2012 and prior. +# Change OKC to SEA for 2008 and prior. +# Change NOH to NOK for 2007 and 2006 +# Change NOK to NOH for 2005 and prior. +# Change NOH to CHH for 2002 and prior. NOLA did not have a team before 2003. +# Change WAS to WSB for 1997 and prior. +# Change MEM to VAN for 2001 and prior. +# Change LAC to SDC for 1984 and prior. + +teams = ['HOU' ,'PHI', 'BOS', 'NYK', 'NJN', 'TOR', 'MEM', 'NOH', 'DAL', 'SAS', 'DEN', 'MIN', 'OKC', 'UTA', 'POR', 'MIL', 'CLE', 'CHI', 'IND', 'DET', 'SAC', 'PHO', 'GSW', 'LAC', 'LAL', 'MIA', 'ATL', 'WAS', 'ORL', 'CHA'] season = input('What season? ') for team in teams: From 2694753d0863ea2a7a133badb91ae646abd72ce8 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Mon, 8 May 2023 22:59:22 -0500 Subject: [PATCH 07/17] usage rates histogram view and url --- nba_data/urls.py | 3 ++- nba_data/views.py | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/nba_data/urls.py b/nba_data/urls.py index acac030..b758db8 100644 --- a/nba_data/urls.py +++ b/nba_data/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from .views import PlayerDataList, PlayerDataByTeamList, PlayerDataBySeasonList, PlayerDataByNameList, TopScorersbySeasonList, TopScorersbySeasonListPlayoffs, ThreeTwoTopPointTrends, TopAssistsBySeasonList, TopReboundsBySeasonList, TopBlocksBySeasonList, TopStealsBySeasonList, TopOffensiveReboundsBySeasonList, TopDefensiveReboundsBySeasonList, PointsPerGameHistogramBySeasonList, TopPtsScatterPlotData, TopPtsScatterPlotDataFast, TopPtsScatterPlotDataFast2018, Top20ScorersPost2009WS, Top20ScorersPost2014WS, Top20ScorersPost2018WS, TopAssistsBySeasonListPlayoffs, Top25ScorersPostSeasonPlayoffs, TopPtsScatterPlotDataFastSelect, OverallDBStats, TopScorersbySeasonTotalsList, TopAssistsBySeasonTotalsList, TopReboundsbySeasonTotalsList, TopStealsbySeasonTotalsList, TopBlocksbySeasonTotalsList, TopDefensiveReboundsBySeasonTotalsList, TopOffensiveReboundsbySeasonTotalsList +from .views import PlayerDataList, PlayerDataByTeamList, PlayerDataBySeasonList, PlayerDataByNameList, TopScorersbySeasonList, TopScorersbySeasonListPlayoffs, ThreeTwoTopPointTrends, TopAssistsBySeasonList, TopReboundsBySeasonList, TopBlocksBySeasonList, TopStealsBySeasonList, TopOffensiveReboundsBySeasonList, TopDefensiveReboundsBySeasonList, PointsPerGameHistogramBySeasonList, TopPtsScatterPlotData, TopPtsScatterPlotDataFast, TopPtsScatterPlotDataFast2018, Top20ScorersPost2009WS, Top20ScorersPost2014WS, Top20ScorersPost2018WS, TopAssistsBySeasonListPlayoffs, Top25ScorersPostSeasonPlayoffs, TopPtsScatterPlotDataFastSelect, OverallDBStats, TopScorersbySeasonTotalsList, TopAssistsBySeasonTotalsList, TopReboundsbySeasonTotalsList, TopStealsbySeasonTotalsList, TopBlocksbySeasonTotalsList, TopDefensiveReboundsBySeasonTotalsList, TopOffensiveReboundsbySeasonTotalsList, UsageRateHistogramView urlpatterns = [ path('api/playerdata/', PlayerDataList.as_view(), name='playerdata_list'), @@ -24,6 +24,7 @@ urlpatterns = [ path('api/top_rebounds_defensive//', TopDefensiveReboundsBySeasonList.as_view(), name='top_rebounds_DRB'), path('api/top_rebounds_defensive/totals//', TopDefensiveReboundsBySeasonTotalsList.as_view(), name='top_rebounds_DRB_totals'), path('api/points_per_game_histogram//', PointsPerGameHistogramBySeasonList.as_view(), name='points_per_game_histogram'), + path('api/usage_histogram//', UsageRateHistogramView.as_view(), name='usage_histogram'), path('api/top_pts_scatter_plot/', TopPtsScatterPlotData.as_view(), name='top_pts_scatter_plot'), path('api/top_pts_scatter_plot_fast/', TopPtsScatterPlotDataFast.as_view(), name='top_pts_scatter_plot_fast'), path('api/top_pts_scatter_plot_fast_2018/', TopPtsScatterPlotDataFast2018.as_view(), name='top_pts_scatter_plot_fast_2018'), diff --git a/nba_data/views.py b/nba_data/views.py index ef637b5..5356b2d 100644 --- a/nba_data/views.py +++ b/nba_data/views.py @@ -245,8 +245,40 @@ class PointsPerGameHistogramBySeasonList(generics.ListAPIView): return histogram_list +# USG Percent Histogram -# Scatter Plot - Top 30 players by total PTS across all seasons in DB. Then map those 30 players on a scatter plot against total PTS/WS for each season. +class UsageRateHistogramView(generics.ListAPIView): + serializer_class = HistogramDataSerializer + + def get_queryset(self): + season = self.kwargs['season'] + queryset = PlayerAdvancedData.objects.filter(season=season) + + # Get the usage rates for each player, ignoring null values + usage_rate_list = [player.usg_percent for player in queryset if player.usg_percent is not None] + + # Define histogram bins for usage rate ranges + bin_ranges = [i * 5 for i in range(21)] # 0, 5, 10, ..., 100 + histogram_data = Counter() + + # Fill the histogram with data + for usage_rate in usage_rate_list: + bin_index = next( + (i for i, r in enumerate(bin_ranges) if r >= usage_rate), -1) + if bin_index >= 1: + bin_label = f"{bin_ranges[bin_index - 1]}-{bin_ranges[bin_index]}" + histogram_data[bin_label] += 1 + + # Convert histogram_data to list of dicts for serialization + histogram_list = [{"range": k, "count": v} + for k, v in histogram_data.items()] + + return histogram_list + + + + +# Scatter Plot - Top 25 players by total PTS across all seasons in DB. Then map those 25 players on a scatter plot against total PTS/WS for each season. class TopPtsScatterPlotData(APIView): def get(self, request, *args, **kwargs): From 6cb01a8c2990814fc7d8f264063d47e3dc2745c7 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Tue, 9 May 2023 21:06:46 -0500 Subject: [PATCH 08/17] new model --- .../migrations/0014_playershotchartdata.py | 34 +++++++++++++++++++ nba_data/models.py | 24 +++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 nba_data/migrations/0014_playershotchartdata.py diff --git a/nba_data/migrations/0014_playershotchartdata.py b/nba_data/migrations/0014_playershotchartdata.py new file mode 100644 index 0000000..81b0220 --- /dev/null +++ b/nba_data/migrations/0014_playershotchartdata.py @@ -0,0 +1,34 @@ +# Generated by Django 4.2 on 2023-05-10 02:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nba_data', '0013_playersalarydata_season_playersalarydata_team'), + ] + + operations = [ + migrations.CreateModel( + name='PlayerShotChartData', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('player_name', models.CharField(default='', max_length=255)), + ('top', models.IntegerField(null=True)), + ('left', models.IntegerField(null=True)), + ('date', models.CharField(default='', max_length=255)), + ('qtr', models.CharField(default='', max_length=6)), + ('time_remaining', models.IntegerField(null=True)), + ('result', models.BooleanField(null=True)), + ('shot_type', models.CharField(default='', max_length=6)), + ('distance_ft', models.IntegerField(null=True)), + ('lead', models.BooleanField(null=True)), + ('lebron_team_score', models.IntegerField(null=True)), + ('opponent_team_score', models.IntegerField(null=True)), + ('opponent', models.CharField(default='', max_length=30)), + ('team', models.CharField(default='', max_length=30)), + ('season', models.IntegerField(null=True)), + ], + ), + ] diff --git a/nba_data/models.py b/nba_data/models.py index 0bc5fda..517ce73 100644 --- a/nba_data/models.py +++ b/nba_data/models.py @@ -208,6 +208,30 @@ class PlayerSalaryData(models.Model): def __str__(self): return self.player_name + + +class PlayerShotChartData(models.Model): + player_name = models.CharField(max_length=255, default='') + top = models.IntegerField(null=True) + left = models.IntegerField(null=True) + date = models.CharField(max_length=255, default='') + qtr = models.CharField(max_length=6, default='') + time_remaining = models.IntegerField(null=True) + result = models.BooleanField(null=True) + shot_type = models.CharField(max_length=6, default='') + distance_ft = models.IntegerField(null=True) + lead = models.BooleanField(null=True) + lebron_team_score = models.IntegerField(null=True) + opponent_team_score = models.IntegerField(null=True) + opponent = models.CharField(max_length=30, default='') + team = models.CharField(max_length=30, default='') + season = models.IntegerField(null=True) + + def __str__(self): + return self.player_name + + + From 65e434fa64a632e3b055e73c04a3ba8e3da3bcaa Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Tue, 9 May 2023 21:08:39 -0500 Subject: [PATCH 09/17] new serial --- nba_data/serializers.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nba_data/serializers.py b/nba_data/serializers.py index 98ba998..a1b2c63 100644 --- a/nba_data/serializers.py +++ b/nba_data/serializers.py @@ -1,6 +1,6 @@ # Serializers are defined that will convert model instances to JSON from rest_framework import serializers -from .models import PlayerData, PlayerTotalsData, PlayerAdvancedData, PlayerPlayoffTotalsData, PlayerPlayoffAdvancedData, PlayerSalaryData +from .models import PlayerData, PlayerTotalsData, PlayerAdvancedData, PlayerPlayoffTotalsData, PlayerPlayoffAdvancedData, PlayerSalaryData, PlayerShotChartData class PlayerDataSerializer(serializers.ModelSerializer): class Meta: @@ -35,5 +35,10 @@ class PlayerSalaryDataSerializer(serializers.ModelSerializer): class Meta: model = PlayerSalaryData fields = '__all__' + +class PlayerShotChartDataSerializer(serializers.ModelSerializer): + class Meta: + model = PlayerShotChartData + fields = '__all__' From 42528fb9179165af88d0ac4ad99b6ede18b3d0ad Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Tue, 9 May 2023 23:52:13 -0500 Subject: [PATCH 10/17] shot chart scraper prints to console --- ...lter_playershotchartdata_time_remaining.py | 18 + nba_data/models.py | 2 +- nba_data/nba_scraper_shot_chart.py | 27 + nba_data/test.html | 3715 +++++++++++++++++ 4 files changed, 3761 insertions(+), 1 deletion(-) create mode 100644 nba_data/migrations/0015_alter_playershotchartdata_time_remaining.py create mode 100644 nba_data/nba_scraper_shot_chart.py create mode 100644 nba_data/test.html diff --git a/nba_data/migrations/0015_alter_playershotchartdata_time_remaining.py b/nba_data/migrations/0015_alter_playershotchartdata_time_remaining.py new file mode 100644 index 0000000..9c0f2bf --- /dev/null +++ b/nba_data/migrations/0015_alter_playershotchartdata_time_remaining.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2 on 2023-05-10 02:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nba_data', '0014_playershotchartdata'), + ] + + operations = [ + migrations.AlterField( + model_name='playershotchartdata', + name='time_remaining', + field=models.CharField(default='', max_length=12), + ), + ] diff --git a/nba_data/models.py b/nba_data/models.py index 517ce73..efc4d94 100644 --- a/nba_data/models.py +++ b/nba_data/models.py @@ -216,7 +216,7 @@ class PlayerShotChartData(models.Model): left = models.IntegerField(null=True) date = models.CharField(max_length=255, default='') qtr = models.CharField(max_length=6, default='') - time_remaining = models.IntegerField(null=True) + time_remaining = models.CharField(max_length=12, default='') result = models.BooleanField(null=True) shot_type = models.CharField(max_length=6, default='') distance_ft = models.IntegerField(null=True) diff --git a/nba_data/nba_scraper_shot_chart.py b/nba_data/nba_scraper_shot_chart.py new file mode 100644 index 0000000..2341a57 --- /dev/null +++ b/nba_data/nba_scraper_shot_chart.py @@ -0,0 +1,27 @@ +import requests +from bs4 import BeautifulSoup, Comment + +url = 'https://www.basketball-reference.com/players/j/jamesle01/shooting/2016' + +response = requests.get(url) +soup = BeautifulSoup(response.text, "html.parser") + +# Find the comment containing the shot chart +comment = soup.find(string=lambda text: isinstance(text, Comment) and "div_shot-chart" in text) + +if comment: # Make sure we've found the comment before we try to parse it + shot_chart_soup = BeautifulSoup(comment, "html.parser") + + # Now you can find the shot chart in the parsed comment + shot_chart = shot_chart_soup.find("div", {"id": "div_shot-chart"}) + + if shot_chart: # Check that we've found the shot chart + shots = shot_chart.find_all("div") + + for shot in shots: + print(shot) + + else: + print('Shot chart not found') +else: + print('Comment not found') diff --git a/nba_data/test.html b/nba_data/test.html new file mode 100644 index 0000000..ea7247e --- /dev/null +++ b/nba_data/test.html @@ -0,0 +1,3715 @@ +
+ +
+

Shot Chart

+
    +
+
+ +
+ + + + + +
+ +
+
+ nbahalfcourt +
●
+
×
+
×
+
●
+
●
+
×
+
●
+
×
+
×
+
×
+
●
+
×
+
●
+
●
+
●
+
●
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
●
+
×
+
●
+
×
+
×
+
×
+
×
+
●
+
×
+
●
+
×
+
×
+
●
+
●
+
×
+
●
+
●
+
●
+
●
+
×
+
×
+
●
+
●
+
×
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
×
+
●
+
×
+
×
+
×
+
●
+
●
+
●
+
●
+
●
+
×
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
●
+
●
+
×
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
●
+
×
+
●
+
×
+
●
+
×
+
×
+
●
+
×
+
×
+
×
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
×
+
●
+
×
+
●
+
×
+
×
+
×
+
×
+
●
+
×
+
●
+
×
+
●
+
×
+
●
+
×
+
●
+
×
+
●
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
×
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
×
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
×
+
×
+
●
+
×
+
●
+
●
+
●
+
×
+
×
+
×
+
●
+
×
+
●
+
●
+
●
+
×
+
×
+
×
+
●
+
●
+
●
+
×
+
×
+
●
+
●
+
×
+
×
+
×
+
●
+
●
+
×
+
●
+
×
+
×
+
●
+
●
+
●
+
●
+
×
+
●
+
×
+
×
+
●
+
●
+
×
+
●
+
×
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
×
+
×
+
×
+
●
+
×
+
●
+
×
+
×
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
×
+
●
+
●
+
●
+
×
+
●
+
×
+
×
+
●
+
×
+
●
+
×
+
×
+
●
+
●
+
×
+
×
+
●
+
●
+
●
+
●
+
●
+
●
+
×
+
×
+
●
+
●
+
●
+
×
+
×
+
●
+
×
+
×
+
×
+
●
+
×
+
×
+
×
+
●
+
●
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
×
+
×
+
●
+
×
+
×
+
×
+
●
+
×
+
●
+
×
+
●
+
×
+
●
+
●
+
●
+
●
+
×
+
●
+
×
+
×
+
×
+
×
+
×
+
×
+
●
+
×
+
×
+
×
+
●
+
×
+
×
+
●
+
●
+
●
+
×
+
×
+
●
+
●
+
×
+
●
+
×
+
●
+
×
+
×
+
●
+
×
+
●
+
×
+
●
+
×
+
●
+
●
+
●
+
×
+
×
+
×
+
●
+
×
+
×
+
●
+
×
+
×
+
●
+
×
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
×
+
×
+
×
+
×
+
●
+
×
+
×
+
●
+
●
+
●
+
●
+
●
+
×
+
×
+
●
+
●
+
●
+
×
+
×
+
×
+
●
+
×
+
×
+
×
+
×
+
●
+
●
+
●
+
●
+
●
+
×
+
●
+
×
+
●
+
●
+
×
+
●
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
●
+
×
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
×
+
×
+
×
+
●
+
×
+
●
+
×
+
●
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
●
+
●
+
●
+
×
+
●
+
×
+
×
+
●
+
●
+
●
+
×
+
×
+
×
+
●
+
×
+
×
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
●
+
×
+
●
+
●
+
●
+
×
+
×
+
×
+
●
+
×
+
●
+
●
+
×
+
●
+
×
+
●
+
×
+
●
+
×
+
●
+
●
+
●
+
×
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
●
+
×
+
×
+
×
+
×
+
×
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
●
+
×
+
×
+
●
+
●
+
×
+
●
+
×
+
×
+
●
+
●
+
●
+
×
+
×
+
×
+
×
+
×
+
●
+
×
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
×
+
×
+
×
+
●
+
×
+
●
+
×
+
×
+
×
+
●
+
×
+
●
+
×
+
●
+
●
+
●
+
●
+
×
+
×
+
×
+
×
+
×
+
×
+
●
+
●
+
×
+
●
+
×
+
×
+
×
+
●
+
●
+
●
+
●
+
×
+
×
+
●
+
×
+
●
+
×
+
×
+
●
+
●
+
×
+
●
+
●
+
●
+
●
+
●
+
×
+
●
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
●
+
×
+
×
+
×
+
●
+
×
+
●
+
×
+
●
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
×
+
×
+
●
+
×
+
●
+
●
+
●
+
●
+
●
+
×
+
●
+
×
+
●
+
×
+
×
+
×
+
●
+
×
+
●
+
×
+
×
+
●
+
×
+
×
+
×
+
●
+
●
+
×
+
●
+
×
+
●
+
●
+
×
+
●
+
×
+
×
+
●
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
●
+
●
+
×
+
×
+
●
+
●
+
●
+
×
+
●
+
×
+
×
+
×
+
●
+
×
+
●
+
●
+
●
+
×
+
×
+
×
+
●
+
●
+
●
+
●
+
×
+
●
+
×
+
●
+
×
+
×
+
×
+
●
+
●
+
●
+
×
+
●
+
×
+
×
+
×
+
×
+
×
+
●
+
●
+
●
+
●
+
×
+
●
+
●
+
×
+
×
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
●
+
×
+
×
+
●
+
●
+
×
+
●
+
●
+
●
+
●
+
×
+
●
+
×
+
●
+
×
+
●
+
×
+
●
+
×
+
●
+
×
+
×
+
●
+
●
+
×
+
×
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
×
+
×
+
●
+
×
+
●
+
●
+
×
+
×
+
●
+
×
+
×
+
×
+
×
+
×
+
●
+
×
+
●
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
×
+
●
+
×
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
×
+
×
+
×
+
●
+
●
+
×
+
×
+
●
+
×
+
●
+
×
+
×
+
×
+
●
+
×
+
×
+
●
+
×
+
×
+
●
+
×
+
●
+
●
+
×
+
×
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
×
+
×
+
●
+
×
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
×
+
×
+
●
+
×
+
×
+
●
+
×
+
×
+
●
+
●
+
×
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
●
+
×
+
×
+
●
+
●
+
●
+
×
+
●
+
×
+
×
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
×
+
●
+
●
+
×
+
×
+
●
+
●
+
×
+
×
+
×
+
●
+
●
+
×
+
×
+
●
+
●
+
×
+
×
+
●
+
●
+
●
+
●
+
×
+
×
+
●
+
×
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
●
+
●
+
●
+
●
+
×
+
●
+
×
+
●
+
●
+
×
+
●
+
×
+
×
+
●
+
●
+
×
+
●
+
×
+
●
+
×
+
×
+
●
+
×
+
●
+
●
+
●
+
×
+
×
+
×
+
●
+
●
+
●
+
×
+
●
+
×
+
●
+
×
+
×
+
●
+
●
+
×
+
×
+
●
+
●
+
●
+
×
+
×
+
●
+
●
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
●
+
●
+
×
+
×
+
●
+
×
+
●
+
●
+
×
+
×
+
●
+
×
+
×
+
×
+
×
+
×
+
×
+
●
+
×
+
×
+
×
+
●
+
×
+
●
+
×
+
●
+
●
+
×
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
●
+
×
+
×
+
●
+
×
+
●
+
×
+
×
+
●
+
×
+
●
+
●
+
●
+
●
+
×
+
×
+
●
+
●
+
●
+
●
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
●
+
×
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
×
+
●
+
×
+
×
+
●
+
●
+
×
+
×
+
●
+
●
+
×
+
●
+
×
+
●
+
×
+
×
+
×
+
×
+
×
+
●
+
×
+
●
+
●
+
×
+
●
+
×
+
×
+
×
+
●
+
●
+
●
+
●
+
×
+
●
+
×
+
●
+
×
+
×
+
●
+
●
+
●
+
×
+
●
+
×
+
●
+
●
+
×
+
×
+
●
+
●
+
×
+
●
+
×
+
●
+
●
+
×
+
×
+
●
+
●
+
●
+
●
+
●
+
●
+
×
+
×
+
×
+
×
+
●
+
●
+
×
+
●
+
×
+
●
+
×
+
×
+
×
+
×
+
×
+
●
+
●
+
×
+
×
+
●
+
●
+
●
+
×
+
●
+
×
+
×
+
●
+
×
+
●
+
×
+
×
+
×
+
●
+
×
+
×
+
●
+
×
+
●
+
×
+
●
+
●
+
●
+
●
+
●
+
×
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
×
+
×
+
×
+
×
+
●
+
×
+
●
+
×
+
×
+
●
+
×
+
●
+
×
+
●
+
×
+
×
+
×
+
●
+
●
+
×
+
●
+
×
+
●
+
×
+
×
+
●
+
×
+
●
+
●
+
×
+
●
+
×
+
●
+
●
+
●
+
×
+
×
+
●
+
●
+
●
+
×
+
×
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
×
+
●
+
×
+
●
+
×
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
●
+
×
+
●
+
×
+
×
+
×
+
●
+
×
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
×
+
×
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
×
+
●
+
×
+
●
+
×
+
●
+
×
+
×
+
●
+
×
+
×
+
●
+
●
+
●
+
●
+
×
+
●
+
×
+
×
+
●
+
×
+
×
+
×
+
●
+
●
+
×
+
●
+
●
+
●
+
●
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
●
+
×
+
●
+
●
+
●
+
×
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
●
+
●
+
●
+
×
+
×
+
×
+
●
+
×
+
×
+
●
+
●
+
●
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
×
+
×
+
●
+
×
+
×
+
×
+
×
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
×
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
×
+
●
+
×
+
●
+
×
+
●
+
×
+
×
+
●
+
×
+
×
+
●
+
●
+
×
+
×
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
×
+
●
+
×
+
×
+
×
+
●
+
×
+
●
+
×
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
×
+
●
+
×
+
×
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
×
+
●
+
●
+
×
+
●
+
×
+
×
+
×
+
×
+
●
+
×
+
●
+
×
+
●
+
●
+
●
+
●
+
●
+
×
+
×
+
●
+
●
+
●
+
●
+
×
+
×
+
×
+
×
+
×
+
●
+
●
+
●
+
×
+
×
+
●
+
×
+
×
+
×
+
●
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
×
+
●
+
×
+
●
+
×
+
×
+
×
+
×
+
●
+
●
+
×
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
●
+
×
+
●
+
×
+
●
+
●
+
×
+
×
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
×
+
●
+
×
+
●
+
×
+
●
+
×
+
●
+
●
+
●
+
×
+
×
+
●
+
●
+
×
+
×
+
●
+
×
+
●
+
×
+
●
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
×
+
×
+
●
+
●
+
●
+
●
+
●
+
×
+
×
+
×
+
×
+
●
+
●
+
●
+
●
+
×
+
×
+
●
+
●
+
×
+
●
+
●
+
●
+
●
+
×
+
●
+
●
+
●
+
●
+
×
+
●
+
×
+
×
+
●
+
×
+
×
+
●
+
●
+
×
+
×
+
●
+
×
+
●
+
×
+
●
+
●
+
●
+
●
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
●
+
×
+
●
+
×
+
×
+
●
+
×
+
×
+
×
+
●
+
×
+
●
+
×
+
×
+
×
+
×
+
×
+
●
+
●
+
×
+
×
+
●
+
●
+
●
+
×
+
●
+
×
+
●
+
×
+
●
+
●
+
●
+
●
+
×
+
×
+
×
+
×
+
×
+
×
+
×
+
●
+
×
+
×
+
●
+
●
+
●
+
●
+
×
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
×
+
●
+
×
+
×
+
●
+
●
+
●
+
●
+
×
+
×
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
●
+
●
+
●
+
×
+
×
+
●
+
●
+
●
+
×
+
●
+
×
+
×
+
●
+
●
+
×
+
●
+
×
+
●
+
●
+
●
+
×
+
●
+
×
+
●
+
●
+
●
+
●
+
×
+
×
+
×
+
×
+
●
+
×
+
●
+
●
+
●
+
●
+
×
+
●
+
×
+
×
+
●
+
×
+
×
+
●
+
×
+
●
+
●
+
●
+
×
+
×
+
×
+
●
+
×
+
×
+
●
+
●
+
●
+
●
+
●
+
●
+
×
+
×
+
●
+
×
+
●
+
×
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+
×
+
●
+
●
+
×
+
●
+
●
+
×
+
×
+
×
+
×
+ +
+
+ + + +
Shot Chart Table
+ + +
+
\ No newline at end of file From dc62c2017fd1d2dadbf0889e90d8588581e76b9c Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Wed, 10 May 2023 00:30:31 -0500 Subject: [PATCH 11/17] csv data out correct --- nba_data/nba_scraper_shot_chart.py | 95 ++++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 13 deletions(-) diff --git a/nba_data/nba_scraper_shot_chart.py b/nba_data/nba_scraper_shot_chart.py index 2341a57..4ea8751 100644 --- a/nba_data/nba_scraper_shot_chart.py +++ b/nba_data/nba_scraper_shot_chart.py @@ -1,27 +1,96 @@ +import os +import sys +import django import requests from bs4 import BeautifulSoup, Comment +import csv +import re -url = 'https://www.basketball-reference.com/players/j/jamesle01/shooting/2016' +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'nba_stats.settings') +django.setup() + +from nba_data.models import PlayerShotChartData + +player_name = 'LeBron James' +season = 2016 +url = 'https://www.basketball-reference.com/players/j/jamesle01/shooting/' + str(season) response = requests.get(url) soup = BeautifulSoup(response.text, "html.parser") # Find the comment containing the shot chart comment = soup.find(string=lambda text: isinstance(text, Comment) and "div_shot-chart" in text) +shot_chart_soup = BeautifulSoup(comment, "html.parser") -if comment: # Make sure we've found the comment before we try to parse it - shot_chart_soup = BeautifulSoup(comment, "html.parser") +# Now you can find the shot chart in the parsed comment +shot_chart = shot_chart_soup.find("div", {"id": "shot-wrapper"}) - # Now you can find the shot chart in the parsed comment - shot_chart = shot_chart_soup.find("div", {"id": "div_shot-chart"}) +shots = shot_chart.find_all("div", {"class": ["tooltip make", "tooltip miss"]}) - if shot_chart: # Check that we've found the shot chart - shots = shot_chart.find_all("div") +team = 'CLE' # You might want to find a way to get this from the webpage - for shot in shots: - print(shot) +# save data to database and CSV +shot_chart_data = [] - else: - print('Shot chart not found') -else: - print('Comment not found') +for shot_div in shots: + # Parse shot data + style_values = shot_div['style'].split(';') + top_value = int(style_values[0].split(':')[1].replace('px', '').strip()) + left_value = int(style_values[1].split(':')[1].replace('px', '').strip()) + + # Parse tip attribute + tip_parts = shot_div['tip'].split('
') + + # Within your loop: + date_and_opponent = re.split(' at | vs ', tip_parts[0]) + date = date_and_opponent[0].strip().split(',')[0] + ',' + date_and_opponent[0].strip().split(',')[1] + opponent = date_and_opponent[1].strip() + + quarter_and_time = tip_parts[1].split(',') + quarter = quarter_and_time[0].strip() + time_remaining = quarter_and_time[1].strip().split(' ')[0] + + result_and_type_and_distance = tip_parts[2].split(' ') + result = result_and_type_and_distance[0].strip() == 'Made' + shot_type = int(result_and_type_and_distance[1][0]) + distance_ft = int(result_and_type_and_distance[-2].strip()) + + team_score_and_opponent_score = tip_parts[3].split(' ')[-1].split('-') + team_score = int(team_score_and_opponent_score[0].strip()) + opponent_score = int(team_score_and_opponent_score[1].strip()) + + lead = team_score > opponent_score + + shot_chart_row = { + 'player_name': player_name, + 'top': top_value, + 'left': left_value, + 'date': date, + 'qtr': quarter, + 'time_remaining': time_remaining, + 'result': result, + 'shot_type': shot_type, + 'distance_ft': distance_ft, + 'lead': lead, + 'lebron_team_score': team_score, + 'opponent_team_score': opponent_score, + 'opponent': opponent, + 'team': team, + 'season': season + } + shot_chart_data.append(shot_chart_row) + + # shot_chart_entry = PlayerShotChartData(**shot_chart_row) + # shot_chart_entry.save() + +# save as CSV file +script_dir = os.path.dirname(os.path.abspath(__file__)) +csv_output_path = os.path.join(script_dir, '..', 'data', f'{player_name}_{season}_shot_chart_output.csv') +with open(csv_output_path, "w", newline="") as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=shot_chart_data[0].keys()) + writer.writeheader() + for row in shot_chart_data: + writer.writerow(row) + +print('success') \ No newline at end of file From 007bd25533176f81c796af2cf647621676bc1e54 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Wed, 10 May 2023 00:34:40 -0500 Subject: [PATCH 12/17] model changes --- .../0016_alter_playershotchartdata_qtr.py | 18 ++++++++++++++++++ .../0017_alter_playershotchartdata_result.py | 18 ++++++++++++++++++ nba_data/models.py | 4 ++-- 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 nba_data/migrations/0016_alter_playershotchartdata_qtr.py create mode 100644 nba_data/migrations/0017_alter_playershotchartdata_result.py diff --git a/nba_data/migrations/0016_alter_playershotchartdata_qtr.py b/nba_data/migrations/0016_alter_playershotchartdata_qtr.py new file mode 100644 index 0000000..337ed34 --- /dev/null +++ b/nba_data/migrations/0016_alter_playershotchartdata_qtr.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2 on 2023-05-10 05:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nba_data', '0015_alter_playershotchartdata_time_remaining'), + ] + + operations = [ + migrations.AlterField( + model_name='playershotchartdata', + name='qtr', + field=models.CharField(default='', max_length=255), + ), + ] diff --git a/nba_data/migrations/0017_alter_playershotchartdata_result.py b/nba_data/migrations/0017_alter_playershotchartdata_result.py new file mode 100644 index 0000000..400f4ee --- /dev/null +++ b/nba_data/migrations/0017_alter_playershotchartdata_result.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2 on 2023-05-10 05:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nba_data', '0016_alter_playershotchartdata_qtr'), + ] + + operations = [ + migrations.AlterField( + model_name='playershotchartdata', + name='result', + field=models.BooleanField(default='', null=True), + ), + ] diff --git a/nba_data/models.py b/nba_data/models.py index efc4d94..810a7b1 100644 --- a/nba_data/models.py +++ b/nba_data/models.py @@ -215,9 +215,9 @@ class PlayerShotChartData(models.Model): top = models.IntegerField(null=True) left = models.IntegerField(null=True) date = models.CharField(max_length=255, default='') - qtr = models.CharField(max_length=6, default='') + qtr = models.CharField(max_length=255, default='') time_remaining = models.CharField(max_length=12, default='') - result = models.BooleanField(null=True) + result = models.BooleanField(null=True, default='') shot_type = models.CharField(max_length=6, default='') distance_ft = models.IntegerField(null=True) lead = models.BooleanField(null=True) From cad7e179c323619a8de1dcaa4c53458d44147516 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Wed, 10 May 2023 00:52:27 -0500 Subject: [PATCH 13/17] db scrape works! --- nba_data/nba_scraper_shot_chart.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/nba_data/nba_scraper_shot_chart.py b/nba_data/nba_scraper_shot_chart.py index 4ea8751..2aab8a7 100644 --- a/nba_data/nba_scraper_shot_chart.py +++ b/nba_data/nba_scraper_shot_chart.py @@ -13,7 +13,8 @@ django.setup() from nba_data.models import PlayerShotChartData player_name = 'LeBron James' -season = 2016 +season = 2022 +# team = 'LAL' # You might want to find a way to get this from the webpage url = 'https://www.basketball-reference.com/players/j/jamesle01/shooting/' + str(season) response = requests.get(url) @@ -28,8 +29,6 @@ shot_chart = shot_chart_soup.find("div", {"id": "shot-wrapper"}) shots = shot_chart.find_all("div", {"class": ["tooltip make", "tooltip miss"]}) -team = 'CLE' # You might want to find a way to get this from the webpage - # save data to database and CSV shot_chart_data = [] @@ -39,12 +38,13 @@ for shot_div in shots: top_value = int(style_values[0].split(':')[1].replace('px', '').strip()) left_value = int(style_values[1].split(':')[1].replace('px', '').strip()) - # Parse tip attribute + # Parse tip attribute tip_parts = shot_div['tip'].split('
') # Within your loop: date_and_opponent = re.split(' at | vs ', tip_parts[0]) date = date_and_opponent[0].strip().split(',')[0] + ',' + date_and_opponent[0].strip().split(',')[1] + team = date_and_opponent[0].strip().split(',')[2].strip() opponent = date_and_opponent[1].strip() quarter_and_time = tip_parts[1].split(',') @@ -92,5 +92,11 @@ with open(csv_output_path, "w", newline="") as csvfile: writer.writeheader() for row in shot_chart_data: writer.writerow(row) + +# Save the records in the database +PlayerShotChartData.objects.bulk_create( + PlayerShotChartData(**row) for row in shot_chart_data +) + print('success') \ No newline at end of file From 1c54ed2627fac1185af1283937a477ca571feece Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Wed, 10 May 2023 01:23:50 -0500 Subject: [PATCH 14/17] scraped until 2016 --- nba_data/nba_scraper_shot_chart.py | 115 ++++++++++++++++------------- 1 file changed, 63 insertions(+), 52 deletions(-) diff --git a/nba_data/nba_scraper_shot_chart.py b/nba_data/nba_scraper_shot_chart.py index 2aab8a7..7bad356 100644 --- a/nba_data/nba_scraper_shot_chart.py +++ b/nba_data/nba_scraper_shot_chart.py @@ -13,15 +13,17 @@ django.setup() from nba_data.models import PlayerShotChartData player_name = 'LeBron James' -season = 2022 +season = 2016 # team = 'LAL' # You might want to find a way to get this from the webpage -url = 'https://www.basketball-reference.com/players/j/jamesle01/shooting/' + str(season) +url = 'https://www.basketball-reference.com/players/j/jamesle01/shooting/' + \ + str(season) response = requests.get(url) soup = BeautifulSoup(response.text, "html.parser") # Find the comment containing the shot chart -comment = soup.find(string=lambda text: isinstance(text, Comment) and "div_shot-chart" in text) +comment = soup.find(string=lambda text: isinstance( + text, Comment) and "div_shot-chart" in text) shot_chart_soup = BeautifulSoup(comment, "html.parser") # Now you can find the shot chart in the parsed comment @@ -33,70 +35,79 @@ shots = shot_chart.find_all("div", {"class": ["tooltip make", "tooltip miss"]}) shot_chart_data = [] for shot_div in shots: - # Parse shot data - style_values = shot_div['style'].split(';') - top_value = int(style_values[0].split(':')[1].replace('px', '').strip()) - left_value = int(style_values[1].split(':')[1].replace('px', '').strip()) + try: + # Parse shot data + style_values = shot_div['style'].split(';') + top_value = int(style_values[0].split( + ':')[1].replace('px', '').strip()) + left_value = int(style_values[1].split( + ':')[1].replace('px', '').strip()) - # Parse tip attribute - tip_parts = shot_div['tip'].split('
') + # Parse tip attribute + tip_parts = shot_div['tip'].split('
') - # Within your loop: - date_and_opponent = re.split(' at | vs ', tip_parts[0]) - date = date_and_opponent[0].strip().split(',')[0] + ',' + date_and_opponent[0].strip().split(',')[1] - team = date_and_opponent[0].strip().split(',')[2].strip() - opponent = date_and_opponent[1].strip() + # Within your loop: + date_and_opponent = re.split(' at | vs ', tip_parts[0]) + date = date_and_opponent[0].strip().split( + ',')[0] + ',' + date_and_opponent[0].strip().split(',')[1] + team = date_and_opponent[0].strip().split(',')[2].strip() + opponent = date_and_opponent[1].strip() - quarter_and_time = tip_parts[1].split(',') - quarter = quarter_and_time[0].strip() - time_remaining = quarter_and_time[1].strip().split(' ')[0] + quarter_and_time = tip_parts[1].split(',') + quarter = quarter_and_time[0].strip() + time_remaining = quarter_and_time[1].strip().split(' ')[0] - result_and_type_and_distance = tip_parts[2].split(' ') - result = result_and_type_and_distance[0].strip() == 'Made' - shot_type = int(result_and_type_and_distance[1][0]) - distance_ft = int(result_and_type_and_distance[-2].strip()) + result_and_type_and_distance = tip_parts[2].split(' ') + result = result_and_type_and_distance[0].strip() == 'Made' + shot_type = int(result_and_type_and_distance[1][0]) + distance_ft = int(result_and_type_and_distance[-2].strip()) - team_score_and_opponent_score = tip_parts[3].split(' ')[-1].split('-') - team_score = int(team_score_and_opponent_score[0].strip()) - opponent_score = int(team_score_and_opponent_score[1].strip()) + team_score_and_opponent_score = tip_parts[3].split(' ')[-1].split('-') + team_score = int(team_score_and_opponent_score[0].strip()) + opponent_score = int(team_score_and_opponent_score[1].strip()) - lead = team_score > opponent_score + lead = team_score > opponent_score - shot_chart_row = { - 'player_name': player_name, - 'top': top_value, - 'left': left_value, - 'date': date, - 'qtr': quarter, - 'time_remaining': time_remaining, - 'result': result, - 'shot_type': shot_type, - 'distance_ft': distance_ft, - 'lead': lead, - 'lebron_team_score': team_score, - 'opponent_team_score': opponent_score, - 'opponent': opponent, - 'team': team, - 'season': season - } - shot_chart_data.append(shot_chart_row) + shot_chart_row = { + 'player_name': player_name, + 'top': top_value, + 'left': left_value, + 'date': date, + 'qtr': quarter, + 'time_remaining': time_remaining, + 'result': result, + 'shot_type': shot_type, + 'distance_ft': distance_ft, + 'lead': lead, + 'lebron_team_score': team_score, + 'opponent_team_score': opponent_score, + 'opponent': opponent, + 'team': team, + 'season': season + } + shot_chart_data.append(shot_chart_row) + + except Exception as e: + print(f"Error processing shot_div: {shot_div}") + print(f"Exception: {e}") - # shot_chart_entry = PlayerShotChartData(**shot_chart_row) - # shot_chart_entry.save() # save as CSV file script_dir = os.path.dirname(os.path.abspath(__file__)) -csv_output_path = os.path.join(script_dir, '..', 'data', f'{player_name}_{season}_shot_chart_output.csv') +csv_output_path = os.path.join( + script_dir, '..', 'data', f'{player_name}_{season}_shot_chart_output.csv') with open(csv_output_path, "w", newline="") as csvfile: writer = csv.DictWriter(csvfile, fieldnames=shot_chart_data[0].keys()) writer.writeheader() for row in shot_chart_data: writer.writerow(row) - + # Save the records in the database -PlayerShotChartData.objects.bulk_create( - PlayerShotChartData(**row) for row in shot_chart_data -) +try: + PlayerShotChartData.objects.bulk_create( + PlayerShotChartData(**row) for row in shot_chart_data + ) +except Exception as e: + print(f"Error inserting data into database: {e}") - -print('success') \ No newline at end of file +print('success') From 6026b51567c544de40753c5679c61fb098d1ce49 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Wed, 10 May 2023 15:19:51 -0500 Subject: [PATCH 15/17] shot chart data is being served up --- nba_data/nba_scraper_shot_chart.py | 2 +- nba_data/serializers.py | 7 ++++++- nba_data/urls.py | 3 ++- nba_data/views.py | 14 ++++++++++---- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/nba_data/nba_scraper_shot_chart.py b/nba_data/nba_scraper_shot_chart.py index 7bad356..1bb7462 100644 --- a/nba_data/nba_scraper_shot_chart.py +++ b/nba_data/nba_scraper_shot_chart.py @@ -13,7 +13,7 @@ django.setup() from nba_data.models import PlayerShotChartData player_name = 'LeBron James' -season = 2016 +season = 2014 # team = 'LAL' # You might want to find a way to get this from the webpage url = 'https://www.basketball-reference.com/players/j/jamesle01/shooting/' + \ str(season) diff --git a/nba_data/serializers.py b/nba_data/serializers.py index a1b2c63..fd0147c 100644 --- a/nba_data/serializers.py +++ b/nba_data/serializers.py @@ -37,8 +37,13 @@ class PlayerSalaryDataSerializer(serializers.ModelSerializer): fields = '__all__' class PlayerShotChartDataSerializer(serializers.ModelSerializer): + color = serializers.SerializerMethodField() + class Meta: model = PlayerShotChartData - fields = '__all__' + fields = ['top', 'left', 'date', 'qtr', 'time_remaining', 'result', 'shot_type', 'distance_ft', 'lead', 'lebron_team_score', 'opponent_team_score', 'opponent', 'team', 'season', 'color'] + + def get_color(self, obj): + return 'green' if obj.result else 'red' diff --git a/nba_data/urls.py b/nba_data/urls.py index b758db8..6b68608 100644 --- a/nba_data/urls.py +++ b/nba_data/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from .views import PlayerDataList, PlayerDataByTeamList, PlayerDataBySeasonList, PlayerDataByNameList, TopScorersbySeasonList, TopScorersbySeasonListPlayoffs, ThreeTwoTopPointTrends, TopAssistsBySeasonList, TopReboundsBySeasonList, TopBlocksBySeasonList, TopStealsBySeasonList, TopOffensiveReboundsBySeasonList, TopDefensiveReboundsBySeasonList, PointsPerGameHistogramBySeasonList, TopPtsScatterPlotData, TopPtsScatterPlotDataFast, TopPtsScatterPlotDataFast2018, Top20ScorersPost2009WS, Top20ScorersPost2014WS, Top20ScorersPost2018WS, TopAssistsBySeasonListPlayoffs, Top25ScorersPostSeasonPlayoffs, TopPtsScatterPlotDataFastSelect, OverallDBStats, TopScorersbySeasonTotalsList, TopAssistsBySeasonTotalsList, TopReboundsbySeasonTotalsList, TopStealsbySeasonTotalsList, TopBlocksbySeasonTotalsList, TopDefensiveReboundsBySeasonTotalsList, TopOffensiveReboundsbySeasonTotalsList, UsageRateHistogramView +from .views import PlayerDataList, PlayerDataByTeamList, PlayerDataBySeasonList, PlayerDataByNameList, TopScorersbySeasonList, TopScorersbySeasonListPlayoffs, ThreeTwoTopPointTrends, TopAssistsBySeasonList, TopReboundsBySeasonList, TopBlocksBySeasonList, TopStealsBySeasonList, TopOffensiveReboundsBySeasonList, TopDefensiveReboundsBySeasonList, PointsPerGameHistogramBySeasonList, TopPtsScatterPlotData, TopPtsScatterPlotDataFast, TopPtsScatterPlotDataFast2018, Top20ScorersPost2009WS, Top20ScorersPost2014WS, Top20ScorersPost2018WS, TopAssistsBySeasonListPlayoffs, Top25ScorersPostSeasonPlayoffs, TopPtsScatterPlotDataFastSelect, OverallDBStats, TopScorersbySeasonTotalsList, TopAssistsBySeasonTotalsList, TopReboundsbySeasonTotalsList, TopStealsbySeasonTotalsList, TopBlocksbySeasonTotalsList, TopDefensiveReboundsBySeasonTotalsList, TopOffensiveReboundsbySeasonTotalsList, UsageRateHistogramView, PlayerShotChartDataList urlpatterns = [ path('api/playerdata/', PlayerDataList.as_view(), name='playerdata_list'), @@ -34,4 +34,5 @@ urlpatterns = [ path('api/top_25_PTS_ws/playoffs//', Top25ScorersPostSeasonPlayoffs.as_view(), name='top_25_PTS_ws_playoffs'), path('api/top_25_PTS_ws/season//', TopPtsScatterPlotDataFastSelect.as_view(), name='top_25_PTS_ws_season'), path('api/stats/', OverallDBStats.as_view(), name='overall_db_stats'), + path('api/shot_chart_data///', PlayerShotChartDataList.as_view(), name='player_shot_chart'), ] \ No newline at end of file diff --git a/nba_data/views.py b/nba_data/views.py index 5356b2d..148759c 100644 --- a/nba_data/views.py +++ b/nba_data/views.py @@ -1,12 +1,12 @@ from django.shortcuts import render from django.http import JsonResponse, Http404 from django.core import serializers -from .models import PlayerData, PlayerTotalsData, PlayerAdvancedData, PlayerPlayoffTotalsData, PlayerPlayoffAdvancedData +from .models import PlayerData, PlayerTotalsData, PlayerAdvancedData, PlayerPlayoffTotalsData, PlayerPlayoffAdvancedData, PlayerShotChartData from rest_framework import generics -from .serializers import PlayerDataSerializer, HistogramDataSerializer, PlayerPlayoffTotalsDataSerializer, PlayerTotalsDataSerializer +from .serializers import PlayerDataSerializer, HistogramDataSerializer, PlayerPlayoffTotalsDataSerializer, PlayerTotalsDataSerializer, PlayerShotChartDataSerializer from rest_framework.views import APIView from rest_framework.response import Response -from django.db.models import Avg, Sum, F, FloatField, Min, Max +from django.db.models import Avg, Sum, F, Min, Max from collections import Counter import math from django.db import connection @@ -659,5 +659,11 @@ class OverallDBStats(APIView): return Response(response_data) - +class PlayerShotChartDataList(generics.ListAPIView): + serializer_class = PlayerShotChartDataSerializer + + def get_queryset(self): + player_name = self.kwargs['player_name'] + season = self.kwargs['season'] + return PlayerShotChartData.objects.filter(player_name__icontains=player_name, season=season).order_by('id') From 5cb5a5db65ac1b0c3f531eef05b40a1b67f0d775 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Wed, 10 May 2023 15:23:16 -0500 Subject: [PATCH 16/17] remove test --- nba_data/test.html | 3715 -------------------------------------------- 1 file changed, 3715 deletions(-) delete mode 100644 nba_data/test.html diff --git a/nba_data/test.html b/nba_data/test.html deleted file mode 100644 index ea7247e..0000000 --- a/nba_data/test.html +++ /dev/null @@ -1,3715 +0,0 @@ -
- -
-

Shot Chart

-
    -
-
- -
- - - - - -
- -
-
- nbahalfcourt -
●
-
×
-
×
-
●
-
●
-
×
-
●
-
×
-
×
-
×
-
●
-
×
-
●
-
●
-
●
-
●
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
●
-
×
-
●
-
×
-
×
-
×
-
×
-
●
-
×
-
●
-
×
-
×
-
●
-
●
-
×
-
●
-
●
-
●
-
●
-
×
-
×
-
●
-
●
-
×
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
×
-
●
-
×
-
×
-
×
-
●
-
●
-
●
-
●
-
●
-
×
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
●
-
●
-
×
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
●
-
×
-
●
-
×
-
●
-
×
-
×
-
●
-
×
-
×
-
×
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
×
-
●
-
×
-
●
-
×
-
×
-
×
-
×
-
●
-
×
-
●
-
×
-
●
-
×
-
●
-
×
-
●
-
×
-
●
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
×
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
×
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
×
-
×
-
●
-
×
-
●
-
●
-
●
-
×
-
×
-
×
-
●
-
×
-
●
-
●
-
●
-
×
-
×
-
×
-
●
-
●
-
●
-
×
-
×
-
●
-
●
-
×
-
×
-
×
-
●
-
●
-
×
-
●
-
×
-
×
-
●
-
●
-
●
-
●
-
×
-
●
-
×
-
×
-
●
-
●
-
×
-
●
-
×
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
×
-
×
-
×
-
●
-
×
-
●
-
×
-
×
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
×
-
●
-
●
-
●
-
×
-
●
-
×
-
×
-
●
-
×
-
●
-
×
-
×
-
●
-
●
-
×
-
×
-
●
-
●
-
●
-
●
-
●
-
●
-
×
-
×
-
●
-
●
-
●
-
×
-
×
-
●
-
×
-
×
-
×
-
●
-
×
-
×
-
×
-
●
-
●
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
×
-
×
-
●
-
×
-
×
-
×
-
●
-
×
-
●
-
×
-
●
-
×
-
●
-
●
-
●
-
●
-
×
-
●
-
×
-
×
-
×
-
×
-
×
-
×
-
●
-
×
-
×
-
×
-
●
-
×
-
×
-
●
-
●
-
●
-
×
-
×
-
●
-
●
-
×
-
●
-
×
-
●
-
×
-
×
-
●
-
×
-
●
-
×
-
●
-
×
-
●
-
●
-
●
-
×
-
×
-
×
-
●
-
×
-
×
-
●
-
×
-
×
-
●
-
×
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
×
-
×
-
×
-
×
-
●
-
×
-
×
-
●
-
●
-
●
-
●
-
●
-
×
-
×
-
●
-
●
-
●
-
×
-
×
-
×
-
●
-
×
-
×
-
×
-
×
-
●
-
●
-
●
-
●
-
●
-
×
-
●
-
×
-
●
-
●
-
×
-
●
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
●
-
×
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
×
-
×
-
×
-
●
-
×
-
●
-
×
-
●
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
●
-
●
-
●
-
×
-
●
-
×
-
×
-
●
-
●
-
●
-
×
-
×
-
×
-
●
-
×
-
×
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
●
-
×
-
●
-
●
-
●
-
×
-
×
-
×
-
●
-
×
-
●
-
●
-
×
-
●
-
×
-
●
-
×
-
●
-
×
-
●
-
●
-
●
-
×
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
●
-
×
-
×
-
×
-
×
-
×
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
●
-
×
-
×
-
●
-
●
-
×
-
●
-
×
-
×
-
●
-
●
-
●
-
×
-
×
-
×
-
×
-
×
-
●
-
×
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
×
-
×
-
×
-
●
-
×
-
●
-
×
-
×
-
×
-
●
-
×
-
●
-
×
-
●
-
●
-
●
-
●
-
×
-
×
-
×
-
×
-
×
-
×
-
●
-
●
-
×
-
●
-
×
-
×
-
×
-
●
-
●
-
●
-
●
-
×
-
×
-
●
-
×
-
●
-
×
-
×
-
●
-
●
-
×
-
●
-
●
-
●
-
●
-
●
-
×
-
●
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
●
-
×
-
×
-
×
-
●
-
×
-
●
-
×
-
●
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
×
-
×
-
●
-
×
-
●
-
●
-
●
-
●
-
●
-
×
-
●
-
×
-
●
-
×
-
×
-
×
-
●
-
×
-
●
-
×
-
×
-
●
-
×
-
×
-
×
-
●
-
●
-
×
-
●
-
×
-
●
-
●
-
×
-
●
-
×
-
×
-
●
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
●
-
●
-
×
-
×
-
●
-
●
-
●
-
×
-
●
-
×
-
×
-
×
-
●
-
×
-
●
-
●
-
●
-
×
-
×
-
×
-
●
-
●
-
●
-
●
-
×
-
●
-
×
-
●
-
×
-
×
-
×
-
●
-
●
-
●
-
×
-
●
-
×
-
×
-
×
-
×
-
×
-
●
-
●
-
●
-
●
-
×
-
●
-
●
-
×
-
×
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
●
-
×
-
×
-
●
-
●
-
×
-
●
-
●
-
●
-
●
-
×
-
●
-
×
-
●
-
×
-
●
-
×
-
●
-
×
-
●
-
×
-
×
-
●
-
●
-
×
-
×
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
×
-
×
-
●
-
×
-
●
-
●
-
×
-
×
-
●
-
×
-
×
-
×
-
×
-
×
-
●
-
×
-
●
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
×
-
●
-
×
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
×
-
×
-
×
-
●
-
●
-
×
-
×
-
●
-
×
-
●
-
×
-
×
-
×
-
●
-
×
-
×
-
●
-
×
-
×
-
●
-
×
-
●
-
●
-
×
-
×
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
×
-
×
-
●
-
×
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
×
-
×
-
●
-
×
-
×
-
●
-
×
-
×
-
●
-
●
-
×
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
●
-
×
-
×
-
●
-
●
-
●
-
×
-
●
-
×
-
×
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
×
-
●
-
●
-
×
-
×
-
●
-
●
-
×
-
×
-
×
-
●
-
●
-
×
-
×
-
●
-
●
-
×
-
×
-
●
-
●
-
●
-
●
-
×
-
×
-
●
-
×
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
●
-
●
-
●
-
●
-
×
-
●
-
×
-
●
-
●
-
×
-
●
-
×
-
×
-
●
-
●
-
×
-
●
-
×
-
●
-
×
-
×
-
●
-
×
-
●
-
●
-
●
-
×
-
×
-
×
-
●
-
●
-
●
-
×
-
●
-
×
-
●
-
×
-
×
-
●
-
●
-
×
-
×
-
●
-
●
-
●
-
×
-
×
-
●
-
●
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
●
-
●
-
×
-
×
-
●
-
×
-
●
-
●
-
×
-
×
-
●
-
×
-
×
-
×
-
×
-
×
-
×
-
●
-
×
-
×
-
×
-
●
-
×
-
●
-
×
-
●
-
●
-
×
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
●
-
×
-
×
-
●
-
×
-
●
-
×
-
×
-
●
-
×
-
●
-
●
-
●
-
●
-
×
-
×
-
●
-
●
-
●
-
●
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
●
-
×
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
×
-
●
-
×
-
×
-
●
-
●
-
×
-
×
-
●
-
●
-
×
-
●
-
×
-
●
-
×
-
×
-
×
-
×
-
×
-
●
-
×
-
●
-
●
-
×
-
●
-
×
-
×
-
×
-
●
-
●
-
●
-
●
-
×
-
●
-
×
-
●
-
×
-
×
-
●
-
●
-
●
-
×
-
●
-
×
-
●
-
●
-
×
-
×
-
●
-
●
-
×
-
●
-
×
-
●
-
●
-
×
-
×
-
●
-
●
-
●
-
●
-
●
-
●
-
×
-
×
-
×
-
×
-
●
-
●
-
×
-
●
-
×
-
●
-
×
-
×
-
×
-
×
-
×
-
●
-
●
-
×
-
×
-
●
-
●
-
●
-
×
-
●
-
×
-
×
-
●
-
×
-
●
-
×
-
×
-
×
-
●
-
×
-
×
-
●
-
×
-
●
-
×
-
●
-
●
-
●
-
●
-
●
-
×
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
×
-
×
-
×
-
×
-
●
-
×
-
●
-
×
-
×
-
●
-
×
-
●
-
×
-
●
-
×
-
×
-
×
-
●
-
●
-
×
-
●
-
×
-
●
-
×
-
×
-
●
-
×
-
●
-
●
-
×
-
●
-
×
-
●
-
●
-
●
-
×
-
×
-
●
-
●
-
●
-
×
-
×
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
×
-
●
-
×
-
●
-
×
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
●
-
×
-
●
-
×
-
×
-
×
-
●
-
×
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
×
-
×
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
×
-
●
-
×
-
●
-
×
-
●
-
×
-
×
-
●
-
×
-
×
-
●
-
●
-
●
-
●
-
×
-
●
-
×
-
×
-
●
-
×
-
×
-
×
-
●
-
●
-
×
-
●
-
●
-
●
-
●
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
●
-
×
-
●
-
●
-
●
-
×
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
●
-
●
-
●
-
×
-
×
-
×
-
●
-
×
-
×
-
●
-
●
-
●
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
×
-
×
-
●
-
×
-
×
-
×
-
×
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
×
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
×
-
●
-
×
-
●
-
×
-
●
-
×
-
×
-
●
-
×
-
×
-
●
-
●
-
×
-
×
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
×
-
●
-
×
-
×
-
×
-
●
-
×
-
●
-
×
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
×
-
●
-
×
-
×
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
×
-
●
-
●
-
×
-
●
-
×
-
×
-
×
-
×
-
●
-
×
-
●
-
×
-
●
-
●
-
●
-
●
-
●
-
×
-
×
-
●
-
●
-
●
-
●
-
×
-
×
-
×
-
×
-
×
-
●
-
●
-
●
-
×
-
×
-
●
-
×
-
×
-
×
-
●
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
×
-
●
-
×
-
●
-
×
-
×
-
×
-
×
-
●
-
●
-
×
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
●
-
×
-
●
-
×
-
●
-
●
-
×
-
×
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
×
-
●
-
×
-
●
-
×
-
●
-
×
-
●
-
●
-
●
-
×
-
×
-
●
-
●
-
×
-
×
-
●
-
×
-
●
-
×
-
●
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
×
-
×
-
●
-
●
-
●
-
●
-
●
-
×
-
×
-
×
-
×
-
●
-
●
-
●
-
●
-
×
-
×
-
●
-
●
-
×
-
●
-
●
-
●
-
●
-
×
-
●
-
●
-
●
-
●
-
×
-
●
-
×
-
×
-
●
-
×
-
×
-
●
-
●
-
×
-
×
-
●
-
×
-
●
-
×
-
●
-
●
-
●
-
●
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
●
-
×
-
●
-
×
-
×
-
●
-
×
-
×
-
×
-
●
-
×
-
●
-
×
-
×
-
×
-
×
-
×
-
●
-
●
-
×
-
×
-
●
-
●
-
●
-
×
-
●
-
×
-
●
-
×
-
●
-
●
-
●
-
●
-
×
-
×
-
×
-
×
-
×
-
×
-
×
-
●
-
×
-
×
-
●
-
●
-
●
-
●
-
×
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
×
-
●
-
×
-
×
-
●
-
●
-
●
-
●
-
×
-
×
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
●
-
●
-
●
-
×
-
×
-
●
-
●
-
●
-
×
-
●
-
×
-
×
-
●
-
●
-
×
-
●
-
×
-
●
-
●
-
●
-
×
-
●
-
×
-
●
-
●
-
●
-
●
-
×
-
×
-
×
-
×
-
●
-
×
-
●
-
●
-
●
-
●
-
×
-
●
-
×
-
×
-
●
-
×
-
×
-
●
-
×
-
●
-
●
-
●
-
×
-
×
-
×
-
●
-
×
-
×
-
●
-
●
-
●
-
●
-
●
-
●
-
×
-
×
-
●
-
×
-
●
-
×
-
×
-
●
-
●
-
×
-
×
-
×
-
×
-
×
-
●
-
●
-
×
-
●
-
●
-
×
-
×
-
×
-
×
- -
-
- - - -
Shot Chart Table
- - -
-
\ No newline at end of file From 04b5488ede47c4ddf84e77bae887bcb202096d61 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Wed, 10 May 2023 15:31:59 -0500 Subject: [PATCH 17/17] landing page created --- nba_data/templates/landing.html | 14 ++++++++++++++ nba_data/urls.py | 3 ++- nba_data/views.py | 3 +++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 nba_data/templates/landing.html diff --git a/nba_data/templates/landing.html b/nba_data/templates/landing.html new file mode 100644 index 0000000..47de3e7 --- /dev/null +++ b/nba_data/templates/landing.html @@ -0,0 +1,14 @@ + + + + + + + NBA Data API + + + +

Welcome to NBA Data API!

+ + + \ No newline at end of file diff --git a/nba_data/urls.py b/nba_data/urls.py index 6b68608..007980c 100644 --- a/nba_data/urls.py +++ b/nba_data/urls.py @@ -1,7 +1,8 @@ from django.urls import path -from .views import PlayerDataList, PlayerDataByTeamList, PlayerDataBySeasonList, PlayerDataByNameList, TopScorersbySeasonList, TopScorersbySeasonListPlayoffs, ThreeTwoTopPointTrends, TopAssistsBySeasonList, TopReboundsBySeasonList, TopBlocksBySeasonList, TopStealsBySeasonList, TopOffensiveReboundsBySeasonList, TopDefensiveReboundsBySeasonList, PointsPerGameHistogramBySeasonList, TopPtsScatterPlotData, TopPtsScatterPlotDataFast, TopPtsScatterPlotDataFast2018, Top20ScorersPost2009WS, Top20ScorersPost2014WS, Top20ScorersPost2018WS, TopAssistsBySeasonListPlayoffs, Top25ScorersPostSeasonPlayoffs, TopPtsScatterPlotDataFastSelect, OverallDBStats, TopScorersbySeasonTotalsList, TopAssistsBySeasonTotalsList, TopReboundsbySeasonTotalsList, TopStealsbySeasonTotalsList, TopBlocksbySeasonTotalsList, TopDefensiveReboundsBySeasonTotalsList, TopOffensiveReboundsbySeasonTotalsList, UsageRateHistogramView, PlayerShotChartDataList +from .views import PlayerDataList, PlayerDataByTeamList, PlayerDataBySeasonList, PlayerDataByNameList, TopScorersbySeasonList, TopScorersbySeasonListPlayoffs, ThreeTwoTopPointTrends, TopAssistsBySeasonList, TopReboundsBySeasonList, TopBlocksBySeasonList, TopStealsBySeasonList, TopOffensiveReboundsBySeasonList, TopDefensiveReboundsBySeasonList, PointsPerGameHistogramBySeasonList, TopPtsScatterPlotData, TopPtsScatterPlotDataFast, TopPtsScatterPlotDataFast2018, Top20ScorersPost2009WS, Top20ScorersPost2014WS, Top20ScorersPost2018WS, TopAssistsBySeasonListPlayoffs, Top25ScorersPostSeasonPlayoffs, TopPtsScatterPlotDataFastSelect, OverallDBStats, TopScorersbySeasonTotalsList, TopAssistsBySeasonTotalsList, TopReboundsbySeasonTotalsList, TopStealsbySeasonTotalsList, TopBlocksbySeasonTotalsList, TopDefensiveReboundsBySeasonTotalsList, TopOffensiveReboundsbySeasonTotalsList, UsageRateHistogramView, PlayerShotChartDataList, landing_page urlpatterns = [ + path('', landing_page, name='landing'), path('api/playerdata/', PlayerDataList.as_view(), name='playerdata_list'), path('api/playerdata/team//', PlayerDataByTeamList.as_view(), name='playerdata_by_team'), path('api/playerdata/season//', PlayerDataBySeasonList.as_view(), name='playerdata_by_season'), diff --git a/nba_data/views.py b/nba_data/views.py index 148759c..7eaf587 100644 --- a/nba_data/views.py +++ b/nba_data/views.py @@ -667,3 +667,6 @@ class PlayerShotChartDataList(generics.ListAPIView): season = self.kwargs['season'] return PlayerShotChartData.objects.filter(player_name__icontains=player_name, season=season).order_by('id') + +def landing_page(request): + return render(request, 'landing.html') \ No newline at end of file