Youtube Most Replayed Scraper for Whole Channels & Playlists avatar

Youtube Most Replayed Scraper for Whole Channels & Playlists

Pricing

from $3.99 / 1,000 results

Go to Apify Store
Youtube Most Replayed Scraper for Whole Channels & Playlists

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

Scrapio

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

0

Monthly active users

2 days ago

Last modified

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/browse continuation — 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 from youtubei/v1/browse continuation requests, capped by maxVideosPerSource (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-character videoId and contentId values rather than matching a specific renderer name, so YouTube's ongoing migration from playlistVideoRenderer to lockupViewModel doesn't silently return zero videos.
  • Two distinct heatmap structures per video: heatSeek[] (startMillis, durationMillis, intensityScoreNormalized — the raw curve) and mostReplayed[] (visibleTimeRangeStartMillis, visibleTimeRangeEndMillis, decorationTimeMillis — the labelled peaks).
  • Sanitised like counts. _sanitize_like_value() in src/yt_extractors.py discards UI button captions like "Like" and only keeps a value that contains a digit — likes is either a real count string or "", never a fabricated label.
  • Provenance fields on every rowsourceUrl, 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:

ParameterRequiredTypeDescriptionExample Value
sourceUrlsNoarray (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"]
sourceTabsNoarray (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"]
maxVideosPerSourceNointeger (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
maxTotalVideosNointeger (0–20000)Hard ceiling across all containers and direct URLs combined, applied after discovery. 0 means no global cap. Default 0.500
urlsNoarray (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"]
skipVideosWithoutHeatmapNobooleanDrop 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
maxConcurrencyNointeger (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
requestTimeoutSecsNointeger (5–120)Seconds to wait for a single YouTube response before retrying. Default 20.30
maxRetriesNointeger (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
proxyNoobject (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/@mkbhd with sourceTabs: ["videos"] — expands the Videos tab.
  • A specific tab directly: https://www.youtube.com/@mkbhd/shorts — the trailing tab in the URL overrides sourceTabs.
  • A playlist, any ID prefix: https://www.youtube.com/playlist?list=PLxxxx or a watch URL with &list=PLxxxx — both resolve to the same canonical playlist URL, per yt_discovery.classify_source().
  • A single video, mixed in: https://youtu.be/_oRgdlJUD18 in urls, alongside containers in sourceUrls — 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
}
FieldTypeMeaning
channelOwnerstringChannel name that uploaded the video
titlestringVideo title
videoIdstring11-character YouTube video ID
viewCountstringView count as YouTube's page reports it
likesstringLike count; "" when only a UI label ("Like") was available or likes are hidden
commentsstringComment count as displayed, e.g. "7.3K"; "" when comments are off or unavailable
dateTextstringAbsolute upload date, e.g. "Jul 13, 2026"
relativeDatestringRelative upload date, e.g. "2 weeks ago"
mostReplayedarrayLabelled replay peaks: visibleTimeRangeStartMillis, visibleTimeRangeEndMillis, decorationTimeMillis (all milliseconds); empty when no heatmap
heatSeekarrayFull intensity curve: startMillis, durationMillis, intensityScoreNormalized; empty when no heatmap
videoUrlstringCanonical watch URL
sourceUrlstring | nullContainer this row was discovered from; null for a directly supplied video
sourceTypestringchannel_videos, channel_shorts, channel_streams, playlist, or direct_url
sourceTitlestring | nullChannel or playlist display name
sourcePositionnumber | null1-based position inside that container's discovery order
hasHeatmapbooleanWhether a replay curve exists for this video
heatmapMarkerCountnumberNumber of entries in heatSeek (typically 100 when present, 0 when not)
peakCountnumberNumber of entries in mostReplayed
errorstring | nullFailure text; only set on uncharged accounting rows
errorReasonstring | nullTyped 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

StrategyBest forRun patternOutput format
Real-time enrichmentPer-video updates as they publishTriggered, small maxVideosPerSourceDataset row appended to an external record
Scheduled monitoringTracking retention changes over timeApify schedule, recurring runDataset diffed against the previous run's snapshot
Bulk dataset buildResearch or training datasetsOne run, many sourceUrls, or parallel runsFull dataset export to CSV/JSON

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.

ScraperWhat 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 ApifyClient
import csv
client = 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.

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.