# YouTube Search Scraper — Keyword Results with Views (`dottti/youtube-search-scraper`) Actor

Search YouTube by keyword and export the results: video title, channel, views, duration and publish date. Sort by upload date, views or rating. No API key, no quota.

- **URL**: https://apify.com/dottti/youtube-search-scraper.md
- **Developed by:** [Mohanad Alshaka](https://apify.com/dottti) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 search result scrapeds

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 — Keyword Results with Views

Search YouTube by keyword and export the results as structured data: title, channel, view count, duration and publish date. No API key, no quota, no login.

Every row records the query that produced it, so one run can cover many keywords and stay sortable afterwards.

### Output

```json
{
  "query": "claude ai",
  "videoId": "r2vYObllqJU",
  "url": "https://www.youtube.com/watch?v=r2vYObllqJU",
  "title": "Claude AI Tutorial for Beginners (Step-by-Step)",
  "channelName": "Kevin Stratvert",
  "channelId": "UC...",
  "viewCount": 1058712,
  "viewCountText": "1,058,648 views",
  "lengthText": "8:37",
  "lengthSeconds": 517,
  "publishedText": "4 months ago",
  "publishedApprox": "2026-05-...",
  "description": "...",
  "isLive": false
}
```

### Paging is real, and it stops honestly

The first results page carries about 25 videos. Going deeper uses YouTube's internal continuation endpoint, which is refused from some datacenter IPs.

When that happens, paging **stops cleanly and keeps what it already collected**, logging how far it got. The query is not failed and the collected rows are still delivered. Verified live: 30 results per query over 2 pages.

### Blocked queries are never charged

YouTube refuses some datacenter IPs with "Sign in to confirm you're not a bot", returned as a normal 200 page with no results. Billing for that would charge you for this Actor's IP being refused, so those rows are written with `blocked: true` and `error: "blocked_by_youtube"` and **no event fires**.

Set `proxyConfiguration` to Apify Proxy with `RESIDENTIAL` groups to avoid it.

### Input

| Field | What it does |
| --- | --- |
| `searchQueries` | Keywords. Each runs separately; rows are tagged with their query. |
| `maxResultsPerQuery` | Cap per query, and therefore on cost. |
| `sort` | Relevance, upload date, view count or rating. |
| `uploadDate` | Last hour, today, this week, month or year. |
| `language` / `country` | Two-letter codes. Search results are regional. |

**One honest caveat on filters:** YouTube encodes sort and upload-date in the *same* URL parameter, so they cannot both apply. Setting `uploadDate` overrides `sort`. A test asserts this rather than letting it surprise you, and an unrecognised value falls back to no filter instead of producing a broken URL.

#### Example

```json
{
  "searchQueries": ["claude ai", "apify scraper"],
  "maxResultsPerQuery": 100,
  "uploadDate": "month"
}
```

### Notes and limits

- Public search results only. No login, no cookies, no Google account.
- View counts on search results are YouTube's displayed values and are exact for smaller videos, abbreviated for large ones. Both the number and the original string are returned.
- Publish dates are relative on this surface ("4 months ago"), so `publishedApprox` is derived and approximate by definition. The original text is kept.
- Paging is capped at 20 requests per query regardless of `maxResultsPerQuery`.

### Development

```bash
npm install
npm test
node src/main.js
```

# Actor input Schema

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

Keywords to search YouTube for. Each query is run separately and every row records which query produced it.

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

Hard cap per query, and therefore on cost. One page carries about 25 results; more are fetched by paging.

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

YouTube's own result ordering.

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

Restrict results to a recency window. Applying this overrides the sort filter, because YouTube encodes both in the same parameter.

## `language` (type: `string`):

Two-letter language code.

## `country` (type: `string`):

Two-letter country code. Search results are regional.

## `requestDelayMs` (type: `integer`):

YouTube throttles bursts. Raise this if the log shows 429 or bot checks.

## `maxRetries` (type: `integer`):

Retries with exponential backoff on 403, 429 and 5xx.

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

YouTube refuses some datacenter IPs with a bot check, and paging beyond the first page needs an endpoint that is refused more often. Blocked queries are never charged, but a residential proxy avoids the problem.

## Actor input object example

```json
{
  "searchQueries": [
    "claude ai"
  ],
  "maxResultsPerQuery": 50,
  "sort": "relevance",
  "uploadDate": "any",
  "language": "en",
  "country": "US",
  "requestDelayMs": 700,
  "maxRetries": 4,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Query, video ID and URL, title, channel name and ID, view count (numeric and as shown), duration, approximate publish date, description snippet, thumbnail and live flag.

## `runSummary` (type: `string`):

Per-query outcome: results delivered, pages fetched, renderer shape, and any query that failed or was blocked.

# 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": [
        "claude ai"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("dottti/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": ["claude ai"] }

# Run the Actor and wait for it to finish
run = client.actor("dottti/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": [
    "claude ai"
  ]
}' |
apify call dottti/youtube-search-scraper --silent --output-dataset

```

## MCP server setup

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