We used to use a gem called espn-scraper which is a fork of aj0strow/espn-scraper but it is defunkt and no longer calls the right API.
If you navigate to the ESPN Scoreboard with Chrome dev tools on, in the Network Tab you can search for text. Search for a team name to get various API calls that had it in the response.
Here is one:
https://site.web.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/scoreboard?region=us&lang=en&contentorigin=espn&limit=300&calendartype=blacklist&includeModules=videos%2Ccards&dates=20220317&tz=America%2FNew_York&buyWindow=1m&showAirings=buy%2Clive&showZipLookup=true
The Preview tab of the response is useful. You can right-click and “expand all” to see interesting data.
The path events
is an array of games. for each game there seems to be a shape:
event.status.type.name is an enum of game states (STATUS_FINAL
is the finalized game)
event.competitions[0].competitors is a 2-element array of teams
Other interesting fields not needed for this project:
We can shorten the url a bit:
curl 'https://site.web.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/scoreboard?dates=20220317&tz=America%2FNew_York'
Now, a bit of ruby to get the data we want:
require 'net/http'
class EspnScores
def self.scores_for(date)
api_url = URI("https://site.web.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/scoreboard?dates=#{date}&tz=America%2FNew_York")
resp = Net::HTTP.get(api_url)
events = JSON.parse(resp).to_h.with_indifferent_access[:events]
events.map do |event|
status = event.dig('status', 'type', 'name')
teams = event['competitions'].first['competitors'].map do |competitor|
{
id: competitor['id'],
seed: competitor.dig('curatedRank', 'current'),
home_away: competitor['homeAway'],
score: competitor['score'].to_i,
abbrev: competitor.dig('team', 'abbreviation')
}
end
{ status:, teams: }
end
end
end
For ESPN on each game we know the seed, abbreviation, and who they’re playing. As a first pass, we can just take the seed number, compare the first char of the abbreviation to the 4 teams with the same seed and assign if it matches.
If needed, we can do it in pairs. For each pair of teams, find the 2 abbreviation first letters that match the pair/slots.
This should allow a fairly accurate script.
Grab the scores for thursday and friday:
first_round = EspnScores.scores_for(Date.today) + EspnScores.scores_for(Date.tomorrow)
first_round.size # should == 32
teams = Team.all.to_a
first_round.each do |score|
score_team_one, score_team_two = score[:teams].map {|t| [t[:seed], t[:abbrev]]}
possible_team_one = teams.find {|t| t.seed == score_team_one.first && t.name.starts_with?(score_team_one[1].chars.first)}
possible_team_two = teams.find {|t| t.seed == score_team_two.first && t.name.starts_with?(score_team_two[1].chars.first)}
if possible_team_one && !possible_team_two
possible_team_two = teams.find {|t| t.starting_slot == possible_team_one.starting_slot + 1}
elsif possible_team_two && !possible_team_one
possible_team_one = teams.find {|t| t.starting_slot == possible_team_two.starting_slot - 1}
end
possible_team_one&.update(score_team_id: score_team_one[1])
possible_team_two&.update(score_team_id: score_team_two[1])
end
#Left over abbrev
out = ["Unused Abbreviations: "]
first_round.each do |score|
score[:teams].each do |team|
if Team.find_by(score_team_id: team[:abbrev]).nil?
out << "#{team[:seed]}, #{team[:abbrev]}"
end
end
end
out.each {|line| puts line}