# YouTube Search Scraper — Videos by Keyword + Transcripts (`steadyfetch/youtube-search-scraper`) Actor

YouTube search scraper, priced per video, no login, no cookies, no API key: type a search phrase and get one row per video — title, link, channel, handle, views, published, length, thumbnail and rank. Turn on transcripts to add each video's captions as text.

- **URL**: https://apify.com/steadyfetch/youtube-search-scraper.md
- **Developed by:** [Steadyfetch Team](https://apify.com/steadyfetch) (community)
- **Categories:** Social media, Videos, SEO tools
- **Features:** Limited permissions, Multi-tenant, Server interface
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 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 Search Scraper — Videos by Keyword + Transcripts

**Type a search phrase and get one row per YouTube video: title, link, channel, handle, views, when it was published, length and thumbnail — and, if you want it, each video's transcript as plain text. No login, no cookies, no API key, no start fee — from $0.50 per 1,000 videos.**

Put one or more search phrases in the list ("sourdough bread", "how to start a podcast") and the actor returns the videos YouTube shows for each, in YouTube's own order, about 20 at a time. Built for researchers, content and SEO teams who want YouTube's results as data — and, with one switch, what is said in each video.

**Using an AI agent?** Pin this actor in Apify's MCP server with one link: `https://mcp.apify.com?tools=steadyfetch/youtube-search-scraper`

- **Actor id:** `steadyfetch/youtube-search-scraper`
- **Input:** `{ "searchQueries": ["sourdough bread"], "maxResults": 20, "transcripts": false }`
- **Cap the bill:** set `maxTotalChargeUsd` on the run (a run option, not Actor input), e.g. `1.00` — the run stops when it reaches it.
- **Price:** from $0.0005 per video ($0.001 on the Apify free plan); a transcript, when you turn them on, from $0.00225 each, only when real text lands. A page that brings fewer videos than $0.0055 is worth is billed one $0.0055 page check instead. Full table below.
- **How often it changes:** YouTube's results for a phrase move daily (new uploads, rising views). Re-run daily or weekly on the same searches; turn on "Only videos I don't have yet" to get just the new ones.

*Unofficial, not affiliated with YouTube.*

### What you get

One row per video:

| Column | What it holds |
|---|---|
| `videoId` · `videoUrl` | the video's id and its YouTube link |
| `title` | the video's title |
| `channelTitle` · `channelHandle` · `channelId` · `channelUrl` | the channel that published it |
| `views` | the view count YouTube shows, as a number |
| `publishedText` | when it was published, in YouTube's own words ("2 months ago") — YouTube's search shows no exact date, so none is made up |
| `durationSeconds` · `isShort` | its length in seconds, and whether it is a Short |
| `thumbnailUrl` | the video's thumbnail |
| `transcript` · `transcriptLanguage` · `transcriptSource` | with transcripts on: the transcript as plain text, its language, and whether it came from the uploader's captions or YouTube's automatic ones |
| `transcriptStatus` | `delivered`, or why there is none: `no_captions`, `no_speech`, `unavailable` (private, age-restricted, removed or region-blocked), `try_again`, `skipped_budget`, `skipped_deadline`, `not_requested` |
| `query` · `rank` · `searchPage` | what you searched, and the video's place in YouTube's list |
| `seenBefore` | `true` when your account received this video for this search before |

Every row also carries `charged`, `chargedEvents`, `status`, `retryable` and `statusReason`, so you can reconcile your bill from the dataset itself. The last row (`run_summary`) is the receipt.

### How to use it

1. Put search phrases in **Search phrases**, one per line, exactly as you would type them into YouTube — or paste a YouTube search results address (`youtube.com/results?search_query=…`) and its phrase is read for you. Up to 50 searches per run.
2. Set **Max videos per search**. Optionally limit by **Uploaded within** (today, this week, this month, this year).
3. Turn on **Add each video's transcript** if you want what is said in each video, and press Start.

Refused before anything is read, and never charged: a single video's link, a channel's link, any other address, and a value with no letters or numbers. **Have video links already?** Use [YouTube Transcript Scraper](https://apify.com/steadyfetch/youtube-transcript-scraper). **Want every video of a channel?** Use [YouTube Channel Transcripts](https://apify.com/steadyfetch/youtube-channel-transcripts).

One search is read to at most 500 videos (25 pages) per run; most lists end sooner.

### Pricing

Pay per result, all-inclusive (no platform usage on top), no start fee:

| Event | Apify Free plan | Bronze | Silver | Gold and up |
|---|---|---|---|---|
| Video (one delivered video) | $0.001 | $0.0008 | $0.0006 | $0.0005 |
| Transcript (only with transcripts on, only when text lands) | $0.005 | $0.003 | $0.0025 | $0.00225 |
| Page check | $0.0055 | $0.0055 | $0.0055 | $0.0055 |

That is $0.50 per 1,000 videos at Gold, and $2.75 per 1,000 videos with their transcripts.

**The page check.** Each page of about 20 videos is read whole, whatever it returns. A page bills **either** its videos **or** one check — whichever is more — never both. A full page of 20 videos at $0.0005 is $0.01 and pays no check. A page with fewer videos than one check is worth is billed one $0.0055 check on its own row, and its videos are not charged one by one (their transcripts still are). That includes **the page that ends a list**, a **short page in the middle** (YouTube sometimes shows only 10), and **a search YouTube finds nothing matching**: YouTube then shows unrelated videos instead, which are delivered as found, marked so, and billed only that one check — the next page of them is never read.

Examples at Gold: 20 videos of one search = $0.01. The same 20 with transcripts, 19 of which have captions = $0.01 + 19 × $0.00225 = $0.053. A search with 42 videos asked for 100 = its full pages per video, its short and last pages one check each.

### Good to know

- **Transcripts are captions.** The transcript is the video's captions — the uploader's own, or YouTube's automatic ones (most videos have them). A video with no captions, or one YouTube will not play without a signed-in account, gets no transcript and no transcript charge, and its row says why. "Preferred transcript language" picks a caption language when the video has it.
- **Transcripts take time:** 1–3 seconds a video on top of 2–3 seconds a page.
- **Re-runs return the list again, charged.** By default a re-run delivers every video with today's views, and charges it. A video your account had before is marked `seenBefore: true`.
- **Only new videos.** Turn "Only videos I don't have yet" on to skip, without charge, videos this actor delivered to your account for the same search in the last 90 days. A re-run then stops after 3 pages in a row with nothing new (`maxEmptyPages`, up to 25), so it costs at most 3 page checks ($0.0165).
- **Your memory lives in your account:** the list of videos delivered to you sits in a key-value store in your own Apify account (`youtube-search-account`). If the run's API token cannot open stores, the run still works, every video reads as new, and a row says so.
- **Shorts:** YouTube shows Shorts on their own shelf beside the results; this version delivers the videos list, and the receipt counts the Shorts it did not deliver.
- **A time limit ends the collecting, never the delivering**: rows already read are delivered, and the receipt says which limit stopped the run.
- **Paused for now:** if collection on this actor is paused, the rest of a search ships as one uncharged `try_again` row — run the same search again later.
- **Free plan:** on the Apify free plan a run reads up to 25 pages (about 500 videos) per 24 hours; the row says when it resets. Paid plans have no daily allowance.
- **Standby endpoint:** one search is also available as one HTTP request (`GET /?query=sourdough%20bread`), up to 40 videos a request (20 on the free plan), at the same prices. The endpoint answers the search only (transcripts come from a run) and does not use your account memory, so `seenBefore` is always empty there.
- **A bare Start** (nothing named) returns 10 dated sample videos for "sourdough bread" and charges nothing.

### Support

Questions, a search that returns something odd, or a column you need: open an issue on the Issues tab and it will be looked at.

If it earned its keep, a rating helps other buyers find it, and saving the actor keeps it one click away.

# Changelog

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

# Actor input Schema

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

One search phrase per line, exactly as you would type it into YouTube's search box ("sourdough bread", "how to start a podcast"), or a pasted YouTube search results address (youtube.com/results?search_query=…) — its phrase is read for you. YouTube answers every phrase with its own results, in its own order (rank). Up to 50 searches per run. Have single video links instead? The YouTube Transcript Scraper takes video links; a whole channel goes to YouTube Channel Transcripts.

## `searchType` (type: `string`):

Leave empty: a run searches videos, the only kind of search on this build.

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

The most videos to return for each search. A page carries about 20 videos; one search is read to at most 500 videos (25 pages) per run, and most lists end sooner. A page that brings fewer videos than one page check is worth is billed one check instead of per video.

## `transcripts` (type: `boolean`):

Off (default): search results only, priced per video. On: each video's transcript (its captions — the uploader's own, or YouTube's automatic ones) is added to its row as plain text, with its language, and charged once per video that returns real text. A video with no captions, or one YouTube will not play without a signed-in account, gets no transcript and no transcript charge — its row says why. Transcripts take 1–3 seconds a video.

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

A language code like "en" or "es". When a video has captions in this language they are used; otherwise its own caption language is. Leave empty to use each video's own.

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

Only videos uploaded within this window: today, this_week, this_month or this_year. Leave empty for any time. Any other value refuses the search (uncharged) and names the values that work.

## `onlyNew` (type: `boolean`):

Off (default): every run returns the search's videos with today's views, each charged, and a video your account was delivered before carries seenBefore: true. On: videos this actor already delivered to your account for the same search in the last 90 days are skipped and not charged, so a scheduled run returns only videos new to the list (a page of nothing new bills one page check).

## `maxEmptyPages` (type: `integer`):

With "Only videos I don't have yet" on: a search stops after this many pages in a row that bring no new video, so a re-run that finds nothing new costs at most this many page checks. Default 3; up to 25.

## `maxRunSeconds` (type: `integer`):

Stop collecting after this many seconds. Videos already collected are still delivered. Leave empty for no limit beyond the run timeout.

## Actor input object example

```json
{
  "searchType": "videos",
  "maxResults": 20,
  "transcripts": false,
  "onlyNew": false,
  "maxEmptyPages": 3
}
```

# Actor output Schema

## `rows` (type: `string`):

One row per video YouTube lists for each search you named: the search, its rank in YouTube's list, video id and link, title, channel id, name, handle and link, views, publishedText (YouTube's own words, like "2 months ago" — never a made-up date), length in seconds, isShort, thumbnail link, seenBefore, and — with transcripts on — the transcript text, its language and source (captions or automatic captions), with transcriptStatus saying why a video has none. Every row carries `charged`, `chargedEvents` and `status`, so the invoice reconciles from the dataset itself. A video row is charged once, as a `video`; its transcript is charged once more, as a `transcript`, only when real text landed on the row. A page that brings fewer videos than one page check is worth (a short page, the page that ends a list, a page of videos your account already had with onlyNew on, or a search YouTube finds nothing matching — it shows unrelated videos instead) is billed one `page-check` on its own row instead, and those rows say the video was not charged on its own.

## `misses` (type: `string`):

One uncharged row for each search that returned less than you asked for, saying why: `no_data` (YouTube lists fewer videos than your limit, or nothing matching the search), `skipped.all_repeat` (onlyNew on: several pages in a row held only videos your account already had — raise "maxEmptyPages" to look further), `skipped.page_cap` (one search is read to at most 25 pages per run), `input_error` (the entry is a video or channel link, not a search, or the upload window is not one of the four — the row names the fix), `feed_unavailable` (YouTube search did not answer — please re-run), `try_again` (collection on this actor is paused for now — run the same search again later), `limit_reached` (this run reached its collection limit — the row gives the next step), `skipped_max_items` / `skipped_budget` / `skipped_deadline` (your own limit, maximum charge or the run clock stopped it first) and `skipped.free_plan_allowance` (Apify free plan only: the plan's daily allowance on this actor; the row names the reset time).

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

The run's receipt: videos delivered, what was asked for, pages read, transcripts added and not added, videos delivered again from an earlier run, videos skipped because your account already had them, the charged events and what stopped the run. Uncharged.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("steadyfetch/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 = {}

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

```

## MCP server setup

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