Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/nrk.py (Python)

Code before:
            'title': 'Backstage - 8. episode',
            'description': 'md5:de6ca5d5a2d56849e4021f2bf2850df4',
            'duration': 1320,
            'series': 'Backstage',
            'season_number': 1,
            'episode_number': 8,
            'episode': '8. episode',
            'age_limit': 0,
        },
        'params': {
            'skip_download': True,
        },
        'skip': 'ProgramRightsHasExpired',
    }]

    def _real_extract(self, url):
        display_id, season_number, episode_number = self._match_valid_url(url).group(
            'id', 'season_number', 'episode_number')
        webpage, urlh = self._download_webpage_handle(url, display_id)

        if NRKTVIE.suitable(urlh.url):
            nrk_id = NRKTVIE._match_id(urlh.url)
        else:
            nrk_id = self._search_json(
                r'<script\b[^>]+\bid="pageData"[^>]*>', webpage,
                'page data', display_id)['initialState']['selectedEpisodePrfId']
            if not re.fullmatch(NRKTVIE._EPISODE_RE, nrk_id):
                raise ExtractorError('Unable to extract NRK ID')

        return self.url_result(
            f'nrk:{nrk_id}', NRKIE, nrk_id,
            season_number=int(season_number),
            episode_number=int(episode_number))


class NRKTVSerieBaseIE(NRKBaseIE):
    def _extract_entries(self, entry_list):
        if not isinstance(entry_list, list):
            return []
        entries = []
        for episode in entry_list:
            nrk_id = episode.get('prfId') or episode.get('episodeId')
            if not nrk_id or not isinstance(nrk_id, str):
                continue
            entries.append(self.url_result(
                f'nrk:{nrk_id}', ie=NRKIE.ie_key(), video_id=nrk_id))
        return entries

    _ASSETS_KEYS = ('episodes', 'instalments')

    def _extract_assets_key(self, embedded):

Review comment (bug):
You might hope that `get_element_text_by_id()` would do the job, but IIRC it mungs the script body.

There are various HTML attribute syntax edge cases that could be handled if it's thought worthwhile. This pattern allows for several such, the main ones that I know of:
* whitespace variations
* `\b` matches `-` which could be contained in an attribute name like `data-id` causing the wrong element to be matched
* single vs double quoting vs none (a quirks hold-over).

```suggestion
                r'<script\s[^>]*(?<!-)\bid\s*=\s*("|\')?pageData(?(1)\1|(?=[\s>]))[^>]*>', webpage,
```
But if the page is machine-generated the actual target may be more likely to change than how the wrapper HTML is laid out.   


Code after:
            'ext': 'mp4',
            'title': 'Backstage - 8. episode',
            'description': 'md5:de6ca5d5a2d56849e4021f2bf2850df4',
            'duration': 1320,
            'series': 'Backstage',
            'season_number': 1,
            'episode_number': 8,
            'episode': '8. episode',
            'age_limit': 0,
        },
        'params': {
            'skip_download': True,
        },
        'skip': 'ProgramRightsHasExpired',
    }]

    def _real_extract(self, url):
        display_id, season_number, episode_number = self._match_valid_url(url).group(
            'id', 'season_number', 'episode_number')
        webpage, urlh = self._download_webpage_handle(url, display_id)

        if NRKTVIE.suitable(urlh.url):
            nrk_id = NRKTVIE._match_id(urlh.url)
        else:
            nrk_id = self._search_json(
                r'<script\b[^>]+\bid="pageData"[^>]*>', webpage,
                'page data', display_id)['initialState']['selectedEpisodePrfId']
            if not re.fullmatch(NRKTVIE._EPISODE_RE, nrk_id):
                raise ExtractorError('Unable to extract NRK ID')

        return self.url_result(
            f'nrk:{nrk_id}', NRKIE, nrk_id,
            season_number=int(season_number),
            episode_number=int(episode_number))


class NRKTVSerieBaseIE(NRKBaseIE):
    def _extract_entries(self, entry_list):
        if not isinstance(entry_list, list):
            return []
        entries = []
        for episode in entry_list:
            nrk_id = episode.get('prfId') or episode.get('episodeId')
            if not nrk_id or not isinstance(nrk_id, str):
                continue
            entries.append(self.url_result(
                f'nrk:{nrk_id}', ie=NRKIE.ie_key(), video_id=nrk_id))
        return entries

    _ASSETS_KEYS = ('episodes', 'instalments')