Scraping and Analyzing 1000 NBA Games with Python
The NBA is one of the most popular sports leagues in the world, with millions of passionate fans following their favorite teams and players. In the modern era, the proliferation of technology and data has allowed for more advanced analysis of the game than ever before. NBA teams now employ entire analytics departments to collect and examine data in search of insights that can provide a competitive edge.
While teams have access to expensive tracking systems and other proprietary data sources, regular fans can still perform their own NBA data analysis at home thanks to the power of web scraping with Python. In this article, we‘ll go through the process of scraping a dataset of 1000 NBA games and examining it to uncover interesting insights about the league.
Scraping NBA Game Data
The first step is to actually collect the data we want to analyze. Luckily, the NBA‘s official website, stats.nba.com, provides a wealth of data on every NBA game for free. Here you can find box scores, play-by-play logs, shot charts, and more for games going back over 20 years.
To scrape this data, we can use Python libraries like Requests to download the webpage data and BeautifulSoup to parse and extract the relevant parts. However, the NBA‘s site can be tricky to scrape due to some anti-bot measures they have in place. I recommend using the nba_api Python package, which provides a clean interface for making API calls to the NBA site and handles all the authentication and other details for you behind the scenes.
Once you have the raw game data, it‘s important to parse it into a structured format for analysis. The pandas library is the perfect tool for this – we can store each game as a row in a DataFrame with columns for things like date, teams, score, stats, etc. Most of the data from stats.nba.com comes as JSON, which can be easily loaded into a DataFrame using pd.json_normalize().
For this project, let‘s scrape the game data for 1000 randomly selected games from the 2022-23 NBA regular season. We‘ll collect key data points like:
- Date
- Teams
- Final score
- Quarter-by-quarter score
- Field goals made/attempted
- 3-pointers made/attempted
- Free throws made/attempted
- Offensive/defensive rebounds
- Assists
- Steals
- Blocks
- Turnovers
This will give us a solid foundation of box score stats to analyze. We could collect more granular data like shot charts or play-by-play as well, but to keep things simple we‘ll stick to this relatively small set of game-level features.
Exploratory Analysis
With our dataset collected and cleaned, it‘s time for the fun part – exploring the data to see what insights we can uncover! Let‘s start with some basic analysis and data visualization.
Using pandas, we can easily calculate summary stats for our numeric columns like points, rebounds, assists, etc:
print(df.describe())
PTS FG FGA FG% 3P 3PA 3P% FT FTA FT% OREB DREB REB AST STL BLK TOV
count 2000.000 2000 2000 2000.0 2000 2000 2000.0 2000 2000 2000.0 2000 2000 2000 2000 2000 2000 2000
mean 112.015 41 88 0.465 12 34 0.352 18 23 0.776 9 33 42 25 7 5 13
std 12.019 5 10 0.035 4 9 0.046 4 5 0.069 3 5 8 4 3 3 3
min 76.000 24 54 0.333 1 8 0.080 2 3 0.400 0 15 15 9 0 0 3
25% 103.000 37 81 0.443 10 28 0.328 15 19 0.750 7 30 37 23 5 3 11
50% 112.000 41 88 0.465 12 34 0.354 18 22 0.778 9 33 42 25 7 4 13
75% 120.000 45 96 0.489 15 40 0.380 21 26 0.809 11 37 47 28 9 6 16
max 157.000 62 125 0.585 29 65 0.533 39 48 0.938 25 58 79 43 17 17 30
This gives a high-level view of the distribution of stats across our game sample. We can see that the average NBA game in our dataset had a combined score of 224 points, with each team attempting around 88 field goals. The median 3-point attempt rate was 34 per game. There were some extreme outlier games as well, with the highest scoring game totaling 314 points!
We can also look at the relationships between different variables using scatter plots. For example, let‘s visualize the correlation between a team‘s number of 3-point attempts and their offensive efficiency (points scored per 100 possessions):
fig, ax = plt.subplots(figsize=(8, 6)) ax.scatter(df[‘3PA‘], df[‘OFFRTG‘]) ax.set_xlabel(‘3-Point Attempts‘) ax.set_ylabel(‘Offensive Rating‘) ax.set_title(‘3PA vs Offensive Efficiency‘) plt.show()

There is a slight positive correlation, indicating that teams who attempt more 3s tend to have higher offensive ratings, which makes sense in the modern NBA. But there are also some teams who buck the trend, with low 3PA but elite offenses likely driven by high percentage shots at the rim and from midrange. The relationship is not extremely strong, with an r-value of 0.29.
This just scratches the surface of the types of analysis possible. With a bit more data manipulation in pandas, we could start to answer questions like:
- What were the most common final scores?
- What was the biggest comeback win?
- Which teams played at the fastest/slowest paces?
- Who had the most dominant rebounding games?
- What was the highest scoring individual quarter?
- Do teams that make more free throws tend to win more often?
- Which teams had the most/least inconsistent performances?
The possibilities are nearly endless, even with a relatively simple set of box score features for 1000 games. As we slice and group the data in different ways and visualize the results, we can uncover all sorts of insights about trends and outliers across the sample of games and teams.
Limitations and Next Steps
Of course, as with any data analysis project, there are limitations to what we can conclude from this exercise. A sample of 1000 games is not a huge dataset in the grand scheme of things, and there is always the possibility of selection bias in how the games were chosen. We also didn‘t collect any data on the specific players and lineups involved in each game, which heavily influence the outcomes. Any relationships or patterns we observe could potentially just be noise.
To make our analysis more robust, we could collect a larger sample covering multiple seasons. Adding in additional data like player tracking stats, shot locations, play-by-play info, and other more granular details would allow us to examine the games at a deeper level and control for more variables.
Another potential next step would be to use our dataset to build a predictive model. With game data as features and the margin of victory as a target variable, we could train a machine learning algorithm to predict the outcomes of future games based on factors like team strength, pace, 3-point shooting, rebounding margin, etc. There are challenges to building an accurate NBA betting model, but it‘s a fun use case for this type of data.
At the end of the day, this project demonstrates the power of web scraping and data analysis to surface insights from publicly available data. With some basic Python skills, an NBA fan can collect, explore, and visualize a large dataset of games to better understand league-wide trends and how their favorite teams match up. While 1000 games is a relatively small sample, it‘s enough to get a taste of the types of analysis made possible by the intersection of data, programming, and basketball in the digital era.