# YouTube Search Scraper - Videos, Views, Channels (`angaba92/youtube-search-scraper`) Actor

Search YouTube for any keyword and get videos with title, views, publish date, duration, channel name/URL/ID, verified badge and description snippet. Filter by upload date, sort by views, min views. No API key. $1 per 1,000 videos.

- **URL**: https://apify.com/angaba92/youtube-search-scraper.md
- **Developed by:** [Andres Garcia-Baquero Leon](https://apify.com/angaba92) (community)
- **Categories:** Videos, Social media, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## YouTube Search Scraper - Videos, Views, Channels

Search YouTube keywords and export supported primary video results: **title, displayed views, relative publish date, duration, channel name, URL and ID, verified badge, description snippet and encounter order**. No user-supplied YouTube API key is needed. This Actor does not consume your YouTube Data API quota, but it is **not unlimited**: YouTube can block, throttle, change layouts or end pagination early.

**Price: $1 per 1,000 videos** ($0.001 per video). Videos filtered out by `minViews` are not charged.

### What is collected

Only `videoRenderer` cards in recognized primary search containers are parsed, including supported item sections, rich-item wrappers and continuation layouts. Recommendation shelves (such as “People also watched”), Shorts shelves, secondary columns and ad/promotion containers are excluded. A Short may appear if YouTube serves it as a regular primary video card. Unsupported primary layouts are reported as extraction failures, not as proof of no results.

`rank` is the **one-based encounter order of unique supported primary videos**, before the local `minViews` filter. It can have gaps after filtering. It is **not an exact YouTube SERP position or an SEO ranking measurement**: excluded cards do not count, and results can vary with region, time, source corrections and source behavior. Continuation requests use the WEB client with `hl=en` and `gl=US`; this is not a guarantee of localized search results.

Duplicates of a video ID within one query are delivered only once, including within a page. The same video appearing in two different queries is a separate query-result row and is charged in each query. Duplicate normalized input queries run once.

### Output example

Illustrative row (not a promise of current video statistics):

```json
{
  "searchQuery": "iphone 17 review",
  "rank": 1,
  "videoId": "rng_yUSwrgU",
  "url": "https://www.youtube.com/watch?v=rng_yUSwrgU",
  "title": "iPhone 17 Review: No Asterisks!",
  "viewsText": "6,811,291 views",
  "views": 6811291,
  "publishedText": "11 months ago",
  "duration": "11:20",
  "durationSeconds": 680,
  "isLive": false,
  "channelName": "Marques Brownlee",
  "channelId": "UCBJycsmduvYEL83R_U4JriQ",
  "channelUrl": "https://www.youtube.com/@mkbhd",
  "channelVerified": true,
  "descriptionSnippet": "...",
  "thumbnailUrl": "https://i.ytimg.com/vi/rng_yUSwrgU/hq720.jpg",
  "scrapedAt": "2026-09-24T00:00:00+00:00"
}
```

| Fields | Meaning / availability |
|---|---|
| `searchQuery`, `videoId`, `url` | Requested normalized query and stable video identity/link. |
| `rank`, `scrapedAt` | Unique primary encounter order and UTC extraction timestamp. |
| `title`, `descriptionSnippet` | Displayed text; null if absent. Not the full description. |
| `viewsText`, `views` | Displayed text and parsed count, potentially rounded or null. Live cards can show concurrent viewers instead of cumulative views. |
| `publishedText` | Source relative date, not a verified publication timestamp. Null if absent. |
| `duration`, `durationSeconds` | Displayed duration and parsed seconds; null if absent/live. |
| `isLive`, `channelVerified` | Inferred from the returned card, not independent verification. |
| `channelName`, `channelId`, `channelUrl`, `thumbnailUrl` | Card metadata, nullable. |

### Input

| Field | Description |
|---|---|
| searchQueries | Array of nonblank strings. Omit this field (`{}`) to run demo `iphone 17 review`. Explicit null, empty array, blank string or any blank/invalid array member fails without demo or result charges. The runtime also accepts one nonblank string. |
| maxResultsPerQuery | Integer 1–500, default 50. Requested delivery cap, **not a guarantee of 500 results**. Invalid values fail rather than being silently clamped. |
| uploadDate | any, hour, today, week, month, year; default any. Passed to YouTube, not independently verified against video publication timestamps. |
| sortBy | relevance, views, rating; default relevance. Only combinations in the table below are supported. |
| minViews | Nonnegative integer, default 0. Local filter on displayed/parsed views. With a positive threshold, missing counts are skipped. |

| Sort | Supported upload dates |
|---|---|
| relevance | any, hour, today, week, month, year |
| views | any, today, week, month, year (not hour) |
| rating | any only |

Unsupported combinations and unknown/null option values fail with a clear `OUTPUT` error **before any search or result charge**. They are never silently substituted. `effectiveFilters` records the accepted date, sort, minimum views, type=video and the exact `sp` sent; this describes requested source filters, not independent proof YouTube honored them.

```json
{
  "searchQueries": ["air fryer recipes"],
  "maxResultsPerQuery": 80,
  "uploadDate": "week",
  "sortBy": "views",
  "minViews": 1000
}
```

### Coverage and OUTPUT

Dataset rows are saved incrementally. The `OUTPUT` key-value record is written on normal completion and before propagating input, billing or extraction errors. It includes:

- `queries`, `scraped`, `perQuery`: normalized query count, actual delivered/charged row count and counts per query (including zero for unstarted queries).
- `demoUsed`, `effectiveFilters`: available after successful input validation.
- `status`: `complete`, `limited`, `partial` or `error`.
- `failed`: queries with extraction/request failures or safety truncation; `chargeLimitReached`: SDK budget stop.
- `queryResults[query]`: `scraped`, `pages`, `scannedVideos` (including repeated cards), `uniqueVideos`, `effectiveFilters`, `coverage`, `stopReason`, `error`.
- Top-level `error` and `errorType` when an exception propagates. Invalid input has no accepted per-query filters/results.

| coverage | stopReason | Interpretation |
|---|---|---|
| complete | exhausted | Supported primary results ended without another continuation. This does not mean every matching YouTube video was available. |
| limited | max\_results | Requested result cap reached; no claim of source exhaustion. |
| limited | charge\_limit | SDK budget limit reached; only its accepted prefix was delivered. |
| not\_started | charge\_limit / not\_started | Query not attempted due to budget or an earlier fatal error. |
| partial / unavailable | missing\_config | More results exist, but initial HTML lacked the key/client version required for continuation. |
| partial / unavailable | invalid\_initial\_json / invalid\_continuation\_json / invalid\_layout | Missing, malformed or unsupported source payload. |
| partial / unavailable | request\_failed | Request retries failed or access was blocked. |
| partial | repeated\_token / page\_limit / scan\_limit | Loop protection or safety ceiling prevented further coverage. |
| partial / unavailable | unexpected\_error | Unexpected parsing/runtime or billing error; run fails rather than hiding it. |

Pagination continues through empty or duplicate-only pages when a new token exists. Each query is capped at **100 pages and 1,000 scanned video cards**, including duplicates; unprocessed data or a remaining continuation at a ceiling is reported explicitly. A missing continuation after a recognized empty result list is a valid no-match outcome, not a reason to switch to demo data.

Partial rows survive later request/JSON failures, with diagnostics in `OUTPUT`. Such runs can succeed with `status=partial`; consumers must inspect coverage, not just platform status. If no rows were delivered and any query failed or hit a safety ceiling, the Actor fails after writing `OUTPUT`. Billing failures propagate even if earlier rows were delivered.

### Use cases

- Keyword/content research using supported primary cards and their displayed metrics.
- Trend discovery using week + views, subject to source ordering and approximate dates.
- Channel discovery; enrich returned channel IDs with a separate channel scraper.
- Scheduled brand/competitor keyword searches, with coverage checks on each run.

### Pricing

Pay per event: **$0.001 per video** plus the standard Apify actor-start fee. Delivery and the `video` event use one atomic SDK operation; its chargeable prefix determines both rows and counters. A run budget stops further delivery when the SDK signals its limit. Result-event tests do not imply zero total cost for empty/invalid runs or certify cloud pricing.

### FAQ / troubleshooting

**Can I always get 500 results?** No. 500 is an input cap, not a measured source guarantee. Filters, budget, upstream availability and safety ceilings can yield fewer. Check each query's `stopReason`.

**Does no API quota mean unlimited scraping?** No. No user API key/quota is required, but request limits, anti-bot checks and Actor budgets still apply.

**Can I track exact SEO ranks?** No. `rank` is supported unique-video encounter order only.

**Why are dates relative?** These are YouTube's display strings. Date filters are passed to YouTube; the Actor does not verify precise publication dates.

**Can I combine hour + views or week + rating?** No. Use a supported combination; unsupported combinations fail explicitly.

**Why did a successful run return fewer rows?** Inspect `OUTPUT.queryResults`: exhausted, requested cap, budget, extraction errors and safety stops are distinct. Retry transient failures; changed layouts may require a parser update.

### Local regression tests

From the Actor directory run `python -B -m unittest -v test_main` with its dependencies installed. The suite executes `main()` using synthetic HTTP and Actor/budget stubs and blocks socket connections. It verifies the local delivery contract, not current YouTube availability or real cloud billing.

Only public data is collected. Use it in compliance with YouTube's terms and applicable laws.

# Actor input Schema

## `searchQueries` (type: `array`):

Primary-video search queries. Omit this field for the demo iphone 17 review. Explicit null, empty arrays, blank strings or invalid members fail before searching; no demo substitution. Duplicate normalized queries run once.

## `maxResultsPerQuery` (type: `integer`):

Requested delivery cap (1–500), not a guarantee of source volume. Pagination also stops at 100 pages/1,000 scanned cards per query; OUTPUT reports coverage and stopReason.

## `uploadDate` (type: `string`):

Upload-date filter sent to YouTube; exact dates are not independently verified. All dates support relevance; hour does not support views; rating requires any. Unsupported combinations fail.

## `sortBy` (type: `string`):

Exact supported combinations only: relevance with all dates; views with any/today/week/month/year; rating with any only. Unsupported combinations fail, never fall back. OUTPUT records effectiveFilters.

## `minViews` (type: `integer`):

Local filter on parsed displayed views (may be rounded or concurrent viewers for live cards). Positive thresholds skip missing counts. Filtered cards are not delivered/charged but count toward the scan ceiling.

## Actor input object example

```json
{
  "searchQueries": [
    "iphone 17 review",
    "air fryer recipes"
  ],
  "maxResultsPerQuery": 50,
  "uploadDate": "any",
  "sortBy": "relevance",
  "minViews": 0
}
```

# Actor output Schema

## `results` (type: `string`):

Delivered supported primary videos; unique by videoId per query. Rank is encounter order, not exact SERP rank. Nullable metadata and approximate displayed counts are documented in README/dataset schema.

## `summary` (type: `string`):

OUTPUT: status, scraped, perQuery counts, failed, chargeLimitReached, effectiveFilters and queryResults. Per-query coverage/stopReason distinguish exhaustion, requested/budget limits, missing config, JSON/layout/request failures and safety ceilings. Written before propagating input/billing errors; consult README for the full contract.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchQueries": [
        "iphone 17 review",
        "air fryer recipes"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("angaba92/youtube-search-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "searchQueries": [
        "iphone 17 review",
        "air fryer recipes",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("angaba92/youtube-search-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchQueries": [
    "iphone 17 review",
    "air fryer recipes"
  ]
}' |
apify call angaba92/youtube-search-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,angaba92/youtube-search-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/wYaKyVr3tqd8v2CPv/builds/uNGrybdfMKHIvY5CA/openapi.json
