Skip to content

Instantly share code, notes, and snippets.

@brandonros
Last active May 18, 2023 06:02
Show Gist options
  • Save brandonros/c3c7bc8632d45f411f38d2c3e04d7682 to your computer and use it in GitHub Desktop.
Save brandonros/c3c7bc8632d45f411f38d2c3e04d7682 to your computer and use it in GitHub Desktop.
/*
scrape from https://www.espn.com/nba/playbyplay/_/gameId/401547671 with
const quarter = document.querySelector('.tabs__wrapper--playByPlay .tabs__list__item--active').innerText
const rows = document.querySelectorAll('.playByPlay__tableRow')
const plays = []
for (const row of rows) {
const index = row.attributes['data-idx']
const time = row.querySelector('.playByPlay__time').innerText
const text = row.querySelector('.playByPlay__text').innerText
const awayScore = row.querySelector('.playByPlay__score--away').innerText
const homeScore = row.querySelector('.playByPlay__score--home').innerText
plays.push({
index,
quarter,
time,
text,
awayScore,
homeScore
})
}
console.log(JSON.stringify(plays))
*/
import fs from 'fs'
const classifyPlay = (teams, players, play) => {
// weird useless message?
const classifications = []
if (play.text === 'vs.') {
classifications.push('unknown')
}
// free throw
if (play.text.indexOf('misses free throw') !== -1) {
classifications.push('freeThrow:miss')
}
if (play.text.indexOf('makes free throw') !== -1) {
classifications.push('freeThrow:make')
}
// rebound
if (play.text.indexOf('offensive team rebound') !== -1) {
classifications.push('rebound:offensiveTeamRebound')
}
if (play.text.indexOf('defensive rebound') !== -1) {
classifications.push('rebound:defensiveRebound')
}
if (play.text.indexOf('defensive team rebound') !== -1) {
classifications.push('rebound:defensiveTeamRebound')
}
if (play.text.indexOf('offensive rebound') !== -1) {
classifications.push('rebound:offensiveRebound')
}
// turnover
if (play.text.indexOf('lost ball turnover') !== -1) {
classifications.push('turnover:lostBallTurnover')
}
if (play.text.indexOf('bad pass') !== -1) {
classifications.push('turnover:badPassTurnover')
}
if (play.text.indexOf('out of bounds lost ball turnover') !== -1) {
classifications.push('turnover:outOfBoundsLostBallTurnover')
}
if (play.text.indexOf('turnover') !== -1) {
classifications.push('turnover')
}
// foul
if (play.text.indexOf('personal foul') !== -1) {
classifications.push('foul:personalFoul')
}
if (play.text.indexOf('shooting foul') !== -1) {
classifications.push('foul:shootingFoul')
}
if (play.text.indexOf('offensive foul') !== -1) {
classifications.push('foul:offensiveFoul')
}
if (play.text.indexOf('loose ball foul') !== -1) {
classifications.push('foul:looseBallFoul')
}
if (play.text.indexOf('personal take foul') !== -1) {
classifications.push('foul:personalTakeFoul')
}
// goaltending
if (play.text.indexOf('offensive goaltending') !== -1) {
// TODO: turnover?
classifications.push('offensiveGoaltending')
}
if (play.text.indexOf('defensive goaltending') !== -1) {
// TODO: turnover?
classifications.push('defensiveGoaltending')
}
// team
if (play.text.indexOf('enters the game') !== -1) {
classifications.push('team:substitution')
}
// kicked ball violation
if (play.text.indexOf('kicked ball violation') !== -1) {
// TODO: turnover?
classifications.push('kickedBallViolation')
}
// timeout
if (play.text.indexOf('timeout') !== -1) {
classifications.push('team:timeout')
}
// block
if (play.text.indexOf('block') !== -1) {
classifications.push('block')
}
// travel
if (play.text.indexOf('traveling') !== -1) {
// TODO: turnover?
classifications.push('travel')
}
// challenge
if (play.text.indexOf('COACH\'S CHALLENGE') !== -1) {
classifications.push('team:challenge')
}
// jump ball
if (play.text.match(/(.*) vs\. (.*) \(.* gains possession\)/)) {
classifications.push('jumpBall')
}
// assist
if (play.text.indexOf('assist') !== -1) {
classifications.push('assist')
}
// steal
if (play.text.indexOf('steal') !== -1) {
classifications.push('steal')
}
// shot
if (play.text.indexOf('misses') !== -1) {
classifications.push('miss')
}
if (play.text.indexOf('makes tip shot') !== -1) {
classifications.push('shot:makeTipShot')
}
if (play.text.indexOf('makes') !== -1) {
classifications.push('make')
}
if (play.text.indexOf('layup') !== -1) {
classifications.push('shot:layup')
}
if (play.text.indexOf('three point jumper') !== -1) {
classifications.push('shot:threePointJumper')
}
if (play.text.indexOf('three pointer') !== -1) {
classifications.push('shot:threePointer')
}
if (play.text.indexOf('three point pullup jump shot') !== -1) {
classifications.push('shot:threePointPullupJumpShot')
}
if (play.text.indexOf('three point shot') !== -1) {
classifications.push('shot:threePointShot')
}
if (play.text.indexOf('two point shot') !== -1) {
classifications.push('shot:twoPointShot')
}
if (play.text.indexOf('driving floating jump shot') !== -1) {
classifications.push('shot:drivingFloatingJumpShot')
}
if (play.text.indexOf('pullup jump shot') !== -1) {
classifications.push('shot:pullupJumpShot')
}
// players
for (const player of players) {
if (play.text.indexOf(player) !== -1) {
classifications.push(`player:${player}`)
}
}
// teams
for (const team of teams) {
if (play.text.indexOf(team) !== -1) {
classifications.push(`team:${team}`)
}
}
// time
if (play.text.indexOf('End of Game') !== -1) {
classifications.push('time:endOfGame')
}
if (play.text.indexOf('End of the') !== -1) {
classifications.push('time:endOfQuarter')
}
if (play.quarter === '1st') {
classifications.push('time:q1')
classifications.push('time:h1')
}
if (play.quarter === '2nd') {
classifications.push('time:q2')
classifications.push('time:h1')
}
if (play.quarter === '3rd') {
classifications.push('time:q3')
classifications.push('time:h2')
}
if (play.quarter === '4th') {
classifications.push('time:q4')
classifications.push('time:h2')
}
return classifications
}
const players = [
'Robert Williams III',
'Payton Pritchard',
'Max Strus',
'Marcus Smart',
'Malcolm Brogdon',
'Kyle Lowry',
'Kevin Love',
'Bam Adebayo',
'Caleb Martin',
'Gabe Vincent',
'Jaylen Brown',
'Max Strus',
'Malcon Brogdon',
'Cody Zeller',
'Al Horford',
'Jimmy Butler',
'Derrick White',
'Jayson Tatum',
'Duncan Robinson',
]
const teams = [
'Heat',
'Celtics'
]
const categoryMap = {
steals: [
'steal'
],
rebounds: [
'rebound:defensiveRebound',
'rebound:defensiveTeamRebound',
'rebound:offensiveRebound',
'rebound:offensiveTeamRebound'
],
freeThrows: [
'freeThrow:make',
'freeThrow:miss'
],
assists: [
'assist',
],
blocks: [
'block',
],
fouls: [
'foul:looseBallFoul',
'foul:offensiveFoul',
'foul:personalFoul',
'foul:personalTakeFoul',
'foul:shootingFoul'
],
shots: [
'shot:drivingFloatingJumpShot',
'shot:layup',
'shot:makeTipShot',
'shot:pullupJumpShot',
'shot:threePointJumper',
'shot:threePointPullupJumpShot',
'shot:threePointShot',
'shot:threePointer',
'shot:twoPointShot'
],
turnovers: [
'turnover',
'turnover:badPassTurnover',
'turnover:lostBallTurnover',
'turnover:outOfBoundsLostBallTurnover'
],
miscellaneous: [
'offensiveGoaltending',
'kickedBallViolation',
'defensiveGoaltending',
'travel'
]
}
const categories = Object.keys(categoryMap)
const plays = JSON.parse(await fs.promises.readFile('./plays.json', 'utf8'))
for (const play of plays) {
play.classifications = classifyPlay(teams, players, play)
if (play.classifications.length === 0) {
// panic on unknown
console.log(play)
throw new Error('TODO')
}
play.playerClassifications = play.classifications.filter(classification => classification.indexOf('player:') !== -1)
play.teamClassifications = play.classifications.filter(classification => classification.indexOf('team:') !== -1)
play.timeClassifications = play.classifications.filter(classification => classification.indexOf('time:') !== -1)
//console.log(play)
}
const jimmyPlays = plays.filter(play => {
return play.playerClassifications.find(playerClassification => playerClassification === 'player:Jimmy Butler')
})
const results = {}
for (const play of jimmyPlays) {
for (const classification of play.classifications) {
for (const category of categories) {
const classificationIsInCategory = categoryMap[category].indexOf(classification) !== -1
if (classificationIsInCategory) {
if (!results[category]) {
results[category] = []
}
const isPlayAlreadyInCategory = results[category].find(categorizedPlay => JSON.stringify(categorizedPlay) === JSON.stringify(play))
if (isPlayAlreadyInCategory === undefined) {
results[category].push(play)
}
}
}
}
}
console.log(JSON.stringify(results, undefined, 2))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment