# YouTube Search Scraper - Keyword to Video Rows, No Browser (`leekung125/youtube-search-scraper`) Actor

Search YouTube by keyword and get one row per result: title, channel, views, duration, publish date, URL and the rank it held. HTTP only, no headless browser. Charged per row delivered.

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

## Pricing

$0.50 / 1,000 video row delivereds

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 — a keyword in, ranked video rows out, no browser

Search YouTube the way a person would and get the results as data: **one row per video**, with the
**rank it held for that query**, title, channel, views, duration, publish date, thumbnail and URL.
Give it a plain search term, several terms, or a pasted YouTube results URL. Get JSON, CSV or Excel
back, or call it from the API, a schedule, an integration or an AI agent.

- **HTTP only — there is no headless browser in this Actor.** It talks to YouTube's own search
  endpoint through `yt-dlp`'s flat extractor. That is a design choice with a specific consequence,
  explained below.
- **Charged per row delivered.** A query that returns nothing costs nothing. A query that fails
  after its retries is written out as a row with a `status` and an `error`, and is not charged.
- **Rank is preserved.** Most tools hand back an unordered bag of videos. The position a video held
  for your query is the whole point if you are tracking visibility, so it is a first-class field.

### Use it for

- **Keyword and visibility research** — who ranks for the terms your audience actually searches,
  and at what position.
- **Competitor monitoring** — schedule it weekly on a set of terms and watch the ranking move.
- **Building a corpus** — search, then feed the `video_id`s straight into
  [`leekung125/youtube-transcript-scraper`](https://apify.com/leekung125/youtube-transcript-scraper)
  for the text, or
  [`leekung125/youtube-channel-transcripts`](https://apify.com/leekung125/youtube-channel-transcripts)
  for whole channels. They are built to chain.
- **Agent tooling** — one call turns a question into a list of real videos with metadata.

### Input

| field | what it does |
|---|---|
| `queries` | Search terms. Also accepts a pasted `youtube.com/results?search_query=…` URL, and tolerates a leading `search:` so inputs are portable from our channel Actor. |
| `maxResultsPerQuery` | Rows per term, up to 1000. Default 20. |
| `minViews` | Drop results below a view count. |
| `minDurationSeconds` / `maxDurationSeconds` | Length window. `61`+ excludes Shorts; `max 60` keeps only Shorts. |
| `skipLiveAndUpcoming` | Exclude live streams and premieres. |
| `country` / `language` | Two-letter country code and language hint to bias results. |
| `maxRetries` | Attempts before a query is reported failed. Default 3. |
| `proxyConfiguration` | Residential by default on the platform, which is what keeps results stable when an IP is rate-limited. |

⛔ **A missing field is never treated as a failed filter.** YouTube does not report a view count or
duration for every result. If a filter is set and the value is unknown, the row is **kept** — because
silently dropping most of a result set is worse than returning a row with a blank column.

### Output

One object per video:

```json
{
  "type": "video",
  "status": "ok",
  "query": "best budget espresso machine",
  "rank": 1,
  "video_id": "…",
  "url": "https://www.youtube.com/watch?v=…",
  "title": "…",
  "channel": "…",
  "channel_id": "…",
  "channel_url": "…",
  "view_count": 63582,
  "duration_seconds": 752,
  "published_at": "2026-04-11T00:00:00+00:00",
  "live_status": null,
  "thumbnail": "…"
}
```

Failed or empty queries appear as `{"type": "query", "query": …, "status": "error" | "no_results"}`.
A run summary is written to the key-value store under `SUMMARY`.

### Why "no browser" is in the title

It is the only reliability claim made anywhere on this page, and it is architectural rather than
promotional — you can verify it by reading `my_actor/main.py`.

Apify publishes 30-day run statistics for every public Actor. For the leader of the adjacent Google
Trends niche, those read: **22,102 runs, 62.4% succeeded**, and the single largest failure bucket is
**TIMED-OUT at 24.4%** — the signature of a headless browser dying under load. An Actor with no
browser in it cannot fail that way.

⛔ **What is deliberately NOT claimed:** any measured uptime advantage for this Actor. It is new, its
run count is small, and quoting that as evidence of reliability would be dishonest. The
architectural statement is checkable today; a statistical one will have to wait until there is a
real sample.

### What it does not do

It does not download videos, and it does not fetch transcripts. Transcripts are a separate product
because they are a separate job — see the two Actors linked above. Nothing here requires a YouTube
account, an API key or a quota.

# Actor input Schema

## `queries` (type: `array`):

What to search YouTube for. Plain terms work best. A pasted YouTube results URL is accepted too - the search\_query is read out of it. For portability, a leading "search:" is tolerated and ignored, so inputs can be moved between this Actor and leekung125/youtube-channel-transcripts unchanged.

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

Rows to return per search term, up to 1000. You are charged per row delivered.

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

Drop results below this view count. A result whose view count YouTube does not report is KEPT, never silently dropped.

## `minDurationSeconds` (type: `integer`):

Drop results shorter than this. Use 61 to exclude Shorts. Unknown durations are kept.

## `maxDurationSeconds` (type: `integer`):

Drop results longer than this. Use 60 to keep only Shorts. Unknown durations are kept.

## `skipLiveAndUpcoming` (type: `boolean`):

Exclude live streams, upcoming premieres and just-ended streams.

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

Two-letter country code to bias results, e.g. US, GB, DE. Leave empty for the default.

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

Interface language hint, e.g. en, de, es. Leave empty for the default.

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

Retries before a query is reported as failed. A failed query is never charged.

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

Residential proxy is used by default on the platform, which is what keeps results stable when an IP gets rate-limited.

## Actor input object example

```json
{
  "queries": [
    "cold plunge recovery",
    "best budget espresso machine"
  ],
  "maxResultsPerQuery": 20,
  "minViews": 0,
  "minDurationSeconds": 0,
  "maxDurationSeconds": 0,
  "skipLiveAndUpcoming": false,
  "maxRetries": 3,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `dataset` (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 = {
    "queries": [
        "cold plunge recovery",
        "best budget espresso machine"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("leekung125/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 = { "queries": [
        "cold plunge recovery",
        "best budget espresso machine",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("leekung125/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 '{
  "queries": [
    "cold plunge recovery",
    "best budget espresso machine"
  ]
}' |
apify call leekung125/youtube-search-scraper --silent --output-dataset

```

## MCP server setup

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