How to Scrape YouTube Videos With Python (5 Methods)
- The fastest no-API way to scrape a YouTube video with Python is a single
requests.geton the watch page, then a regex to pull theytInitialPlayerResponseJSON blob andjson.loadsit. The visible numbers are not in HTML tags, they sit in that JSON. - yt-dlp is the most reliable single-video route. One
extract_infocall returned title, channel, duration, view count, like count, and upload date in my run, and the project tracks YouTube's changes for you. - pytube has not shipped a release since version 15.0.0 in May 2023, so it breaks often against current YouTube. The maintained fork pytubefix is the working drop-in. For captions, youtube-transcript-api is the cleaner tool.
- The official YouTube Data API videos.list call costs 1 quota unit out of 10,000 per day. Past that, our YouTube scraper API that returns the parsed video object is the lower-maintenance route.
I scraped one YouTube video five different ways in Python this week to see which method still returns clean data in 2026. The request everyone tries first, a plain requests.get on the watch page, returns HTTP 200 and about 1.4 MB of HTML, but BeautifulSoup finds almost none of the numbers you want in it. The view count, the duration, the channel, the like count, none of it sits in normal HTML tags. To scrape YouTube video data you read the JSON the page already ships with, or you hand the job to a library that does that for you.
This guide is the Python code I ran against a live YouTube target, the data each method returned, and the point where each one breaks. Every snippet below is code I executed in June 2026 against the video dQw4w9WgXcQ, so the values are real. I cover the no-API JSON route, yt-dlp, pytube and its fork pytubefix, youtube-transcript-api for captions, Selenium, and a managed scraper API for when you outgrow a single machine.
What data can you scrape from a YouTube video?
You can scrape a YouTube video’s title, channel, channel ID, duration, view count, like count, upload date, tags, and description, all of which YouTube already serves to a logged-out browser. When you scrape a YouTube video with Python, this is the field set you get back, and almost none of it lives in the static HTML as tags.
Here is where each video field lives and which method reaches it cleanly:
| Field | Where it lives | Easiest method |
|---|---|---|
| Title | <title>, og:title meta, and JSON | requests + parse, or any library |
| View count | ytInitialPlayerResponse → videoDetails | parse JSON, yt-dlp, or Data API |
| Duration (seconds) | videoDetails.lengthSeconds | parse JSON or yt-dlp |
| Channel and channel ID | videoDetails.author / channelId | parse JSON or yt-dlp |
| Like count | page JSON / Data API | yt-dlp or Data API |
| Upload date | microformat / Data API | yt-dlp or Data API |
| Tags / keywords | videoDetails.keywords | parse JSON or yt-dlp |
| Description | videoDetails.shortDescription | parse JSON or yt-dlp |
| Captions / transcript | timed-text track | youtube-transcript-api or yt-dlp |
The pattern across every row is the same. The visible numbers you see on a video page are rendered by JavaScript, so they are not in the static HTML as tags. They are packed into a JSON object the page ships with, ytInitialPlayerResponse, whose videoDetails block holds the core fields. Once you know that, scraping a YouTube video becomes a JSON-reading job, which is where the Python starts.
How do you scrape a YouTube video with Python and requests?
You scrape a YouTube video with Python and requests by fetching the watch page, pulling the ytInitialPlayerResponse JSON blob out of the HTML with a regex, and loading it with json.loads. This is how you scrape YouTube video data without the official API: the data is already in the page, packed into a JSON object that YouTube’s own front end reads to render the numbers.
Install the two libraries first:
pip install requests beautifulsoup4 lxml
Then request the page and confirm what comes back. This is the part that trips people up, so I print the status and the byte count before parsing:
import requests
from bs4 import BeautifulSoup
URL = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/126.0 Safari/537.36")
r = requests.get(URL, headers={"User-Agent": UA,
"Accept-Language": "en-US,en;q=0.9"}, timeout=20)
print(r.status_code) # -> 200
print(len(r.text)) # -> ~1,446,000
soup = BeautifulSoup(r.text, "lxml")
print(soup.title.string) # -> "...Never Gonna Give You Up... - YouTube"
og = soup.find("meta", property="og:title")
print(og["content"] if og else None) # -> the clean video title
When I ran this, the request returned 200 and roughly 1.44 MB of HTML. BeautifulSoup read the title fine, because the title and the Open Graph meta tags are baked into the static markup. The view count and duration are not, which is why the next step matters.
To get the numbers, parse the JSON. The watch page assigns a variable called ytInitialPlayerResponse, and inside it videoDetails holds the fields you actually want:
import re, json
m = re.search(r"ytInitialPlayerResponse\s*=\s*(\{.+?\})\s*;\s*(?:var|</script>)", r.text)
data = json.loads(m.group(1))
vd = data["videoDetails"]
print(vd["title"]) # Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)
print(vd["author"]) # Rick Astley
print(vd["channelId"]) # UCuAXFkgsw1L7xaCfnd5JJOw
print(vd["lengthSeconds"]) # 213
print(vd["viewCount"]) # 1783376226
print(vd["keywords"][:5]) # ['rick astley', 'Never Gonna Give You Up', 'nggyu', ...]
That returned a duration of 213 seconds and a view count of 1,783,376,226 in my run, with the channel ID and the video keywords alongside. This is the core no-API scrape: one HTTP request, one regex, one json.loads. It works because videoDetails is the same object YouTube’s front end reads to paint the page, so the field names stay stable even when the visible markup shifts.
The fragile part is the regex. YouTube changes the surrounding markup periodically, and when the delimiter after the JSON object moves, the pattern misses and m comes back None. That is the maintenance cost of parsing the page yourself, and it is the reason the next method exists. The same ytInitialPlayerResponse trick scales to a list of video IDs if you loop it, which is the foundation under the broader how to scrape YouTube walkthrough.
How do you scrape YouTube video metadata with yt-dlp?
You scrape YouTube video metadata with yt-dlp by calling extract_info with download=False, which returns a Python dictionary of the video’s title, channel, duration, view count, like count, and upload date without downloading the file. yt-dlp does the JSON parsing internally, so it survives YouTube’s layout changes far better than a hand-written regex.
yt-dlp is the actively maintained successor to youtube-dl, with thousands of commits and frequent releases on its GitHub repository, which is exactly why it holds up when the page-parsing route breaks. Install it, then read one video:
pip install yt-dlp
from yt_dlp import YoutubeDL
URL = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
opts = {"quiet": True, "skip_download": True, "noplaylist": True}
with YoutubeDL(opts) as ydl:
info = ydl.extract_info(URL, download=False)
for field in ["title", "channel", "channel_id", "duration",
"view_count", "like_count", "upload_date"]:
print(field, "=", info.get(field))
print("tags:", info.get("tags", [])[:5])
My run returned these values:
| Field | Value returned |
|---|---|
title | Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster) |
channel | Rick Astley |
channel_id | UCuAXFkgsw1L7xaCfnd5JJOw |
duration | 213 |
view_count | 1783377080 |
like_count | 19159219 |
upload_date | 20091025 |
yt-dlp is the gold standard for single-video metadata in Python because it tracks YouTube’s changes for you and ships updates often. One honest caveat from my run: yt-dlp now prints a warning that it wants a JavaScript runtime installed, and without one some download formats are skipped. For metadata extraction the warning did not block the result, but it signals that YouTube is making pure-Python extraction harder over time. If you only need captions rather than the full metadata dictionary, a lighter library does that job, which is the next method.
How do you scrape a YouTube video transcript with Python?
You scrape a YouTube video transcript with Python using the youtube-transcript-api library, which reads YouTube’s caption tracks directly without a headless browser. It returns the timed text snippets for a video and also works on auto-generated captions, which makes it the cleanest tool for transcripts specifically.
The library changed its interface in the 1.x line, so the current call creates an instance and uses .fetch, not the old static method:
pip install youtube-transcript-api
from youtube_transcript_api import YouTubeTranscriptApi
ytt_api = YouTubeTranscriptApi()
fetched = ytt_api.fetch("dQw4w9WgXcQ")
for snippet in fetched[:3]:
print(round(snippet.start, 2), "-", snippet.text)
full_text = " ".join(snippet.text for snippet in fetched)
print(len(full_text), "characters of transcript")
Each snippet carries the caption text plus its start time and duration, so you can keep the timing or flatten it into one string for analysis. The library does not need an API key, because it reads the same timed-text endpoint the player uses. When a video has captions disabled it raises an exception rather than returning empty text, so wrap the call in a try/except for batch jobs. For the full caption workflow, including translation and formatting into SRT or plain text, I go deeper in how to get a YouTube transcript. yt-dlp can also pull captions through its writesubtitles option if you already have it installed, which keeps your dependency list short.
Can you still use pytube to scrape YouTube videos?
You can install pytube, but it breaks often in 2026 because its last release was version 15.0.0 on May 7, 2023, per its PyPI page, and YouTube has changed its internal API many times since. The actively maintained fork pytubefix is the working replacement, and it keeps nearly the same object model as the original, so existing pytube code ports with small changes.
Install the pytubefix fork and read the same video’s metadata through its object interface:
pip install pytubefix
from pytubefix import YouTube
yt = YouTube("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
print("title:", yt.title)
print("author:", yt.author)
print("channel id:", yt.channel_id)
print("length (s):", yt.length)
print("views:", yt.views)
print("published:", yt.publish_date)
pytubefix exposes the video fields as attributes (title, author, length, views, publish_date), which reads more naturally than walking a raw JSON dictionary. The reason to prefer it over the original is maintenance: when YouTube changes the signature logic that gates stream access, pytube tends to throw errors until someone patches it, while pytubefix ships fixes faster. For pure metadata, yt-dlp is still the sturdier pick, but pytubefix is the better fit when you want a clean object API or need to enumerate a video’s downloadable streams. Either way, you are still bound to a library that depends on YouTube not changing, which is the constraint Selenium sidesteps by rendering the real page.
When should you use Selenium to scrape a YouTube video?
You should use Selenium to scrape a YouTube video when you need data that only appears after the page renders or after you scroll, such as the live like count text, an expanded description, or comments loading in. Selenium drives a real Chrome instance, so YouTube’s JavaScript runs and the rendered values become readable in the DOM.
pip install selenium
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.get("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
title = WebDriverWait(driver, 15).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "h1 yt-formatted-string"))
).text
print("title:", title)
driver.quit()
Selenium reads the rendered page, so the title element resolves once Chrome has executed YouTube’s scripts, and an explicit WebDriverWait is steadier than a fixed sleep. The cost is speed and overhead: a browser session uses far more memory and time than an HTTP request, and YouTube’s consent and bot-check screens show up more readily for automated browsers, so you often add a consent click and longer waits. Selenium earns its place for one-off pulls of genuinely dynamic fields, but for volume the ytInitialPlayerResponse parse and yt-dlp are faster. Before scaling any of these, the quota and blocking limits decide which one is sustainable.
What are the limits when scraping YouTube videos?
The hard limits on scraping YouTube videos are the official API quota and YouTube’s anti-bot blocking on the unauthenticated pages. The YouTube Data API v3 gives each project 10,000 quota units per day, and a videos.list read costs only 1 unit, so the API is the cheapest path for videos you can look up by ID. The catch is that broad discovery is expensive, because search.list costs 100 units per call.
The Data API is the route Google sanctions, and it returns the same fields as clean JSON without any regex. You enable the API in the Google Cloud Console, create a key, then call videos.list with the parts you need:
from googleapiclient.discovery import build
youtube = build("youtube", "v3", developerKey="YOUR_API_KEY")
resp = youtube.videos().list(
part="snippet,statistics,contentDetails",
id="dQw4w9WgXcQ",
).execute()
item = resp["items"][0]
print(item["snippet"]["title"])
print(item["snippet"]["channelTitle"])
print(item["statistics"]["viewCount"])
print(item["contentDetails"]["duration"]) # ISO 8601, e.g. PT3M33S
Per Google’s videos.list documentation, the snippet, statistics, and contentDetails parts return the title, channel, view and like counts, and an ISO 8601 duration. Scraping the pages directly has no quota, but it has a different ceiling: blocking. From my own machine the requests above returned clean 200s. From a cloud server, YouTube starts answering automated traffic with consent interstitials and bot challenges. These are the levers that keep page scraping working, in rough order of impact:
- Slow the request rate. A steady, human-like cadence with a few seconds between requests survives far longer than parallel bursts.
- Use residential IPs for volume. Datacenter ranges get challenged quickly. Residential proxies present as ordinary home connections.
- Set a real User-Agent and
Accept-Language. A missing or empty User-Agent makes a borderline request worse. - Cache video IDs. The cheapest request is the one you skip. Store IDs and only refetch what changed.
- Respect robots.txt and the Terms of Service. YouTube disallows paths like
/resultsand/youtubei/in its robots.txt, and its Terms of Service restrict automated access without permission.
On the legal question, US courts have read the Computer Fraud and Abuse Act narrowly for public data. In hiQ Labs v. LinkedIn, the Ninth Circuit affirmed in 2022 that scraping public website data likely does not violate the CFAA. A site’s own terms are a separate matter from the CFAA, so read both before you scale a job.
I cover where that line sits for this platform specifically in is scraping YouTube legal. The practical reading is to build the Python yourself for a few hundred videos, then move to a managed service once the proxy rotation, consent handling, and parser upkeep cost more time than the data is worth.
How do you scrape YouTube videos at scale without managing proxies?
You scrape YouTube videos at scale without managing proxies by sending a video ID or URL to a scraper API that returns the parsed video object, with the proxy rotation, consent handling, and retries handled on the server side. You send one request and get structured data back, so there is no ytInitialPlayerResponse regex to maintain when YouTube shifts its markup, and no browser fleet to run.
Our YouTube API covers this, the managed route I reach for once a project outgrows a single machine. The request shape is a single GET with the video ID and an API key, and it returns the video object directly:
curl "https://api.youtubescraperapi.com/api/v1/youtube/video?video_id=dQw4w9WgXcQ&api_key=$API_KEY"
In Python, the same call returns the video fields parsed and ready, including a related list of related video rows alongside the core metadata:
import requests, os
resp = requests.get(
"https://api.youtubescraperapi.com/api/v1/youtube/video",
params={
"video_id": "dQw4w9WgXcQ",
"api_key": os.environ["API_KEY"],
},
timeout=30,
)
video = resp.json()
print(video["title"]) # the video title
print(video["channel"]) # the channel name
print(video["views"]) # view count, already parsed
print(video["related_count"]) # how many related videos came back
for row in video["related"][:5]:
print(row["title"]) # each related video's title
The response is the video object itself, not a wrapper, so video["title"], video["channel"], and video["views"] are the fields you read first, and video["related"] is a list of related video rows with related_count telling you how many came back. That related list is the part you cannot get cleanly from a single videoDetails parse, which is where the managed route earns its keep for recommendation analysis. You can start free and test the shape against your own targets on the youtubescraperapi.com sign-up.
For a one-off pull of a few hundred videos, the Python in this guide is enough, and yt-dlp covers most single-video needs. For continuous collection across thousands of videos, channels, and searches, offloading the blocking and the parser upkeep is usually the cheaper path once you price in your own time. If you also need channel-level data, the same request pattern applies in how to scrape YouTube channels with Python.
If you would rather compare the managed options before writing any code, I ranked them in the best YouTube scrapers for 2026.
A note on cleaning scraped YouTube video text
Scraped YouTube titles, descriptions, and transcripts arrive as raw text that usually needs preprocessing before analysis, because they carry emoji, hashtags, timestamps, and inconsistent casing. A common next step after the scrape is to strip the noise and normalize casing before any word count or model input.
A minimal cleanup on a batch of scraped titles looks like this:
import re
def clean(text):
text = re.sub(r"http\S+", "", text) # drop URLs
text = re.sub(r"[#@]\w+", "", text) # drop hashtags and handles
text = re.sub(r"\s+", " ", text).strip() # collapse whitespace
return text.lower()
titles = ["Learn Python in 10 Minutes! #python", "Full Course for Beginners"]
print([clean(t) for t in titles])
From there a frequency count or a sentence split is straightforward, and the output feeds whatever model or report you are building. That two-step shape, extract then clean, is the reason to capture structured fields like title, view_count, and the transcript text cleanly at the source. Clean fields at the source save hours of downstream HTML cleanup.
FAQ
Can you scrape YouTube videos with Python without the API?
Yes. You can scrape a YouTube video without the YouTube Data API by requesting the watch page with Python and parsing the embedded ytInitialPlayerResponse JSON, where the title, view count, channel, and duration live. yt-dlp and pytubefix do this parsing for you. The tradeoff is maintenance: YouTube changes its page structure periodically, so a hand-rolled regex breaks more often than a maintained library.
Is pytube still working in 2026?
pytube is unreliable in 2026 because its last release was version 15.0.0 in May 2023, and YouTube has changed its internal API many times since. The actively maintained fork pytubefix is the working replacement and is close to a drop-in for the same code. For metadata specifically, yt-dlp tracks YouTube's changes more aggressively than either.
How do you get a YouTube video's view count in Python?
You get a YouTube video's view count in Python by reading the viewCount field inside the videoDetails object of ytInitialPlayerResponse, which the watch page ships in its HTML. With requests you pull that blob with a regex and json.loads. yt-dlp exposes the same number as info['view_count'] after one extract_info call, and the Data API returns it under statistics.viewCount.
How do you scrape a YouTube video transcript in Python?
You scrape a YouTube video transcript in Python with the youtube-transcript-api library, which reads YouTube's caption tracks without a headless browser. In version 1.x you create a YouTubeTranscriptApi() instance and call .fetch(video_id), which returns the timed caption snippets. I walk through the full caption workflow in how to get a YouTube transcript.
Do you need proxies to scrape YouTube videos with Python?
For a handful of videos from your own machine, no. For sustained scraping from a cloud server, YouTube starts answering automated traffic with consent walls and bot checks, so residential proxies and slower request rates become necessary. A scraper API bundles the proxy rotation and consent handling so you send a video URL and get a JSON object back.