# YouTube Search Scraper — Videos & Shorts by Keyword (`datafabrik/youtube-search`) Actor

Search YouTube by keyword and get every video or Short in result order: title, channel, views, duration, upload time, badges and snippet. YouTube's own filters: type, upload date, duration, 4K, live, subtitles, popularity. No browser, no API key.

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

## Pricing

from $0.40 / 1,000 search 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

### What is YouTube Search Scraper?

Search YouTube by keyword and get **every video or Short in result order** as clean JSON, CSV or Excel: **title, channel, views, duration, upload time, badges, description snippet and thumbnail**, with the **rank** of each video. Use **YouTube's own filters**: type, upload date, duration, 4K, live, subtitles, Creative Commons, popularity.

- 💲 **Pay per result**: from **$0.40 per 1,000 results** ($0.70 on the Free plan), plus Apify's minimum start fee of $0.00005 per run. Searches without results and failures are **free**.
- 🔎 **Many searches in one run**: up to 1,000 search terms, up to 1,000 results each.
- 🎛️ **The filters of YouTube's menu**: videos or Shorts, today / this week / this month / this year, under 3 / 3–20 / over 20 minutes, live, 4K, HD, subtitles, Creative Commons, 360°, VR180, 3D, HDR, location, and "Prioritize: popularity".
- 🌍 **Any country**: see what ranks in the US, the UK, France, India…
- ⚡ **No browser, no YouTube API key, no login, no quota**: light HTTP requests, five searches at a time.

### What data can you extract from YouTube search?

| Field | Example |
|---|---|
| Rank in the results | 1 |
| Title, link, video id | Python Tutorial for Beginners - Learn the basics in 20 Min |
| Type | video, short, live, upcoming |
| Channel name, id, handle, link, verified badge | Tech With Tim, @TechWithTim, ✔ |
| Views (number and text) | 68318 · "68,318 views" |
| People watching now (live streams) | 1204 |
| Upload time | "2 months ago" |
| Duration | "19:39" · 1179 s |
| Description snippet | "Never written a line of code before? …" |
| Badges | 4K, CC, New, LIVE |
| Thumbnail | https://i.ytimg.com/vi/… |

### What can you do with YouTube search data?

- **SEO and keyword research for YouTube**: see who ranks for your keywords, with what titles and durations, in each country.
- **Competitor and trend watch**: run the same searches every day with **Upload date: Today** and get the new videos of your niche.
- **Content ideas**: find the most viewed videos on a topic with **Prioritize: popularity**.
- **Influencer discovery**: list the channels that rank on your topics, then check them.
- **AI and research datasets**: collect video ids on a topic, then get their transcripts with our [YouTube Transcript Scraper](https://apify.com/datafabrik/youtube-transcripts).

### Why run it on Apify?

- **API**: every run is an API call, with ready clients for Python and JavaScript (see below).
- **Scheduling**: run your searches every hour, day or week without a server.
- **Integrations**: Google Sheets, Slack, Zapier, Make, n8n, webhooks and your own database.
- **AI agents**: available through Apify's MCP server, so Claude, ChatGPT or Cursor can call it.

### How does it work?

1. Each term in `queries` is searched on YouTube with the filters you chose (`contentType`, `sortBy`, `uploadDate`, `duration`, `features`) from the `country` you chose.
2. The Actor reads the result pages one after the other, like scrolling down, until it has `maxResultsPerQuery` videos or YouTube has no more.
3. Each video becomes one item with its `position` (rank). Shorts shelves, playlists and courses shown between results are not videos of the search and are left out.
4. Each search that returns nothing, fails or is cut by your cost limit leaves one free item with `status` and `note`, so nothing disappears silently. Only `ok` items are charged.

### How to scrape YouTube search results

1. Click **Try for free**.
2. Type one or more searches in **Search terms**, one per line.
3. Optional: set **Max results per search**, **Type**, **Upload date**, **Duration**, **Features**, **Prioritize** or **Country**.
4. Click **Start**. The results appear in the **Search results** view within seconds.
5. Download them as JSON, CSV or Excel, or connect an integration.

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `queries` | list of text | — (required) | Search terms, one per line (up to 1,000, 200 characters each). Duplicates are searched once. |
| `maxResultsPerQuery` | number | 50 | Videos to take from each search, 1 to 1,000. YouTube usually stops after a few hundred. |
| `contentType` | `videos`, `shorts`, `all` | `videos` | YouTube's type filter. `all` = no type filter: videos and Shorts of the result list. |
| `sortBy` | `relevance`, `popularity` | `relevance` | YouTube's "Prioritize" filter. |
| `uploadDate` | `any`, `today`, `week`, `month`, `year` | `any` | Only videos uploaded in that period. |
| `duration` | `any`, `under3`, `3to20`, `over20` | `any` | Only videos under 3 minutes, from 3 to 20 minutes, or over 20 minutes. |
| `features` | list | none | Only videos with all of: `live`, `4k`, `hd`, `subtitles`, `creativeCommons`, `360`, `vr180`, `3d`, `hdr`, `location`. |
| `country` | 2-letter code | `US` | Country YouTube searches from; it changes the ranking. Texts stay in English. |
| `maxConcurrency` | number | 5 | Searches run at the same time, 1 to 10. |

Example:

```json
{
  "queries": ["python tutorial", "learn javascript"],
  "maxResultsPerQuery": 100,
  "uploadDate": "month",
  "sortBy": "popularity",
  "country": "US"
}
```

### Output

One item per video, in the order of the results:

```json
{
  "status": "ok",
  "note": null,
  "query": "python tutorial",
  "position": 1,
  "videoId": "Js05B8Z1ivE",
  "url": "https://www.youtube.com/watch?v=Js05B8Z1ivE",
  "title": "Python Tutorial for Beginners - Learn the basics in 20 Min",
  "type": "video",
  "channelName": "Tech With Tim",
  "channelId": "UC4JX40jDee_tINbkjycV4Sg",
  "channelHandle": "@TechWithTim",
  "channelUrl": "https://www.youtube.com/channel/UC4JX40jDee_tINbkjycV4Sg",
  "isVerifiedChannel": true,
  "viewCount": 68318,
  "watchingCount": null,
  "viewCountText": "68,318 views",
  "publishedTimeText": "2 months ago",
  "durationText": "19:39",
  "durationSeconds": 1179,
  "descriptionSnippet": "Never written a line of code before? Start with this Python tutorial to learn the basics. …",
  "badges": ["4K", "CC"],
  "thumbnailUrl": "https://i.ytimg.com/vi/Js05B8Z1ivE/hq720.jpg?…",
  "searchUrl": "https://www.youtube.com/results?search_query=python%20tutorial&sp=EgIQAQ%3D%3D",
  "scrapedAt": "2026-10-16T18:00:00.000Z"
}
```

| Field | Meaning |
|---|---|
| `status` | `ok` (charged). Free: `no-results`, `failed`, `skipped-cost-limit` (one item per search, with `query` and `note`). |
| `note` | Why an item has no video; `null` when `status` is `ok`. |
| `query`, `position` | The search term and the rank of the video in its results (1 = first). |
| `videoId`, `url`, `title` | The video; `url` is a `/shorts/` link for a Short. |
| `type` | `video`, `short`, `live` (streaming now) or `upcoming` (scheduled). |
| `channelName`, `channelId`, `channelHandle`, `channelUrl`, `isVerifiedChannel` | The channel; for a joint video ("A and B"), the id is the first channel's. |
| `viewCount`, `viewCountText` | Views as a number and as written by YouTube; `viewCount` is `null` for a live stream in progress or a TV episode, and rounded for Shorts shown as a grid. |
| `watchingCount` | People watching now, for a live stream in progress. |
| `publishedTimeText` | Upload time as shown in search ("2 months ago"). |
| `durationText`, `durationSeconds` | Length; `null` for a live stream. |
| `descriptionSnippet` | Extract of the description shown under the result. |
| `badges` | `4K`, `CC`, `New`, `LIVE`… |
| `thumbnailUrl` | Largest thumbnail. |
| `searchUrl` | The same search on youtube.com, with the same filters, to check by eye. |
| `scrapedAt` | When the item was produced (UTC). |

The run summary (counts, errors, duration) is in the key-value store under `OUTPUT`.

### How much does it cost to scrape YouTube search results?

You pay **per result delivered**:

| Apify plan | Price per result | Per 1,000 results |
|---|---|---|
| Free | $0.0007 | $0.70 |
| Starter (Bronze) | $0.0005 | $0.50 |
| Scale (Silver) | $0.00045 | $0.45 |
| Business (Gold) and above | $0.0004 | $0.40 |

Plus Apify's start fee of $0.00005 per run. **Free**: searches without results, failures, and searches cut by your cost limit. The $5 monthly credit of Apify's Free plan covers about **7,000 results** a month. Set **Maximum cost per run** in the run options to cap a run: the Actor stops before going over it and never delivers more than it charged.

### Is it free to try?

Yes. Apify's Free plan gives $5 of credit every month, with no card needed.

### Use it from code

Python (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("datafabrik/youtube-search").call(run_input={
    "queries": ["python tutorial"],
    "maxResultsPerQuery": 100,
    "uploadDate": "week",
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item["status"] == "ok":
        print(item["position"], item["title"], item["viewCount"])
```

JavaScript (`npm install apify-client`):

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('datafabrik/youtube-search').call({ queries: ['lofi hip hop'], contentType: 'shorts' });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((i) => i.url));
```

### Limits

- YouTube search results give an **upload time like "2 months ago"**, not an exact date, and no like count. For the exact details and the full transcript of each video, pass the `url`s to our [YouTube Transcript Scraper](https://apify.com/datafabrik/youtube-transcripts).
- With **Type: Shorts**, YouTube often shows the Shorts as a grid that gives only the **title and rounded views** ("7.3M views" → 7300000): channel, upload time and duration are then `null`.
- YouTube usually stops a search after a few hundred results; the Actor then delivers what YouTube has.
- The filters are YouTube's own: a few results may not match exactly (a 4K filter result without the 4K badge, for example), as on youtube.com.
- Searches that YouTube shows only to signed-in users ("Confirm your age") come back as a free `no-results` item quoting YouTube's message: the Actor never signs in.
- Results are those of a visitor who is not logged in, in English, from `country`.
- Channel and playlist results are not included yet.

### FAQ

#### Is it legal to scrape YouTube search results?

The Actor reads public search results that YouTube shows to any visitor, without login. It does not collect personal data such as emails. You are responsible for how you use the data, in line with YouTube's terms; check with a lawyer for commercial reuse.

#### Why are the results a bit different from what I see on youtube.com?

YouTube personalises results for logged-in users and by location. The Actor searches as a visitor who is not logged in, from `country`. Open `searchUrl` in a private window to compare.

#### Can I get only the newest videos of a topic every day?

Yes: set `uploadDate` to `today` and schedule the run every day in Apify.

#### Do I need a YouTube API key or an account?

No, and there is no daily quota. The Actor uses the same public endpoints as YouTube's website.

#### Can you add a feature or build a custom scraper?

Yes: open an issue on the **Issues** tab, we answer within a day.

# Actor input Schema

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

What you would type in YouTube's search box, one search per line (up to 1,000, 200 characters each). Each search returns its videos in YouTube's order.

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

How many videos to take from each search (1 to 1,000). YouTube usually stops after a few hundred results: the run then delivers what YouTube has.

## `contentType` (type: `string`):

Videos (YouTube's "Videos" filter), Shorts only, or all: the unfiltered result list (videos and Shorts; playlists and Shorts shelves shown between results are left out).

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

YouTube's "Prioritize" filter: relevance (default) or popularity.

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

Only videos uploaded today, this week, this month or this year.

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

Only videos under 3 minutes, from 3 to 20 minutes, or over 20 minutes.

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

Only videos with all of these features, as in YouTube's filter menu: live, 4k, hd, subtitles, creativeCommons, 360, vr180, 3d, hdr, location.

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

2-letter code of the country YouTube searches from (US, GB, FR, DE, IN…). It changes which videos rank first. Texts stay in English.

## `maxConcurrency` (type: `integer`):

How many searches run at the same time (1 to 10). The default suits most runs.

## Actor input object example

```json
{
  "queries": [
    "python tutorial"
  ],
  "maxResultsPerQuery": 50,
  "contentType": "videos",
  "sortBy": "relevance",
  "uploadDate": "any",
  "duration": "any",
  "features": [],
  "country": "US",
  "maxConcurrency": 5
}
```

# Actor output Schema

## `results` (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": [
        "python tutorial"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("datafabrik/youtube-search").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": ["python tutorial"] }

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

```

## MCP server setup

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

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/D8gNZZ2aAlCAlCcI9/builds/mzaj0ZyBqgujpMXBb/openapi.json
