YouTube Scraper : Videos, Channels & Comments
Pricing
from $9.00 / 1,000 results
YouTube Scraper : Videos, Channels & Comments
Scrape YouTube videos, channels & comments by keyword, hashtag, or URL, no API key or quota limits. Get titles, views, likes, upload dates, channel stats, and comments as clean JSON
Pricing
from $9.00 / 1,000 results
Rating
0.0
(0)
Developer
Techforce Global
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
1
Monthly active users
8 days ago
Last modified
Categories
Share
YouTube Data API Alternative - Videos, Channels & Comments
No API key, no Google Cloud project, no daily quota. Search terms, hashtags, channel URLs or video URLs in — video metadata, channel stats and full comment threads out, as clean JSON in a single dataset. Built for content research, competitor tracking and audience analysis, with optional delivery into Notion, Slack, Linear, Jira, GitHub or Airtable over MCP.
🔐 Trust & compliance at a glance
| Credentials | None required. No Google or YouTube account, and delivery runs through the Apify MCP Proxy, so the Actor never sees your connector tokens. |
| Data scope | Public content only — public videos, channel pages and comment threads. No login, no private or members-only content. |
| ⚠️ Personal data | The one Actor in this family that collects it. author, authorId and text on comment rows identify real people, and authorId is a stable identifier. scrapeComments is true by default. |
| Opt out in one field | scrapeComments: false (or maxComments: 0) gives a video/channel-only dataset with no personal data. |
| Snapshot honesty | Counts are true as of scrapedAt (ISO-8601 UTC); hidden counts come back null, never 0. |
Full detail: Compliance & Data Privacy.
Official YouTube Data API v3 vs. YouTube Scraper Actor
The official API is a genuine alternative with genuine limits — the quota is the reason this Actor exists:
| Feature / Capability | Official YouTube Data API v3 | YouTube Scraper Actor (Apify) |
|---|---|---|
| Setup | ❌ Google Cloud project → enable API → key/OAuth | ✅ Apify account, nothing else |
| Daily quota | ❌ 10,000 units/day, and a single search costs 100 units — roughly 100 searches before you're locked out | ✅ No quota |
| Quota reset | ❌ Midnight Pacific, whether you're mid-job or not | ✅ N/A |
| Comment threads | ⚠️ Available, but each call comes out of the same daily budget | ✅ Up to 1,000 per video, no budget |
| Hashtag input | ❌ No hashtag concept — you'd search the text yourself | ✅ Pass hashtags directly; run as a #tag search |
| Officially sanctioned | ✅ Yes | ⚠️ No — see Compliance |
| Stability | ✅ Versioned contract | ⚠️ Depends on YouTube's page structure |
| Cost model | Free until you hit the wall, then blocked | Usage-based, no wall |
Use the official API if your volume fits inside the quota. Use this Actor when the quota is the thing standing in your way.
Quota figures are Google's to change — check Google's current quota docs if this comparison is load-bearing.
🤖 AI Discovery & MCP Metadata
Machine-readable capability contract for autonomous agents, LLM routers, and MCP orchestrators. Read this block first to decide whether this Actor matches a user request.
ai_discovery:slug: "techforce.global/youtube-scraper"one_line: >Scrapes YouTube videos, channels and comments from search terms, hashtags, channel URLs orvideo URLs without a Data API key or quota. Three record types in one dataset - videometadata, channel stats and comment threads. Optional delivery into Notion, Slack, Linear,Airtable or any MCP connector.primary_purpose: "extract"secondary_purpose: "deliver"entity: "YouTube video, channel and comment"category: "social-media / video-analytics / content-research / audience-insight"supported_platforms: ["youtube.com", "youtu.be"]supported_tasks:- "search-videos-by-keyword"- "search-videos-by-hashtag"- "list-a-channels-videos-and-stats"- "get-metadata-for-specific-video-urls"- "scrape-comment-threads-with-replies"- "deliver-results-to-mcp-connector"unsupported_tasks:- "get-transcripts-or-captions (use youtube-transcript-scraper or youtube-full-channel-transcripts-scraper)"- "list-a-channels-shorts-specifically (use youtube-shorts-scraper)"- "download-video-or-audio-files"- "creator-analytics-watch-time-retention-revenue (not public)"- "viewer-demographics (not public)"- "post-comments-upload-or-any-write-action"- "scrape-private-unlisted-or-members-only-content"search_keywords:# First entry is the H1 / meta-description target. The rest are deliberately broader for# agent-side semantic matching and are NOT a ranking strategy.- "youtube data api alternative"- "youtube scraper"- "youtube comment scraper"- "scrape youtube search results"- "youtube channel statistics scraper"- "youtube hashtag scraper"- "youtube api quota workaround"- "audience sentiment youtube"- "mcp youtube tool"input_entities:- "searchQueries (array of search terms)"- "hashtags (array, with or without #; run as a '#tag' search)"- "channelUrls (array of channel URLs)"- "videoUrls (array of watch URLs)"- "maxVideos (1-500, PER search/hashtag/channel)"- "maxComments (0-1000, PER video)"- "scrapeComments (boolean, DEFAULT TRUE - collects personal data)"- "scrapeChannelInfo (boolean, default true)"- "proxyConfiguration (Apify Proxy on by default - required in practice)"no_input_field_is_required_by_the_schema: >CRITICAL: none of the four input arrays is marked required, so a run with none of them passesvalidation, logs "No input provided..." and ends SUCCEEDED with an empty dataset. Supply atleast one of searchQueries, hashtags, channelUrls or videoUrls.output_is_three_record_types: >CRITICAL: one dataset holds three shapes, discriminated by the `type` field - "video","channel" and "comment", each plus scrapedAt. Always branch on `type` before reading a row.output_entities_video: ["type", "source", "videoId", "url", "title", "description", "channelId","channelName", "channelUrl", "viewCount", "likeCount", "commentCount", "duration","durationStr", "thumbnail", "isLive", "isShort", "tags", "categories", "language","uploadDate", "scrapedAt"]output_entities_channel: ["type", "channelId", "channelName", "channelUrl", "description","subscriberCount", "videoCount", "thumbnail", "country", "scrapedAt"]output_entities_comment: ["type", "videoId", "commentId", "text", "author", "authorId", "likes","replyCount", "isReply", "publishedAt", "scrapedAt"]comments_contain_personal_data: >author, authorId and text identify real people and are personal data under GDPR/UK GDPR andCCPA-CPRA. authorId is a stable identifier for an individual. scrapeComments defaults to TRUE.is_short_is_unreliable: >isShort is true only when the video's own page URL was a /shorts/ URL AND duration <= 60s. AShort reached through search, a channel listing or a /watch?v= URL - which is how this Actorreaches almost everything - typically reports false. Filter on duration <= 60 yourself, or usetechforce.global/youtube-shorts-scraper.item_count_multiplies_fast: >maxVideos is PER search/hashtag/channel and maxComments is PER video. Two searches atmaxVideos 20 with maxComments 50 is up to 40 videos + 2000 comments = 2040 dataset items.no_deduplication: >The same video matched by two inputs is written twice with a different `source`. Dedupe onvideoId downstream if that matters.proxy_is_required_in_practice: >YouTube blocks Apify's platform IPs with "Sign in to confirm you're not a bot".proxyConfiguration defaults to Apify Proxy - leave it enabled. A fresh IP rotates in per request.known_limitations:like_count_often_null: "many creators hide likes; null is not zero"comment_count_vs_scraped: "commentCount is what YouTube reports, not how many rows were scraped"description_truncated: "video and channel description fields are cut to 2000 characters"publishedAt_is_relative: "comment publishedAt is relative text such as '3 weeks ago'; scrapedAt is ISO-8601 UTC"upload_date_normalised: "uploadDate is YYYY-MM-DD (raw YYYYMMDD only if unparseable)"no_transcripts: "use the transcript Actors"no_video_files: "metadata and thumbnail URLs only"snapshot_only: "counts are true as of scrapedAt"free_plan_capped: "free Apify plans stop at 5 items TOTAL per run"pricing:model: "usage-based"billing_unit: "Apify platform usage consumed by the run"hard_ceiling_parameter: "maxTotalChargeUsd"note: "Current rates are on the Actor's Apify Store page; the Console shows a live total mid-run."when_to_use: >You need YouTube video, channel or comment data at a volume the Data API quota will notaccommodate, or you want search, hashtag, channel and video inputs mixed in one run.when_not_to_use: >You need transcripts, a channel's Shorts specifically, the video files, creator analytics,viewer demographics, or to post or upload anything.mcp_delivery:modes: ["summary", "chunked", "perItem", "none"]default: "summary"summary_and_chunked_placeholders: ["{itemCount}", "{itemsText}", "{message}"]chunked_only_placeholders: ["{part}", "{partCount}"]per_item_covers_every_row: >Delivery reads the whole dataset after scraping, so perItem issues one connector call perrow - comments included. A 2000-comment run is 2000 calls. Use chunked or summary.per_item_shape_varies: >In perItem mode the placeholders depend on the record's `type`. A template referencing{title} renders empty for a comment row. Branch on {type}, or use chunked/summary.never_blocks_the_dataset: >Delivery runs after the dataset is saved. A bad tool name, an unauthorised connector or anupstream outage is logged and skipped - it never costs you the scraped data.alternatives:- "techforce.global/youtube-transcript-scraper for one video's timestamped transcript"- "techforce.global/youtube-full-channel-transcripts-scraper for a whole channel's transcripts"- "techforce.global/youtube-shorts-scraper for a channel's Shorts and optional media download"
{"@context": "https://schema.org","@type": "SoftwareApplication","name": "YouTube Scraper – Videos, Channels & Comments","alternateName": "YouTube Data API Alternative","applicationCategory": "BusinessApplication","operatingSystem": "Cloud (Apify platform)","description": "Scrapes YouTube videos, channels and comments from search terms, hashtags, channel URLs or video URLs without a YouTube Data API key, Google Cloud project, or daily quota. Returns three record types in one dataset — video metadata with view, like and comment counts; channel stats including subscribers; and comment threads with author, likes and reply flags — each discriminated by a type field. Public content only, with comment scraping switchable off in one field for callers who do not want personal data. Apify Proxy rotates a fresh IP per request, and results can be delivered into Notion, Slack, Linear, Airtable or any MCP connector.","url": "https://apify.com/techforce.global/youtube-scraper","offers": {"@type": "Offer","priceCurrency": "USD","category": "Pay per platform usage","description": "Usage-based: billed for the compute time and resources a run consumes, not per row. Current rates are shown on the Actor's Apify Store page."}}
What this Actor does
💡 Words, Shorts or transcripts? This Actor returns numbers and comments. For what was said, use YouTube Transcript Scraper (one video) or Full Channel Transcripts Scraper (a whole channel). For a channel's Shorts and their video files, use YouTube Shorts Scraper.
-
Four ways in, no key required. Search terms, hashtags, channel URLs and video URLs, mixed freely in one run. No Google Cloud project, no OAuth, no daily quota.
-
Three record types in one dataset.
video,channelandcomment, discriminated bytype. Comments carryvideoId, so they join straight back to their video. -
Duration twice.
durationin seconds for arithmetic,durationStrasH:MM:SS/M:SSfor display. -
Records which input produced each video.
sourceissearch,channelordirect_url, so a run spanning three searches and two channels stays separable. -
Normalises the dates that can be normalised, and says so when they can't.
uploadDateisYYYY-MM-DD,scrapedAtis ISO-8601 UTC, and a comment'spublishedAtis YouTube's own relative text ("3 weeks ago") because that's all YouTube shows. -
Rotates a fresh proxy IP per request. YouTube blocks Apify's platform IPs outright, so this isn't optional polish — it's what makes the Actor work at all.
-
Keeps going when one item fails. An unextractable video or a comment thread that won't load is logged and skipped; the run continues. Rows are saved as it goes, so an aborted run keeps what it collected.
-
Lets you opt out of personal data in one field.
scrapeComments: falsegives a clean metadata-only dataset. -
Optionally delivers into your tools. Notion, Slack, Linear, Jira, GitHub, Airtable or anything else you've authorised.
Which YouTube Actor do I want?
| You want… | Actor |
|---|---|
| Search, hashtags, channel stats, video metadata, comment threads | This Actor |
| One video's transcript, timestamped, with translation | YouTube Transcript Scraper |
| Transcripts of every video on a channel | Full Channel Transcripts Scraper |
| A channel's Shorts, with engagement and optional file download | YouTube Shorts Scraper |
None need a YouTube Data API key or a quota. They're complementary, not alternatives.
When to use it — and when not to
| Scenario | Fit |
|---|---|
| Videos for a search term or hashtag | ✅ Strong — searchQueries, hashtags (run as a #tag search) |
| A channel's videos and channel-level stats | ✅ Strong — channelUrls |
| Metadata for specific videos | ✅ Strong — videoUrls |
| Comment threads with replies | ✅ Strong — up to 1,000 per video, isReply marks replies |
| Audience sentiment / social listening | ✅ Strong — comment text, likes, reply counts |
| Skipping the Data API quota entirely | ✅ Strong — the main reason to use this |
| View, like and subscriber counts | ✅ Strong — likeCount / subscriberCount often null (hidden) |
| Metadata without personal data | ✅ Strong — scrapeComments: false |
| Results in Notion / Slack / Airtable | ✅ Strong — native MCP delivery |
| Telling Shorts from long-form | ⚠️ isShort under-reports — filter duration <= 60, or use the Shorts Scraper |
| A guaranteed-complete comment thread | ⚠️ Capped at 1,000/video; YouTube paginates its own way |
| A deduplicated video list across inputs | ⚠️ No dedup — same video can appear twice with different source |
| Transcripts, captions or subtitles | ❌ Use the Transcript or Full Channel Scraper |
| Downloading video or audio files | ❌ Metadata and thumbnail URLs only |
| Creator analytics — watch time, retention, revenue | ❌ Not public |
| Viewer demographics | ❌ Not public |
| Historical view-count series from one run | ❌ Snapshot per run — schedule to build history |
| Posting comments, uploads, playlist management | ❌ Read-only |
| Private, unlisted or members-only content | ❌ Public content only |
⚡ Execution Rules (Avoid Common Agent Failures)
-
Supply at least one of
searchQueries,hashtags,channelUrls,videoUrls. None is marked required in the schema, so a run with none of them passes validation, logsNo input provided. Supply at least one of: searchQueries, channelUrls, videoUrls, hashtags.and endsSUCCEEDEDwith an empty dataset. Never readitemCount: 0as a crash. -
Keep the proxy enabled. Default is
{"useApifyProxy": true}. Without it YouTube returns "Sign in to confirm you're not a bot" and you get nothing — the log warnsNo proxy configured — YouTube is likely to block requests…. -
Item counts multiply.
maxVideosis per search, hashtag and channel;maxCommentsis per video. Two searches atmaxVideos: 20withmaxComments: 50is up to 2,040 items. -
scrapeCommentsistrueby default — your first run collects personal data unless you set itfalse(ormaxComments: 0). Metadata-only is faster, cheaper, and out of scope for most of Compliance. -
Branch on
typebefore reading any row. One dataset, three shapes. A consumer that assumes every row hastitlebreaks on the first comment. -
isShortis a hint, not a filter. True only when the page URL was a/shorts/URL and duration ≤ 60s — rarely how this Actor reaches a video. Shorts found via search or a channel typically reportfalse. Filter onduration <= 60, or use the Shorts Scraper. -
likeCountandsubscriberCountare frequentlynull— hidden, not zero. Never average them as zero. -
commentCount≠ the number of comment rows. It's YouTube's figure; your rows are capped bymaxCommentsand by what YouTube served. -
Know which dates are which.
uploadDate=YYYY-MM-DD,scrapedAt= ISO-8601 UTC, a comment'spublishedAt= relative text. Sort comments by scrape order, notpublishedAt. -
descriptionis truncated at 2,000 characters on video and channel rows. -
No deduplication. A video matched by two inputs is written twice with a different
source. -
Inputs run in a fixed order: searches → hashtags → channels → video URLs. A run that stops early keeps what came first.
-
perItemdelivery issues one connector call per dataset row — comments included. Delivery reads the whole dataset, so a 2,000-comment run is 2,000 calls. Usechunkedorsummary. InperItem, placeholders depend ontype;{title}renders empty for comment rows. -
deliveryModedefaults tosummary, notnone. For dataset-only runs, leavemcpConnectorempty or setdeliveryMode: "none". -
Free Apify plans stop at 5 items total per run — not per input.
dependencies:- rule: "at least one of searchQueries / hashtags / channelUrls / videoUrls must be non-empty"on_violation: "run ends SUCCEEDED with an empty dataset and an error line in the log - it does NOT fail validation"- rule: "delivery requires mcpConnector AND mcpTool"on_violation: "delivery is skipped with a warning; every dataset row is still written"- rule: "mcpArguments must reference {message} for the rendered template to appear anywhere"on_violation: "message body is empty; the tool is still called"- rule: "mcpConnector only resolves when the Actor runs on the Apify platform"on_violation: "local runs log 'APIFY_MCP_PROXY_URL is not set' and skip delivery"authentication:actor_input: noneplatform: "Apify API token required for API / SDK / MCP invocation"target_site: "no YouTube or Google account, API key, or OAuth app required (public content only)"
📥 Input Contract
.actor/input_schema.json is authoritative — if this page disagrees with the schema files,
the schema files win.
| Parameter | Type | Required | Default | Allowed values / format |
|---|---|---|---|---|
searchQueries | array<string> | see note | — | Search terms, each looked up separately |
hashtags | array<string> | see note | — | With or without #; run as a #tag search |
channelUrls | array<string> | see note | — | Channel URLs, e.g. https://www.youtube.com/@mkbhd |
videoUrls | array<string> | see note | — | Watch URLs. Scraped directly, no search |
maxVideos | integer | ⬜ No | 20 | 1–500, per search / hashtag / channel |
maxComments | integer | ⬜ No | 50 | 0–1000, per video. 0 disables comments |
scrapeComments | boolean | ⬜ No | true | Collects personal data when on. false = metadata only |
scrapeChannelInfo | boolean | ⬜ No | true | Emit a channel record for channel URLs |
proxyConfiguration | object | ⬜ No | {"useApifyProxy": true} | Apify proxy editor object. Leave enabled. |
At least one of the four input arrays must be non-empty. None is marked
requiredin the schema, so this is enforced at runtime: a run without any of them succeeds with an empty dataset and an error line in the log.
What each input produces
| Input | Records emitted per item |
|---|---|
searchQueries | up to maxVideos × video (source: "search"), each optionally followed by its comment rows |
hashtags | the same, searched as #tag |
channelUrls | one channel row (if scrapeChannelInfo), then up to maxVideos × video (source: "channel") + comments |
videoUrls | one video row (source: "direct_url") plus its comment rows |
Delivery (optional)
| Parameter | Type | Default | Description |
|---|---|---|---|
mcpConnector | string | "" | A connector you've authorised on your Apify account — Notion, Slack, Linear, Jira, GitHub, Airtable, or any MCP-compatible connector. Leave empty for dataset-only runs. |
deliveryMode | string (enum) | "summary" | summary (one call) · chunked (split across calls) · perItem (one call per row) · none. |
mcpTool | string | "" | Tool to call — create_page, send_message, create_issue. Required when a connector is set; run once and the log lists the connector's tools. |
mcpArguments | object | {} | Arguments for the tool. String leaves support {placeholder} tokens. |
mcpMessageTemplate | string | "" | Template rendered and exposed to mcpArguments as {message}. |
Placeholders by mode:
| Mode | Placeholders |
|---|---|
summary | {itemCount} {itemsText} (formatted list of every result) {message} |
chunked | the same, plus {part} {partCount} — {itemsText} holds one part |
perItem | {type} {scrapedAt} {message} {itemCount}, plus whichever fields that record type carries — video: {videoId} {title} {url} {viewCount} {likeCount} {duration} {tags} …; channel: {channelName} {subscriberCount} {videoCount} …; comment: {author} {text} {likes} {isReply} … |
Unknown tokens render empty rather than erroring — which is why {type} matters in perItem.
chunked groups results so no single call exceeds roughly 72,000 characters, keeping Notion and
similar services from rejecting the payload.
📤 Output Contract & Data Structure
One dataset, three record types. Branch on type first. Every row carries scrapedAt
(ISO-8601 UTC).
type: "video"
| Field | Type | Nullable | Meaning |
|---|---|---|---|
type | string | No | Always "video". |
source | string | No | Which input produced it: search, channel or direct_url. |
videoId | string | Yes | YouTube's 11-character ID. The join key for comments. |
url | string | Yes | Canonical watch URL. |
title | string | Yes | Video title. |
description | string | Yes | Truncated to 2,000 characters. |
channelId / channelName / channelUrl | string | Yes | Channel identity. |
viewCount | integer | Yes | Views as of scrapedAt. |
likeCount | integer | Yes | Frequently null — creators hide it. Null ≠ zero. |
commentCount | integer | Yes | What YouTube reports — not the number of comment rows scraped. |
duration | integer | Yes | Length in seconds. |
durationStr | string | Yes | H:MM:SS, or M:SS under an hour. |
thumbnail | string | Yes | Highest-resolution thumbnail available. |
isLive | boolean | No | true for live streams. |
isShort | boolean | No | Unreliable — see the caveat below. |
tags / categories | array | No | [] when none. |
language | string | Yes | Language code YouTube reports. |
uploadDate | string | Yes | YYYY-MM-DD (raw YYYYMMDD only if unparseable). |
scrapedAt | string | No | ISO-8601 UTC capture time. |
isShortcaveat. True only when the video's own page URL was a/shorts/URL and duration ≤ 60 seconds. Videos reached through search, a channel listing or a/watch?v=URL — nearly everything this Actor returns — normally reportfalseeven when they are Shorts. Filter onduration <= 60, or use the YouTube Shorts Scraper, which reads the Shorts tab directly.
type: "channel"
| Field | Type | Nullable | Meaning |
|---|---|---|---|
type | string | No | Always "channel". |
channelId / channelName / channelUrl | string | Yes | Channel identity. |
description | string | Yes | Truncated to 2,000 characters. |
subscriberCount | integer | Yes | Null when the channel hides it. |
videoCount | integer | Yes | Public video count as YouTube reports it. |
thumbnail | string | Yes | Channel avatar URL. |
country | string | Yes | Declared country. Often null. |
scrapedAt | string | No | ISO-8601 UTC capture time. |
type: "comment" — contains personal data
| Field | Type | Nullable | Meaning |
|---|---|---|---|
type | string | No | Always "comment". |
videoId | string | No | Join key back to the video row. |
commentId | string | Yes | YouTube's comment ID. |
text | string | Yes | Comment body. Personal data. |
author | string | Yes | Display name. Personal data. |
authorId | string | Yes | Stable channel identifier for that person. Personal data. |
likes | integer | Yes | Likes on the comment. |
replyCount | integer | Yes | Replies YouTube reports. |
isReply | boolean | No | true when the row is a reply. |
publishedAt | string | Yes | Relative text — e.g. "3 weeks ago". Not a date. |
scrapedAt | string | No | ISO-8601 UTC capture time. |
Example output — one of each type
[{"type": "video","source": "search","videoId": "aBcDeFgHiJk","url": "https://www.youtube.com/watch?v=aBcDeFgHiJk","title": "How We Rebuilt Our Ingestion Pipeline","description": "Everything we learned moving 40M events a day…","channelId": "UC000000000000000000000","channelName": "Practical Systems","channelUrl": "https://www.youtube.com/@practicalsystems","viewCount": 148203,"likeCount": null,"commentCount": 412,"duration": 964,"durationStr": "16:04","thumbnail": "https://i.ytimg.com/vi/aBcDeFgHiJk/maxresdefault.jpg","isLive": false,"isShort": false,"tags": ["data engineering", "kafka"],"categories": ["Science & Technology"],"language": "en","uploadDate": "2026-06-14","scrapedAt": "2026-08-27T09:12:44.183921+00:00"},{"type": "channel","channelId": "UC000000000000000000000","channelName": "Practical Systems","channelUrl": "https://www.youtube.com/@practicalsystems","description": "Weekly deep dives into systems that actually run in production.","subscriberCount": 128000,"videoCount": 214,"thumbnail": "https://yt3.googleusercontent.com/…","country": null,"scrapedAt": "2026-08-27T09:12:41.006512+00:00"},{"type": "comment","videoId": "aBcDeFgHiJk","commentId": "UgxAbCdEf1234567890","text": "The backpressure section finally made this click for me.","author": "@some_viewer","authorId": "UC111111111111111111111","likes": 34,"replyCount": 2,"isReply": false,"publishedAt": "3 weeks ago","scrapedAt": "2026-08-27T09:12:47.771044+00:00"}]
Values illustrate shape and formatting — the channel, video and commenter are invented. Note
likeCount: null(that creator hid likes) andpublishedAtas relative text.
What a successful run looks like
- Status:
SUCCEEDED - Dataset: interleaved
video,channelandcommentrows, in input order - Log: one line per input (
Searching YouTube for: …,Scraping channel: …), thenScraping complete. - Zero results is also a success. No inputs supplied, a bot-block, or an unresolvable channel
all return an empty dataset with
SUCCEEDED. Check the log. - Per-item failures are logged and skipped —
[yt-dlp] Error extracting …or[comments] Failed for …— and the run continues.
Dataset views
| View | Shows |
|---|---|
| Results overview | Type, title, channel, views, likes, comments, duration, upload date |
| Video detail | Engagement, duration, live/Short flags, language, tags, categories, source |
| Comments | Video ID, author, text, likes, reply count and reply flag |
| Channels | Channel name, subscribers, video count, country, description |
▶️ Invocation & Integration
Authenticate every call with Authorization: Bearer <APIFY_TOKEN>.
Apify Console
Open the Actor page → Input tab → fill in
at least one of search queries, hashtags, channel URLs or video URLs → set maxVideos and
maxComments → decide whether you want comments at all → leave the proxy on → Start.
Model Context Protocol (MCP)
claude mcp add --transport http apify \"https://mcp.apify.com?tools=techforce.global/youtube-scraper"
Then ask your agent for "the top videos and comment sentiment for this search term".
Python (apify-client)
import osfrom collections import Counterfrom apify_client import ApifyClientclient = ApifyClient(os.getenv("APIFY_TOKEN"))run = client.actor("techforce.global/youtube-scraper").call(run_input={"searchQueries": ["apify web scraping"],"maxVideos": 20,"scrapeComments": False, # metadata only — no personal data"maxComments": 0,"deliveryMode": "none","proxyConfiguration": {"useApifyProxy": True},})rows = list(client.dataset(run["defaultDatasetId"]).iterate_items())# An empty dataset is a SUCCEEDED run — check it before assuming failure.if not rows:raise SystemExit("Nothing scraped — check the run log (no input? blocked?).")print(Counter(r["type"] for r in rows)) # e.g. Counter({'video': 20})# ALWAYS branch on type — one dataset, three shapes.videos = [r for r in rows if r["type"] == "video"]# likeCount is often null (creator hid it) — exclude, don't coerce to 0.liked = [v for v in videos if v.get("likeCount") is not None]for v in sorted(liked, key=lambda v: v["viewCount"] or 0, reverse=True)[:10]:rate = v["likeCount"] / v["viewCount"] if v.get("viewCount") else 0print(f'{v["viewCount"]:>10,} {rate:>6.2%} {v["title"][:60]}')# isShort is unreliable through search — use duration instead.print(f'{sum(1 for v in videos if (v.get("duration") or 999) <= 60)} likely Shorts by duration')
JavaScript / TypeScript (apify-client)
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('techforce.global/youtube-scraper').call({channelUrls: ['https://www.youtube.com/@mkbhd'],maxVideos: 10,scrapeComments: true,maxComments: 100,deliveryMode: 'none',proxyConfiguration: { useApifyProxy: true },});const { items } = await client.dataset(run.defaultDatasetId).listItems();// Three record types in one dataset — split them first.const videos = items.filter((i) => i.type === 'video');const comments = items.filter((i) => i.type === 'comment');const channels = items.filter((i) => i.type === 'channel');// Comments join back to their video on videoId.const byVideo = new Map(videos.map((v) => [v.videoId, { ...v, comments: [] as any[] }]));for (const c of comments) byVideo.get(c.videoId)?.comments.push(c);for (const v of byVideo.values()) {// commentCount is YouTube's number; v.comments.length is what we actually got.console.log(`${v.title}: ${v.comments.length}/${v.commentCount ?? '?'} comments scraped`);}console.log(channels[0]?.subscriberCount ?? 'subscriber count hidden');
cURL — synchronous (waits, returns the dataset)
curl -X POST \-H 'Content-Type: application/json' \-H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' \-d '{"searchQueries": ["python tutorial 2024"],"maxVideos": 10,"scrapeComments": false,"deliveryMode": "none","proxyConfiguration": { "useApifyProxy": true }}' \'https://api.apify.com/v2/acts/techforce.global~youtube-scraper/run-sync-get-dataset-items'
cURL — asynchronous with a spend cap (recommended for large runs)
# 1. Start with a hard spend limitcurl -X POST -H 'Content-Type: application/json' \-H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' \-d '{ "channelUrls": ["https://www.youtube.com/@handle"], "maxVideos": 200, "maxComments": 200,"proxyConfiguration": { "useApifyProxy": true } }' \'https://api.apify.com/v2/acts/techforce.global~youtube-scraper/runs?maxTotalChargeUsd=10'# 2. Poll, then 3. fetch the dataset when SUCCEEDEDcurl -H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' 'https://api.apify.com/v2/actor-runs/<runId>'curl -H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' \'https://api.apify.com/v2/datasets/<DATASET_ID>/items?clean=true&format=json'
maxTotalChargeUsd is the hard ceiling. Always set it when maxVideos × maxComments runs
into the thousands.
Metadata-only configuration — no personal data
{"searchQueries": ["ai agents 2026"],"hashtags": ["#llm"],"maxVideos": 50,"scrapeComments": false,"maxComments": 0,"deliveryMode": "none","proxyConfiguration": { "useApifyProxy": true }}
Video rows only — no author, no authorId, no comment text. Faster, cheaper, and out of scope
for most of the compliance burden. Start here.
With MCP delivery — a digest into Slack
{"searchQueries": ["apify web scraping"],"maxVideos": 15,"scrapeComments": false,"proxyConfiguration": { "useApifyProxy": true },"mcpConnector": "<your-authorized-slack-connector>","mcpTool": "send_message","deliveryMode": "summary","mcpMessageTemplate": "Scraped {itemCount} YouTube results:\n\n{itemsText}","mcpArguments": { "channel": "#youtube-watch", "text": "{message}" }}
For competitor tracking, put a fixed channelUrls input on a
schedule with comments off, and join runs on videoId
to watch viewCount curves over time.
⚠️ Autonomous Agent Error Handling & Resolution Matrix
Read this first: nothing about the content fails the run. Missing inputs, a blocked request,
an unextractable video and a comment thread that won't load are all logged while the run continues
to SUCCEEDED. An agent that checks run status alone will read an empty dataset as
success-with-data — check the row count and the log.
The CONDITION labels are documentation handles for observable states; this Actor writes no error
code into the dataset.
| Condition | What you observe | Cause | Deterministic agent action |
|---|---|---|---|
NO_INPUT_PROVIDED | SUCCEEDED, empty dataset; log: No input provided. Supply at least one of: … | All four input arrays empty — not caught by validation | Modify Input: supply at least one array. |
BOT_CHECK_BLOCKED | Empty/thin dataset; log: [yt-dlp] Error extracting …, sign-in-to-confirm errors | Proxy disabled or platform IP blocked | Modify Input: {"useApifyProxy": true} and retry. |
NO_PROXY_CONFIGURED | Log: No proxy configured — YouTube is likely to block requests… | proxyConfiguration omitted or unusable | Modify Input: enable Apify Proxy before retrying anything else. |
CHANNEL_UNRESOLVED | That channel contributes no rows; log: [yt-dlp] Error extracting <url> | Not a resolvable channel, or blocked | Modify Input: use the @handle or /channel/ID form. |
VIDEO_EXTRACT_FAILED | A requested video URL produced no row | Private, deleted, region-blocked, or challenged | Do not retry blindly. Verify the URL opens signed-out. |
COMMENTS_FAILED | Video rows present, no comment rows; log: [comments] Failed for <videoId> | Comments disabled on that video, or the fetch was blocked | Expected, partial. Retry once. |
TYPE_MISMATCH_IN_CONSUMER | Consumer crashes reading title / text | One dataset, three shapes | Fix consumer: branch on type. |
SHORT_NOT_FLAGGED | A Short has isShort: false | Flag needs a /shorts/ page URL and ≤60s | Fix expectation: filter duration <= 60, or use the Shorts Scraper. |
NULL_LIKE_COUNT | likeCount / subscriberCount null | Creator or channel hides the number | Expected. Null ≠ zero. |
COMMENT_COUNT_MISMATCH | Fewer comment rows than commentCount | maxComments cap, or YouTube served fewer | Expected. Raise maxComments (max 1,000). |
PUBLISHED_AT_NOT_PARSEABLE | publishedAt won't parse as a date | Relative text by design | Fix expectation: use scrapedAt. |
DUPLICATE_VIDEOS | Same videoId twice, different source | Matched by two inputs; no dedup | Expected. Dedupe on videoId. |
ITEM_EXPLOSION | Dataset far larger than expected | maxVideos per input × maxComments per video | Modify Input: lower both, or scrapeComments: false. |
FREE_PLAN_CAPPED | 5 items; log: Free plan detected - capping results to 5 item(s). | Free plan — cap is total, not per input | Upgrade the plan. |
CONNECTOR_CALL_STORM | Hundreds/thousands of connector calls | perItem sends one call per row, comments included | Modify Input: switch to chunked or summary. |
CONNECTOR_TOOL_NOT_FOUND | Log: Tool 'x' is not available on this connector. Available tools: … | Wrong mcpTool name (or empty — logged separately) | Modify Input: copy an exact name from that log line. Dataset unaffected. |
CONNECTOR_UNREACHABLE | Log: Could not reach the connector: … or APIFY_MCP_PROXY_URL is not set | Connector not authorised, or running locally | Authorise it, or apify push and run on the platform. Data already saved. |
AUTH_INVALID | HTTP 401 | Missing or malformed APIFY_TOKEN | Abort & prompt user for a valid token. |
RATE_LIMITED | HTTP 429 | Apify API rate / concurrency limits | Retry: exponential backoff (2s, 4s, 8s). |
SYNC_TIMEOUT | HTTP 408 on the sync endpoint | Run exceeded the synchronous ceiling | Reconfigure: async POST /runs + polling. |
Provenance: every log line, status and dataset condition above is taken from this Actor's own source and verified against it. The three HTTP rows describe the Apify API transport layer, not this Actor — confirm them against the Apify API reference before wiring them into automated retry logic.
🗣️ Natural Language → Actor Mapping
| A user says… | Intent | Constructed Actor input |
|---|---|---|
| "Find YouTube videos about X" | Search | {"searchQueries":["X"],"maxVideos":20} |
| "Top videos for this hashtag" | Hashtag discovery | {"hashtags":["#X"],"maxVideos":20} |
| "What has this channel posted lately?" | Channel listing | {"channelUrls":["<url>"],"maxVideos":25} |
| "How many subscribers does this channel have?" | Channel stats | {"channelUrls":["<url>"],"maxVideos":1,"scrapeChannelInfo":true} |
| "Get the stats for these three videos" | Direct metadata | {"videoUrls":["<a>","<b>","<c>"],"scrapeComments":false} |
| "What are people saying in the comments?" | Comment mining | {"videoUrls":["<url>"],"scrapeComments":true,"maxComments":500} |
| "Sentiment on this topic across YouTube" | Social listening | {"searchQueries":["<topic>"],"maxVideos":10,"maxComments":200} |
| "Just the video data, no personal data" | Privacy-safe pull | {"searchQueries":["<topic>"],"scrapeComments":false,"maxComments":0} |
| "Compare two competitors' channels" | Competitive analysis | {"channelUrls":["<A>","<B>"],"maxVideos":25,"scrapeComments":false} |
| "Which of their videos performed best?" | Ranking | Channel input, then sort on viewCount excluding nulls |
| "Track this channel weekly" | Scheduled monitor | Fixed input on a schedule; join runs on videoId |
| "Which of these are Shorts?" | Format filter | Scrape, then filter duration <= 60 — isShort under-reports |
| "Put these results in Notion" | Scrape + deliver | Above, plus mcpConnector, mcpTool, deliveryMode: "chunked" |
| "It returned nothing" | Empty result | Check an input array was supplied, and that Apify Proxy is on |
| "What did they actually say in the video?" | Transcript | ➡️ Transcript Scraper |
| "Transcripts for their whole channel" | Corpus | ➡️ Full Channel Transcripts Scraper |
| "List all their Shorts / download them" | Short-form | ➡️ Shorts Scraper |
Should NOT route here: transcripts or captions (→ Transcript / Full Channel) · a channel's Shorts catalogue or video files (→ Shorts Scraper) · creator analytics — watch time, retention, revenue · viewer demographics · private, unlisted or members-only content · posting comments, uploading, or any write action · non-YouTube video platforms (Vimeo, TikTok, Twitch).
🧭 Agent Execution & Routing Logic
Does the user want YouTube NUMBERS or COMMENTS (not words)?├── NO → one of the siblings.│ one video's transcript → YouTube Transcript Scraper│ a channel's transcripts → Full Channel Transcripts Scraper│ a channel's Shorts/files → YouTube Shorts Scraper│ watch time / revenue → creator's own account only│└── YES│├── Which input do they have? (mix freely; at least ONE is mandatory at│ runtime even though the schema marks none required)│ ├── a topic → searchQueries│ ├── a hashtag → hashtags (run as a "#tag" search)│ ├── a creator → channelUrls (adds a channel row)│ └── specific videos → videoUrls (most predictable item count)│├── ALWAYS keep proxyConfiguration enabled (useApifyProxy: true)│├── Do they want COMMENTS?│ ├── no → scrapeComments: false, maxComments: 0│ │ → smaller, faster, and NO personal data│ └── yes → maxComments is PER VIDEO (0-1000)│ ├── personal data: author, authorId, text → see Compliance│ └── multiply: videos × maxComments = row count│├── How many videos? maxVideos is PER search / hashtag / channel (1-500)│ ├── a probe → 3 ├── analysis → 20-50 └── deep → + maxTotalChargeUsd│├── What is the actual question?│ ├── best performers → sort viewCount, EXCLUDING nulls│ ├── like-rate → likeCount / viewCount, both non-null only│ ├── Shorts vs long → duration <= 60 (NOT isShort)│ ├── audience mood → comment text + likes, join on videoId│ └── growth over time→ SCHEDULE it; join on videoId│└── Should results land in another app?├── yes → mcpConnector + mcpTool + {message} in mcpArguments│ one digest → summary · long list → chunked│ per record → perItem (ONE CALL PER ROW, comments included)└── no → deliveryMode "none", or leave mcpConnector empty[Execute Actor]├──► SUCCEEDED with rows → split by `type`, then consume├──► SUCCEEDED, 0 rows → READ THE LOG: no input / blocked / unresolvable└──► exactly 5 rows → free plan cap, not a data pull
💰 Cost Control & Pricing Transparency
Billing model: usage-based. This Actor bills for platform usage — the compute time and resources a run consumes — not per row. Current rates are on the Actor's Apify Store page; the Console shows the live total mid-run.
| Choice | Effect on cost |
|---|---|
maxVideos | The main driver, and it applies per search, hashtag and channel. |
| Number of inputs | Multiplies maxVideos — three searches at 20 is 60 videos, not 20. |
scrapeComments | The second-biggest lever: one extra fetch per video, and comment rows dominate dataset size. |
maxComments | Up to 1,000 rows per video. 20 videos × 500 = 10,000 rows. |
| Channel inputs | Each video in a channel needs its own full metadata request. |
scrapeChannelInfo | Negligible — one row per channel from data already fetched. |
| Being blocked | Costs run time and returns nothing. A disabled proxy is the most expensive setting on this page. |
Worked examples — relative run shape
| Scenario | Videos | Comment rows | Total rows | Notes |
|---|---|---|---|---|
Probe — 1 search, maxVideos: 3, no comments | 3 | 0 | 3 | Baseline. Confirms proxy and input work. |
| Topic scan — 1 search, 20 videos, no comments | 20 | 0 | 20 | The cheap research shape. |
Same, with maxComments: 50 | 20 | ≤ 1,000 | ≤ 1,020 | Comments dominate immediately. |
Two searches, 20 each, maxComments: 50 | 40 | ≤ 2,000 | ≤ 2,040 | The execution-rules example. |
| Channel audit — 1 channel, 25 videos, no comments | 25 | 0 | 26 | Plus one channel row. |
Comment deep-dive — 2 video URLs, maxComments: 500 | 2 | ≤ 1,000 | ≤ 1,002 | Most predictable comment-heavy shape. |
| Broad sweep — 5 searches × 100 videos × 200 comments | 500 | ≤ 100,000 | ≤ 100,500 | Set maxTotalChargeUsd before running this. |
Keeping runs predictable:
- First run: one input,
maxVideos: 3,scrapeComments: false. - Decide the comment question up front — it's the difference between a 20-row dataset and a 1,020-row one, and between being in and out of scope for data-protection obligations.
- Prefer
videoUrlswhen you can — a search's item count is a prediction; a list of video URLs is arithmetic. - Always set
maxTotalChargeUsdon runs whose row count reaches the thousands. - Cache by
videoIdso scheduled re-runs don't re-pay for unchanged videos. - Free Apify plans get 5 items per run — a connectivity check, not a data pull.
🛠️ Troubleshooting
Most problems are missing input, the proxy, or the three-record-type shape — not scraper bugs.
| # | Symptom you see | Most likely cause | Fix |
|---|---|---|---|
| 1 | SUCCEEDED but the dataset is empty | No input array supplied — the schema doesn't enforce it | Read the log for No input provided…, then supply searchQueries, hashtags, channelUrls or videoUrls. |
| 2 | Empty or thin results despite valid input | Bot check — YouTube blocks Apify's platform IPs | Confirm proxyConfiguration is {"useApifyProxy": true}; the log warns when no proxy is configured. |
| 3 | Consumer crashes on a missing field | One dataset holds three shapes | Branch on type before reading any field. |
| 4 | A Short shows isShort: false | Flag needs a /shorts/ page URL and ≤60s; search/channel inputs don't provide that | Filter duration <= 60, or use the Shorts Scraper. |
| 5 | likeCount / subscriberCount null | Creator or channel hides the number | Expected. Null ≠ zero — exclude from averages. |
| 6 | Far fewer comment rows than commentCount | maxComments cap, or YouTube served fewer | Raise maxComments (max 1,000). Completeness isn't guaranteed. |
| 7 | No comment rows at all | scrapeComments: false, maxComments: 0, or comments disabled | Check the input, then the log for [comments] Failed for <videoId>. |
| 8 | publishedAt won't parse as a date | It's YouTube's relative text ("3 weeks ago") | Use scrapedAt (ISO-8601 UTC) for timing. |
| 9 | The description looks cut off | Truncated at 2,000 characters by design | Fetch the page yourself if you need the full text. |
| 10 | The same video appears twice | Matched two inputs; source differs | No dedup by design — dedupe on videoId. |
| 11 | Dataset far bigger than expected | maxVideos is per input; maxComments is per video | Do the multiplication, lower the caps, or turn comments off. |
| 12 | The run takes far longer than expected | Comments add a fetch per video; channels add a full request per video | Run metadata-only first, then target specific videos. |
| 13 | Exactly 5 rows came back | Free plan cap — total, not per input | Upgrade the plan; the log says Free plan detected…. |
| 14 | A free-plan run returned slightly more than 5 rows | The cap counts push batches, and a videoUrls entry pushes its video and comments together | Expected on free plans; paid runs are uncapped. |
| 15 | One channel produced nothing in a multi-channel run | That channel's extraction failed — [yt-dlp] Error extracting <url> | Re-run it alone with the @handle form. |
| 16 | A specific video URL produced no row | Private, deleted, region-blocked, or challenged | Open it signed-out in a browser to confirm. |
| 17 | Hundreds of connector calls fired | perItem sends one call per row, comments included | Switch to chunked or summary. |
| 18 | Connector messages have blank fields | perItem template referenced a field that record type lacks | Branch the template on {type}, or use summary. |
| 19 | Slack / Notion received nothing | Delivery needs both mcpConnector and mcpTool | Set both. Every dataset row is still saved. |
| 20 | Delivery fired when you only wanted a dataset | deliveryMode defaults to summary, not none | Set deliveryMode: "none" or leave mcpConnector empty. |
| 21 | Log: APIFY_MCP_PROXY_URL is not set | Ran locally; connectors only resolve on the platform | apify push and run on the platform. |
| 22 | HTTP 401 / 403 | Missing, expired or malformed APIFY_TOKEN | Regenerate in Console → Settings → API & Integrations; send as Authorization: Bearer <token>. |
| 23 | HTTP 429 / 408 | Apify rate limits / the sync endpoint's time ceiling | Back off (2s → 4s → 8s), or switch to async POST /runs + polling. |
Diagnostic checklist before opening an issue
- Confirm an input array is non-empty — the most common cause of an empty run.
- Confirm Apify Proxy is enabled and the log has no no-proxy warning.
- Baseline run: one
searchQueriesentry,maxVideos: 3,scrapeComments: false. - Count rows by
typebefore concluding anything is missing. - Open the failing URL signed-out — private, deleted and region-blocked videos look identical to a scraper.
- Read the log — per-item failures appear as
[yt-dlp] Error extracting …and[comments] Failed for ….
If the issue survives all six, open an Issues ticket (or email support) with the run ID, the input you used, and what you expected.
❓ FAQ
Do I need a YouTube Data API key or a Google Cloud project?
No. The Actor reads publicly available YouTube pages. You need an Apify account and API token; you need no Google credentials whatsoever.
Is this the official YouTube API?
No. This is an independent Actor and is not affiliated with, endorsed by, or sponsored by YouTube or Google.
What's the real advantage over the Data API?
The quota. The official API gives you 10,000 units a day and a single search costs 100 of them — roughly 100 searches before you're locked out until midnight Pacific. This Actor has no quota, no Google Cloud project and no OAuth flow. The trade-off: the API is the sanctioned route with a versioned contract, while this Actor depends on YouTube's page structure.
Do I need to configure proxies?
Apify Proxy is on by default and you should leave it on. YouTube blocks the platform's shared IPs with a "Sign in to confirm you're not a bot" wall, which shows up as an empty or thin dataset. A fresh IP is rotated in per request.
Can I plug it into Claude, Cursor, or a LangChain agent?
Yes — it is a native MCP tool: claude mcp add --transport http apify "https://mcp.apify.com?tools=techforce.global/youtube-scraper"
Which inputs can I combine in one run?
All four — searchQueries, hashtags, channelUrls and videoUrls — freely mixed. They're processed in that order, and every video row carries a source field so you can tell which input produced it.
How do hashtags work?
Each hashtag is run as a search for #tag (the # is added if you leave it off). That's the honest description: it is a search, not a separate hashtag-page crawl. The official API has no hashtag concept at all.
How many items will my run produce?
Multiply before you run. maxVideos is per search, hashtag and channel; maxComments is per video. Two searches at maxVideos: 20 with maxComments: 50 is up to 40 videos + 2,000 comments = 2,040 rows.
How do I get video metadata without collecting personal data?
Set scrapeComments: false (or maxComments: 0). You get video and channel rows only — no author names, no author IDs, no comment text.
Why do my rows have different shapes, and how do I join them?
One dataset holds three record types — video, channel and comment — discriminated by the type field. Always branch on type before reading fields; a consumer that assumes every row has title breaks on the first comment. Comment rows carry videoId, which joins them back to their video.
Why is likeCount null?
The creator hid the like count; subscriberCount behaves the same way on channels. Null is not zero — exclude those rows from averages rather than coercing them.
Are the dates ISO-8601?
uploadDate is normalised to YYYY-MM-DD and scrapedAt is full ISO-8601 UTC. A comment's publishedAt is relative text such as "3 weeks ago", because that is all YouTube publishes — use scrapedAt for anything time-sensitive.
Why is isShort false for a video I know is a Short?
Because the flag requires the video's own page URL to be a /shorts/ URL and the duration to be 60 seconds or less. Videos reached through search, a channel listing or a /watch?v= URL — nearly everything this Actor returns — normally report false. Filter on duration <= 60, or use the YouTube Shorts Scraper, which reads the Shorts tab directly.
Why did I get fewer comments than commentCount says?
commentCount is the number YouTube reports for the video. The rows you receive are capped by maxComments (max 1,000) and by what YouTube actually served.
Can the same video appear twice?
Yes. There is no deduplication: a video matched by two searches, or by a search and a channel, is written twice with a different source. Dedupe on videoId downstream.
Can I get transcripts, or download the video files?
Neither. For transcripts use the YouTube Transcript Scraper (one video) or the Full Channel Transcripts Scraper. For a channel's Shorts as MP4 or audio, use the YouTube Shorts Scraper. This Actor returns metadata and thumbnail URLs only.
Which delivery mode should I use, and does it work locally?
summary for a single digest, chunked for large result sets into services with request caps (Notion). Avoid perItem unless you really want one record each: delivery reads the whole dataset, so it issues one call per row — comments included, which can be thousands. Delivery only works on the Apify platform; locally you'll see APIFY_MCP_PROXY_URL is not set and the dataset is saved as normal.
Do comments contain personal data?
Yes. author, authorId and text identify real people, and authorId is a stable identifier for an individual. scrapeComments defaults to true, so your first run collects them unless you turn it off. Read Compliance & Data Privacy before you store or reuse that data.
What does it cost, and what do I get on the free plan?
Usage-based: you pay for the platform usage a run consumes, not per row. The drivers are maxVideos, the number of inputs, and whether comments are on. Free Apify plans return 5 items total per run — a connectivity check, not a data pull. Pass maxTotalChargeUsd on the run endpoint for a hard per-run ceiling.
🔍 Companion machine-readable files
| File | What it defines |
|---|---|
| .actor/input_schema.json | Every input, its type, default, bounds and the delivery enum |
| .actor/dataset_schema.json | Every field across all three record types, and the four Console views |
| .actor/output_schema.json | Where the run's output is exposed |
| .actor/actor.json | Actor identity and storage wiring |
If this page and the schema files disagree, the schema files win. They are what the platform validates against at run time; this page is documentation and can lag a release.
🔗 Related Actors by Techforce Global
This Actor is the numbers-and-comments specialist: discovery, metadata and audience reaction.
| Actor | Best for | Why pick it over this one |
|---|---|---|
| YouTube Transcript Scraper | One video's timestamped transcript, 13 languages with auto-translation | You want what was said in a specific video, not how it performed. |
| YouTube Full Channel Transcripts Scraper | Transcripts for every video on a channel | You're building a RAG corpus or searchable archive of a creator's words. |
| YouTube Shorts Scraper | A channel's Shorts with engagement, optional MP4/audio download | You need the Shorts tab specifically — this Actor's isShort under-reports — or the media files. |
| Advanced Website Crawling Actor | Whole-site text, Markdown or HTML | You want a creator's or competitor's site in the same corpus as their video data. |
| Pinterest Scraper | Pins from searches, boards, profiles | You're comparing the same content strategy on another visual platform. |
Pipeline patterns: topic landscape — several searchQueries, comments off, rank by
viewCount, then send the winners to the Full Channel Transcripts Scraper for a content-gap
analysis · audience sentiment — videoUrls with maxComments: 500, comment text into your LLM,
digest to Slack via MCP · competitor tracking — channelUrls on a schedule, comments off, join
runs on videoId to watch view curves and posting cadence.
Browse all Actors by Techforce Global at scraper.techforce.global.
🔐 Compliance & Data Privacy
Trademark & affiliation. This is an independent Actor. It is not affiliated with, endorsed by, or sponsored by YouTube or Google. YouTube™ is a trademark of Google LLC; all trademarks are the property of their respective owners.
-
Public content only. Everything returned is visible to any signed-out visitor. No private, unlisted or members-only content, no login, no stored credentials.
-
⚠️ Comments are personal data — and they're on by default.
author,authorIdandtextidentify real people, andauthorIdis a stable identifier that lets you track one person across videos. Under GDPR / UK GDPR and CCPA/CPRA, collecting and storing them is processing personal data, and the obligations sit with you, not with the Actor. -
The cheapest compliance measure is a single field. If your question is about videos rather than people, set
scrapeComments: false. You keep every video and channel metric and remove the personal data entirely. -
If you do collect comments: have a lawful basis, keep retention short, restrict access, and don't re-publish individuals' comments out of context. Comment text is unfiltered public speech — it can contain special-category content (health, politics, religion, sexuality) and abuse, and you cannot know which threads contain it until after retrieval.
-
Aggregate rather than store where you can. Sentiment scores, theme counts and engagement ratios rarely require keeping the raw
author/authorIdcolumns. Drop them at ingest. -
Public does not mean consented. Someone commenting under a video did not agree to be profiled, ranked, or added to a dataset that follows them across channels. Treat
authorIdlinkage as the sensitive operation it is. -
Video content and metadata are the creators' work. Internal research, analysis and reporting sit on far safer ground than republishing scraped descriptions, thumbnails or metrics as your own content.
-
Counts are a snapshot.
viewCount,likeCountandsubscriberCountare true as ofscrapedAt, and hidden values come backnull, not0. Publishing rankings or like-rates without saying when you captured them — or while silently treating nulls as zeros — produces numbers that look authoritative and aren't. -
YouTube's Terms of Service prohibit automated access. This Actor does not remove that risk — it is a risk you accept by running it. The official Data API is the sanctioned route; its quota is the reason this Actor exists.
-
Attribution over mirroring.
urlandchannelUrlare on every video row. Link back and credit the creator rather than presenting their content or their numbers as your own.
🆘 Support & Custom Pipeline Engineering
Need automated multi-city scheduled feeds, webhook enrichment, or enterprise data warehouse syncs?
- Email: bhavin.shah@techforceglobal.com
- Custom Enterprise Integrations: Book a 15-Minute Technical Consultation
- Maintained by: Techforce Global — Specialists in High-Performance Web Scrapers and Agentic Workflows.
🏷️ Structured data for search & AI discovery
SoftwareApplication describes the Actor; FAQPage wraps the FAQ above so those answers are
eligible for rich results, matching it question-for-question.
{"@context": "https://schema.org","@type": "SoftwareApplication","name": "YouTube Scraper – Videos, Channels & Comments","alternateName": "YouTube Data API Alternative","applicationCategory": "BusinessApplication","applicationSubCategory": "Web Scraping / Video & Audience Analytics","operatingSystem": "Cloud (Apify platform)","description": "Scrapes YouTube videos, channels and comments from search terms, hashtags, channel URLs or video URLs without a YouTube Data API key, Google Cloud project, or daily quota. Returns three record types in one dataset — video metadata with view, like and comment counts; channel stats including subscribers; and comment threads with author, likes and reply flags — each discriminated by a type field. Public content only, with comment scraping switchable off in one field for callers who do not want personal data. Apify Proxy rotates a fresh IP per request, and results can be delivered into Notion, Slack, Linear, Airtable or any MCP connector.","url": "https://apify.com/techforce.global/youtube-scraper","author": { "@type": "Organization", "name": "Techforce Global", "url": "https://www.techforceglobal.com" },"provider": { "@type": "Organization", "name": "Apify", "url": "https://apify.com" },"offers": {"@type": "Offer","priceCurrency": "USD","category": "Pay per platform usage","description": "Usage-based: billed for the compute time and resources a run consumes, not per row. Current rates are shown on the Actor's Apify Store page.","url": "https://apify.com/techforce.global/youtube-scraper"},"featureList": ["Four input types in one run: search terms, hashtags, channel URLs and video URLs","No YouTube Data API key, Google Cloud project, OAuth or daily quota","Three record types in one dataset, discriminated by a type field","Video metadata with view, like and comment counts, duration and tags","Channel stats including subscriber and video counts","Comment threads with author, likes, reply count and reply flag, up to 1,000 per video","Comment scraping switchable off in one field for a personal-data-free dataset","source field records which input produced each video","uploadDate normalised to YYYY-MM-DD and scrapedAt in ISO-8601 UTC","Apify Proxy with a fresh IP rotated in per request","Per-item failures logged and skipped so one bad video never ends the run","Optional MCP delivery to Notion, Slack, Linear, Airtable and more","Four Console dataset views covering videos, comments and channels"],"isAccessibleForFree": false,"softwareRequirements": "Apify account and API token","keywords": "YouTube Data API alternative, YouTube scraper, YouTube comment scraper, video metadata extraction, channel statistics, hashtag search, audience insight, social listening, content research, MCP connector"}
{"@context": "https://schema.org","@type": "FAQPage","mainEntity": [{"@type": "Question","name": "Do I need a YouTube Data API key or a Google Cloud project?","acceptedAnswer": { "@type": "Answer", "text": "No. The Actor reads publicly available YouTube pages. You need an Apify account and API token; you need no Google credentials whatsoever." }},{"@type": "Question","name": "Is this the official YouTube API?","acceptedAnswer": { "@type": "Answer", "text": "No. This is an independent Actor and is not affiliated with, endorsed by, or sponsored by YouTube or Google." }},{"@type": "Question","name": "What's the real advantage over the Data API?","acceptedAnswer": { "@type": "Answer", "text": "The quota. The official API gives you 10,000 units a day and a single search costs 100 of them — roughly 100 searches before you're locked out until midnight Pacific. This Actor has no quota, no Google Cloud project and no OAuth flow. The trade-off: the API is the sanctioned route with a versioned contract, while this Actor depends on YouTube's page structure." }},{"@type": "Question","name": "Do I need to configure proxies?","acceptedAnswer": { "@type": "Answer", "text": "Apify Proxy is on by default and you should leave it on. YouTube blocks the platform's shared IPs with a \"Sign in to confirm you're not a bot\" wall, which shows up as an empty or thin dataset. A fresh IP is rotated in per request." }},{"@type": "Question","name": "Can I plug it into Claude, Cursor, or a LangChain agent?","acceptedAnswer": { "@type": "Answer", "text": "Yes — it is a native MCP tool: claude mcp add --transport http apify \"https://mcp.apify.com?tools=techforce.global/youtube-scraper\"" }},{"@type": "Question","name": "Which inputs can I combine in one run?","acceptedAnswer": { "@type": "Answer", "text": "All four — searchQueries, hashtags, channelUrls and videoUrls — freely mixed. They're processed in that order, and every video row carries a source field so you can tell which input produced it." }},{"@type": "Question","name": "How do hashtags work?","acceptedAnswer": { "@type": "Answer", "text": "Each hashtag is run as a search for #tag (the # is added if you leave it off). That's the honest description: it is a search, not a separate hashtag-page crawl. The official API has no hashtag concept at all." }},{"@type": "Question","name": "How many items will my run produce?","acceptedAnswer": { "@type": "Answer", "text": "Multiply before you run. maxVideos is per search, hashtag and channel; maxComments is per video. Two searches at maxVideos: 20 with maxComments: 50 is up to 40 videos + 2,000 comments = 2,040 rows." }},{"@type": "Question","name": "How do I get video metadata without collecting personal data?","acceptedAnswer": { "@type": "Answer", "text": "Set scrapeComments: false (or maxComments: 0). You get video and channel rows only — no author names, no author IDs, no comment text." }},{"@type": "Question","name": "Why do my rows have different shapes, and how do I join them?","acceptedAnswer": { "@type": "Answer", "text": "One dataset holds three record types — video, channel and comment — discriminated by the type field. Always branch on type before reading fields; a consumer that assumes every row has title breaks on the first comment. Comment rows carry videoId, which joins them back to their video." }},{"@type": "Question","name": "Why is likeCount null?","acceptedAnswer": { "@type": "Answer", "text": "The creator hid the like count; subscriberCount behaves the same way on channels. Null is not zero — exclude those rows from averages rather than coercing them." }},{"@type": "Question","name": "Are the dates ISO-8601?","acceptedAnswer": { "@type": "Answer", "text": "uploadDate is normalised to YYYY-MM-DD and scrapedAt is full ISO-8601 UTC. A comment's publishedAt is relative text such as \"3 weeks ago\", because that is all YouTube publishes — use scrapedAt for anything time-sensitive." }},{"@type": "Question","name": "Why is isShort false for a video I know is a Short?","acceptedAnswer": { "@type": "Answer", "text": "Because the flag requires the video's own page URL to be a /shorts/ URL and the duration to be 60 seconds or less. Videos reached through search, a channel listing or a /watch?v= URL — nearly everything this Actor returns — normally report false. Filter on duration <= 60, or use the YouTube Shorts Scraper, which reads the Shorts tab directly." }},{"@type": "Question","name": "Why did I get fewer comments than commentCount says?","acceptedAnswer": { "@type": "Answer", "text": "commentCount is the number YouTube reports for the video. The rows you receive are capped by maxComments (max 1,000) and by what YouTube actually served." }},{"@type": "Question","name": "Can the same video appear twice?","acceptedAnswer": { "@type": "Answer", "text": "Yes. There is no deduplication: a video matched by two searches, or by a search and a channel, is written twice with a different source. Dedupe on videoId downstream." }},{"@type": "Question","name": "Can I get transcripts, or download the video files?","acceptedAnswer": { "@type": "Answer", "text": "Neither. For transcripts use the YouTube Transcript Scraper (one video) or the Full Channel Transcripts Scraper. For a channel's Shorts as MP4 or audio, use the YouTube Shorts Scraper. This Actor returns metadata and thumbnail URLs only." }},{"@type": "Question","name": "Which delivery mode should I use, and does it work locally?","acceptedAnswer": { "@type": "Answer", "text": "summary for a single digest, chunked for large result sets into services with request caps (Notion). Avoid perItem unless you really want one record each: delivery reads the whole dataset, so it issues one call per row — comments included, which can be thousands. Delivery only works on the Apify platform; locally you'll see APIFY_MCP_PROXY_URL is not set and the dataset is saved as normal." }},{"@type": "Question","name": "Do comments contain personal data?","acceptedAnswer": { "@type": "Answer", "text": "Yes. author, authorId and text identify real people, and authorId is a stable identifier for an individual. scrapeComments defaults to true, so your first run collects them unless you turn it off. Read the Compliance & Data Privacy section before you store or reuse that data." }},{"@type": "Question","name": "What does it cost, and what do I get on the free plan?","acceptedAnswer": { "@type": "Answer", "text": "Usage-based: you pay for the platform usage a run consumes, not per row. The drivers are maxVideos, the number of inputs, and whether comments are on. Free Apify plans return 5 items total per run — a connectivity check, not a data pull. Pass maxTotalChargeUsd on the run endpoint for a hard per-run ceiling." }}]}