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.
204 lines
3.9 KiB
JSON
204 lines
3.9 KiB
JSON
{
|
|
"season_id": 20961,
|
|
"layout": "legacy",
|
|
"title": "Schedule and Results",
|
|
"last_update": "2019-05-02 22:02",
|
|
"games": [
|
|
{
|
|
"game_id": "90002023",
|
|
"event_id": "423859",
|
|
"round": "8",
|
|
"date": "2019-03-30",
|
|
"time": "15:15",
|
|
"home": "Färjestad BK",
|
|
"away": "HV 71",
|
|
"home_goals": 3,
|
|
"away_goals": 2,
|
|
"periods": [
|
|
{
|
|
"label": "1",
|
|
"home": 0,
|
|
"away": 0
|
|
},
|
|
{
|
|
"label": "2",
|
|
"home": 0,
|
|
"away": 1
|
|
},
|
|
{
|
|
"label": "3",
|
|
"home": 2,
|
|
"away": 1
|
|
},
|
|
{
|
|
"label": "4",
|
|
"home": 0,
|
|
"away": 0
|
|
},
|
|
{
|
|
"label": "5",
|
|
"home": 0,
|
|
"away": 0
|
|
},
|
|
{
|
|
"label": "6",
|
|
"home": 1,
|
|
"away": 0
|
|
}
|
|
],
|
|
"spectators": 8250,
|
|
"venue": "Löfbergs Arena"
|
|
},
|
|
{
|
|
"game_id": "90002026",
|
|
"event_id": "423862",
|
|
"round": "8",
|
|
"date": "2019-03-30",
|
|
"time": "18:00",
|
|
"home": "Luleå HF",
|
|
"away": "Växjö Lakers HC",
|
|
"home_goals": 4,
|
|
"away_goals": 1,
|
|
"periods": [
|
|
{
|
|
"label": "1",
|
|
"home": 1,
|
|
"away": 0
|
|
},
|
|
{
|
|
"label": "2",
|
|
"home": 1,
|
|
"away": 0
|
|
},
|
|
{
|
|
"label": "3",
|
|
"home": 2,
|
|
"away": 1
|
|
}
|
|
],
|
|
"spectators": 6150,
|
|
"venue": "Coop Norrbotten Arena"
|
|
},
|
|
{
|
|
"game_id": "90002024",
|
|
"event_id": "423860",
|
|
"round": "8",
|
|
"date": "2019-03-31",
|
|
"time": "15:00",
|
|
"home": "Frölunda HC",
|
|
"away": "IF Malmö Redhawks",
|
|
"home_goals": 4,
|
|
"away_goals": 1,
|
|
"periods": [
|
|
{
|
|
"label": "1",
|
|
"home": 0,
|
|
"away": 0
|
|
},
|
|
{
|
|
"label": "2",
|
|
"home": 3,
|
|
"away": 1
|
|
},
|
|
{
|
|
"label": "3",
|
|
"home": 1,
|
|
"away": 0
|
|
}
|
|
],
|
|
"spectators": 6044,
|
|
"venue": "Scandinavium"
|
|
},
|
|
{
|
|
"game_id": "90002025",
|
|
"event_id": "423861",
|
|
"round": "8",
|
|
"date": "2019-03-31",
|
|
"time": "15:00",
|
|
"home": "Djurgårdens IF",
|
|
"away": "Skellefteå AIK",
|
|
"home_goals": 2,
|
|
"away_goals": 3,
|
|
"periods": [
|
|
{
|
|
"label": "1",
|
|
"home": 0,
|
|
"away": 0
|
|
},
|
|
{
|
|
"label": "2",
|
|
"home": 0,
|
|
"away": 2
|
|
},
|
|
{
|
|
"label": "3",
|
|
"home": 2,
|
|
"away": 1
|
|
}
|
|
],
|
|
"spectators": 8094,
|
|
"venue": "Hovet, Johanneshov"
|
|
},
|
|
{
|
|
"game_id": "90002053",
|
|
"event_id": "423889",
|
|
"round": "22",
|
|
"date": "2019-04-30",
|
|
"time": "19:30",
|
|
"home": "Frölunda HC",
|
|
"away": "Djurgårdens IF",
|
|
"home_goals": 6,
|
|
"away_goals": 2,
|
|
"periods": [
|
|
{
|
|
"label": "1",
|
|
"home": 1,
|
|
"away": 0
|
|
},
|
|
{
|
|
"label": "2",
|
|
"home": 3,
|
|
"away": 0
|
|
},
|
|
{
|
|
"label": "3",
|
|
"home": 2,
|
|
"away": 2
|
|
}
|
|
],
|
|
"spectators": 12044,
|
|
"venue": "Scandinavium"
|
|
},
|
|
{
|
|
"game_id": "90002054",
|
|
"event_id": "423890",
|
|
"round": "23",
|
|
"date": "2019-05-02",
|
|
"time": "19:30",
|
|
"home": "Djurgårdens IF",
|
|
"away": "Frölunda HC",
|
|
"home_goals": 2,
|
|
"away_goals": 6,
|
|
"periods": [
|
|
{
|
|
"label": "1",
|
|
"home": 1,
|
|
"away": 3
|
|
},
|
|
{
|
|
"label": "2",
|
|
"home": 0,
|
|
"away": 1
|
|
},
|
|
{
|
|
"label": "3",
|
|
"home": 1,
|
|
"away": 2
|
|
}
|
|
],
|
|
"spectators": 13850,
|
|
"venue": "Ericsson Globe"
|
|
}
|
|
]
|
|
}
|