Youtube Most Replayed Scraper for Whole Channels & Playlists
Pricing
from $3.99 / 1,000 results
Youtube Most Replayed Scraper for Whole Channels & Playlists
Extracts the most replayed segments from YouTube videos, capturing peak moments, timestamps, replay intensity, and video metadata. Ideal for content analysis, highlight detection, viewer-behavior insights, and automated identification of high-engagement scenes
Pricing
from $3.99 / 1,000 results
Rating
0.0
(0)
Developer
Scrapio
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
0
Monthly active users
2 days ago
Last modified
Categories
Share
YouTube Most Replayed Scraper — Videos, Peaks and Heatmaps
Paste a YouTube channel URL, a playlist URL, or a handful of watch links, and this YouTube most replayed scraper returns the audience-retention heatmap for every video it finds — the same "most replayed" curve YouTube draws on its own seek bar, plus title, channel, views, likes, comments and upload date. Unlike a scraper that returns raw HTML for you to parse, it returns typed JSON: heatSeek[] (the raw intensity curve) and mostReplayed[] (the labelled replay peaks) sit next to the video's own metadata in one row, ready for a database, a spreadsheet, or an LLM context window with no cleanup step. This guide covers every input and output field, how the container pagination and proxy fallback actually behave, and three deployment patterns — enrichment, monitoring and bulk dataset builds — for running it as more than a one-off job.
🧭 What Does This YouTube Most Replayed Scraper Do?
It expands a channel's /videos, /shorts or /streams tab, or a playlist, into its individual videos, then opens each video's page and pulls the Most Replayed heatmap along with its public metadata. No YouTube account, API key, or login is required — the source only reads requests.get() against public watch and browse pages (see src/main.py, src/yt_discovery.py). Individual watch URLs can be supplied directly instead of, or alongside, a container.
- Expands channel handles, channel IDs, and playlists into their full video list via YouTube's
youtubei/v1/browsecontinuation — not just the first page - Extracts the raw heatmap intensity curve (
heatSeek) and the labelled replay peaks (mostReplayed) for each video - Returns title, channel name, view count, likes, comment count, absolute date and relative date per video
- Tags every row with where it came from — container URL, container type, container title, and position inside it
- Accepts individual watch URLs (
/watch?v=,youtu.be/,/shorts/, or a bare 11-character ID) mixed in with containers - De-duplicates video IDs across every source in a run, so a video appearing in two playlists is fetched once
- Filters out videos with no heatmap yet, before the row is saved — never after it's billed
⚡ Features & Capabilities
Three things define what this Actor is built to do: how deep it paginates, what it extracts per video, and how it stays online against YouTube's own bot checks.
Core features
- Real pagination, not a first-page scrape. A channel tab's first page returns roughly 30 items on
/videos, 48 on/shorts, or 100 on a playlist — everything past that comes fromyoutubei/v1/browsecontinuation requests, capped bymaxVideosPerSource(up to 5000) and a hard internal ceiling of 200 continuation rounds per container. - Renderer-agnostic ID discovery.
yt_discovery.walk_video_ids()walks the parsed page JSON looking for 11-charactervideoIdandcontentIdvalues rather than matching a specific renderer name, so YouTube's ongoing migration fromplaylistVideoRenderertolockupViewModeldoesn't silently return zero videos. - Two distinct heatmap structures per video:
heatSeek[](startMillis,durationMillis,intensityScoreNormalized— the raw curve) andmostReplayed[](visibleTimeRangeStartMillis,visibleTimeRangeEndMillis,decorationTimeMillis— the labelled peaks). - Sanitised like counts.
_sanitize_like_value()insrc/yt_extractors.pydiscards UI button captions like"Like"and only keeps a value that contains a digit —likesis either a real count string or"", never a fabricated label. - Provenance fields on every row —
sourceUrl,sourceType,sourceTitle,sourcePosition— so a flat dataset can still answer "which channel/playlist did this come from, and where in it."
When another tool might suit you better
This Actor is scoped narrowly to the Most Replayed heatmap and the metadata attached to it — it does not return video transcripts, tags, category, description text, subscriber counts, or comment content (the comments field is a display count only, not comment text or authors). If your workflow needs full video descriptions, channel-level statistics, or actual comment threads, you need a broader YouTube data or comment scraper alongside this one, not instead of it.
🎥 This Actor within the Scrapio data stack
This Actor covers YouTube video and heatmap data. For comment-level engagement and sentiment on a different short-video platform, Scrapio's TikTok Data Scraper (Comments, Replies & AI Sentiment) covers that adjacent workflow; for post-level engagement analytics on Threads, see Threads Search Post Scraper (With Engagement Analytics). Neither returns a Most Replayed heatmap — that data structure is unique to YouTube.
Why do developers and data teams scrape YouTube's Most Replayed data?
🎥 Video editors and clippers
Instead of scrubbing a creator's back catalogue by hand, run this Actor against a channel's /videos tab and sort the returned rows by peakCount or by the decorationTimeMillis values inside mostReplayed[] to jump straight to the seconds viewers rewatched most. Feed the same timestamps into a clip-generation pipeline to auto-cut highlight reels without watching every source video first.
📊 AI training data and RAG indexing
title, channelOwner, viewCount, likes, comments, dateText and relativeDate are typed strings requiring no HTML parsing before they reach a prompt or a vector store. For RAG, title plus the numeric engagement fields let an agent answer "which of this channel's videos performed best" directly from the dataset. For training data, mostReplayed[] and heatSeek[] give a structurally consistent (start, duration, intensity) tuple across every video that has a heatmap, useful for modelling what makes a segment rewatchable.
📱 Competitive and market intelligence
Point the Actor at a rival channel's /videos tab on a schedule and track peakCount and heatmapMarkerCount over time to see which of their formats are earning genuine rewatches, not just views. sourcePosition lets you confirm whether a spike in replay activity lines up with a recently published video moving up the channel's own listing.
🔬 Research and academic use
Video-retention data is otherwise locked inside YouTube's own player UI with no public export. This Actor turns it into a reproducible dataset — each row carries its own provenance (sourceUrl, sourceType, sourcePosition) so a study can cite exactly which channel, tab and position each data point came from. Scope is limited to what YouTube already serves on public pages; no private or unlisted content is reachable.
🎬 Product and SaaS development
hasHeatmap, heatmapMarkerCount and peakCount are cheap boolean/numeric signals for building a creator-analytics dashboard or a clip-recommendation feature on top of raw YouTube data, without maintaining your own page-parsing code.
🍚 Input Parameters
None of the ten parameters are required — an empty run falls back to the schema's own urls default. Full table, in schema order:
| Parameter | Required | Type | Description | Example Value |
|---|---|---|---|---|
sourceUrls | No | array (string list) | One container URL per line: @handle, @handle/videos, @handle/shorts, @handle/streams, channel/UC..., or playlist?list=PL... (a watch URL carrying &list= is treated as that playlist). A bare channel URL uses the tabs picked in sourceTabs. Default []. | ["https://www.youtube.com/@mkbhd/videos"] |
sourceTabs | No | array (select, enum: videos, shorts, streams) | Which tabs to read when a bare channel URL is given (no /videos, /shorts or /streams suffix). Ignored for playlists and for channel URLs that already name a tab. Default ["videos"]. | ["videos", "shorts"] |
maxVideosPerSource | No | integer (1–5000) | How deep to paginate inside each channel tab or playlist. Page one gives roughly 30 (videos), 48 (shorts) or 100 (playlist) items; anything above that triggers continuation requests. Every discovered video costs one result row. Default 30. | 120 |
maxTotalVideos | No | integer (0–20000) | Hard ceiling across all containers and direct URLs combined, applied after discovery. 0 means no global cap. Default 0. | 500 |
urls | No | array (string list) | One watch URL per line: watch?v=..., youtu.be/..., /shorts/..., or a bare 11-character video ID. Can be left empty when container URLs are supplied. Default ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"]. | ["https://www.youtube.com/watch?v=_oRgdlJUD18"] |
skipVideosWithoutHeatmap | No | boolean | Drop any video whose Most Replayed graph does not exist yet, before the row is saved. Recommended when expanding a whole channel, since recent uploads usually have none yet. Default false. | true |
maxConcurrency | No | integer (1–10) | How many video pages to fetch at once. 1 keeps strictly sequential fetching; raise it to finish large channels faster. Default 1. | 4 |
requestTimeoutSecs | No | integer (5–120) | Seconds to wait for a single YouTube response before retrying. Default 20. | 30 |
maxRetries | No | integer (0–10) | Extra attempts after a failed request. A video YouTube reports as unavailable is not retried, because retrying cannot change that answer. Default 3. | 5 |
proxy | No | object (proxy editor) | Off by default (direct connection). If YouTube blocks a request, the Actor switches to the Apify Residential proxy and stays on it for the rest of the run. Default {}. | { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] } |
⚠️ urls carries a schema-default trap. Apify prefills the unmodified urls default (the Rick Astley watch URL) into every run. The Actor only treats it as a real request when you leave sourceUrls empty — the moment you supply any sourceUrls, the untouched urls default is ignored rather than scraped alongside your containers. Change or clear urls explicitly if you want a specific video scraped together with a container.
{"sourceUrls": ["https://www.youtube.com/@mkbhd/videos","https://www.youtube.com/playlist?list=UUBa659QWEk1AI4Tg--mrJ2A"],"sourceTabs": ["videos"],"maxVideosPerSource": 120,"maxTotalVideos": 0,"urls": [],"skipVideosWithoutHeatmap": true,"maxConcurrency": 4,"requestTimeoutSecs": 20,"maxRetries": 3,"proxy": {}}
Supported URL types and input formats
- Whole channel, default tab:
https://www.youtube.com/@mkbhdwithsourceTabs: ["videos"]— expands the Videos tab. - A specific tab directly:
https://www.youtube.com/@mkbhd/shorts— the trailing tab in the URL overridessourceTabs. - A playlist, any ID prefix:
https://www.youtube.com/playlist?list=PLxxxxor a watch URL with&list=PLxxxx— both resolve to the same canonical playlist URL, peryt_discovery.classify_source(). - A single video, mixed in:
https://youtu.be/_oRgdlJUD18inurls, alongside containers insourceUrls— both are scraped in the same run and de-duplicated together.
📦 Output Format
Every run pushes one dataset row per discovered video — typed JSON, no HTML, no per-field parsing required. The default dataset view surfaces 17 of the row's fields as a table; mostReplayed, heatSeek and error are present on every row but only shown when you open the raw JSON.
Output for a video row
{"channelOwner": "Marques Brownlee","title": "iOS 27 Hands-On: Top 5 New Features!","videoId": "_oRgdlJUD18","viewCount": "5201389","likes": "138110","comments": "7.3K","dateText": "Jul 13, 2026","relativeDate": "2 weeks ago","mostReplayed": [{ "visibleTimeRangeStartMillis": 340030, "visibleTimeRangeEndMillis": 358410, "decorationTimeMillis": 349220 }],"heatSeek": [{ "startMillis": "0", "durationMillis": "9190", "intensityScoreNormalized": 0.2593673712135 }],"videoUrl": "https://www.youtube.com/watch?v=_oRgdlJUD18","sourceUrl": "https://www.youtube.com/@mkbhd/videos","sourceType": "channel_videos","sourceTitle": "Marques Brownlee","sourcePosition": 4,"hasHeatmap": true,"heatmapMarkerCount": 100,"peakCount": 1,"error": null,"errorReason": null}
| Field | Type | Meaning |
|---|---|---|
channelOwner | string | Channel name that uploaded the video |
title | string | Video title |
videoId | string | 11-character YouTube video ID |
viewCount | string | View count as YouTube's page reports it |
likes | string | Like count; "" when only a UI label ("Like") was available or likes are hidden |
comments | string | Comment count as displayed, e.g. "7.3K"; "" when comments are off or unavailable |
dateText | string | Absolute upload date, e.g. "Jul 13, 2026" |
relativeDate | string | Relative upload date, e.g. "2 weeks ago" |
mostReplayed | array | Labelled replay peaks: visibleTimeRangeStartMillis, visibleTimeRangeEndMillis, decorationTimeMillis (all milliseconds); empty when no heatmap |
heatSeek | array | Full intensity curve: startMillis, durationMillis, intensityScoreNormalized; empty when no heatmap |
videoUrl | string | Canonical watch URL |
sourceUrl | string | null | Container this row was discovered from; null for a directly supplied video |
sourceType | string | channel_videos, channel_shorts, channel_streams, playlist, or direct_url |
sourceTitle | string | null | Channel or playlist display name |
sourcePosition | number | null | 1-based position inside that container's discovery order |
hasHeatmap | boolean | Whether a replay curve exists for this video |
heatmapMarkerCount | number | Number of entries in heatSeek (typically 100 when present, 0 when not) |
peakCount | number | Number of entries in mostReplayed |
error | string | null | Failure text; only set on uncharged accounting rows |
errorReason | string | null | Typed failure code, e.g. PLAYABILITY_ERROR, PLAYABILITY_UNPLAYABLE, FETCH_FAILED, PARSE_FAILED |
⚠️ Not every video is billed. A video YouTube reports as unavailable, private, age-restricted, or offline produces this same row shape with error and errorReason set and every content field empty — pushed with Actor.push_data(row) and no charged_event_name, so it is never billed. Only rows pushed with the row_result charged event (a successfully scraped video) cost anything. Filter uncharged rows out of any dataset export with errorReason == null (or equivalently, error == null).
Schema stability and export options
Field names come from the Actor's own row-building code (build_row() in src/main.py), not from YouTube's page structure directly — video-ID discovery already walks the parsed JSON rather than matching a specific renderer name (yt_discovery.walk_video_ids()), so a YouTube front-end renaming pass is less likely to break field names, though a change to the underlying ytInitialData/ytInitialPlayerResponse structures could still require an Actor update. Results are available through the standard Apify dataset — export to JSON, CSV, Excel, or XML from the Console or via the API, or read them programmatically with apify_client. The Actor also writes discovery_summary.json (per-container pagination stats), most_replayed.json (all successful rows) and OUTPUT (a run summary) to the run's key-value store.
💡 YouTube Most Replayed Scraper Strategy Guide
🎯 Strategy 1: Real-time enrichment pipeline
Trigger a run whenever a new video is detected on a monitored channel (via an external feed or a lightweight polling job): run the Actor with sourceUrls set to the channel's /videos tab and maxVideosPerSource small (e.g. 5), so only the newest videos are re-fetched. Append hasHeatmap, peakCount and heatmapMarkerCount to your CMS or CRM record for that video as soon as the row lands — sourcePosition confirms it's the newest entry. Re-run the same video later once hasHeatmap flips to true, since a brand-new upload legitimately has no heatmap yet.
🎯 Strategy 2: Scheduled monitoring and alerting
Use an Apify schedule to run the Actor daily against a fixed list of channel or playlist URLs in sourceUrls. Diff each run's peakCount and heatmapMarkerCount per videoId against the previous run's most_replayed.json snapshot, and alert only when a video's hasHeatmap changes from false to true, or when peakCount increases — both signal that YouTube has updated the retention data since the last check, which is the delta that actually matters for a content team.
🎯 Strategy 3: Bulk dataset build
For a research or training dataset, list every channel or playlist you need in a single run's sourceUrls array (one run can hold many containers) or split them across parallel runs kicked off from your own orchestration. Raise maxConcurrency (up to 10) to fetch video pages faster within a single run, and set maxTotalVideos to bound total dataset size before it's discovered. Export the finished dataset directly to CSV from the Apify Console or via apify_client, filtering out uncharged rows with errorReason == null.
Strategy comparison at a glance
| Strategy | Best for | Run pattern | Output format |
|---|---|---|---|
| Real-time enrichment | Per-video updates as they publish | Triggered, small maxVideosPerSource | Dataset row appended to an external record |
| Scheduled monitoring | Tracking retention changes over time | Apify schedule, recurring run | Dataset diffed against the previous run's snapshot |
| Bulk dataset build | Research or training datasets | One run, many sourceUrls, or parallel runs | Full dataset export to CSV/JSON |
🌴 Related YouTube Scrapers & Tools
This Actor is scoped to one thing: the Most Replayed heatmap and the metadata attached to each video. It does not cover channel-level statistics, video transcripts, or comment content — no other Scrapio Actor in this catalogue currently covers those for YouTube either.
| Scraper | What it extracts |
|---|---|
| TikTok Data Scraper (Comments, Replies & AI Sentiment) | Comment-level engagement and AI sentiment on TikTok videos — a different platform, same audience-engagement analytics use case |
| Threads Search Post Scraper (With Engagement Analytics) | Post-level engagement analytics on Threads |
How to integrate this Actor with your stack
This Actor works with any language or tool that can call the Apify API — the Console, apify_client, or a plain HTTP request to the run-sync or run endpoints.
Python
from apify_client import ApifyClientimport csvclient = ApifyClient("<APIFY_API_TOKEN>")run_input = {"sourceUrls": ["https://www.youtube.com/@mkbhd/videos"],"maxVideosPerSource": 100,"skipVideosWithoutHeatmap": True,}run = client.actor("<YOUR_USERNAME>/youtube-most-replayed-scraper-for-channels-and-playlists").call(run_input=run_input)rows = list(client.dataset(run["defaultDatasetId"]).iterate_items())charged_rows = [r for r in rows if r.get("errorReason") is None]with open("most_replayed.csv", "w", newline="", encoding="utf-8") as f:writer = csv.DictWriter(f, fieldnames=["videoId", "title", "peakCount", "heatmapMarkerCount", "viewCount"])writer.writeheader()for row in charged_rows:writer.writerow({k: row.get(k) for k in writer.fieldnames})
Node.js
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<APIFY_API_TOKEN>' });const run = await client.actor('<YOUR_USERNAME>/youtube-most-replayed-scraper-for-channels-and-playlists').call({sourceUrls: ['https://www.youtube.com/@mkbhd/videos'],maxVideosPerSource: 100,skipVideosWithoutHeatmap: true,});const { items } = await client.dataset(run.defaultDatasetId).listItems();const charged = items.filter((r) => r.errorReason === null);console.log(`${charged.length} scraped videos, ${items.length - charged.length} uncharged failures`);
Async and scheduled pipelines
For large channels, start the run and poll client.run(runId).get() for status instead of waiting synchronously — the Actor streams rows into the dataset as each video is scraped, so partial results are readable before the run finishes. For recurring jobs, use an Apify schedule to trigger the Actor on a cron interval against a fixed sourceUrls list rather than polling externally for new content.
🎯 Who Needs This YouTube Most Replayed Scraper? (Use Cases & Industries)
🎥 Video editors and clippers
Feed a channel's back catalogue through the Actor once, then sort by peakCount to find every video's most-rewatched moment without scrubbing through hours of footage by hand.
📊 AI and data teams
Use title, viewCount, likes, comments and the heatmap arrays as structured input to a RAG index or a retention-prediction model — every field is a typed primitive, not HTML to be parsed first.
📱 Competitive and market intelligence
Schedule runs against a competitor channel's /videos tab and track peakCount and heatmapMarkerCount over time to see which content formats earn genuine rewatches rather than just view counts.
🔬 Researchers
Build a reproducible video-retention dataset with per-row provenance (sourceUrl, sourceType, sourcePosition), scoped to whatever YouTube already serves on public pages — no private or unlisted content is reachable.
Is it legal to scrape YouTube?
Scraping publicly accessible web pages is generally lawful in the United States — see hiQ Labs v. LinkedIn, 9th Circuit, 2019 (reaffirmed on remand in 2022), which held that scraping data that is not behind a login wall does not violate the Computer Fraud and Abuse Act. That case involved LinkedIn profile pages, not YouTube specifically, but the underlying "public means public" reasoning is the same one that applies here. Separately, YouTube's own Terms of Service restrict automated access to the site; violating a platform's terms is a contract dispute between the platform and the account used, not a criminal matter, but it is a real business risk if you scrape at scale on an account you care about keeping. This Actor returns video and channel metadata — titles, view/like/comment counts, upload dates, and heatmap data — not personal profiles, private messages, or comment authorship; it does not extract comment text or commenter identities. Because it does not collect personal data about private individuals, GDPR/CCPA data-subject obligations generally do not attach to its output the way they would for a scraper of user profiles or reviews. This Actor returns only publicly accessible data. What you do with that data is your responsibility — consult legal counsel for commercial applications, especially where a channel name may correspond to an identifiable individual rather than a business.
❓ Frequently asked questions
Does this Actor work without a YouTube account?
Yes. It reads only public watch and browse pages via plain HTTP requests — no login, API key, or cookies are used anywhere in the source.
How does it handle YouTube's anti-scraping measures?
It starts with a direct connection by default. If a request comes back with a retryable HTTP status, or a watch page returns LOGIN_REQUIRED with a "Sign in to confirm you're not a bot" style message, the Actor switches to the Apify Residential proxy (ProxyFallbackManager.switch_to_residential()) and stays on it for the rest of the run — it does not switch back. Each failed request also retries with a capped exponential backoff (min(10, 1.5 ** attempt) seconds) up to maxRetries times.
Can I run this at scale without getting blocked?
There's no published uptime or success-rate figure, but the Actor is built to keep going: once it detects a bot-check response it escalates to residential proxy automatically, and a video YouTube marks as genuinely unavailable (private, removed, age-restricted) is never retried, so retry budget isn't wasted on videos that can't succeed. Pagination inside one container is capped at 200 continuation requests, which in practice is well above what maxVideosPerSource's maximum of 5000 requires.
How fresh is the data this Actor returns?
Live per run — every video page and every container page is fetched fresh from YouTube each time the Actor runs. Nothing is cached between runs.
Which fields work best for AI training and RAG indexing?
For RAG, title combined with viewCount, likes and comments lets an agent rank or summarise a channel's catalogue directly from the dataset. For training data, mostReplayed[] and heatSeek[] give a consistent (start, duration, intensity) structure across every video that has a heatmap. All fields return as typed strings, numbers, booleans or arrays — no HTML parsing or normalization needed before use.
Does this Actor collect personal data?
No individual profiles, private messages, or comment authorship are collected — comments is a display count, not comment text or commenter names. channelOwner and sourceTitle are the channel or playlist's own public display name. Lawful basis for storing and using any of this data, including public display names, sits with you as the user.
Does this Actor work with Claude, ChatGPT, and other AI agent tools?
There is no MCP server for this Actor. It is callable as a standard Apify Actor run by any agent framework that can make an HTTP request or use apify_client — every response is typed JSON, ready to pass into an LLM context window without a parsing step.
Do I pay for videos that fail?
No. A video that YouTube reports as unavailable, private, or otherwise unplayable produces a row with error and errorReason set, pushed without the row_result charged event — it is never billed. Videos removed by skipVideosWithoutHeatmap are filtered out before the push happens at all, so they are never saved or billed either.
What happens if I supply both a channel URL and the default urls value?
The unmodified urls default (a single sample watch URL) is ignored the moment you supply any sourceUrls — it is only treated as a real request when no container sources are given. Change or clear urls explicitly if you want a specific video scraped alongside a channel or playlist.
ℹ️ Disclaimer
This Actor extracts only publicly available data from YouTube. It is intended for lawful use cases only. Users are responsible for complying with YouTube's Terms of Service and applicable data protection laws in their jurisdiction.