# YouTube Search Scraper (`danthedataman/youtube-search-scraper`) Actor

Export YouTube keyword search results: videos, Shorts, channels or playlists. Apply YouTube's filters, language and country. Optional likes, comments and publish day for videos.

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

## Pricing

from $0.20 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 exports YouTube keyword search results as dataset rows: videos, Shorts, channels or playlists. Apply YouTube's filters, set a language and country, and optionally read each video's publication day and engagement counts.

### Try a search

The form prefills this example. API calls with omitted or empty `searchQueries` collect no rows and finish successfully with guidance.

```json
{"searchQueries": ["home espresso setup"], "proxyConfiguration": {"useApifyProxy": true}}
```

The example requests up to 20 video results. Change `maxResults` per query or choose another `resultType`. Collection follows result pages until that limit or until YouTube returns no more. Results can be partial when YouTube blocks a request, repeats a cursor or the 270-second collection budget ends. Check the run's SUMMARY and ERRORS records.

### Output

Each result carries `query`, `rank`, `page`, `type` and `scrapedAt` (UTC). Rank is the position among distinct results of the selected type before exact-date filtering; page starts at 1. The result identity comes from the source, and duplicates are removed within each query.

A source-derived excerpt from an espresso search:

```json
{
  "type": "video",
  "videoId": "wGK5x1K1BIg",
  "url": "https://www.youtube.com/watch?v=wGK5x1K1BIg",
  "title": "The BEST Espresso Setup Under $500… for YOU",
  "duration": 1179,
  "lengthText": "19:39"
}
```

Video rows include `videoId`, `url`, `title`, `channelName`, `channelId`, collaborator `channelIds`, `channelHandle`, `channelUrl`, `channelAvatar`, `isVerified`, `publishedTimeText`, `viewCount`, `viewCountText`, `duration`, `lengthText`, still-image `thumbnails`, nullable `richThumbnail` (the source's moving-preview URL), `descriptionSnippet`, `badges` and `isLive`. Duration is seconds. A source-absent duration stays null, including streams without a length. Relative publication text is kept as text; no date is derived from it.

Shorts supply their video identity, title, thumbnails and view label; channel and publication fields absent from the search tile remain null. Channel rows supply channel identity, name, handle, thumbnails, description snippet and the source subscriber label. Playlist rows supply playlist identity, title, owner information, thumbnails and the source video-count label where present.

With `details`, videos and Shorts add `publishedDate`, `publishedDatePrecision`, `likes`, `commentsCount`, `subscribers`, the full `description`, `hashtags` and `descriptionLinks`. Counts are integers, read from the source's labels at their stated precision. Count text and precision fields are `viewCountText`/`viewCountPrecision`, `likesText`/`likesPrecision`, `commentsCountText`/`commentsCountPrecision` and `subscribersText`/`subscribersPrecision`: `exact` or `abbreviated`. Abbreviated numbers are expanded estimates of the source label, never presented as exact. Unsupported or absent values stay null; `isVerified` is null when the source tile exposes no owner badge.

### Inputs

| Input | Type | Default | Meaning |
| --- | --- | --- | --- |
| `searchQueries` | array | No API default | Keyword batch. Form prefill: `["home espresso setup"]`. |
| `maxResults` | integer | `20` | Maximum delivered rows per query; `0` collects none. |
| `resultType` | string | `video` | `video`, `short`, `channel` or `playlist`. |
| `uploadDate` | string | `any` | `any`, `today`, `week`, `month` or `year`. |
| `duration` | string | `any` | `any`, `under3`, `3to20` or `over20`, in minutes. |
| `features` | array | `[]` | Any combination of `live`, `4k`, `hd`, `subtitles`, `creativeCommons`, `360`, `vr180`, `3d`, `hdr`, `location`, `purchased`. |
| `sort` | string | `relevance` | `relevance` or `popularity`. |
| `hl` | string | `en` | Search language code; for example `de`. |
| `gl` | string | `US` | Two-letter search country code; for example `DE`. |
| `details` | boolean | `false` | Read per-video publication and engagement details; adds requests. |
| `proxyConfiguration` | object | Automatic datacenter | Optional proxy settings. Form prefill enables Apify Proxy without an account-specific group. |
| `publishedAfter` | string | Unset | Inclusive source calendar-day lower bound, `YYYY-MM-DD`. |
| `publishedBefore` | string | Unset | Inclusive source calendar-day upper bound, `YYYY-MM-DD`. |

For example, combine `resultType: "video"`, `uploadDate: "month"`, `duration: "over20"` and `features: ["hd"]`. Filters use YouTube's semantics. Not every feature has an observable signal on a search row, especially 360, VR180, 3D, HDR, location and purchased. `popularity` is YouTube's relevance-dependent order; rows are not guaranteed to descend by view count.

### Exact calendar-date windows

YouTube sometimes returns rows outside a selected upload or duration bucket. The Actor skips rows only when their source label or duration proves the mismatch, counts them as `sourceFilterMismatches` per query in SUMMARY, and follows continuations to fill the requested limit. Skipped rows are not charged. Missing, localized or ambiguous publication labels remain unchanged; bucket filters cannot replace an exact source-date window.

Date bounds read publication days from video details, even when `details` is off, and attach the detail fields that response supplies. Extra comment-header requests are made only when `details` is on. Bounds are inclusive calendar days, not timestamp-precision bounds. Dates missing from the source cannot match. Set an `uploadDate` bucket that contains the window to reduce rejected candidates. There is no newest-first source order, so a narrow window can require reading many rejected candidates and may end with partial results. SUMMARY reports candidates, pages, delivered rows and the stop reason per query, including queries not attempted after a run-level stop.

Enrichment requests English date labels while preserving the search locale. A calendar day is emitted with `publishedDatePrecision: "day"`; no time of day is fabricated. Exact windows and details apply to videos and Shorts.

### Billing

Each delivered dataset row triggers one YouTube search result event. Basic and detailed rows have the same price: USD 0.80 per 1,000 results on FREE, USD 0.24 on BRONZE, USD 0.22 on SILVER, and USD 0.20 on GOLD, PLATINUM and DIAMOND. Each run also incurs a USD 0.00005 Actor Start charge, including runs with no result rows. Rows dropped for proven source upload/duration mismatches are not charged as results. If the run's maximum total charge is reached, collection stops before the next source read. Failed queries go to ERRORS and are not charged as results.

### Access and limits

The default path reads public, logged-out YouTube responses through automatic datacenter proxies or your proxy configuration. Search is its first source request, and the connection and session are reused for subsequent reads. If a new session's first request has no response after one second, the same request is sent once on a second fresh session and the first answer is used; SUMMARY counts these in `hedgedRequests`. Empty or zero-result input makes no source call. Blocks and connection failures fall back to a private browser service. In that service, search pages use datacenter egress and watch pages use residential egress (datacenter watch requests hit Google's unusual-traffic page). The service returns the first search page only; later pages of that query are read on the default path when it answers, otherwise the run reports partial coverage. If the service is unavailable, the query is recorded in ERRORS. Browser calls and response bytes are counted in SUMMARY.

Channel creation dates, total channel views, channel video counts and video keywords are not provided; they would require additional channel or player requests. Location, members-only status and paid-promotion flags are also not provided: the search and watch metadata read by this Actor does not identify them. A channel's Join button does not establish that a video is members-only.

YouTube's robots file disallows `/results` and `/youtubei/`. Its Terms restrict automated access except public search engines following robots rules or access with written permission. Login-gated data is excluded. The Actor downloads no comments, transcripts or media.

YouTube can remove or change fields, localize labels or return no matches for a filter combination. Source availability determines the row values. Use ERRORS and SUMMARY to distinguish exhausted, capped and unresolved queries. For help, include your run URL and input when opening an Actor issue.

# Changelog

This Actor's version history is a separate document: https://apify.com/danthedataman/youtube-search-scraper/changelog.md

# Actor input Schema

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

Keywords to search. The form prefill is an example; omitted or empty queries collect no rows.

## `maxResults` (type: `integer`):

Maximum delivered rows per query. Zero collects no rows. Continuations stop when reached, exhausted, blocked or the collection time budget ends.

## `resultType` (type: `string`):

Search for videos, Shorts, channels or playlists.

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

YouTube upload-date bucket. Exact calendar bounds are separate inputs.

## `duration` (type: `string`):

YouTube duration buckets: under 3, 3–20 or over 20 minutes.

## `features` (type: `array`):

Compose YouTube feature filters. These are the source's filter semantics; not every feature is exposed on result rows.

## `sort` (type: `string`):

YouTube relevance or popularity order. There is no newest-first order.

## `hl` (type: `string`):

YouTube search language code. Enrichment date labels are requested in English so source calendar dates can be parsed.

## `gl` (type: `string`):

Two-letter YouTube search country code; for example DE.

## `details` (type: `boolean`):

Read source publication day, likes, comments count, subscribers and full description for videos or Shorts. Adds requests; unsupported source values remain null.

## `proxyConfiguration` (type: `object`):

Apify automatic datacenter selection by default. Buyers may supply their proxy configuration; no account-specific group is required.

## `publishedAfter` (type: `string`):

Inclusive source calendar date, YYYY-MM-DD. Automatically reads video details and over-fetches candidates until enough matching rows, exhaustion or the time budget.

## `publishedBefore` (type: `string`):

Inclusive source calendar date, YYYY-MM-DD. No inferred timestamp or relative-age conversion. Videos and Shorts only.

## Actor input object example

```json
{
  "searchQueries": [
    "home espresso setup"
  ],
  "maxResults": 20,
  "resultType": "video",
  "uploadDate": "any",
  "duration": "any",
  "features": [],
  "sort": "relevance",
  "hl": "en",
  "gl": "US",
  "details": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

No description

## `errors` (type: `string`):

No description

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

No description

# 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": [
        "home espresso setup"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("danthedataman/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": ["home espresso setup"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("danthedataman/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": [
    "home espresso setup"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call danthedataman/youtube-search-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,danthedataman/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/mmm6l57neSlLerwLb/builds/nYL7I9ajJMWYGXsdY/openapi.json
