Spaces:
Sleeping
Sleeping
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +82 -65
src/streamlit_app.py
CHANGED
|
@@ -19,7 +19,7 @@ def fetch_wnba_data():
|
|
| 19 |
"LastNGames": "0",
|
| 20 |
"LeagueID": "10",
|
| 21 |
"Location": "",
|
| 22 |
-
"MeasureType": "
|
| 23 |
"Month": "0",
|
| 24 |
"OpponentTeamID": "0",
|
| 25 |
"Outcome": "",
|
|
@@ -37,23 +37,36 @@ def fetch_wnba_data():
|
|
| 37 |
"VsDivision": ""
|
| 38 |
}
|
| 39 |
headers = {
|
| 40 |
-
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
| 41 |
"Accept": "application/json, text/plain, */*",
|
| 42 |
-
"x-nba-stats-origin": "stats",
|
| 43 |
-
"x-nba-stats-token": "true",
|
| 44 |
-
"Referer": "https://stats.wnba.com/players/boxscores-traditional/",
|
| 45 |
"Accept-Encoding": "gzip, deflate, br",
|
| 46 |
"Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8",
|
| 47 |
-
"
|
|
|
|
|
|
|
|
|
|
| 48 |
"sec-ch-ua-mobile": "?0",
|
| 49 |
"sec-ch-ua-platform": '"Windows"',
|
| 50 |
-
"
|
| 51 |
-
"
|
| 52 |
-
"
|
| 53 |
-
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
}
|
| 56 |
-
|
| 57 |
try:
|
| 58 |
st.write("Attempting API request with cloudscraper...")
|
| 59 |
scraper = cloudscraper.create_scraper()
|
|
@@ -61,9 +74,8 @@ def fetch_wnba_data():
|
|
| 61 |
response = scraper.get(url, params=params, headers=headers, timeout=60)
|
| 62 |
response.raise_for_status()
|
| 63 |
fetch_time = time.time() - start_time
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
game_logs = data.get("resultSets", [])[0].get("rowSet", [])
|
| 67 |
if not game_logs:
|
| 68 |
st.warning("No game logs found in the response.")
|
| 69 |
return []
|
|
@@ -77,18 +89,17 @@ def fetch_wnba_data():
|
|
| 77 |
response = session.get(url, params=params, headers=headers, timeout=60)
|
| 78 |
response.raise_for_status()
|
| 79 |
fetch_time = time.time() - start_time
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
game_logs = data.get("resultSets", [])[0].get("rowSet", [])
|
| 83 |
if not game_logs:
|
| 84 |
st.warning("No game logs found in the response.")
|
| 85 |
return []
|
| 86 |
return game_logs
|
| 87 |
except requests.Timeout:
|
| 88 |
-
st.error("Request timed out after 60 seconds. Try uploading a local JSON file
|
| 89 |
return []
|
| 90 |
except requests.HTTPError as e:
|
| 91 |
-
st.error(f"HTTP error: {e}.
|
| 92 |
return []
|
| 93 |
except requests.RequestException as e:
|
| 94 |
st.error(f"Request failed: {e}. Possible API or network issue.")
|
|
@@ -107,7 +118,11 @@ def create_dataframe(game_logs):
|
|
| 107 |
"TO_Rank", "PF_Rank", "PTS_Rank", "PLUS_MINUS_Rank", "Fantasy_Score_Rank",
|
| 108 |
"Double_Double_Rank", "Triple_Double_Rank", "Minutes_Formatted"
|
| 109 |
]
|
| 110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
# Function to convert DataFrame to CSV for download
|
| 113 |
def convert_to_csv(df):
|
|
@@ -116,15 +131,16 @@ def convert_to_csv(df):
|
|
| 116 |
return csv_buffer.getvalue()
|
| 117 |
|
| 118 |
# Streamlit app
|
| 119 |
-
st.title("WNBA 2025 Player Game Logs")
|
| 120 |
|
| 121 |
-
# Instructions for
|
| 122 |
st.markdown("""
|
| 123 |
**Troubleshooting API Access**:
|
| 124 |
-
If the API request fails
|
| 125 |
-
1.
|
| 126 |
-
2.
|
| 127 |
-
3. Copy the `Cookie` header
|
|
|
|
| 128 |
""")
|
| 129 |
|
| 130 |
# Option to upload local JSON file
|
|
@@ -154,44 +170,45 @@ if game_logs:
|
|
| 154 |
with st.spinner("Converting data to DataFrame..."):
|
| 155 |
df = create_dataframe(game_logs)
|
| 156 |
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
|
|
|
| 195 |
else:
|
| 196 |
st.warning("No data available to display. Check error messages above or upload a JSON file.")
|
| 197 |
|
|
|
|
| 19 |
"LastNGames": "0",
|
| 20 |
"LeagueID": "10",
|
| 21 |
"Location": "",
|
| 22 |
+
"MeasureType": "Scoring",
|
| 23 |
"Month": "0",
|
| 24 |
"OpponentTeamID": "0",
|
| 25 |
"Outcome": "",
|
|
|
|
| 37 |
"VsDivision": ""
|
| 38 |
}
|
| 39 |
headers = {
|
| 40 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
|
| 41 |
"Accept": "application/json, text/plain, */*",
|
|
|
|
|
|
|
|
|
|
| 42 |
"Accept-Encoding": "gzip, deflate, br",
|
| 43 |
"Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8",
|
| 44 |
+
"Connection": "keep-alive",
|
| 45 |
+
"Host": "stats.wnba.com",
|
| 46 |
+
"Referer": "https://stats.wnba.com/players/boxscores-scoring/",
|
| 47 |
+
"sec-ch-ua": '"Not_A Brand";v="99", "Google Chrome";v="109", "Chromium";v="109"',
|
| 48 |
"sec-ch-ua-mobile": "?0",
|
| 49 |
"sec-ch-ua-platform": '"Windows"',
|
| 50 |
+
"Sec-Fetch-Dest": "empty",
|
| 51 |
+
"Sec-Fetch-Mode": "cors",
|
| 52 |
+
"Sec-Fetch-Site": "same-origin",
|
| 53 |
+
"x-nba-stats-origin": "stats",
|
| 54 |
+
"x-nba-stats-token": "true",
|
| 55 |
+
"X-NewRelic-ID": "VQECWF5UChABUVFUBwYEUVwD",
|
| 56 |
+
"newrelic": "eyJ2IjpbMCwxXSwiZCI6eyJ0eSI6IkJyb3dzZXIiLCJhYyI6IjE3NTk4NzIiLCJhcCI6IjE1ODkwMTY1OTQiLCJpZCI6ImQwMTY2YTBlYjJhNGU3NjEiLCJ0ciI6Ijc5OTcwZjg2YmRmMzFiODMwZDIyYjQ2OTNlZDViZjg2IiwidGkiOjE3NTEwMjUyNTAzMjQsInRrIjoiMjg5MDE5OSJ9fQ==",
|
| 57 |
+
"traceparent": "00-79970f86bdf31b830d22b4693ed5bf86-d0166a0eb2a4e761-01",
|
| 58 |
+
"tracestate": "2890199@nr=0-1-1759872-1589016594-d0166a0eb2a4e761----1751025250324",
|
| 59 |
+
"Cookie": (
|
| 60 |
+
"bm_sv=81F390751AEC1D1FB260953D398C5A4D~YAAQDwHARf6uj6+XAQAAAfA5sRzqRJgRh3B32ZqWy6it7Z6fxqJfhoJnTBNhXxLrad7PcTRelS3tWrr8UZ0igPSGq5RPSbx74Th8HRr7JesXhayd4S6iMSj9lY6iDW8DwtZMJ1VV6Z53UgrKLPJNYRNqrXvNW6HpUvolDeVs2DrB1QvWjgUsftRuNmgEb34LqKZKJS8Wb16Nl3Cnz/U5S451tH3wjHJmfEiW54wOIRtb/4mcmteH1Jeg6Lyrzg==~1;"
|
| 61 |
+
"bm_sz=2D0D6F1234C7ABD81BFA31A8E8436692~YAAQDwHARXoaj6+XAQAAK4TysBywZPNxEID0PIkx3xRXCUd0d/29PGKxBcY8BbfGpXK2mQmY4JFFo6jvaCg7oDKoFnuYpEvzSdS4P+HIoJ2YJT44TIRGGONf2u6YhkIdmN+7HML3CAVZbiqkhJrebrrcY0tDKb7oAmjcYbhZFaToEifUsi6Kzqg2/qjX5ZskYiX6HevYgvqKJG2enicqjp1g9BoYzLRKBualYuB8iafHN03nowT28yk86TjZpDVHdB6KZiRIkE/hOQHwY4+4lspWFjXyWZABbigixU4WduxUOieGFkuelkjSSTJCjMxugYyxZ5f5LNvKvDzYa7b8REHx4T4EMCB1CUF72yIJetJDWGptNPnrK7d2GAMsX7hBC4ot9t+rU+c9f7VqDnI=~4339254~3354693;"
|
| 62 |
+
"_abck=8EDF6F70F2EBD7C030552A7F17AE0613~-1~YAAQDwHARYcaj6+XAQAAb43ysA6SWJgciVxlMdQRfU1xnJKoSsGljvAAyewPCum5U5OhlUgVK6JpjvknmoJ25v4W2UuzDyJ9zotyuSnqoel6MclUARV6tAH5UeZVMT29GJ7hiG8lUV9cGmJLbJ1FeFou2EUSMOuLDSx0TF43TL+8jRVLPtG7kLxgeFLJjZdoeUqVfVP5g0RdClaK5VIzHhE0AWBlK2+1ciAiLJHI85LBhkUpikkxGAmO/v7M6I7+PC75bQh6v2b0kIw/BjtNVuY2JwqhagyfJdhFJjBBw3yhZH8AAiRZqEm25erLGhcoELtBLkOmah3AxxWuXKljfWL47mUiOTi0/XdXG+VTK4U2ep6i+c9n1f9/ykoumWR/2+wmpooBZsWAQx6MwMz8QR4zMDQ4M9r92hb6QhLnZvXWQ+nAbzON7WJ/SJBQ1kqXyNzw9y8pETwvI4VmTFamrAV8UAFx4Vnx+CpobQT+6P2wRo07QzqV2Ai6GCmy1aKT3NU4ktXJO2A+ftzmbyNy+fmSkEjXCC3sJKOrAaBrX93eWJ9Siy3+8tOJTobLJIkzmh5FCRkSz4twbpZKH08=~-1~||0||~1751023975;"
|
| 63 |
+
"ak_bmsc=B7BABDC398E92F1DF6650FC251EACFCF~000000000000000000000000000000~YAAQDwHARY0aj6+XAQAAvo/ysBwjFhKD1uQJMSb1wp40PfdvSnbuU85scjOO5zGLWbgaZDTozOc3oJpqfgUyNPOfunAmS964TA6cI3z9rwEzk/kP+ypn4b1wdhToyxyC0swmVj13Gj+09GTAhyXfnG/GW8lJBLMfmxOhQY4EzGGZwX6cY8eBxe8ZWgUUa3zZKo92fZEM5iLkdjyr5X5EoNsqIFHbEtNlWyJBvhyTxmv4n4q8Xr4+YtaDf/dW5xGITmfcSWzzcR1zQTUY6GgDW0NWsW5Zgz4RFqLu9d5fJkROwuaQwH2EHo9nZu5FXT5B3V+rmnGwPsCPRkYuxUc88u1clSTrZ3qpWXkBuBHdCLE/jGH91XelBfHZu00aDh18czfDmdSCiCVSIhKIIbKBMLO+wT4vLcjZjznyLN3wzY0XFGcZ8Bua0vzan0YhFNVR6+3kOO5HZLXjfSDwmw==;"
|
| 64 |
+
"usprivacy=1---;"
|
| 65 |
+
"_ga=GA1.2.141871504.1751020378;"
|
| 66 |
+
"_gid=GA1.2.808457365.1751020378;"
|
| 67 |
+
"_ga_GELQE9ZCTK=GS2.2.s1751024075$o2$g1$t1751025052$j60$l0$h0"
|
| 68 |
+
)
|
| 69 |
}
|
|
|
|
| 70 |
try:
|
| 71 |
st.write("Attempting API request with cloudscraper...")
|
| 72 |
scraper = cloudscraper.create_scraper()
|
|
|
|
| 74 |
response = scraper.get(url, params=params, headers=headers, timeout=60)
|
| 75 |
response.raise_for_status()
|
| 76 |
fetch_time = time.time() - start_time
|
| 77 |
+
game_logs = response.json().get("resultSets", [])[0].get("rowSet", [])
|
| 78 |
+
st.success(f"API request successful. Fetched {len(game_logs)} records in {fetch_time:.2f} seconds.")
|
|
|
|
| 79 |
if not game_logs:
|
| 80 |
st.warning("No game logs found in the response.")
|
| 81 |
return []
|
|
|
|
| 89 |
response = session.get(url, params=params, headers=headers, timeout=60)
|
| 90 |
response.raise_for_status()
|
| 91 |
fetch_time = time.time() - start_time
|
| 92 |
+
game_logs = response.json().get("resultSets", [])[0].get("rowSet", [])
|
| 93 |
+
st.success(f"API request successful (requests). Fetched {len(game_logs)} records in {fetch_time:.2f} seconds.")
|
|
|
|
| 94 |
if not game_logs:
|
| 95 |
st.warning("No game logs found in the response.")
|
| 96 |
return []
|
| 97 |
return game_logs
|
| 98 |
except requests.Timeout:
|
| 99 |
+
st.error("Request timed out after 60 seconds. Try uploading a local JSON file or checking your network.")
|
| 100 |
return []
|
| 101 |
except requests.HTTPError as e:
|
| 102 |
+
st.error(f"HTTP error: {e}. Verify cookies and headers.")
|
| 103 |
return []
|
| 104 |
except requests.RequestException as e:
|
| 105 |
st.error(f"Request failed: {e}. Possible API or network issue.")
|
|
|
|
| 118 |
"TO_Rank", "PF_Rank", "PTS_Rank", "PLUS_MINUS_Rank", "Fantasy_Score_Rank",
|
| 119 |
"Double_Double_Rank", "Triple_Double_Rank", "Minutes_Formatted"
|
| 120 |
]
|
| 121 |
+
try:
|
| 122 |
+
return pd.DataFrame(game_logs, columns=columns)
|
| 123 |
+
except ValueError:
|
| 124 |
+
st.error("Data structure mismatch. The API response for MeasureType=Scoring may have different columns. Please verify the response format.")
|
| 125 |
+
return pd.DataFrame()
|
| 126 |
|
| 127 |
# Function to convert DataFrame to CSV for download
|
| 128 |
def convert_to_csv(df):
|
|
|
|
| 131 |
return csv_buffer.getvalue()
|
| 132 |
|
| 133 |
# Streamlit app
|
| 134 |
+
st.title("WNBA 2025 Player Game Logs (Scoring)")
|
| 135 |
|
| 136 |
+
# Instructions for troubleshooting
|
| 137 |
st.markdown("""
|
| 138 |
**Troubleshooting API Access**:
|
| 139 |
+
If the API request fails:
|
| 140 |
+
1. Verify the `Cookie` header in the code matches a recent browser request.
|
| 141 |
+
2. Open `https://stats.wnba.com/players/boxscores-scoring/` in Chrome, press F12, go to Network tab, refresh, and find the `playergamelogs` request.
|
| 142 |
+
3. Copy the `Cookie` header and update the `headers` in the code.
|
| 143 |
+
4. Upload a local JSON file as a fallback.
|
| 144 |
""")
|
| 145 |
|
| 146 |
# Option to upload local JSON file
|
|
|
|
| 170 |
with st.spinner("Converting data to DataFrame..."):
|
| 171 |
df = create_dataframe(game_logs)
|
| 172 |
|
| 173 |
+
if not df.empty:
|
| 174 |
+
# Display total number of records
|
| 175 |
+
st.write(f"Total records fetched: {len(df)}")
|
| 176 |
+
|
| 177 |
+
# Display filters
|
| 178 |
+
st.subheader("Filter Data")
|
| 179 |
+
player_filter = st.selectbox("Select Player", options=["All"] + sorted(df["Player_Name"].unique()))
|
| 180 |
+
team_filter = st.selectbox("Select Team", options=["All"] + sorted(df["Team_Abbreviation"].unique()))
|
| 181 |
+
|
| 182 |
+
# Apply filters
|
| 183 |
+
filtered_df = df
|
| 184 |
+
if player_filter != "All":
|
| 185 |
+
filtered_df = filtered_df[filtered_df["Player_Name"] == player_filter]
|
| 186 |
+
if team_filter != "All":
|
| 187 |
+
filtered_df = filtered_df[filtered_df["Team_Abbreviation"] == team_filter]
|
| 188 |
+
|
| 189 |
+
# Display first 1000 records
|
| 190 |
+
st.subheader("Game Log Data (First 1000 Records)")
|
| 191 |
+
display_df = filtered_df.iloc[:1000]
|
| 192 |
+
st.write(f"Displaying {len(display_df)} records (out of {len(filtered_df)} after filtering)")
|
| 193 |
+
st.dataframe(display_df, use_container_width=True)
|
| 194 |
+
|
| 195 |
+
# Download button for all records
|
| 196 |
+
st.subheader("Download Data")
|
| 197 |
+
csv_data = convert_to_csv(filtered_df)
|
| 198 |
+
st.download_button(
|
| 199 |
+
label=f"Download All {len(filtered_df)} Records as CSV",
|
| 200 |
+
data=csv_data,
|
| 201 |
+
file_name="wnba_player_gamelogs_2025_filtered.csv",
|
| 202 |
+
mime="text/csv"
|
| 203 |
+
)
|
| 204 |
+
|
| 205 |
+
# Visualization
|
| 206 |
+
if player_filter != "All":
|
| 207 |
+
st.subheader(f"Points Scored by {player_filter}")
|
| 208 |
+
player_data = filtered_df[["Game_Date", "PTS"]].copy()
|
| 209 |
+
player_data["Game_Date"] = pd.to_datetime(player_data["Game_Date"])
|
| 210 |
+
player_data = player_data.sort_values("Game_Date")
|
| 211 |
+
st.line_chart(player_data.set_index("Game_Date")["PTS"])
|
| 212 |
else:
|
| 213 |
st.warning("No data available to display. Check error messages above or upload a JSON file.")
|
| 214 |
|