"""Free path first; hosted path only for videos YouTube blocks. pip install youtube-transcript-api shephouse-yt export APIFY_TOKEN=... # only needed when a block actually happens """ from youtube_transcript_api import YouTubeTranscriptApi from youtube_transcript_api._errors import RequestBlocked, CouldNotRetrieveTranscript def get_transcripts(video_ids, languages=("en",), give_up_after=3): """Return {video_id: (source, [ {text,start,duration}, ... ]) or Exception}.""" free = YouTubeTranscriptApi() results, blocked, streak = {}, [], 0 for vid in video_ids: if streak >= give_up_after: # IP is blocked; stop wasting time blocked.append(vid) continue try: results[vid] = ("library", free.fetch(vid, languages=languages).to_raw_data()) streak = 0 except RequestBlocked: # includes IpBlocked blocked.append(vid) streak += 1 except CouldNotRetrieveTranscript as e: # no captions, unavailable, ... a real answer results[vid] = e streak = 0 if blocked: from shephouse_yt import YouTubeTranscriptApi as Hosted for vid, r in Hosted().fetch_many(blocked, languages=languages).items(): # one batch run results[vid] = r if isinstance(r, Exception) else ("hosted", r.to_raw_data()) return results if __name__ == "__main__": for vid, r in get_transcripts(["jNQXAC9IVRw", "arj7oStGLkU", "kJQP7kiw5Fk", "AAAAAAAAAAA"]).items(): if isinstance(r, Exception): print(vid, "no transcript:", type(r).__name__) else: source, snippets = r print(vid, source, len(snippets), "snippets:", snippets[0]["text"][:40])