# YouTube Search Scraper (`thenetaji/youtube-search-scraper`) Actor

Search YouTube by keyword, hashtag, Trending chart, Hype chart, or Home feed. Filter by content type, upload date, and duration, paginate automatically, and export structured video, channel, and playlist rows.

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

## Pricing

from $0.45 / 1,000 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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## YouTube Search Scraper

The Actor collects public YouTube listings from six different surfaces — keyword search, hashtag feeds, the regional Trending charts, the Hype chart, the localized Home feed, and search autocomplete — and returns them as structured rows. It follows YouTube's own pagination where pagination exists, and stops cleanly where it does not. No YouTube account, API key, or cookie is required.

### Selecting a workflow

`scraperType` decides what a run collects, and it is the only required field.

| Value | Collects | Needs |
|---|---|---|
| `search` | Keyword search results | `search_term` |
| `hashtag` | Videos or Shorts carrying a hashtag | `hashtag` |
| `trending` | A regional Trending chart | `region_code` |
| `hype` | The localized Hype chart | Nothing |
| `home` | The localized Home feed | Nothing |
| `suggestions` | Search autocomplete phrases | `search_term` |

Because one Actor covers six surfaces, every field is visible in every mode. Each field's description names the workflow it belongs to, and fields belonging to other workflows are ignored rather than rejected.

### Accepted input

| Field | Type | Default | Description |
|---|---|---|---|
| `scraperType` | enum | `search` | Required. Workflow to run. |
| `search_term` | string | — | Keyword search and Search suggestions. Suggestions also accepts partial text. |
| `hashtag` | string | — | Hashtag workflow. Text without the `#`. |
| `maxItems` | integer | `50` | Maximum rows saved. `0` continues to the last available page. |
| `resume_cursor` | string | — | Continue a previous run. Paste the resume token from that run's log. |
| `region_code` | string | `US` | Two-letter country code used to localize results. |
| `language_code` | string | `en` | Language code for result text, such as `en`, `es`, `pt-BR`. |

Keyword search only:

| Field | Type | Description |
|---|---|---|
| `result_kind` | enum | `video`, `shorts`, `channel`, `playlist`, `movie`. |
| `length_filter` | enum | `under_3_minutes`, `between_3_and_20_minutes`, `over_20_minutes`. |
| `published_within` | enum | `today`, `week`, `month`, `year`. |
| `sort_order` | enum | `relevance` or `popularity`. |

Hashtag and Trending:

| Field | Type | Default | Description |
|---|---|---|---|
| `content_kind` | enum | `all` | Hashtag workflow. `all` reads the hashtag's video feed, `shorts` reads its Shorts feed. |
| `trend_category` | enum | `now` | Trending workflow. `now`, `music`, or `games`. |

```json
{
  "scraperType": "search",
  "search_term": "indoor herb garden",
  "result_kind": "video",
  "published_within": "month",
  "sort_order": "popularity",
  "maxItems": 100,
  "region_code": "US"
}
```

### How much each surface returns

This is the single most useful thing to know before setting `maxItems`, because only three of the six surfaces paginate at all.

| Workflow | Paginates | Typical ceiling per run |
|---|---|---|
| Keyword search | Yes | Continues while YouTube serves pages |
| Home feed | Yes | Continues while YouTube serves pages |
| Hashtag, `content_kind: shorts` | Yes | Continues while YouTube serves pages |
| Hashtag, `content_kind: all` | No | One page, around 25 rows |
| Trending | No | One chart: about 49 rows for `now` and `games`, about 30 for `music` |
| Hype | No | One chart, about 100 rows |
| Search suggestions | No | One response, usually under 20 phrases |

Setting `maxItems` to 500 on Trending returns the chart and stops. That is the chart's real length, not a truncated run.

Page sizes are not fixed even where pagination works. Keyword search has returned 21 rows on a first page and 11 on the next, so `maxItems` is enforced on rows saved rather than on pages fetched.

### Response fields

One row per result. Fields present depend on the surface — Hype returns a compact row, keyword search returns the richest one.

| Field | Contents |
|---|---|
| `recordType` | Which workflow produced the row, such as `search_result` or `trending_result` |
| `source` | The query, hashtag, or workflow that produced the row |
| `type` | YouTube's own label: `video`, `shorts`, `channel`, `playlist` |
| `videoId` | Video or Shorts ID |
| `channelId`, `channelTitle`, `channelHandle` | Publishing channel |
| `title` | Public title |
| `url` | Canonical URL, derived from the identifier |
| `description` | Description snippet as shown in the listing |
| `thumbnail` | Thumbnail images, largest last |
| `viewCount`, `viewCountText` | Numeric and human-readable view counts |
| `lengthText` | Duration, such as `2:22` |
| `publishedTimeText`, `publishedAt` | Relative and ISO 8601 publication time |
| `isVerifiedChannel` | Whether YouTube shows a verified badge |
| `suggestion` | The suggested phrase, in the Search suggestions workflow |
| `position` | One-based position within the run |
| `meta` | Context for the listing, where YouTube supplies it |

```json
{
  "recordType": "trending_result",
  "type": "video",
  "videoId": "G6VHDpCtvLU",
  "title": "VonOff1700 - All Is (Official Video)",
  "channelTitle": "VonOff1700",
  "url": "https://www.youtube.com/watch?v=G6VHDpCtvLU",
  "viewCount": "82195",
  "likeCount": "6741",
  "commentCount": "385",
  "lengthText": "2:22",
  "publishedAt": "2026-07-29T19:00:38Z",
  "position": 1
}
```

YouTube does not return a URL on any of these surfaces — it returns bare identifiers — so `url` is derived from the row's own ID. Shorts get the `/shorts/` form, playlists the `?list=` form.

Trending is the only listing that carries `likeCount` and `commentCount`, because the chart is the only surface where YouTube publishes them alongside the listing.

### Behaviour on partial results

Promoted results are removed before rows are saved. YouTube injects advertisements into search and browse feeds marked `type: "ad"`; they are dropped, so they are neither saved nor billed.

A failing page is retried rather than abandoned: three attempts, waiting 3 and then 10 seconds. Three is deliberate rather than cautious — transient failures are already retried closer to the source before this Actor sees them, so a longer ladder here would multiply into a great many redundant requests without improving the odds. What it adds is patience, since the waits nearer the source are only seconds apart and do nothing for a source that is briefly refusing traffic.

Pages cannot be skipped. The cursor for the next page arrives inside the current page's response, so a page that never returns takes the rest of the walk with it; retrying it is the only way forward. When the retries are spent, collection ends, the rows already gathered are kept, and the log prints a resume token.

The exception is the first page: if that fails, the run fails. An empty run reported as successful would read as "nothing matched" when the truth is "the source could not be reached", so that case is surfaced as an error rather than an empty dataset.

### Resuming a run

Whenever a run stops with more results still available — because `maxItems` was reached, or because a page could not be fetched — the log ends with:

```
Resume cursor for the next run: EpUDEhJpbmRvb3IgaGVyYiBnYXJkZW4a_gJTQlND…
```

Paste that into `resume_cursor` on the next run, keeping the query and filters the same, and collection continues from that point instead of paying to re-collect rows you already have.

Two caveats, both YouTube's. The feed occasionally re-serves an item across a cursor boundary, so a resumed run can repeat one or two rows from the previous one — deduplicate on `videoId` if that matters. And cursors are tied to the query that produced them: reusing one against a different `search_term` or a different set of filters gives meaningless results rather than an error.

Trending, Hype, and the hashtag video feed are single pages with no cursor, so nothing is printed for them and there is nothing to resume.

### Frequently asked questions

**Why did Trending return 49 rows when `maxItems` was 500?**
The chart is 49 entries long. Trending, Hype, and the hashtag video feed are single pages that YouTube serves complete, with no cursor to follow. `maxItems` caps those runs but cannot extend them.

**Why do fields for other workflows still appear in the input form?**
One Actor covers six surfaces, and Apify input forms are static. Every field's description names the workflow it applies to; the rest are ignored at run time, so leaving them filled in causes no error.

**What is the difference between the two hashtag options?**
They are two separate YouTube feeds, not a filter. `content_kind: all` reads the hashtag's video feed, which comes back as a single page of about 25 rows. `content_kind: shorts` reads its Shorts feed, which paginates and returns considerably more. The two never mix.

**Does `region_code` change which results are returned?**
Yes, and substantially so for Trending, Hype, and Home, which are region-specific by definition. Keyword search is also localized. `language_code` is separate: it controls the language of the returned text, not which results are selected.

**Is a YouTube API key or account needed?**
No. Only publicly visible listings are read, and no credentials, cookies, or logged-in session are supplied. There is no YouTube Data API quota involved, so the 10,000-unit daily cap that applies to the official API does not apply here.

**Why is `description` empty on some rows?**
YouTube omits the description snippet on compact listings — Hype rows and hashtag Shorts rows carry little more than an ID, a title, and a view count. The video's full description is available from the [YouTube Video Scraper](https://apify.com/thenetaji/youtube-video-scraper).

**Can more than about 20 autocomplete phrases be returned?**
No. Search suggestions is one request returning YouTube's own suggestion list, which is short by design. `maxItems` can reduce it but not extend it.

### Related Actors

| Actor | Purpose |
|---|---|
| [YouTube Video Scraper](https://apify.com/thenetaji/youtube-video-scraper) | Full metadata and transcripts for known videos and Shorts |
| [YouTube Channel Scraper](https://apify.com/thenetaji/youtube-channel-scraper) | Channel details, videos, Shorts, playlists, and community posts |
| [YouTube Comments Scraper](https://apify.com/thenetaji/youtube-comments-scraper) | Comments from videos, Shorts, and community posts |
| [YouTube Playlist Scraper](https://apify.com/thenetaji/youtube-playlist-scraper) | Playlist metadata and every video in it |

# Actor input Schema

## `scraperType` (type: `string`):

Choose Keyword search, Hashtag, Trending, Hype, Home feed, or Search suggestions.

## `search_term` (type: `string`):

Used by Keyword search and Search suggestions. Words or a phrase to search for; Search suggestions also accepts partial text. Ignored by the other workflows.

## `hashtag` (type: `string`):

Used by the Hashtag workflow only. Hashtag text without the # symbol.

## `maxItems` (type: `integer`):

Maximum number of primary records to save across all targets. Set 0 to continue until no more pages remain.

## `resume_cursor` (type: `string`):

Continue a previous run instead of starting again. Paste the resume token printed near the end of that run's log. Leave empty to start from the first page.

## `result_kind` (type: `string`):

Keyword search only. Limit results to one public content type.

## `length_filter` (type: `string`):

Keyword search only. Keep videos within one duration range.

## `published_within` (type: `string`):

Keyword search only. Restrict results by upload period.

## `sort_order` (type: `string`):

Order the returned results. The available values depend on the workflow.

## `content_kind` (type: `string`):

Hashtag workflow only. Choose the hashtag's video feed or its Shorts feed.

## `trend_category` (type: `string`):

Trending workflow only. Choose which regional chart to collect.

## `region_code` (type: `string`):

Optional two-letter country code used to localize results, such as US, IN, or DE.

## `language_code` (type: `string`):

Optional language code used to localize result text, such as en, es, or pt-BR.

## Actor input object example

```json
{
  "scraperType": "search",
  "search_term": "indoor herb garden",
  "hashtag": "urbanphotography",
  "maxItems": 20,
  "content_kind": "all",
  "trend_category": "now",
  "region_code": "US",
  "language_code": "en"
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset containing all scraped data

# 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 = {
    "search_term": "indoor herb garden",
    "hashtag": "urbanphotography",
    "maxItems": 20,
    "region_code": "US"
};

// Run the Actor and wait for it to finish
const run = await client.actor("thenetaji/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 = {
    "search_term": "indoor herb garden",
    "hashtag": "urbanphotography",
    "maxItems": 20,
    "region_code": "US",
}

# Run the Actor and wait for it to finish
run = client.actor("thenetaji/youtube-search-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "search_term": "indoor herb garden",
  "hashtag": "urbanphotography",
  "maxItems": 20,
  "region_code": "US"
}' |
apify call thenetaji/youtube-search-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=thenetaji/youtube-search-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/bTQxzg5VrPSGaff91/builds/ltfGC79jOcoAnFedT/openapi.json
