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

Code before:
            'title': "The Guardian's Women's Football Weekly | Football | The Guardian",
            'description': 'md5:e2cc021311e582d29935a73614a43f51'
        },
        'playlist_mincount': 69
    }, {
        'url': 'https://www.theguardian.com/news/series/todayinfocus',
        'info_dict': {
            'id': 'todayinfocus',
            'title': 'Today in Focus | News | The Guardian',
            'description': 'md5:0f097764fc0d359e0b6eb537be0387e2'
        },
        'playlist_mincount': 37
    }, {
        'url': 'https://www.theguardian.com/news/series/the-audio-long-read',
        'info_dict': {
            'id': 'the-audio-long-read',
            'title': 'The Audio Long Read | News | The Guardian',
            'description': 'md5:5462994a27527309562b25b6defc4ef3'
        },
        'playlist_mincount': 1008
    }]

    def _fetch_page(self, url, playlist_id, page):
        page += 1
        webpage = self._download_webpage(
            f'{url}?page={page}', playlist_id, note=f'Downloading page: {page}', expected_status=404)

        episodes = traverse_obj(
            get_elements_html_by_class('fc-item--type-media', webpage),
            (..., {extract_attributes}, 'data-id'))

        for entry in episodes:
            yield self.url_result(urljoin('https://www.theguardian.com', entry), TheGuardianPodcastIE)

    def _real_extract(self, url):
        podcast_id = self._match_id(url)
        webpage = self._download_webpage(url, podcast_id)

        title = self._generic_title(url, webpage, default='')
        description = self._og_search_description(webpage) or get_element_by_class(
            'header__description', webpage)

        return self.playlist_result(OnDemandPagedList(
            functools.partial(self._fetch_page, url, podcast_id), self._PAGE_SIZE),
            podcast_id, title, description)


Review comment (suggestion):
The user could have passed a URL with `?page=2`, so we need to use the `query` param here to update it rather than appending to it with an f-string

Also, the site doesn't 404 when we request a page that doesn't exist, it just redirects to the first page (no query). That means if every page has 20 results, then we could get stuck in an endless loop. So instead of assuming `episodes` will be falsy if we request a non-existent page, we need to do this:
```suggestion
        webpage, urlh = self._download_webpage_handle(
            url, playlist_id, note=f'Downloading page {page}', query={'page': page})
        if '?' not in urlh.url:  # site will redirect to 1st page w/o query when page not found
            return
```

Code after:
            'title': "The Guardian's Women's Football Weekly",
            'description': 'md5:e2cc021311e582d29935a73614a43f51'
        },
        'playlist_mincount': 69
    }, {
        'url': 'https://www.theguardian.com/news/series/todayinfocus?page=2',
        'info_dict': {
            'id': 'todayinfocus',
            'title': 'Today in Focus',
            'description': 'md5:0f097764fc0d359e0b6eb537be0387e2'
        },
        'playlist_mincount': 1261
    }, {
        'url': 'https://www.theguardian.com/news/series/the-audio-long-read',
        'info_dict': {
            'id': 'the-audio-long-read',
            'title': 'The Audio Long Read',
            'description': 'md5:5462994a27527309562b25b6defc4ef3'
        },
        'playlist_mincount': 996
    }]

    def _entries(self, url, playlist_id):
        for page in itertools.count(1):
            webpage, urlh = self._download_webpage_handle(
                url, playlist_id, f'Downloading page {page}', query={'page': page})
            if 'page' not in parse_qs(urlh.url):
                break

            episodes = get_elements_html_by_class('fc-item--type-media', webpage)
            for url_path in traverse_obj(episodes, (..., {extract_attributes}, 'data-id')):
                yield url_path

    def _real_extract(self, url):
        podcast_id = self._match_id(url)

        webpage = self._download_webpage(url, podcast_id)

        title = clean_html(get_element_by_class(
            'index-page-header__title', webpage) or get_element_by_class('flagship-audio__title', webpage))
        description = self._og_search_description(webpage) or self._html_search_meta(
            'description', webpage)

        return self.playlist_from_matches(
            self._entries(url, podcast_id), podcast_id, title, description=description,
            ie=TheGuardianPodcastIE, getter=lambda x: urljoin('https://www.theguardian.com', x))