How to scrape table off a website in dataframe format?
Question:
I tried scraping a table of the NBA site but got the error no tables found.
import requests
from bs4 import BeautifulSoup
import pandas as pd
url = 'https://www.nba.com/game/0022101100/play-by-play?latest=0'
html = requests.get(url).content
df_list = pd.read_html(html)
How do I go about getting the play-by-play table?
Answers:
Data is loaded dynamically via API as json format.So you can extract them using json() and pandas as follows:
import requests
import pandas as pd
api_url = 'https://cdn.cookielaw.org/vendorlist/iab2Data.json'
headers= {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36",
}
req=requests.get(api_url,headers=headers).json()
df = pd.DataFrame(req)
print(df)
Output:
gvlSpecificationVersion tcfPolicyVersion ... vendorListVersion lastUpdated
1 2 2 ... 159 2022-09-01T16:05:33Z
2 2 2 ... 159 2022-09-01T16:05:33Z
3 2 2 ... 159 2022-09-01T16:05:33Z
4 2 2 ... 159 2022-09-01T16:05:33Z
5 2 2 ... 159 2022-09-01T16:05:33Z
... ... ... ... ... ...
1146 2 2 ... 159 2022-09-01T16:05:33Z
1147 2 2 ... 159 2022-09-01T16:05:33Z
1148 2 2 ... 159 2022-09-01T16:05:33Z
1149 2 2 ... 159 2022-09-01T16:05:33Z
1150 2 2 ... 159 2022-09-01T16:05:33Z
[907 rows x 10 columns]
As stated, that data is dynamically rendered. You could a) use Selenium to simulate opeing the browser, allowing the page to render, THEN use pandas to parse the table tags. or b) use the nba api and get the data in json format.
import requests
import pandas as pd
gameId = '0022101100'
url = f'https://cdn.nba.com/static/json/liveData/playbyplay/playbyplay_{gameId}.json'
jsonData = requests.get(url).json()
df = pd.json_normalize(jsonData,
record_path=['game', 'actions'])
Here is Option 2:
import requests
from bs4 import BeautifulSoup
import pandas as pd
import json
gameId = '0021900709'
url = f'https://www.nba.com/game/{gameId}/play-by-play'
headers = {
'referer': 'https://www.nba.com/',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'}
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, 'html.parser')
jsonStr = soup.find('script', {'id':'__NEXT_DATA__'}).text
jsonData = json.loads(jsonStr)['props']['pageProps']['playByPlay']
df = pd.json_normalize(jsonData,
record_path=['actions'])
Output: first 10 rows of 548
print(df.head(10).to_markdown())
| | actionNumber | clock | timeActual | period | periodType | actionType | subType | qualifiers | personId | x | y | possession | scoreHome | scoreAway | edited | orderNumber | xLegacy | yLegacy | isFieldGoal | side | description | personIdsFilter | teamId | teamTricode | descriptor | jumpBallRecoveredName | jumpBallRecoverdPersonId | playerName | playerNameI | jumpBallWonPlayerName | jumpBallWonPersonId | jumpBallLostPlayerName | jumpBallLostPersonId | shotDistance | shotResult | pointsTotal | assistPlayerNameInitial | assistPersonId | assistTotal | officialId | foulPersonalTotal | foulTechnicalTotal | foulDrawnPlayerName | foulDrawnPersonId | shotActionNumber | reboundTotal | reboundDefensiveTotal | reboundOffensiveTotal | turnoverTotal | stealPlayerName | stealPersonId | blockPlayerName | blockPersonId | value |
|---:|---------------:|:------------|:-----------------------|---------:|:-------------|:-------------|:----------|:---------------------|-----------:|---------:|---------:|-------------:|------------:|------------:|:---------------------|--------------:|----------:|----------:|--------------:|:-------|:-------------------------------------------------------|:--------------------------|--------------:|:--------------|:-------------|:------------------------|---------------------------:|:-------------|:---------------|:------------------------|----------------------:|:-------------------------|-----------------------:|---------------:|:-------------|--------------:|:--------------------------|-----------------:|--------------:|-------------:|--------------------:|---------------------:|:----------------------|--------------------:|-------------------:|---------------:|------------------------:|------------------------:|----------------:|------------------:|----------------:|------------------:|----------------:|--------:|
| 0 | 2 | PT12M00.00S | 2022-03-25T23:10:44.0Z | 1 | REGULAR | period | start | [] | 0 | nan | nan | 0 | 0 | 0 | 2022-03-25T23:10:44Z | 20000 | nan | nan | 0 | | Period Start | [] | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| 1 | 4 | PT11M55.00S | 2022-03-25T23:10:47.2Z | 1 | REGULAR | jumpball | recovered | [] | 1626220 | nan | nan | 1610612762 | 0 | 0 | 2022-03-25T23:10:47Z | 40000 | nan | nan | 0 | | Jump Ball R. Gobert vs. M. Plumlee: Tip to R. O'Neale | [1626220, 203497, 203486] | 1.61061e+09 | UTA | startperiod | R. O'Neale | 1.62622e+06 | O'Neale | R. O'Neale | Gobert | 203497 | Plumlee | 203486 | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| 2 | 7 | PT11M36.00S | 2022-03-25T23:11:06.3Z | 1 | REGULAR | 2pt | DUNK | ['pointsinthepaint'] | 203497 | 92.8548 | 47.0588 | 1610612762 | 0 | 2 | 2022-03-25T23:11:12Z | 70000 | -15 | 15 | 1 | right | R. Gobert DUNK (2 PTS) (D. Mitchell 1 AST) | [203497, 1628378] | 1.61061e+09 | UTA | nan | nan | nan | Gobert | R. Gobert | nan | nan | nan | nan | 2.08 | Made | 2 | D. Mitchell | 1.62838e+06 | 1 | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| 3 | 9 | PT11M21.00S | 2022-03-25T23:11:25.8Z | 1 | REGULAR | foul | personal | ['2freethrow'] | 203497 | nan | nan | 1610612766 | 0 | 2 | 2022-03-25T23:11:38Z | 90000 | nan | nan | 0 | | R. Gobert shooting personal FOUL (1 PF) (Plumlee 2 FT) | [203497, 203486] | 1.61061e+09 | UTA | shooting | nan | nan | Gobert | R. Gobert | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | 200832 | 1 | 0 | Plumlee | 203486 | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| 4 | 11 | PT11M21.00S | 2022-03-25T23:11:50.7Z | 1 | REGULAR | freethrow | 1 of 2 | [] | 203486 | nan | nan | 1610612766 | 0 | 2 | 2022-03-25T23:11:50Z | 110000 | nan | nan | 0 | | MISS M. Plumlee Free Throw 1 of 2 | [203486] | 1.61061e+09 | CHA | nan | nan | nan | Plumlee | M. Plumlee | nan | nan | nan | nan | nan | Missed | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| 5 | 12 | PT11M21.00S | 2022-03-25T23:11:50.7Z | 1 | REGULAR | rebound | offensive | ['deadball', 'team'] | 0 | nan | nan | 1610612766 | 0 | 2 | 2022-03-25T23:11:50Z | 120000 | nan | nan | 0 | | TEAM offensive REBOUND | [] | 1.61061e+09 | CHA | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | 11 | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| 6 | 13 | PT11M21.00S | 2022-03-25T23:12:06.4Z | 1 | REGULAR | freethrow | 2 of 2 | [] | 203486 | nan | nan | 1610612766 | 1 | 2 | 2022-03-25T23:12:06Z | 130000 | nan | nan | 0 | | M. Plumlee Free Throw 2 of 2 (1 PTS) | [203486] | 1.61061e+09 | CHA | nan | nan | nan | Plumlee | M. Plumlee | nan | nan | nan | nan | nan | Made | 1 | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| 7 | 14 | PT11M06.00S | 2022-03-25T23:12:22.2Z | 1 | REGULAR | 3pt | Jump Shot | [] | 1626220 | 69.7273 | 75.2451 | 1610612762 | 1 | 2 | 2022-03-25T23:12:29Z | 140000 | 126 | 232 | 1 | right | MISS R. O'Neale 26' 3PT | [1626220] | 1.61061e+09 | UTA | nan | nan | nan | O'Neale | R. O'Neale | nan | nan | nan | nan | 26.42 | Missed | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| 8 | 15 | PT11M02.00S | 2022-03-25T23:12:26.2Z | 1 | REGULAR | rebound | offensive | [] | 1627823 | nan | nan | 1610612762 | 1 | 2 | 2022-03-25T23:12:29Z | 150000 | nan | nan | 0 | | J. Hernangomez REBOUND (Off:1 Def:0) | [1627823] | 1.61061e+09 | UTA | nan | nan | nan | Hernangomez | J. Hernangomez | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | 14 | 1 | 0 | 1 | nan | nan | nan | nan | nan | nan |
| 9 | 16 | PT10M56.00S | 2022-03-25T23:12:33.1Z | 1 | REGULAR | 3pt | Jump Shot | ['2ndchance'] | 1628378 | 68.6761 | 70.098 | 1610612762 | 1 | 5 | 2022-03-25T23:12:38Z | 160000 | 100 | 242 | 1 | right | D. Mitchell 26' 3PT (3 PTS) (J. Hernangomez 1 AST) | [1628378, 1627823] | 1.61061e+09 | UTA | nan | nan | nan | Mitchell | D. Mitchell | nan | nan | nan | nan | 26.19 | Made | 3 | J. Hernangomez | 1.62782e+06 | 1 | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan |
I tried scraping a table of the NBA site but got the error no tables found.
import requests
from bs4 import BeautifulSoup
import pandas as pd
url = 'https://www.nba.com/game/0022101100/play-by-play?latest=0'
html = requests.get(url).content
df_list = pd.read_html(html)
How do I go about getting the play-by-play table?
Data is loaded dynamically via API as json format.So you can extract them using json() and pandas as follows:
import requests
import pandas as pd
api_url = 'https://cdn.cookielaw.org/vendorlist/iab2Data.json'
headers= {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36",
}
req=requests.get(api_url,headers=headers).json()
df = pd.DataFrame(req)
print(df)
Output:
gvlSpecificationVersion tcfPolicyVersion ... vendorListVersion lastUpdated
1 2 2 ... 159 2022-09-01T16:05:33Z
2 2 2 ... 159 2022-09-01T16:05:33Z
3 2 2 ... 159 2022-09-01T16:05:33Z
4 2 2 ... 159 2022-09-01T16:05:33Z
5 2 2 ... 159 2022-09-01T16:05:33Z
... ... ... ... ... ...
1146 2 2 ... 159 2022-09-01T16:05:33Z
1147 2 2 ... 159 2022-09-01T16:05:33Z
1148 2 2 ... 159 2022-09-01T16:05:33Z
1149 2 2 ... 159 2022-09-01T16:05:33Z
1150 2 2 ... 159 2022-09-01T16:05:33Z
[907 rows x 10 columns]
As stated, that data is dynamically rendered. You could a) use Selenium to simulate opeing the browser, allowing the page to render, THEN use pandas to parse the table tags. or b) use the nba api and get the data in json format.
import requests
import pandas as pd
gameId = '0022101100'
url = f'https://cdn.nba.com/static/json/liveData/playbyplay/playbyplay_{gameId}.json'
jsonData = requests.get(url).json()
df = pd.json_normalize(jsonData,
record_path=['game', 'actions'])
Here is Option 2:
import requests
from bs4 import BeautifulSoup
import pandas as pd
import json
gameId = '0021900709'
url = f'https://www.nba.com/game/{gameId}/play-by-play'
headers = {
'referer': 'https://www.nba.com/',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'}
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, 'html.parser')
jsonStr = soup.find('script', {'id':'__NEXT_DATA__'}).text
jsonData = json.loads(jsonStr)['props']['pageProps']['playByPlay']
df = pd.json_normalize(jsonData,
record_path=['actions'])
Output: first 10 rows of 548
print(df.head(10).to_markdown())
| | actionNumber | clock | timeActual | period | periodType | actionType | subType | qualifiers | personId | x | y | possession | scoreHome | scoreAway | edited | orderNumber | xLegacy | yLegacy | isFieldGoal | side | description | personIdsFilter | teamId | teamTricode | descriptor | jumpBallRecoveredName | jumpBallRecoverdPersonId | playerName | playerNameI | jumpBallWonPlayerName | jumpBallWonPersonId | jumpBallLostPlayerName | jumpBallLostPersonId | shotDistance | shotResult | pointsTotal | assistPlayerNameInitial | assistPersonId | assistTotal | officialId | foulPersonalTotal | foulTechnicalTotal | foulDrawnPlayerName | foulDrawnPersonId | shotActionNumber | reboundTotal | reboundDefensiveTotal | reboundOffensiveTotal | turnoverTotal | stealPlayerName | stealPersonId | blockPlayerName | blockPersonId | value |
|---:|---------------:|:------------|:-----------------------|---------:|:-------------|:-------------|:----------|:---------------------|-----------:|---------:|---------:|-------------:|------------:|------------:|:---------------------|--------------:|----------:|----------:|--------------:|:-------|:-------------------------------------------------------|:--------------------------|--------------:|:--------------|:-------------|:------------------------|---------------------------:|:-------------|:---------------|:------------------------|----------------------:|:-------------------------|-----------------------:|---------------:|:-------------|--------------:|:--------------------------|-----------------:|--------------:|-------------:|--------------------:|---------------------:|:----------------------|--------------------:|-------------------:|---------------:|------------------------:|------------------------:|----------------:|------------------:|----------------:|------------------:|----------------:|--------:|
| 0 | 2 | PT12M00.00S | 2022-03-25T23:10:44.0Z | 1 | REGULAR | period | start | [] | 0 | nan | nan | 0 | 0 | 0 | 2022-03-25T23:10:44Z | 20000 | nan | nan | 0 | | Period Start | [] | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| 1 | 4 | PT11M55.00S | 2022-03-25T23:10:47.2Z | 1 | REGULAR | jumpball | recovered | [] | 1626220 | nan | nan | 1610612762 | 0 | 0 | 2022-03-25T23:10:47Z | 40000 | nan | nan | 0 | | Jump Ball R. Gobert vs. M. Plumlee: Tip to R. O'Neale | [1626220, 203497, 203486] | 1.61061e+09 | UTA | startperiod | R. O'Neale | 1.62622e+06 | O'Neale | R. O'Neale | Gobert | 203497 | Plumlee | 203486 | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| 2 | 7 | PT11M36.00S | 2022-03-25T23:11:06.3Z | 1 | REGULAR | 2pt | DUNK | ['pointsinthepaint'] | 203497 | 92.8548 | 47.0588 | 1610612762 | 0 | 2 | 2022-03-25T23:11:12Z | 70000 | -15 | 15 | 1 | right | R. Gobert DUNK (2 PTS) (D. Mitchell 1 AST) | [203497, 1628378] | 1.61061e+09 | UTA | nan | nan | nan | Gobert | R. Gobert | nan | nan | nan | nan | 2.08 | Made | 2 | D. Mitchell | 1.62838e+06 | 1 | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| 3 | 9 | PT11M21.00S | 2022-03-25T23:11:25.8Z | 1 | REGULAR | foul | personal | ['2freethrow'] | 203497 | nan | nan | 1610612766 | 0 | 2 | 2022-03-25T23:11:38Z | 90000 | nan | nan | 0 | | R. Gobert shooting personal FOUL (1 PF) (Plumlee 2 FT) | [203497, 203486] | 1.61061e+09 | UTA | shooting | nan | nan | Gobert | R. Gobert | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | 200832 | 1 | 0 | Plumlee | 203486 | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| 4 | 11 | PT11M21.00S | 2022-03-25T23:11:50.7Z | 1 | REGULAR | freethrow | 1 of 2 | [] | 203486 | nan | nan | 1610612766 | 0 | 2 | 2022-03-25T23:11:50Z | 110000 | nan | nan | 0 | | MISS M. Plumlee Free Throw 1 of 2 | [203486] | 1.61061e+09 | CHA | nan | nan | nan | Plumlee | M. Plumlee | nan | nan | nan | nan | nan | Missed | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| 5 | 12 | PT11M21.00S | 2022-03-25T23:11:50.7Z | 1 | REGULAR | rebound | offensive | ['deadball', 'team'] | 0 | nan | nan | 1610612766 | 0 | 2 | 2022-03-25T23:11:50Z | 120000 | nan | nan | 0 | | TEAM offensive REBOUND | [] | 1.61061e+09 | CHA | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | 11 | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| 6 | 13 | PT11M21.00S | 2022-03-25T23:12:06.4Z | 1 | REGULAR | freethrow | 2 of 2 | [] | 203486 | nan | nan | 1610612766 | 1 | 2 | 2022-03-25T23:12:06Z | 130000 | nan | nan | 0 | | M. Plumlee Free Throw 2 of 2 (1 PTS) | [203486] | 1.61061e+09 | CHA | nan | nan | nan | Plumlee | M. Plumlee | nan | nan | nan | nan | nan | Made | 1 | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| 7 | 14 | PT11M06.00S | 2022-03-25T23:12:22.2Z | 1 | REGULAR | 3pt | Jump Shot | [] | 1626220 | 69.7273 | 75.2451 | 1610612762 | 1 | 2 | 2022-03-25T23:12:29Z | 140000 | 126 | 232 | 1 | right | MISS R. O'Neale 26' 3PT | [1626220] | 1.61061e+09 | UTA | nan | nan | nan | O'Neale | R. O'Neale | nan | nan | nan | nan | 26.42 | Missed | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| 8 | 15 | PT11M02.00S | 2022-03-25T23:12:26.2Z | 1 | REGULAR | rebound | offensive | [] | 1627823 | nan | nan | 1610612762 | 1 | 2 | 2022-03-25T23:12:29Z | 150000 | nan | nan | 0 | | J. Hernangomez REBOUND (Off:1 Def:0) | [1627823] | 1.61061e+09 | UTA | nan | nan | nan | Hernangomez | J. Hernangomez | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | 14 | 1 | 0 | 1 | nan | nan | nan | nan | nan | nan |
| 9 | 16 | PT10M56.00S | 2022-03-25T23:12:33.1Z | 1 | REGULAR | 3pt | Jump Shot | ['2ndchance'] | 1628378 | 68.6761 | 70.098 | 1610612762 | 1 | 5 | 2022-03-25T23:12:38Z | 160000 | 100 | 242 | 1 | right | D. Mitchell 26' 3PT (3 PTS) (J. Hernangomez 1 AST) | [1628378, 1627823] | 1.61061e+09 | UTA | nan | nan | nan | Mitchell | D. Mitchell | nan | nan | nan | nan | 26.19 | Made | 3 | J. Hernangomez | 1.62782e+06 | 1 | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan |