from nhlscrapi._tools import to_int
from nhlscrapi._tools import exclude_from as ex_junk
from nhlscrapi.scrapr.reportloader import ReportLoader
from nhlscrapi.games.events import EventType as ET
class TOIRepBase(ReportLoader):
[docs] """
Scrapes TOI reports. (home/away are same format).
"""
def __init__(self, game_key, rep_type):
super(TOIRepBase, self).__init__(game_key, rep_type)
self.by_player = { }
"""
By player dictionary of shift summaries. The only events recorded per shift are :py:class:`.EventType.Goal`
or :py:class:`.EventType.Penalty`. For ``period = 0``, the game totals are returned. Format is
.. code:: python
{
player_num: {
'player_name': { 'first': '', 'last': '' },
'shifts': [
{
'shift_num': shift_num,
'period': period_num,
'start': start_time (elapsed)
'end': end_time (elapsed)
'dur': length_of_shift,
'event': event_enum_goal_or_penalty
}
],
'by_period': {
'period': period_num,
'shifts': shift_count,
'avg': { 'min': min, 'sec': sec },
'toi': { 'min': min, 'sec': sec },
'ev_toi': { 'min': min, 'sec': sec },
'pp_toi': { 'min': min, 'sec': sec },
'sh_toi': { 'min': min, 'sec': sec }
}
}
}
"""
def __player_shifts(self, shift):
parsed_shifts = []
while True:
sh_arr = ex_junk(shift.xpath('.//text()'), ['\r','\n'], [''])
parsed_shifts.append(self.__build_shift(sh_arr))
# get next row
shift = shift.xpath('following-sibling::tr')[0]
cl = shift.get('class')
if cl is None or cl.strip() not in ['oddColor', 'evenColor']:
break
return parsed_shifts, shift
def __get_time(self, time_str):
mins, secs = time_str.split(':')
return { 'min': to_int(mins), 'sec': to_int(secs) }
def __build_shift(self, shift):
shift = [s.strip() for s in shift]
ev = None
if shift[-1] == 'G':
ev = ET.Goal
elif shift[-1] == 'P':
ev = ET.Penalty
return {
'shift_num': to_int(shift[0]),
'period': 4 if shift[1] == 'OT' else to_int(shift[1]),
'start': self.__get_time(shift[2].split(' / ')[0]),
'end': self.__get_time(shift[3].split(' / ')[0]),
'dur': self.__get_time(shift[4]),
'event': ev
}
def __get_by_per_summ(self, per_summ):
summ = { }
while True:
cl = per_summ.get('class')
if cl is not None and cl.strip() in ['oddColor', 'evenColor']:
txt = ex_junk(per_summ.xpath('.//text()'), ['\r', '\n'], [''])
if txt:
per = to_int(txt[0])
per = per if per > 0 else 4 if txt[0] == 'OT' else 0
ps = {
'shifts': to_int(txt[1]),
'avg': self.__get_time(txt[2]),
'toi': self.__get_time(txt[3]),
'ev_toi': self.__get_time(txt[4]),
'pp_toi': self.__get_time(txt[5]),
'sh_toi': self.__get_time(txt[6])
}
summ[per] = ps
per_summ = per_summ.xpath('following-sibling::tr')
if per_summ:
per_summ = per_summ[0]
else:
break
return summ, per_summ
def parse(self):
[docs] """
Parse full TOI document.
:returns: ``self`` if successful else ``None``
"""
try:
return super(TOIRepBase, self).parse().self.parse_shifts()
except:
return None
def parse_shifts(self):
[docs] """
Parse shifts from TOI report
:returns: self if successfule else None
"""
lx_doc = self.html_doc()
pl_heads = lx_doc.xpath('//td[contains(@class, "playerHeading")]')
for pl in pl_heads:
sh_sum = { }
pl_text = pl.xpath('text()')[0]
num_name = pl_text.replace(',','').split(' ')
sh_sum['player_num'] = int(num_name[0]) if num_name[0].isdigit() else -1
sh_sum['player_name'] = { 'first': num_name[2], 'last': num_name[1] }
first_shift = pl.xpath('../following-sibling::tr')[1]
sh_sum['shifts'], last_shift = self.__player_shifts(first_shift)
while ('Per' not in last_shift.xpath('.//text()')):
last_shift = last_shift.xpath('following-sibling::tr')[0]
per_summ = last_shift.xpath('.//tr')[0]
sh_sum['by_period'], last_sum = self.__get_by_per_summ(per_summ)
self.by_player[sh_sum['player_num']] = sh_sum
return self if self.by_player else None
class HomeTOIRep(TOIRepBase):
[docs] """Scrapes the home team TOI report"""
def __init__(self, game_key):
super(HomeTOIRep, self).__init__(game_key, "home_toi")
class AwayTOIRep(TOIRepBase):
[docs] """Scrapes the home team TOI report"""
def __init__(self, game_key):
super(AwayTOIRep, self).__init__(game_key, "away_toi")