diff --git a/youtube_transcript_api/__init__.py b/youtube_transcript_api/__init__.py index baefd02..2d58d34 100644 --- a/youtube_transcript_api/__init__.py +++ b/youtube_transcript_api/__init__.py @@ -10,5 +10,6 @@ TranslationLanguageNotAvailable, NoTranscriptAvailable, CookiePathInvalid, - CookiesInvalid + CookiesInvalid, + FailedToCreateConsentCookie, ) diff --git a/youtube_transcript_api/_api.py b/youtube_transcript_api/_api.py index ad69b90..37bd6b2 100644 --- a/youtube_transcript_api/_api.py +++ b/youtube_transcript_api/_api.py @@ -129,12 +129,11 @@ def get_transcript(cls, video_id, languages=('en',), proxies=None, cookies=None) @classmethod def _load_cookies(cls, cookies, video_id): - cookie_jar = {} try: cookie_jar = cookiejar.MozillaCookieJar() cookie_jar.load(cookies) + if not cookie_jar: + raise CookiesInvalid(video_id) + return cookie_jar except CookieLoadError: raise CookiePathInvalid(video_id) - if not cookie_jar: - raise CookiesInvalid(video_id) - return cookie_jar diff --git a/youtube_transcript_api/_errors.py b/youtube_transcript_api/_errors.py index c3afb32..cd645b5 100644 --- a/youtube_transcript_api/_errors.py +++ b/youtube_transcript_api/_errors.py @@ -40,10 +40,15 @@ class VideoUnavailable(CouldNotRetrieveTranscript): class TooManyRequests(CouldNotRetrieveTranscript): - CAUSE_MESSAGE = ("YouTube is receiving too many requests from this IP and now requires solving a captcha to continue. One of the following things can be done to work around this:\n\ - - Manually solve the captcha in a browser and export the cookie. Read here how to use that cookie with youtube-transcript-api: https://github.com/jdepoix/youtube-transcript-api#cookies\n\ - - Use a different IP address\n\ - - Wait until the ban on your IP has been lifted") + CAUSE_MESSAGE = ( + 'YouTube is receiving too many requests from this IP and now requires solving a captcha to continue. ' + 'One of the following things can be done to work around this:\n\ + - Manually solve the captcha in a browser and export the cookie. ' + 'Read here how to use that cookie with ' + 'youtube-transcript-api: https://github.com/jdepoix/youtube-transcript-api#cookies\n\ + - Use a different IP address\n\ + - Wait until the ban on your IP has been lifted' + ) class TranscriptsDisabled(CouldNotRetrieveTranscript): @@ -70,6 +75,10 @@ class CookiesInvalid(CouldNotRetrieveTranscript): CAUSE_MESSAGE = 'The cookies provided are not valid (may have expired)' +class FailedToCreateConsentCookie(CouldNotRetrieveTranscript): + CAUSE_MESSAGE = 'Failed to automatically give consent to saving cookies' + + class NoTranscriptFound(CouldNotRetrieveTranscript): CAUSE_MESSAGE = ( 'No transcripts were found for any of the requested language codes: {requested_language_codes}\n\n' diff --git a/youtube_transcript_api/_transcripts.py b/youtube_transcript_api/_transcripts.py index 10c5ec0..b0d6f38 100644 --- a/youtube_transcript_api/_transcripts.py +++ b/youtube_transcript_api/_transcripts.py @@ -20,6 +20,7 @@ NotTranslatable, TranslationLanguageNotAvailable, NoTranscriptAvailable, + FailedToCreateConsentCookie, ) from ._settings import WATCH_URL @@ -32,7 +33,7 @@ def fetch(self, video_id): return TranscriptList.build( self._http_client, video_id, - self._extract_captions_json(self._fetch_html(video_id), video_id) + self._extract_captions_json(self._fetch_video_html(video_id), video_id) ) def _extract_captions_json(self, html, video_id): @@ -55,6 +56,21 @@ def _extract_captions_json(self, html, video_id): return captions_json + def _create_consent_cookie(self, html, video_id): + match = re.search('name="v" value="(.*?)"', html) + if match is None: + raise FailedToCreateConsentCookie(video_id) + self._http_client.cookies.set('CONSENT', 'YES+' + match.group(1), domain='.youtube.com') + + def _fetch_video_html(self, video_id): + html = self._fetch_html(video_id) + if 'action="https://consent.youtube.com/s"' in html: + self._create_consent_cookie(html, video_id) + html = self._fetch_html(video_id) + if 'action="https://consent.youtube.com/s"' in html: + raise FailedToCreateConsentCookie(video_id) + return html + def _fetch_html(self, video_id): return self._http_client.get(WATCH_URL.format(video_id=video_id)).text.replace( '\\u0026', '&' diff --git a/youtube_transcript_api/test/assets/youtube_consent_page.html.static b/youtube_transcript_api/test/assets/youtube_consent_page.html.static new file mode 100644 index 0000000..40b4235 --- /dev/null +++ b/youtube_transcript_api/test/assets/youtube_consent_page.html.static @@ -0,0 +1,160 @@ +Bevor Sie zu YouTube weitergehen
Anmelden
YouTube ein Google-Unternehmen

Bevor Sie zu YouTube weitergehen

Google verwendet Cookies und Daten, um Dienste und Werbung zur Verfügung zu stellen, zu verwalten und zu verbessern. Wenn Sie zustimmen, nutzen wir Cookies für diese Zwecke und dazu, Inhalte und Werbung für Sie zu personalisieren, damit Sie z. B. relevantere Google-Suchergebnisse und relevantere Werbung bei YouTube erhalten. Die Personalisierung erfolgt auf Grundlage Ihrer Aktivitäten, beispielsweise Ihrer Google-Suchanfragen und der Videos, die Sie sich bei YouTube ansehen. Wir verwenden diese Daten auch für Analysen und Messungen. Klicken Sie auf „Anpassen“, um sich weitere Optionen anzusehen, oder besuchen Sie g.co/privacytools. Darüber hinaus haben Sie die Möglichkeit, Ihre Browsereinstellungen so zu konfigurieren, dass einige oder alle Cookies blockiert werden.

Anpassen
\ No newline at end of file diff --git a/youtube_transcript_api/test/assets/youtube_consent_page_invalid.html.static b/youtube_transcript_api/test/assets/youtube_consent_page_invalid.html.static new file mode 100644 index 0000000..db74c38 --- /dev/null +++ b/youtube_transcript_api/test/assets/youtube_consent_page_invalid.html.static @@ -0,0 +1,160 @@ +Bevor Sie zu YouTube weitergehen
Anmelden
YouTube ein Google-Unternehmen

Bevor Sie zu YouTube weitergehen

Google verwendet Cookies und Daten, um Dienste und Werbung zur Verfügung zu stellen, zu verwalten und zu verbessern. Wenn Sie zustimmen, nutzen wir Cookies für diese Zwecke und dazu, Inhalte und Werbung für Sie zu personalisieren, damit Sie z. B. relevantere Google-Suchergebnisse und relevantere Werbung bei YouTube erhalten. Die Personalisierung erfolgt auf Grundlage Ihrer Aktivitäten, beispielsweise Ihrer Google-Suchanfragen und der Videos, die Sie sich bei YouTube ansehen. Wir verwenden diese Daten auch für Analysen und Messungen. Klicken Sie auf „Anpassen“, um sich weitere Optionen anzusehen, oder besuchen Sie g.co/privacytools. Darüber hinaus haben Sie die Möglichkeit, Ihre Browsereinstellungen so zu konfigurieren, dass einige oder alle Cookies blockiert werden.

Anpassen
\ No newline at end of file diff --git a/youtube_transcript_api/test/test_api.py b/youtube_transcript_api/test/test_api.py index 7650cf4..240164d 100644 --- a/youtube_transcript_api/test/test_api.py +++ b/youtube_transcript_api/test/test_api.py @@ -17,7 +17,8 @@ NotTranslatable, TranslationLanguageNotAvailable, CookiePathInvalid, - CookiesInvalid + CookiesInvalid, + FailedToCreateConsentCookie, ) @@ -44,6 +45,7 @@ def setUp(self): ) def tearDown(self): + httpretty.reset() httpretty.disable() def test_get_transcript(self): @@ -125,6 +127,43 @@ def test_get_transcript__fallback_language_is_used(self): self.assertEqual(len(query_string['lang']), 1) self.assertEqual(query_string['lang'][0], 'en') + def test_get_transcript__create_consent_cookie_if_needed(self): + httpretty.register_uri( + httpretty.GET, + 'https://www.youtube.com/watch', + body=load_asset('youtube_consent_page.html.static') + ) + + YouTubeTranscriptApi.get_transcript('F1xioXWb8CY') + self.assertEqual(len(httpretty.latest_requests()), 3) + for request in httpretty.latest_requests()[1:]: + self.assertEqual(request.headers['cookie'], 'CONSENT=YES+cb.20210328-17-p0.de+FX+119') + + def test_get_transcript__exception_if_create_consent_cookie_failed(self): + httpretty.register_uri( + httpretty.GET, + 'https://www.youtube.com/watch', + body=load_asset('youtube_consent_page.html.static') + ) + httpretty.register_uri( + httpretty.GET, + 'https://www.youtube.com/watch', + body=load_asset('youtube_consent_page.html.static') + ) + + with self.assertRaises(FailedToCreateConsentCookie): + YouTubeTranscriptApi.get_transcript('F1xioXWb8CY') + + def test_get_transcript__exception_if_consent_cookie_age_invalid(self): + httpretty.register_uri( + httpretty.GET, + 'https://www.youtube.com/watch', + body=load_asset('youtube_consent_page_invalid.html.static') + ) + + with self.assertRaises(FailedToCreateConsentCookie): + YouTubeTranscriptApi.get_transcript('F1xioXWb8CY') + def test_get_transcript__exception_if_video_unavailable(self): httpretty.register_uri( httpretty.GET,