YouTube Video Scraper by Hashtag With Full Descriptions
Pricing
from $3.99 / 1,000 results
YouTube Video Scraper by Hashtag With Full Descriptions
The Youtube Video Scraper By Hashtag collects videos tied to any hashtag, including titles, views, likes, comments, creators, and upload details. Export clean JSON or CSV for trend tracking, content research, audience analysis, and marketing insights at scale.
Pricing
from $3.99 / 1,000 results
Rating
0.0
(0)
Developer
Scrapio
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
0
Monthly active users
5 days ago
Last modified
Categories
Share
YouTube Video Scraper — Extract Videos, Shorts and Full Descriptions
Scrape YouTube by hashtag, plain keyword or @handle and get typed JSON for every video — the full, untruncated description and the exact ISO-8601 upload date, not the ~100-character snippet and the relative "4 months ago" text that YouTube's own search page shows. Every result carries a stable video ID, view count, duration, and (when it applies) the hashtag's own headline stats. Unlike scraping frameworks that return raw HTML, this scraper returns typed JSON — ready for your model, your database, or your pipeline without any parsing. The rest of this guide covers every field the scraper writes and how teams run it for enrichment, monitoring, and dataset-building workloads.
🧭 What does this YouTube video scraper do?
It runs a YouTube search for one or more terms — a #hashtag, a bare keyword, or an @handle — and collects the resulting videos and Shorts, then optionally makes one extra lightweight request per video to pull the complete description and the real upload date straight from YouTube's own InnerTube API. No YouTube account, API key, or cookies are required — every request goes to keyless, public endpoints. Capabilities:
- 🔎 Search by hashtag, keyword, or channel handle in the same run
- 🩳 Filter to YouTube Shorts only, or mix Shorts and regular videos
- 📝 Fetch the full, untruncated video description (not the search snippet)
- 📅 Resolve the exact upload date instead of a relative "N months ago" string
- ⏱️ Filter by duration, upload age, or required keywords in the title/description
- 🌐 Optional proxy support to unlock the ISO timestamp, category, and exact runtime
- 🔁 Backward-compatible with inputs built for the original hashtag-only scraper
⚡ Features & capabilities
The scraper's job splits into two stages — collection and enrichment — and both are configurable.
Core features
- Three search modes in one field: type
#chatgptfor a hashtag search,chatgptfor a plain keyword search, or@mkbhdfor a handle search — each is honoured exactly as typed inautomode. - Full-description enrichment: one extra JSON request per video fills
descriptionFull,descriptionLength,publishedDate, andvideoAgeDays— fields the plain search page never exposes. - Client-profile fallback for detail lookups: the scraper probes three different YouTube InnerTube client profiles (
player,metadata,player-mobile) and keeps whichever one actually answers from the network it is running on, instead of failing outright. - Never guesses a value it cannot verify: a video whose duration, category, or upload date can't be read keeps that field
nullrather than being backfilled with a guess —detailsFetchedtells you whether the lookup ran at all. - Post-collection filtering: keyword match (title, snippet, or full description), min/max duration, and "uploaded within N days" all run after collection, so a row with an unknown value is kept, never silently dropped.
- Legacy input compatibility: the original
hashtagsarray field still works and takes priority over the newersearchTermsfield when both are filled in.
When another tool might suit you better
If your job is pulling every video from a specific channel or playlist rather than searching by topic, a channel-scoped scraper will get you there with fewer runs than repeated hashtag searches. If you need the full comment thread under each video, this Actor doesn't fetch comments at all — it stops at video-level metadata. And if you need guaranteed ISO timestamps and categories on every run without configuring a residential proxy, budget for that setup step; without it, those three fields come back null on most datacenter-IP runs (see the Input section below).
YouTube Video Scraper within the Scrapio data stack
This Actor covers hashtag, keyword, and handle search results — videos and Shorts. For a full channel's upload history and comments, use youtube-channel-scraper-with-comments. For the highest-replay-count moments inside a channel's or playlist's existing videos, use youtube-most-replayed-scraper-for-channels-and-playlists. All three return typed JSON rows and take the same Apify proxy configuration pattern.
Why do developers and data teams scrape YouTube?
🎥 Content strategy and creator research
Teams pull videos matching a hashtag or keyword to see which titles, thumbnails, and upload cadences are working for a niche right now — the row-level title, viewCount, durationSeconds, and publishedDate fields are enough to rank what a topic's top performers look like this month, without opening YouTube once.
📊 AI training data and RAG indexing
descriptionFull is the field that makes this data usable for AI — full video descriptions routinely run to hundreds of words, versus the ~100-character description snippet YouTube's search page shows. For RAG enrichment, index title + descriptionFull + category as the retrievable text block per video. For training data, viewCount, durationSeconds, and publishedDate give consistently typed numeric/date fields across every row, with no relative-time strings to normalize.
📱 Competitive and market intelligence
Track a competitor's or a topic's hashtag over time by scheduling repeat runs and diffing viewCount and videoAgeDays between runs — a sudden jump in views on a recent upload is the signal worth alerting on, and searchTerm tells you which query surfaced it.
🔬 Research and academic use
publishedTimeText versus the resolved publishedDate lets researchers study how YouTube's own UI represents time versus the actual calendar date, and hashtagCategoryData.categoryInfoText gives the platform's own video/channel counts for a hashtag at the time of the run. Public search-result data only — no private account or watch-history data is collected.
🎬 Product and SaaS development
Video-metadata dashboards, hashtag-tracking tools, and content-monitoring products can build directly on the dataset's typed rows — id and url are stable enough to key a video record, and detailsFetched/detailsSource tell your product whether a given row's enrichment fields are trustworthy before you display them.
🍚 Input parameters
No parameter is required — leave everything blank and the scraper falls back to its defaults (10 videos for the prefilled #chatgpt term).
| Parameter | Required | Type | Constraints | Description |
|---|---|---|---|---|
searchTerms | No | array (stringList) | prefill: ["#chatgpt"] | One or more YouTube search terms. Examples: #chatgpt (hashtag search), chatgpt (plain keyword search), @mkbhd (handle search). Leave empty if using the legacy hashtags field. |
hashtags | No | array (stringList) | — | Backward-compatible alias of searchTerms. If filled in, it wins over searchTerms. Kept so inputs written for the original hashtag scraper keep working unchanged. |
searchMode | No | string (select) | default: "auto"; enum: "auto", "hashtag" | auto = a term is searched exactly as typed (#tag stays a hashtag search, a bare word stays a keyword search). hashtag = every term is forced into a #hashtag search, matching the original scraper's behaviour. |
maxResults | No | integer | default: 10; min: 1; max: 500 | How many videos to collect for each search term, before filters are applied. YouTube search exhausts at roughly 300 videos per term (measured 301 on #chatgpt), so values above that simply return everything available. |
scrapeShortsOnly | No | boolean | default: false | Return only YouTube Shorts. Shorts rows come from YouTube's Shorts shelf, which carries no duration and no description snippet — turn on fetchExactDates to fill those in. |
fetchExactDates | No | boolean | default: true | Costs one small JSON request per video. Always fills descriptionFull, descriptionLength, publishedDate (YYYY-MM-DD), and videoAgeDays. The ISO-8601 timestamp (publishDate, uploadDate), category, and lengthSeconds come from a YouTube client that refuses Apify's own datacenter IP — turn the proxy on to get those too. When off, all of these are null and detailsFetched is false. |
maxVideoLookups | No | integer | default: 50; min: 0; max: 2000 | Hard ceiling on how many videos get the extra description/date request in one run. Videos beyond the ceiling are still returned, with detailsFetched = false and null detail fields, so "we never looked" is never confused with "YouTube had nothing". |
descriptionKeywords | No | array (stringList) | — | Case-insensitive. A video is kept when any of these strings appears in its title, its search snippet, or its full description (when fetched). Leave empty to keep everything. |
minDurationSeconds | No | integer | min: 0; max: 86400 | Drop videos shorter than this. Uses the duration shown in search results, or the exact length from the description lookup. Videos with no known duration are kept. |
maxDurationSeconds | No | integer | min: 1; max: 86400 | Drop videos longer than this. Same duration source as above. Videos with no known duration are kept. |
publishedWithinDays | No | integer | min: 1; max: 3650 | Needs the exact upload date, so it only takes effect when the description/date lookup is on. Videos whose upload date could not be read are kept and reported in the log, never guessed from the relative "4 months ago" text. |
proxyConfiguration | No | object (proxy) | prefill: {"useApifyProxy": false} | Optional but recommended. The proxy is attached to every YouTube request — the search page, every continuation page, and every description lookup. Without it, YouTube answers the detail lookup from a reduced surface that carries the description and the upload day but no ISO timestamp, no category, and no exact length. With a residential proxy, all of those fields are returned. |
Example input:
{"searchTerms": ["#chatgpt", "ai coding", "@mkbhd"],"searchMode": "auto","maxResults": 50,"scrapeShortsOnly": false,"fetchExactDates": true,"maxVideoLookups": 200,"descriptionKeywords": ["tutorial"],"minDurationSeconds": 60,"publishedWithinDays": 30,"proxyConfiguration": {"useApifyProxy": true}}
Supported search term formats
#hashtag— e.g.#chatgpt. Inautomode, stays a hashtag search; the response also carrieshashtagCategoryData.categoryInfoText(YouTube's own "2.6M videos • 822K channels" headline) when the term is a real hashtag.plain keyword— e.g.chatgpt. Inautomode, runs as an ordinary keyword search. Inhashtagmode, the same term is rewritten to#chatgptbefore the request is sent.@handle— e.g.@mkbhd. Runs as a channel-handle search; results are whatever videos YouTube's search surfaces for that handle, not a full channel listing.
📦 Output format
Every kept video is pushed as one typed JSON row to the dataset, with a row_result charged event. Results export from the Apify Console (or API) as JSON, CSV, Excel, XML, or RSS.
Output for a regular video
{"thumbnailUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg","title": "How ChatGPT Actually Works","id": "dQw4w9WgXcQ","url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ","viewCount": 1420000,"type": "video","hashtagCategoryData.categoryInfoText": "2.6M videos • 822K channels","searchTerm": "#chatgpt","channelName": "Example Channel","description": "In this video we break down how ChatGPT works under the hood, covering...","publishedTimeText": "4 months ago","durationText": "12:31","durationSeconds": 751,"viewCountText": "1,420,000 views","shortViewCountText": "1.4M views","detailsFetched": true,"detailsSource": "player","descriptionFull": "In this video we break down how ChatGPT works under the hood, covering transformer architecture, tokenization, reinforcement learning from human feedback, and how the model is served in production. Chapters and links to the full paper are in the description below...","descriptionLength": 842,"publishDate": "2025-04-02T14:00:12-07:00","publishedDate": "2025-04-02","uploadDate": "2025-04-02","videoAgeDays": 124,"category": "Education","lengthSeconds": 751}
Output for a YouTube Short
Shorts come from a different YouTube renderer that natively carries less than a regular video result — no duration, no description snippet — until the description/date lookup fills the detail fields in:
{"thumbnailUrl": "https://i.ytimg.com/vi/abc123XYZ90/hqdefault.jpg","title": "This #ChatGPT trick saves hours","id": "abc123XYZ90","url": "https://www.youtube.com/shorts/abc123XYZ90","viewCount": 3200000,"type": "shorts","hashtagCategoryData.categoryInfoText": "2.6M videos • 822K channels","searchTerm": "#chatgpt","channelName": null,"description": null,"publishedTimeText": null,"durationText": null,"durationSeconds": null,"viewCountText": null,"shortViewCountText": "3.2M views","detailsFetched": true,"detailsSource": "player-mobile","descriptionFull": "3 seconds that will change how you prompt ChatGPT forever...","descriptionLength": 96,"publishDate": null,"publishedDate": "2025-06-11","uploadDate": null,"videoAgeDays": 54,"category": null,"lengthSeconds": 47}
Every kept row carries all 25 keys shown above — channelName, description, durationText, and the other search-payload fields are null on rows where YouTube's Shorts shelf simply doesn't provide them, not omitted.
If a run collects zero videos (empty search results, filters removed everything, or YouTube refused every request), the dataset receives one extra row instead: {"_accounting": true, "runId": ..., "searchTerms": [...], "collectedBeforeFilters": ..., "blockSignals": [...], "note": "No videos saved for this run."}. This row is pushed without the row_result charged event — it is not billed. Filter it out of any downstream processing with item._accounting !== true (or simply check that item.id exists).
Schema stability and export options
Field names stay fixed across runs regardless of which YouTube client profile (player, metadata, or player-mobile) actually answered — detailsSource records which one it was, so you can see whether a row's detail fields came from the richest surface or a fallback. Fields the current run couldn't verify are null, never removed from the row shape. Results are available from the Apify Console or API as JSON, CSV, Excel, XML, or RSS, and via the dataset's default table view for quick inspection.
💡 YouTube Video Scraper Strategy Guide
🎯 Strategy 1: Real-time enrichment pipeline
Trigger a run whenever a new video ID needs full metadata — a webhook, a form submission, or a queue consumer. Run the Actor with that video's title as a searchTerms keyword and maxVideoLookups set high enough to cover the batch. Append descriptionFull, publishedDate, and category to your CRM or content-management record, then write the enriched record back to your destination system.
🎯 Strategy 2: Scheduled monitoring and alerting
Set up an Apify schedule to re-run the same searchTerms hashtag daily or weekly. Diff each run's rows on id and viewCount against the previous run's dataset — a video that's new since yesterday, or whose viewCount jumped sharply, is the delta worth alerting a Slack channel or webhook about.
🎯 Strategy 3: Bulk dataset build
Feed a long list of hashtags or keywords as searchTerms in one run (or fan out one run per term for parallelism), set maxResults and maxVideoLookups to your target size, and export the combined dataset to CSV once every run finishes. Detail-lookup requests inside a single run are capped by the Actor's own maxVideoLookups value — no other concurrency limit is documented for this Actor.
Strategy comparison at a glance
| Strategy | Best for | Run pattern | Output format |
|---|---|---|---|
| Real-time enrichment | Filling in full descriptions/dates for records you already have IDs or titles for | Triggered, one run per event | JSON via API, written back to your system |
| Scheduled monitoring | Tracking a hashtag's view-count and upload trend over time | Apify schedule, recurring | Dataset diffed run-over-run |
| Bulk dataset build | Research or training datasets across many hashtags/keywords | Batch or fan-out runs | CSV/JSON export |
🌴 Related YouTube scrapers & tools
| Scraper | What it extracts |
|---|---|
| YouTube Channel Scraper With Comments | Full upload history and comment threads for a given channel |
| YouTube Most Replayed Scraper for Channels & Playlists | The highest-replay "most rewatched" moments inside a channel's or playlist's videos |
| TikTok Data Scraper: Comments, Replies & AI Sentiment | Comparable short-video metadata and comment data on TikTok |
| TikTok Trending Creator Insights Scraper Across Every Country | Trending short-video creator data on TikTok, for cross-platform comparison |
How to integrate this YouTube scraper with your stack
This Actor works with any language or tool that can make an HTTP request — the Apify API and the apify-client SDKs (Python, JavaScript) are the two most common paths.
Python
from apify_client import ApifyClientimport csvclient = ApifyClient("<YOUR_APIFY_TOKEN>")run_input = {"searchTerms": ["#chatgpt", "ai coding"],"maxResults": 50,"fetchExactDates": True,"maxVideoLookups": 200,}run = client.actor("<YOUR_USERNAME>/youtube-video-scraper-by-hashtag-with-full-descriptions").call(run_input=run_input)rows = list(client.dataset(run["defaultDatasetId"]).iterate_items())rows = [r for r in rows if not r.get("_accounting")]with open("youtube_videos.csv", "w", newline="", encoding="utf-8") as f:writer = csv.DictWriter(f, fieldnames=list(rows[0].keys()) if rows else [])writer.writeheader()writer.writerows(rows)print(f"Saved {len(rows)} video(s) to youtube_videos.csv")
Node.js
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('<YOUR_USERNAME>/youtube-video-scraper-by-hashtag-with-full-descriptions').call({searchTerms: ['#chatgpt', 'ai coding'],maxResults: 50,fetchExactDates: true,maxVideoLookups: 200,});const { items } = await client.dataset(run.defaultDatasetId).listItems();const videos = items.filter((item) => !item._accounting);console.log(`Fetched ${videos.length} video(s)`);
Async and scheduled pipelines
For large jobs, start the run via the API and poll the run status or the dataset's item count instead of waiting synchronously — the Apify API supports both. For recurring jobs, use an Apify schedule (Console → Schedules, or the Schedules API) to re-run the same input on a cron-style interval; each scheduled run writes to its own dataset that you fetch afterward.
🎯 Who needs this YouTube video scraper? (Use cases & industries)
🎥 Content strategists and creators
A creator researching a niche hashtag pulls the top videos for #chatgpt, sorts locally by viewCount and durationSeconds, and studies which title/duration combinations are landing views this month — all from one run's dataset.
📊 AI/ML and RAG engineers
Teams building a YouTube-grounded chatbot or research assistant index descriptionFull and title as retrievable passages, keyed by the stable id field, avoiding the truncated snippet a plain search scrape would leave in the index.
📱 Marketing and competitive intelligence teams
Agencies track a brand or campaign hashtag across weekly runs, watching viewCount deltas and searchTerm matches to see which creators are picking up a topic before it trends elsewhere.
🔬 Researchers
Academic and market researchers building a public dataset of hashtag-tagged video metadata use publishedDate, videoAgeDays, and category for time-series analysis — all sourced from publicly available search results, no private account data involved.
🎬 SaaS and product builders
Hashtag-tracking dashboards and content-monitoring tools use the typed rows directly as their backing data, relying on detailsFetched/detailsSource to flag which records have verified enrichment versus search-only fields.
Is it legal to scrape YouTube?
Yes — scraping publicly accessible YouTube pages is generally lawful; U.S. courts have held that scraping data a website makes available to the public does not violate the Computer Fraud and Abuse Act (hiQ Labs, Inc. v. LinkedIn Corp., 9th Cir., 2019, on remand 2022). That precedent concerns unauthorized-access law, not YouTube's own Terms of Service — running an automated scraper against YouTube may still be a breach of those terms, which is a contractual/civil matter between the user and YouTube, not a criminal one.
This Actor returns video and Shorts metadata: titles, descriptions, view counts, durations, upload dates, and each video's public channel display name. It does not collect email addresses, private account details, or any data behind a login. A channel's display name can identify an individual creator, so if you combine that name with other identifying data for a specific individual, ordinary data-protection principles (GDPR, CCPA, etc.) may apply to your downstream use — this Actor itself only reads what YouTube already serves on a public search page.
This scraper returns only publicly accessible data. What you do with that data is your responsibility — consult legal counsel for commercial applications involving personal data.
❓ Frequently asked questions
Does this YouTube scraper work without a YouTube account?
Yes. Every request goes to YouTube's public, keyless search and InnerTube endpoints — no login, cookies, or API key are used or required.
How does it handle YouTube's anti-scraping measures?
Every request (search page, continuation pages, and detail lookups) can be routed through Apify Proxy via the proxyConfiguration input. Requests that get a 403, 429, or 503 response are logged and retried up to three times with a short backoff before the scraper moves on; detail lookups additionally try three different YouTube client profiles (player, metadata, player-mobile) and keep whichever one actually answers.
Can I run this at scale without getting blocked?
The scraper paginates through YouTube's own continuation tokens until it hits maxResults, YouTube runs out of results, or three consecutive continuation pages add nothing new. Description/date lookups run with a concurrency of 5 requests at a time, capped overall by maxVideoLookups (max 2000 per run). No uptime or block-rate guarantee is published — using a residential proxy is the documented way to reduce refusals on the detail-lookup step.
How fresh is the data this scraper returns?
Live. Every run performs a fresh YouTube search and, when enabled, a fresh detail lookup per video — nothing is served from a cache.
Which fields work best for AI training and RAG indexing?
For RAG, index title and descriptionFull (the full, untruncated description) as the retrievable text, with category as a coarse topic filter when it's available. For training data, viewCount, durationSeconds/lengthSeconds, and publishedDate are typed and consistently structured across every row — no relative-time strings to parse.
Does this scraper collect personal data?
It collects public video metadata and each video's public channel display name — not email addresses, private profile fields, or anything behind a login. The Actor returns publicly available data; the lawful basis for storing and using any of it, including a channel name that identifies a specific person, sits with you as the user.
Does this scraper work with Claude, ChatGPT, and other AI agent tools?
It's not exposed through an MCP server, but any agent framework that can call the Apify API can invoke it as an HTTP endpoint. Every response is typed JSON — descriptionFull, title, and the rest of the row can be dropped straight into an LLM context window without any HTML parsing first.
What happens to videos beyond the description-lookup cap?
They're still returned and still counted toward maxResults, but with detailsFetched: false and every detail field null. The run log reports how many videos were "not attempted (lookup cap)" so a capped run is never mistaken for one where YouTube had nothing to give.
Does the scraper ever guess a duration, category, or upload date it can't confirm?
No. Any field the current network and client profile can't verify is left null rather than backfilled with a guess — durationSeconds only reflects a length YouTube actually reported, and publishedDate is only set from a real parsed date, never inferred from relative text like "4 months ago".
ℹ️ Disclaimer
This scraper 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.