# YouTube Video Search Scraper (`lightmoon/youtube-video-search-scraper`) Actor

Search YouTube by keyword and export the results: title, channel, exact views, length, upload age, snippet and link, in YouTube's own ranking order. From $0.22 per 1,000 videos. Filter by upload date, length or 4K - YouTube applies them before it answers, so a filtered video is never charged.

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

## Pricing

from $0.22 / 1,000 videos

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 Video Search Scraper

Type the phrases you would type into YouTube's own search box and get the answer
as a table: title, channel, exact view count, length, upload age, the snippet
YouTube shows under the title, and the link — **in YouTube's own ranking order**,
with the rank stored on every row.

No account, no API key, no quota.

### What one row looks like

```json
{
  "query": "python tutorial",
  "rank": 1,
  "videoId": "x7X9w_GIm1s",
  "videoUrl": "https://www.youtube.com/watch?v=x7X9w_GIm1s",
  "title": "Python in 100 Seconds",
  "description": "Python is arguably the world's most popular programming language...",
  "channelName": "Fireship",
  "channelId": "UCsBjURrPoezykLs9EqgamOA",
  "channelUrl": "https://www.youtube.com/channel/UCsBjURrPoezykLs9EqgamOA",
  "viewCount": 3100447,
  "viewCountText": "3,100,447 views",
  "publishedText": "4 years ago",
  "durationText": "2:24",
  "durationSeconds": 144,
  "isLive": false,
  "badges": ["4K"],
  "thumbnailUrl": "https://i.ytimg.com/vi/x7X9w_GIm1s/hq720.jpg",
  "estimatedResults": 2238166,
  "searchUrl": "https://www.youtube.com/results?search_query=python+tutorial",
  "scrapedAt": "2026-09-24T12:03:41+00:00"
}
```

20 fields. Views in the dataset tab: **Results**, **Channels**, **Research**,
**All fields**.

### The limit that matters, stated up front

**YouTube never answers "no results".** Ask it for something that does not
exist and it will still hand back twenty videos — measured on 2026-09-24, the
phrase `qzxwvfoo nonsense query 12345` returned seventeen videos including Baby
Shark. The only thing that tells you the difference is YouTube's own count, and
it is on every row:

| phrase | `estimatedResults` |
|---|---|
| `python tutorial` | 2,441,088 |
| `python tutorial`, uploaded today | 2,480 |
| nonsense | 160 |

So: read `estimatedResults` before you trust the tail of a long answer. This
Actor does not pretend a padded answer is a match, and it does not hide the
number that gives it away.

### Filters, applied by YouTube before it answers

| filter | what it does |
|---|---|
| **Sort by** | relevance, upload date, view count, rating |
| **Uploaded within** | last hour, today, this week, month, year |
| **Video length** | under 4 minutes, 4 to 20, over 20 |
| **Must have** | HD, 4K, subtitles, Creative Commons, live, 360°, HDR |
| **Minimum views** | applied here, before the row is stored — a quiet video is never charged |
| **Skip live streams** | they have no length and no final view count |
| **Language / Country** | `hl` and `gl`; YouTube ranks differently per country |

These are YouTube's own filters, sent with the request, not a filter applied to
a page we already paid for. And because the filter blob is built rather than
copied, **the run re-reads its own answer to check the filter actually bit** and
reports it in the run summary. A filter that silently does nothing would bill
you for everything; this one says so instead.

### Input

| field | what it does |
|---|---|
| **Search phrases** | one per line, as you would type them |
| **Maximum videos in total** | ceiling for the whole run |
| **Maximum per phrase** | so one broad phrase cannot eat the run |

Each phrase is searched separately, and every row says which phrase it came
from and where it ranked in that answer.

### What it does not do

- **It does not return channels or playlists** — videos only, by default. That
  is what the ranking order is worth reading for.
- **It does not give exact numbers YouTube rounds.** `viewCount` is filled only
  when YouTube states an exact figure; when it only says `3.1M views`, the row
  keeps the text and leaves the number empty rather than inventing one.
- **It does not log in**, so it sees what a signed-out visitor sees.

### Free plan

Everything works on the free plan: the same fields, the same filters. Filtered
and dropped videos cost nothing there either.

# Actor input Schema

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

One per line, exactly as you would type them into YouTube's search box. Each phrase is searched separately and every row says which phrase it came from and where it ranked.

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

A ceiling for the whole run, across every phrase. Videos dropped by a filter are never charged.

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

So one broad phrase cannot eat the whole run. YouTube serves about twenty videos per page.

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

YouTube's own sort, applied by YouTube before it answers - not a re-sort of the page we got.

## `uploadedWithin` (type: `string`):

Narrows the answer at YouTube's end. Measured: the phrase python tutorial goes from 2.4 million results to 2 480 with Today.

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

Under 4 minutes, 4 to 20, or over 20 - YouTube's own three windows. The run re-reads the answer and reports whether the filter actually bit.

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

Extra conditions YouTube itself can filter on.

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

Drop videos under this many views before the row is stored, so a quiet video is never charged. A video whose count YouTube only rounds is kept.

## `skipLiveStreams` (type: `boolean`):

YouTube mixes live streams into an ordinary answer. They have no length and no final view count.

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

Two-letter code sent to YouTube as `hl`, e.g. en, de, es.

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

Two-letter code sent as `gl`. YouTube ranks its answer differently per country, so this changes the result.

## Actor input object example

```json
{
  "searchQueries": [
    "home coffee roasting",
    "indie game devlog"
  ],
  "maxResults": 40,
  "maxResultsPerQuery": 20,
  "sortBy": "relevance",
  "uploadedWithin": "any",
  "duration": "any",
  "features": [],
  "minViews": 0,
  "skipLiveStreams": false,
  "language": "en",
  "country": "US"
}
```

# Actor output Schema

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

No description

## `channels` (type: `string`):

No description

## `research` (type: `string`):

No description

## `all` (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 coffee roasting",
        "indie game devlog"
    ],
    "maxResults": 40,
    "maxResultsPerQuery": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("lightmoon/youtube-video-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 coffee roasting",
        "indie game devlog",
    ],
    "maxResults": 40,
    "maxResultsPerQuery": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("lightmoon/youtube-video-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 coffee roasting",
    "indie game devlog"
  ],
  "maxResults": 40,
  "maxResultsPerQuery": 20
}' |
apify call lightmoon/youtube-video-search-scraper --silent --output-dataset

```

## MCP server setup

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