Files
swehockey/testdata/seasons_20961.html
T
Mats Tornberg 6b701819ca Deterministic swehockey.se schedule crawler
One HTTP GET per season, no browser: the page is server-rendered, so every row
arrives in one response and the extraction becomes a pure function of the HTML.

- layouts[] declares one profile per version of the site's table (modern and
  legacy), each identified by its header row, so a new version of the page is a
  new profile plus a fixture instead of a guess.
- The table is located by header signature, not by a CSS path.
- Every cell is shape-checked, and facts the site renders twice (desktop and
  mobile date, time in the tooltip) must agree.
- Cross-field invariants: periods sum to the score, ids are unique, played games
  always have an event link and unplayed ones never do.
- Output is sorted by date, time and id, so it does not inherit the site's row
  order, and it carries no fetch timestamp: same HTML, same bytes.
- Drift is a typed error and exit code 3, with the header it actually found.
- Tests run on committed HTML fixtures and golden JSON; TestLive is opt-in.

Verified over the full SHL archive: 27/27 seasons, 2119 games, 1779 played.
2026-09-27 08:47:25 +00:00

42 lines
4.8 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8"><title>SHL | stats.swehockey.se</title></head>
<body>
<h1>SHL</h1>
<ul>
<a href="/ScheduleAndResults/Live/20961" onclick="pageVisited('/ScheduleAndResults/Live/20961','SHL')" target="_self">SHL</a><a href="/ScheduleAndResults/Overview/20962" onclick="pageVisited('/ScheduleAndResults/Overview/20962','HockeyAllsvenskan')" target="_self">HockeyAllsvenskan</a><a href="/ScheduleAndResults/Schedule/21041" onclick="pageVisited('/ScheduleAndResults/Schedule/21041','Hockeyettan')" target="_self">Hockeyettan</a><a href="/ScheduleAndResults/Overview/21970" onclick="pageVisited('/ScheduleAndResults/Overview/21970','Svenska Universitetshockeyligan')" target="_self">Svenska Universitetshockeyligan</a><a href="/ScheduleAndResults/Schedule/19767" onclick="pageVisited('/ScheduleAndResults/Schedule/19767','Svenska Cupen U20')" target="_self">Svenska Cupen U20</a><a href="/ScheduleAndResults/Overview/20964" onclick="pageVisited('/ScheduleAndResults/Overview/20964','U20 Nationell')" target="_self">U20 Nationell</a>
</ul>
<select class="form-control form-control-sm" onchange="doNavigate(this)">
<option id="/ScheduleAndResults/Live/20961" selected="selected" value="/ScheduleAndResults/Live/20961">2026-27</option>
<option id="/ScheduleAndResults/Overview/18263" value="/ScheduleAndResults/Overview/18263">2025-26</option>
<option id="/ScheduleAndResults/Overview/17556" value="/ScheduleAndResults/Overview/17556">2024-25</option>
<option id="/ScheduleAndResults/Overview/15791" value="/ScheduleAndResults/Overview/15791">2023-24</option>
<option id="/ScheduleAndResults/Overview/14296" value="/ScheduleAndResults/Overview/14296">2022-23</option>
<option id="/ScheduleAndResults/Overview/13348" value="/ScheduleAndResults/Overview/13348">2021-22</option>
<option id="/ScheduleAndResults/Overview/12267" value="/ScheduleAndResults/Overview/12267">2020-21</option>
<option id="/ScheduleAndResults/Overview/10371" value="/ScheduleAndResults/Overview/10371">2019-20</option>
<option id="/ScheduleAndResults/Overview/9820" value="/ScheduleAndResults/Overview/9820">2018-19</option>
<option id="/ScheduleAndResults/Overview/9006" value="/ScheduleAndResults/Overview/9006">2017-18</option>
<option id="/ScheduleAndResults/Overview/7978" value="/ScheduleAndResults/Overview/7978">2016-17</option>
<option id="/ScheduleAndResults/Overview/6699" value="/ScheduleAndResults/Overview/6699">2015-16</option>
<option id="/ScheduleAndResults/Overview/5995" value="/ScheduleAndResults/Overview/5995">2014-15</option>
<option id="/ScheduleAndResults/Overview/4870" value="/ScheduleAndResults/Overview/4870">2013-14</option>
<option id="/ScheduleAndResults/Schedule/3810" value="/ScheduleAndResults/Schedule/3810">2012-13</option>
<option id="/ScheduleAndResults/Schedule/965" value="/ScheduleAndResults/Schedule/965">2011-12</option>
<option id="/ScheduleAndResults/Overview/841" value="/ScheduleAndResults/Overview/841">2010-11</option>
<option id="/ScheduleAndResults/Overview/9025" value="/ScheduleAndResults/Overview/9025">2009-10</option>
<option id="/ScheduleAndResults/Overview/9053" value="/ScheduleAndResults/Overview/9053">2008-09</option>
<option id="/ScheduleAndResults/Overview/9078" value="/ScheduleAndResults/Overview/9078">2007-08</option>
<option id="/ScheduleAndResults/Overview/9142" value="/ScheduleAndResults/Overview/9142">2006-07</option>
<option id="/ScheduleAndResults/Overview/9147" value="/ScheduleAndResults/Overview/9147">2005-06</option>
<option id="/ScheduleAndResults/Overview/9150" value="/ScheduleAndResults/Overview/9150">2004-05</option>
<option id="/ScheduleAndResults/Overview/9153" value="/ScheduleAndResults/Overview/9153">2003-04</option>
<option id="/ScheduleAndResults/Overview/12153" value="/ScheduleAndResults/Overview/12153">2002-03</option>
<option id="/ScheduleAndResults/Overview/12158" value="/ScheduleAndResults/Overview/12158">2001-02</option>
<option id="/ScheduleAndResults/Overview/12173" value="/ScheduleAndResults/Overview/12173">2000-01</option>
<option class="disabled font-weight-bold text-black" style="font-size:16px" disabled value="-1"> Cups</option>
<hr />
</select>
</body>
</html>