A common small task turns out to be surprisingly fiddly: "how many total hours of video are in this playlist / channel / search result?" Whether you're estimating how long a course will take, sizing a back catalog, or budgeting a binge, you need to traverse a list of videos and sum their durations. This guide covers scraping YouTube videos to do exactly that, three ways — the official API, the yt-dlp command-line tool, and an in-browser script — with working code and the durability trade-offs of each.
The short version: for anything repeatable, use the YouTube Data API or yt-dlp. The browser-console approach still works for a quick one-off, but YouTube's front end changes constantly, so treat any DOM selector as temporary.
Why the old "read the page and click Next" scripts break
Classic YouTube-scraping snippets grabbed a span.video-time element from each result and clicked a Next » button to paginate. Both assumptions are dead in 2026:
- The markup is web components now. YouTube's front end is built on custom
ytd-*elements (ytd-video-renderer,ytd-playlist-video-renderer,ytd-thumbnail-overlay-time-status-renderer). Duration text lives inside those, often exposed cleanly in anaria-label, and the old class names are gone. - There is no "Next" button. Search, channel, and playlist pages use infinite scroll — items load as you scroll, there's no pagination to click.
- Lists are virtualized. Very long lists may recycle DOM nodes, so "everything is in the DOM at once" isn't guaranteed on huge pages.
So a modern DOM approach has to scroll to load items and read durations from current selectors. But before writing any DOM code, consider the two approaches that don't touch the page at all.
Best approach: the YouTube Data API v3
For a channel or playlist, the official API is the correct tool. It returns each video's duration as a clean ISO 8601 string (PT1H2M10S) that you parse and sum — no HTML, no scrolling, no broken selectors. You'll need a free Google Cloud project and an API key, and you should mind the daily quota (a videos.list call is cheap; large channels mean many calls).
The flow: list the video IDs in the playlist (a channel's full uploads live in a special "uploads" playlist whose ID is the channel ID with UC swapped for UU), then fetch contentDetails for those IDs in batches of 50 and total the durations.
import isodate # pip install isodate google-api-python-client
from googleapiclient.discovery import build
API_KEY = "YOUR_API_KEY"
youtube = build("youtube", "v3", developerKey=API_KEY)
def playlist_video_ids(playlist_id):
ids, token = [], None
while True:
resp = youtube.playlistItems().list(
part="contentDetails", playlistId=playlist_id,
maxResults=50, pageToken=token,
).execute()
ids += [i["contentDetails"]["videoId"] for i in resp["items"]]
token = resp.get("nextPageToken")
if not token:
return ids
def total_seconds(video_ids):
total = 0
for i in range(0, len(video_ids), 50): # API takes up to 50 IDs
resp = youtube.videos().list(
part="contentDetails", id=",".join(video_ids[i:i + 50]),
).execute()
for item in resp["items"]:
dur = item["contentDetails"]["duration"] # e.g. "PT1H2M10S"
total += int(isodate.parse_duration(dur).total_seconds())
return total
# A channel's uploads playlist = channel ID with UC... -> UU...
ids = playlist_video_ids("UU_x5XG1OV2P6uZZ5FSM9Ttw")
secs = total_seconds(ids)
print(f"{len(ids)} videos, {secs // 3600}h {secs % 3600 // 60}m total")
This is stable (Google won't change the API shape without notice), respects the rules, and scales to large catalogs. isodate.parse_duration handles the PT#H#M#S format so you don't hand-roll a parser.
No-API option: yt-dlp
If you don't want to set up an API key, yt-dlp (the actively maintained successor to youtube-dl) reads durations directly and is a one-liner for totals. It accepts a playlist, channel, or search URL.
Fast metadata-only pass (doesn't download videos), printing each duration in seconds:
# Sum a playlist's total length, in seconds, without downloading anything
yt-dlp --flat-playlist --print "%(duration)s" \
"https://www.youtube.com/playlist?list=PLxxxxxxxx" \
| paste -sd+ - | bc
--flat-playlist skips per-video page loads for speed; --print "%(duration)s" emits the duration field; paste -sd+ | bc adds the numbers. For a channel, point it at the channel's /videos URL. For a search, use "ytsearch200:north pole fauna" to pull the first 200 results.
For richer control, dump JSON and process it:
yt-dlp -J --flat-playlist "https://www.youtube.com/@somechannel/videos" \
> channel.json
# then sum entries[].duration in Python/jq
yt-dlp handles pagination and layout changes for you — it's maintained specifically to keep working as YouTube shifts, which is why it's the pragmatic default for command-line scraping of YouTube metadata.
The browser-console approach (updated)
When you can't use the API — say you want the total of an ad-hoc search or a filtered view exactly as it appears in your logged-in session — a console script still works. The modern version must auto-scroll to load the list, then read durations from current selectors. Open the page, press F12, go to Console, paste, and run:
// Sum durations of the currently loaded video list, then keep scrolling
// until nothing new loads. Reads the time badge on each thumbnail.
function parseDuration(text) {
// text like "1:02:10" or "12:34"
const p = text.trim().split(':').map(Number);
if (p.some(isNaN)) return 0;
return p.reduce((acc, n) => acc * 60 + n, 0); // -> seconds
}
async function totalPlayLength() {
let prevCount = -1;
// scroll until the number of loaded items stops growing
while (true) {
const badges = document.querySelectorAll(
'ytd-thumbnail-overlay-time-status-renderer #text, ' +
'badge-shape .badge-shape-wiz__text'
);
if (badges.length === prevCount) break; // no new items -> done
prevCount = badges.length;
window.scrollTo(0, document.documentElement.scrollHeight);
await new Promise(r => setTimeout(r, 1500)); // let the next batch load
}
const badges = document.querySelectorAll(
'ytd-thumbnail-overlay-time-status-renderer #text, ' +
'badge-shape .badge-shape-wiz__text'
);
let seconds = 0, counted = 0;
badges.forEach(b => {
const s = parseDuration(b.textContent);
if (s > 0) { seconds += s; counted++; }
});
console.log(`${counted} videos, ` +
`${Math.floor(seconds/3600)}h ${Math.floor(seconds%3600/60)}m total`);
}
totalPlayLength();
Two things to know. First, the selectors will drift — if it returns zero, open DevTools, inspect a duration badge, and update the selector to whatever class or element YouTube is using that week. Reading the badge's aria-label is often more stable than its text. Second, live streams and shorts may have no duration or an odd format; the if (s > 0) guard skips them. This is the same auto-scroll pattern used for any infinite-scroll page.
Parsing durations reliably
Whatever the source, you'll normalize one of two formats:
- ISO 8601 (
PT1H2M10S) from the API — use a library (isodatein Python,Durationparsers in most languages) rather than a regex. - Colon time (
1:02:10,12:34) from the page — split on:, then fold left multiplying by 60, asparseDurationabove does. This handles bothH:MM:SSandMM:SSwithout branching.
Sum in seconds internally and format to hours/minutes only for display; adding pre-rounded minutes (a bug in many old scripts) accumulates error across hundreds of videos.
Scale, rate limits, and proxies
YouTube tolerates a normal browsing rhythm but pushes back on aggressive automated access from a single IP: it will throttle, change layout, or challenge you after a few hundred rapid requests. Mitigations, in order of preference:
- Prefer the API or yt-dlp — they page efficiently and are far less likely to trip defenses than a browser hammering scroll.
- Mind the API quota — the Data API's daily quota is the real ceiling for large jobs; request more or spread work across days.
- Add rotating proxies if you're scraping many search/channel pages via a browser, and pace requests with delays. Residential IPs draw less scrutiny than datacenter ranges.
- Randomize timing — fixed-interval loops look robotic; jitter the delays.
A note on terms of service
YouTube's Terms of Service restrict automated access to the site outside its API, and its API has its own terms (for example, limits on storing or repurposing data). Using the official API is the sanctioned path and the safest choice for anything you'll build on. Browser and yt-dlp scraping of public metadata is widely done, but understand you're operating outside the sanctioned interface — keep it light, respect the platform, and see is web scraping legal for how public-data scraping is treated more generally. Don't collect personal data or circumvent access controls.
FAQ
What's the most reliable way to total a playlist's length? The YouTube Data API. It returns exact ISO 8601 durations you parse and sum, won't break when the site's HTML changes, and stays within the rules. yt-dlp is the best no-API-key alternative.
Why does my console script suddenly return zero?
YouTube changed the duration element's class or structure. Inspect a time badge in DevTools and update the selector; reading the aria-label tends to survive redesigns better than the visible text.
Can I get durations without an API key at all?
Yes — yt-dlp --flat-playlist --print "%(duration)s" prints each video's length in seconds for a playlist, channel, or ytsearch query, and you just add them up.
How many videos can I process? The API handles very large channels within quota (batch IDs in groups of 50). Browser scraping realistically tops out in the low hundreds before rate limits and layout changes get in the way — beyond that, switch to the API or yt-dlp with proxies.
Totaling a playlist is a small job; pulling YouTube data reliably at scale — across many channels, on a schedule, staying inside quotas and rate limits — is a bigger one. If you need YouTube (or other platform) datasets delivered clean and structured rather than maintaining scrapers yourself, scraping.pro offers web scraping as a service and handles the API quotas, proxies, and parsing for you.