# YouTube Channel Search Scraper (`adaptable_wheelhouse/youtube-channel-search-scraper`) Actor

Find public YouTube channels from search terms or results URLs. Each row is a channel with its handle, subscriber and view counts, profile, and the search result that found it first. No API key needed.

- **URL**: https://apify.com/adaptable_wheelhouse/youtube-channel-search-scraper.md
- **Developed by:** [Mr T](https://apify.com/adaptable_wheelhouse) (community)
- **Categories:** Social media, Marketing, Lead generation
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 channels

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

Find public YouTube channels from keywords or from YouTube search-result URLs. Each saved row is one channel, kept from the first result that passed your filters, with the channel's public profile and the search hit that found it. No API key, no quota, no Google account.

**You only pay per channel saved.** Channels dropped by a filter, an exclusion, or a repeat match are not charged.

#### What you get for each channel

- **channel:** id, handle, name, URL, description, verified badge, family-safe flag
- **metrics:** subscribers, video count, total views (null when YouTube hides the count; large subscriber counts are rounded by YouTube)
- **profile:** country, join text, tags, tabs, public links, emails written in the profile text, available country codes, alternate URLs
- **images:** banner URL and avatar sizes
- **discoverySource:** the keyword or search URL, `video_results` or `channel_filter`, rank starting at 1, and the results URL
- **sourceVideo:** video id, title, published text, and view text when a video result found the channel; `null` for channel-only search

#### How to use it

1. Keep the sample keyword or add your own under **Search terms**. You can also upload a TXT or CSV file, or paste `https://www.youtube.com/results?search_query=…` URLs.
2. Choose video results, channel-only search, or both. With both, video results run first.
3. Set **Channels per source** and **Total channels**. Leave either one empty for no limit on that side.
4. Optionally set a subscriber range, a minimum video count, verified-only, or channels to exclude.
5. Run the Actor and open the dataset.

The same channel is saved once. A later keyword does not replace the first match.

##### Example input

```json
{
    "discoveryMode": "both",
    "searchTerms": ["coffee roasting"],
    "maxChannelsPerSearchTerm": 10,
    "maxTotalResults": 10,
    "verifiedOnly": false
}
```

##### Example row

```json
{
    "channel": {
        "id": "UCMb0O2CdPBNi-QqPk5T3gsQ",
        "handle": "@jameshoffmann",
        "title": "James Hoffmann",
        "url": "https://www.youtube.com/@jameshoffmann",
        "description": "…",
        "isVerified": true,
        "isFamilySafe": true
    },
    "metrics": { "subscribers": 2570000, "videos": 412, "totalViews": 399111475 },
    "profile": {
        "country": "United Kingdom",
        "joinedText": "Joined Dec 8, 2014",
        "tags": ["coffee", "james hoffmann", "jimseven"],
        "tabs": [{ "title": "Videos", "url": "https://www.youtube.com/@jameshoffmann/videos" }],
        "externalLinks": ["https://www.jameshoffmann.co.uk/contact-me"],
        "emails": [],
        "availableCountryCodes": ["GB", "US"],
        "alternateUrls": ["https://m.youtube.com/@jameshoffmann/about"]
    },
    "images": { "bannerUrl": null, "avatarImages": [{ "url": "https://yt3.googleusercontent.com/…", "width": 72, "height": 72 }] },
    "discoverySource": {
        "value": "coffee roasting",
        "discoverySurface": "video_results",
        "rank": 1,
        "sourceUrl": "https://www.youtube.com/results?search_query=coffee+roasting&hl=en&gl=US"
    },
    "sourceVideo": {
        "videoId": "N6BJVM5tvnw",
        "title": "Coffee Roasting Explained",
        "publishedText": "3 years ago",
        "viewText": "2,158,042 views"
    }
}
```

#### Good to know

- **Rank** is the position in that search's result list, starting at 1. Video results and channel-only search are numbered separately, and the numbers continue across pages. Filtered and excluded channels still take a number.
- **Channels per source** counts a channel once per results page. A channel removed by a filter or the exclusion list does not count the first time it shows up in that search; a later appearance does. A channel already saved earlier in the run counts, and is not saved again.
- **Hidden subscriber counts** are `null`. If you set a minimum or a maximum, channels with no public count are skipped.
- **Subscriber counts** can use K, M, B, lakh, or crore. YouTube rounds large counts.
- **Emails** are only the addresses written in the public profile text. The "sign in to see the email" button is not used.
- **Page language** and **Page country** default to `en` and `US`. They also replace the language and country on a pasted results URL.
- Two inputs that search for the same words are run once. The first one keeps the discovery value.

#### Proxies

The default Apify datacenter proxy is used. If YouTube starts blocking it during a run, the scraper switches to Apify **Residential** on its own.

#### Using it from code

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('adaptable_wheelhouse/youtube-channel-search-scraper').call({
    discoveryMode: 'both',
    searchTerms: ['coffee roasting'],
    maxChannelsPerSearchTerm: 10,
    maxTotalResults: 10,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

#### Is it legal to scrape YouTube?

This actor reads only public pages that anyone can see on youtube.com without logging in. Channel descriptions can contain personal data such as business email addresses, which is protected by laws such as GDPR. Only collect it if you have a legitimate reason, and check with a lawyer if you are unsure.

# Actor input Schema

## `discoveryMode` (type: `string`):

Video results, channel-only search, or both. With both, video results run first for each search term or search URL, and they can use up Channels per source before channel-only search runs.

## `searchTerms` (type: `array`):

Topics, products, or types of creators to search for on YouTube. Up to 500 terms, including terms from uploaded files.

## `searchTermsFiles` (type: `array`):

Up to 20 TXT or CSV files. A TXT file has one term per line. A CSV file uses a column named searchTerm, term, query, keyword, or keywords; without one of those, every cell is a term.

## `searchUrls` (type: `array`):

Public youtube.com/results URLs. The search words are kept. The URL's language, country, and filter are ignored; Page language, Page country, and Find channels by are used instead.

## `maxChannelsPerSearchTerm` (type: `integer`):

Stop each search term or search URL after this many channels. Leave it empty for no per-source limit. A channel counts once per results page, so it can count again on a later page. A channel dropped by a filter or the exclusion list does not count the first time it appears; a later appearance does. A channel already saved earlier in the run counts, but it is not saved again.

## `maxTotalResults` (type: `integer`):

Stop after saving this many channels in the whole run. Channels left out by filters do not count. Leave it empty for no total limit.

## `minSubscribers` (type: `integer`):

Save only channels at or above this public subscriber count. Channels with no public count are not saved.

## `maxSubscribers` (type: `integer`):

Save only channels at or below this public subscriber count. Channels with no public count are not saved.

## `minVideos` (type: `integer`):

Save only channels at or above this public video count. Channels with no public video count are not saved.

## `verifiedOnly` (type: `boolean`):

Save only channels that YouTube marks as verified.

## `excludedChannels` (type: `array`):

Channel URLs to leave out, such as https://www.youtube.com/@Google or https://www.youtube.com/channel/UCK8sQmJBp8GCxrOtXWBpyEA. Matching uses the channel id and handle, not only the URL text.

## `languageHint` (type: `string`):

Two-letter language code, such as en or de. YouTube uses it for page text, including channel About pages. Defaults to en.

## `countryHint` (type: `string`):

Two-letter country code, such as US or DE. YouTube uses it as the search country. Defaults to US.

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

The default Apify datacenter proxy is used for search and channel pages. If YouTube starts blocking it, the run switches to Residential automatically.

## Actor input object example

```json
{
  "discoveryMode": "both",
  "searchTerms": [
    "coffee roasting"
  ],
  "maxChannelsPerSearchTerm": 10,
  "maxTotalResults": 10,
  "verifiedOnly": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

One row per channel, saved on the first search result that passed the filters.

# 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 = {
    "discoveryMode": "both",
    "searchTerms": [
        "coffee roasting"
    ],
    "maxChannelsPerSearchTerm": 10,
    "maxTotalResults": 10,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("adaptable_wheelhouse/youtube-channel-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 = {
    "discoveryMode": "both",
    "searchTerms": ["coffee roasting"],
    "maxChannelsPerSearchTerm": 10,
    "maxTotalResults": 10,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("adaptable_wheelhouse/youtube-channel-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 '{
  "discoveryMode": "both",
  "searchTerms": [
    "coffee roasting"
  ],
  "maxChannelsPerSearchTerm": 10,
  "maxTotalResults": 10,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call adaptable_wheelhouse/youtube-channel-search-scraper --silent --output-dataset

```

## MCP server setup

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