Game Report Periods to JSON Script - notnotjeff/ahl_scraper GitHub Wiki
Sometimes games have missing JSON penalty and/or scoring data (see 1001050
) and you will need to create a manual JSON game file from the Game Report (see 1001050
). Place the custom JSON file in the in the fixed_games
folder and add its id to the BROKEN_GAMES
array in AhlScraper::GameDataFetcher
to override the call with your custom fixed JSON.
If you fix a game make sure to create a pull request to add the game to the gem to help others get accurate data.
Code
In order to easily create JSON data for a games scoring and penalties you can use the following Ruby script to generate period JSON data from the Game Report
# frozen_string_literal: true
# RUBY VERSION: 2.7.3
gem "pry"
gem "json"
gem "nokogiri"
require 'pry'
require "json"
require "nokogiri"
require "open-uri"
# CHOOSE GAME ID
game_id = 1020527
# GET JSON FOR ROSTER AND TEAM DATA
GAME_JSON = JSON.parse(Nokogiri::HTML(URI.parse("http://lscluster.hockeytech.com/feed/index.php?feed=statviewfeed&view=gameSummary&game_id=#{game_id}&key=50c2cd9b5e18e390&site_id=1&client_code=ahl&lang=en&league_id=&callback=json").open).text[5..-2], symbolize_names: true)
# UNCOMMENT THE FOLLOWING LINE IF YOU WANT TO CREATE A FILE TO LOOK THROUGH
# File.write("#{game_id}_original.json", GAME_JSON.to_json)
TEAM_HASH = {
"H" => GAME_JSON[:homeTeam][:info],
"V" => GAME_JSON[:visitingTeam][:info],
GAME_JSON[:homeTeam][:info][:abbreviation] => "H",
GAME_JSON[:visitingTeam][:info][:abbreviation] => "V"
}.freeze
ROSTERS = {
"H" => [*GAME_JSON[:homeTeam][:skaters].map { |player| player[:info] }, *GAME_JSON[:homeTeam][:goalies].map { |player| player[:info] }],
"V" => [*GAME_JSON[:visitingTeam][:skaters].map { |player| player[:info] }, *GAME_JSON[:visitingTeam][:goalies].map { |player| player[:info] }]
}.freeze
PERIOD_INFO = {
"1st" => { id: "1", shortName: "", longName: "1st" },
"2nd" => { id: "2", shortName: "", longName: "2nd" },
"3rd" => { id: "3", shortName: "", longName: "3rd" },
"OT" => { id: "4", shortName: "OT", longName: "OT" }
}.freeze
data = {
periods: [
{ info: PERIOD_INFO["1st"], stats: { homeGoals: "0", homeShots: "0", visitingGoals: "0", visitingShots: "0" }, goals: [], penalties: [] },
{ info: PERIOD_INFO["2nd"], stats: { homeGoals: "0", homeShots: "0", visitingGoals: "0", visitingShots: "0" }, goals: [], penalties: [] },
{ info: PERIOD_INFO["3rd"], stats: { homeGoals: "0", homeShots: "0", visitingGoals: "0", visitingShots: "0" }, goals: [], penalties: [] }
]
}
document = Nokogiri::HTML(URI.parse("https://lscluster.hockeytech.com/game_reports/official-game-report.php?client_code=ahl&game_id=#{game_id}&lang_id=1").open)
penalty_rows = document.css("table")[13].css("tr")[2..-1].map { |pen| pen.css("td").map(&:text) }
scoring_rows = document.css("table")[7].css("tr")[1..-1].map { |pen| pen.css("td").map(&:text) }
visiting_shots, home_shots = document.css("table")[5].css("tr")[1..-1].map { |pen| pen.css("td").map(&:text) }.map { |team| team[1..-2] }
# ADD OVERTIME PERIOD IF IT EXISTS
overtime = penalty_rows.filter { |pen| pen[0] == "OT" }.any? || scoring_rows.filter { |goal| goal[2] == "OT" }.any? ? true : false
data[:periods] << { info: PERIOD_INFO["OT"], stats: { homeGoals: "0", homeShots: "0", visitingGoals: "0", visitingShots: "0" }, goals: [], penalties: [] } if overtime
# SHOTS
visiting_shots.each.with_index do |shots, i|
data[:periods][i][:stats][:visitingShots] = shots.to_i.to_s
end
home_shots.each.with_index do |shots, i|
data[:periods][i][:stats][:homeShots] = shots.to_i.to_s
end
# PENALTIES
penalty_rows.map do |row|
served_first, served_last = row[2].scan(/Served by(.*)/)&.dig(0, 0)&.strip&.split(" ")
first, last = served_last ? row[2].scan(/(.*)Served by/)&.dig(0, 0)&.strip&.split(" ") : row[2].split(" ")
taken_player_data = last ? ROSTERS[row[1]].find { |player| player[:firstName][0] == first[0] && player[:lastName] == last } : nil
served_player_data = served_last ? ROSTERS[row[1]].find { |player| player[:firstName][0] == served_first[0] && player[:lastName] == served_last } : nil
penalty = {
game_penalty_id: nil,
period: PERIOD_INFO[row[0]],
time: row[5],
againstTeam: TEAM_HASH[row[1]],
minutes: row[3].split(".")[0].to_i,
description: row[4].strip,
ruleNumber: "",
takenBy: taken_player_data,
servedBy: served_player_data || taken_player_data,
isPowerPlay: true,
isBench: false
}
data[:periods][PERIOD_INFO[row[0]][:id].to_i - 1][:penalties] << penalty
end
# SCORING
scoring_rows.map do |row|
scoring_team = TEAM_HASH[row[3]] # H or V
against_team = scoring_team == "H" ? "V" : "H"
scorer_first, scorer_last, goal_number = row[5].split(" ")
goal_number = /(?<=\().*?(?=\))/.match(goal_number)[0]
a1_full_name, a2_full_name = row[6].split(", ")
a1_first, a1_last = a1_full_name&.split(" ")
a2_first, a2_last = a2_full_name&.split(" ")
scorer_data = ROSTERS[scoring_team].find { |player| player[:firstName][0] == scorer_first[0] && player[:lastName] == scorer_last }
a1_data = a1_last.nil? ? nil : ROSTERS[scoring_team].find { |player| player[:firstName][0] == a1_first[0] && player[:lastName] == a1_last }
a2_data = a2_last.nil? ? nil : ROSTERS[scoring_team].find { |player| player[:firstName][0] == a2_first[0] && player[:lastName] == a2_last }
scoring_team_player_numbers = scoring_team == "H" ? row[11].scan(/\d{1,2}/).map(&:to_i) : row[9].scan(/\d{1,2}/).map(&:to_i)
against_team_player_numbers = scoring_team == "H" ? row[9].scan(/\d{1,2}/).map(&:to_i) : row[11].scan(/\d{1,2}/).map(&:to_i)
plus_players = ROSTERS[scoring_team].filter { |player| scoring_team_player_numbers.include?(player[:jerseyNumber]) }.sort { |a, b| a[:jerseyNumber] <=> b[:jerseyNumber] }
minus_players = ROSTERS[against_team].filter { |player| against_team_player_numbers.include?(player[:jerseyNumber]) }.sort { |a, b| a[:jerseyNumber] <=> b[:jerseyNumber] }
goal = {
game_goal_id: nil,
team: TEAM_HASH[scoring_team],
period: PERIOD_INFO[row[2]],
time: row[4],
scorerGoalNumber: goal_number,
scoredBy: scorer_data,
assists: [a1_data, a2_data].filter { |assist| !assist.nil? },
assistNumbers: [nil, nil],
properties: {
isPowerPlay: row[7] == "PP" ? "1" : "0",
isShortHanded: row[7] == "SH" ? "1" : "0",
isEmptyNet: row[7] == "EN" ? "1" : "0",
isPenaltyShot: row[7] == "PS" ? "1" : "0",
isInsuranceGoal: "0",
isGameWinningGoal: "0"
},
plus_players: plus_players,
minus_players: minus_players
}
data[:periods][PERIOD_INFO[row[2]][:id].to_i - 1][:goals] << goal
end
# PERIOD GOAL TOTALS
data[:periods].each do |period|
home_goal_count = period[:goals].filter { |goal| TEAM_HASH[goal[:team][:abbreviation]] == "H" }.length
visiting_goal_count = period[:goals].filter { |goal| TEAM_HASH[goal[:team][:abbreviation]] == "V" }.length
period[:stats][:homeGoals] = home_goal_count.to_s
period[:stats][:visitingGoals] = visiting_goal_count.to_s
end
File.write("#{game_id}_created_data.json", data.to_json)
Caveats
There are a few things you'll still need to do manually:
-
Goals
- assistNumbers
- game_goal_id
- isInsuranceGoal
- isGameWinningGoal
-
Penalties
- game_penalty_id
- isPowerPlay
- isBench
- ruleNumber
Some of these may be able to be automated but are not implemented yet.