ytdl-org/youtube-dl · error · ExtractorError

PornHub said: %s

Error message

PornHub said: %s

What it means

PornHubIE._real_extract raises ExtractorError('PornHub said: %s' % error_msg, expected=True) right after downloading the PC webpage, when the HTML contains an error block matched by either the 'removed|userMessageSection' div regex or the 'noVideo' section regex. The scraped block text (whitespace-collapsed) is appended, so the message is the site's own removal/unavailability notice.

Source

Thrown at youtube_dl/extractor/pornhub.py:284

        self._login(host)

        self._set_cookie(host, 'age_verified', '1')

        def dl_webpage(platform):
            self._set_cookie(host, 'platform', platform)
            return self._download_webpage(
                'https://www.%s/view_video.php?viewkey=%s' % (host, video_id),
                video_id, 'Downloading %s webpage' % platform)

        webpage = dl_webpage('pc')

        error_msg = self._html_search_regex(
            (r'(?s)<div[^>]+class=(["\'])(?:(?!\1).)*\b(?:removed|userMessageSection)\b(?:(?!\1).)*\1[^>]*>(?P<error>.+?)</div>',
             r'(?s)<section[^>]+class=["\']noVideo["\'][^>]*>(?P<error>.+?)</section>'),
            webpage, 'error message', default=None, group='error')
        if error_msg:
            error_msg = re.sub(r'\s+', ' ', error_msg)
            raise ExtractorError(
                'PornHub said: %s' % error_msg,
                expected=True, video_id=video_id)

        if any(re.search(p, webpage) for p in (
                r'class=["\']geoBlocked["\']',
                r'>\s*This content is unavailable in your country')):
            self.raise_geo_restricted()

        # video_title from flashvars contains whitespace instead of non-ASCII (see
        # http://www.pornhub.com/view_video.php?viewkey=1331683002), not relying
        # on that anymore.
        title = self._html_search_meta(
            'twitter:title', webpage, default=None) or self._html_search_regex(
            (r'(?s)<h1[^>]+class=["\']title["\'][^>]*>(?P<title>.+?)</h1>',
             r'<div[^>]+data-video-title=(["\'])(?P<title>(?:(?!\1).)+)\1',
             r'shareTitle["\']\s*[=:]\s*(["\'])(?P<title>(?:(?!\1).)+)\1'),
            webpage, 'title', group='title')

View on GitHub (pinned to 956b8c5855)

Solutions

  1. Read the trailing site message in the error — it states whether the video was removed or is otherwise unavailable.
  2. Drop the dead URL from the batch and continue with remaining entries.
  3. Confirm in a browser that the video still exists; if the browser shows it fine, update youtube-dl (markup drift can false-positive this regex).
  4. If the page says deleted/removed, no client-side fix exists — obtain a re-uploaded or canonical URL.
Defensive patterns

Strategy: try-catch

Try / catch

try:
    info = ydl.extract_info(url)
except ExtractorError as e:
    if 'PornHub said:' in str(e):
        mark_dead(url)  # removed/private; skip permanently

Prevention

When it happens

Trigger: Extracting a video that was removed (removed div), deleted by its uploader, made private, or blocked, where the page renders a userMessageSection/noVideo block instead of the player; checked before geo-blocking detection, so plain removal hits this branch first.

Common situations: Stale links in saved playlists pointing to taken-down videos; DMCA removals; uploader-deleted content; age/verification gating that renders the same error section. Distinct from geo-blocking, which is detected just after this check and raises raise_geo_restricted instead.

Related errors


AI-assisted analysis of ytdl-org/youtube-dl@956b8c5855 (2026-08-14). Data as JSON: /api/errors/d45728cf84f23ceb. Report an issue: GitHub.