# YouTube Channel Scraper with Keyword Search (`scrapier/youtube-channel-scraper`) Actor

YouTube Channel Finder by Keyword Research: Discover relevant YouTube channels using targeted keywords and extract channel names, URLs, descriptions, subscribers, video counts, views, and metadata. Find creators, research niches, analyze competitors, and build targeted channel lists.

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

## Pricing

from $5.99 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#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 Channel Scraper — Search & Scrape Channels by Keyword

Find YouTube channels by keyword instead of by URL, then scrape every one of them across five surfaces in a single run: channel profile and About data, long-form videos, Shorts, live and past streams, and community posts. Every response is structured JSON — one row per item, ready to pass directly to an LLM, load into a spreadsheet, or feed a monitoring pipeline. Give it a search term like "home workout" or "ai news", a subscriber floor, and how deep to go per channel, and it handles the searching, filtering, and scraping itself.

***

### 🔎 What is the YouTube Channel Scraper (Channel Search)?

This Actor turns a keyword into a list of matching YouTube channels, then runs a full channel scrape on each one — channel profile, uploads, Shorts, live/past streams, and community posts — without you ever having to look up a channel URL first. Channel links, `@handles`, or `UC…` IDs can be supplied directly too, next to or instead of keywords, so a discovery run and a known-channel run can share one job. No YouTube account or login is required — everything scraped is public, logged-out data.

What sets keyword discovery apart from just pasting a URL is the provenance it keeps: every channel found by a search term carries the query, its rank in that search, and (if several keywords found the same channel) every keyword that matched it. That trail travels down to every video, Short, stream, and post row the channel produces, so a single row can always be traced back to the search that surfaced its channel.

- 🔎 **Channel search by keyword** — `searchQueries` runs YouTube's own channel search and pages through the results
- 👥 **Subscriber floor** — `minSubscribers` drops discovered channels below a threshold before they're ever scraped
- 🏠 **Channel profile & About data** — description, join date, location, lifetime views, verification badge
- 🎬 **Long-form videos, 📱 Shorts, 🔴 live/past streams** — each its own tab, each its own row type
- 💬 **Community posts** — text, image, poll, and video attachments
- 🌍 **Query controls** — search market (`searchMarket`), search order (`searchSortBy`), and Videos-tab sort order (`sortOrderVideos`)

***

### 📊 What data can you get with the YouTube Channel Scraper?

Five row types come out of a run, each identified by a stable `rowType`:

| Result type | Extracted fields | Primary use case |
| --- | --- | --- |
| `channel` | `channelDescription`, `channelJoinedDate`, `channelLocation`, `channelTotalVideos`, `channelTotalViews`, `isChannelVerified`, `availableTabs`, plus the discovery record | Creator profiling, outreach lists, subscriber tracking |
| `video` | `videoId`, `title`, `url`, `duration`, `viewCount`, `likesCount`, `publishedAt` | Content-gap analysis, upload-cadence tracking |
| `short` | Same fields as `video` | Shorts strategy and format research |
| `livestream` | Video fields plus `isLive`, `isUpcoming`, `concurrentViewersText` | Live-schedule and stream-performance tracking |
| `post` | `postId`, `postType`, `textContent`, `imageUrls`, `pollOptions`, `attachedVideoId`, `likesCount` | Community-engagement and announcement monitoring |

#### Keyword-driven channel discovery

Every channel found through `searchQueries` carries a full discovery record on its `channel` row: which keyword found it, its rank in that keyword's result list, the search page it came from, every keyword that matched it (`discoveryFoundByQueries`), the market the search ran in, and the raw fields YouTube's search response returned — title, handle, subscriber label, description snippet, avatar, verification badge. This is the one thing a plain "paste a channel URL" scraper cannot give you, because there is no channel URL yet when the run starts — only a keyword.

```json
{
  "rowType": "channel",
  "channelName": "GetFit Studio",
  "discoverySource": "keyword",
  "discoveryQuery": "home workout",
  "discoveryRank": 3,
  "discoveryPage": 1,
  "discoveryFoundByQueries": ["home workout", "bodyweight fitness"],
  "discoverySubscriberText": "2.81M subscribers",
  "discoverySubscriberCountApprox": 2810000,
  "discoveryMarket": "en-US"
}
```

#### Community posts

`post` rows cover text, image, poll, and video/playlist-attachment posts from the Community tab, each with `postType`, `textContent`, `likesCount`, `likesCountText`, and `commentsCountText`. Poll posts also carry `pollOptions` and `pollTotalVotesText`; video/playlist shares carry `attachedVideoId`. Channels with no Community tab return zero `post` rows rather than a guessed or empty placeholder row.

***

### 🆚 How does this Actor differ from the official YouTube Data API?

The YouTube Data API v3 is Google's public, official interface to YouTube, and it does cover channel search (`search.list`), channel metadata (`channels.list`), and video statistics (`videos.list`). It does not, as of the current resource list on developers.google.com (checked 2026-08-04), expose Community-tab posts at all — there is no resource for them in the official API.

| Feature | YouTube Data API v3 | This Actor |
| --- | --- | --- |
| Community posts | Not available — no API resource for the Community tab | Returned as `post` rows |
| Setup | Google Cloud project + API key (OAuth for some resources) | Runs from the Apify Console; no API key or Google account |
| Daily quota | Default 10,000 units/day; a `search.list` call costs 100 units — about 100 keyword searches/day — per Google's quota documentation (checked 2026-08-04) | No Google quota; usage is metered by Apify instead |
| Channel discovery | `search.list`, one HTTP call per page of results | `searchQueries`, with automatic pagination up to your `maxChannelsPerQuery`/`maxChannels` limits |
| Exact view/like counts | `videos.list` returns them directly | `deepItemDetails` returns them at one extra request per item |
| Output shape | Separate JSON per resource; a full channel profile needs several calls | One normalized JSON row per item, all five surfaces in one dataset |

The official API is the right choice for an application that needs a small, quota-tracked, Google-ToS-compliant volume of channel or video lookups. This Actor is the better fit for ad hoc or repeated bulk discovery and research jobs — especially ones that need community posts, which the official API cannot supply at any quota.

***

### 🧭 How to scrape YouTube channels with this Actor

1. Open the Actor's page in the Apify Console and click **Start**.
2. Enter your search terms in **Keywords to find channels** (`searchQueries`) — one per line — or paste known channel links, `@handles`, or `UC…` IDs into **YouTube channels** (`startUrls`).
3. Set the query controls that matter to your search: a **Subscriber floor** (`minSubscribers`), a **Search market** (`searchMarket`), and a **Channel search order** (`searchSortBy`).
4. Choose how deep to go per channel — toggle **Channel profile & About**, **Long-form videos**, **Shorts**, **Live & past streams**, and **Community posts**, and set **Items per section, per channel** (`maxResults`).
5. Click **Start** to run, then export the dataset as JSON, CSV, Excel, or any other format the Apify Console offers.

#### How to run multiple queries in one job

`searchQueries` is an array — add one keyword per line and the Actor discovers channels for each one in a single run, de-duplicating channels found by more than one keyword (their `discoveryFoundByQueries` lists every keyword that matched). `startUrls` can hold as many channel links as you like in the same run, and they're scraped alongside the keyword results without counting against `maxChannels`.

***

### ⬇️ Input

None of the 17 input fields are required — leave everything blank and the Actor uses its defaults, provided you supply at least one keyword or channel link (an empty run with neither raises an error naming what's missing).

| Parameter | Required | Type | Description |
| --- | --- | --- | --- |
| `searchQueries` | No | array | Search terms, one per line. Default `[]`. Leave empty to scrape only `startUrls`. |
| `maxChannelsPerQuery` | No | integer | Channels to keep per keyword. Default `5`, min `1`, max `500`. |
| `maxChannels` | No | integer | Hard cap on total channels discovered across all keywords. Default `10`, min `1`, max `1000`. Channels in `startUrls` don't count against it. |
| `minSubscribers` | No | integer | Drop discovered channels below this subscriber count. Default `0`, min `0`, max `1000000000`. |
| `searchSortBy` | No | string (enum) | `relevance` (default, YouTube's default match order) or `viewCount` (biggest channels first). |
| `searchMarket` | No | string | Language-COUNTRY the search runs in, e.g. `en-US`, `de-DE`, `pt-BR`. Default `en-US`. Channel pages themselves are always fetched in English. |
| `discoveryOnly` | No | boolean | Emit one `channel` row per discovered channel straight from the search response and fetch nothing else. Default `false`. |
| `deepItemDetails` | No | boolean | Open each video/Short/stream through YouTube's player endpoint for exact `viewCount`, `likesCount`, and `publishedAt`. Default `true`. Costs one extra request per item. |
| `startUrls` | No | array | Channel URLs (`https://www.youtube.com/@Handle` or `.../channel/UC…`), bare `@handles`, or bare `UC…` IDs, one per line. Default `[]`. |
| `maxResults` | No | integer | Items per enabled section (videos, Shorts, streams, posts), per channel. Default `10`, min `1`, max `1000`. |
| `sortOrderVideos` | No | string (enum) | `date` (default, newest first), `viewCount` (most viewed first), or `oldest` (first uploads first). |
| `scrapeChannelInfo` | No | boolean | Emit `channel` rows. Default `true`. |
| `scrapeVideos` | No | boolean | Emit `video` rows from the Videos tab. Default `true`. |
| `scrapeShorts` | No | boolean | Emit `short` rows. Default `true`. |
| `scrapeLiveStreams` | No | boolean | Emit `livestream` rows from the Live tab. Default `true`. |
| `scrapePosts` | No | boolean | Emit `post` rows from the Community tab. Default `true`. |
| `proxyConfiguration` | No | object | Apify Proxy settings, attached to every request. Default `{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "US"}`. |

```json
{
  "searchQueries": ["home workout", "kettlebell training"],
  "maxChannelsPerQuery": 10,
  "maxChannels": 15,
  "minSubscribers": 1000000,
  "searchSortBy": "relevance",
  "searchMarket": "en-US",
  "discoveryOnly": false,
  "deepItemDetails": true,
  "startUrls": ["https://www.youtube.com/@MrBeast"],
  "maxResults": 10,
  "sortOrderVideos": "date",
  "scrapeChannelInfo": true,
  "scrapeVideos": true,
  "scrapeShorts": true,
  "scrapeLiveStreams": true,
  "scrapePosts": true,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "US" }
}
```

⚠️ **`minSubscribers` only parses English subscriber labels.** YouTube publishes a rounded label such as "9.06M subscribers"; that's the only format this Actor's parser reads. If `searchMarket` is set to a non-English locale and a subscriber floor is active, every channel whose label comes back localized is dropped as unreadable rather than mis-parsed — the run log counts those separately from channels that genuinely fell below the floor. Set `minSubscribers` to `0` if you need a non-English market without losing channels to this.

⚠️ **Keyword discovery pages up to 25 search-result pages per keyword internally** (roughly 20 channels per page), a limit that is not exposed as an input. `maxChannelsPerQuery`'s own maximum of 500 sits right at that ceiling, so raising it further will not surface more channels for a single keyword — split the search into more specific keywords instead.

***

### ⬆️ Output

Every run produces one Apify dataset with typed, normalized JSON rows and a consistent schema across runs — the same keys appear on every row of a given `rowType`, `null` where a field isn't available rather than omitted. Export it from the Apify Console as JSON, CSV, Excel, XML, or HTML, or pull it with the API/SDK.

#### Scraped results

Every row carries `rowType`, `channelId`, `channelName`, `channelUsername`, `channelUrl`, `numberOfSubscribers`, `inputUrl`, `scrapedAt`, `order`, `title`, `url`, `viewCount`, `viewCountText`, `publishedTimeText`, and the discovery-provenance columns `discoverySource`, `discoveryQuery`, `discoveryRank`, `discoveryPage`.

`channel` rows additionally carry `channelDescription`, `channelJoinedDate`, `channelLocation`, `channelAvatarUrl`, `channelBannerUrl`, `channelDescriptionLinks`, `channelTotalVideos`, `channelTotalVideosText`, `channelTotalViews`, `isChannelVerified`, `availableTabs`, and the full discovery record: `discoveryFoundByQueries`, `discoveryChannelId`, `discoveryHandle`, `discoveryTitle`, `discoverySubscriberText`, `discoverySubscriberCountApprox`, `discoveryDescriptionSnippet`, `discoveryThumbnailUrl`, `discoveryIsVerified`, `discoveryMarket`, `discoveryCountry`, `discoveryLanguage`, `discoverySortBy`. On a `channel` row, `viewCountText` is repurposed to carry the channel's lifetime view count text, not a per-video figure.

`video`, `short`, and `livestream` rows additionally carry `videoId`, `duration`, `thumbnailUrl`, `likesCount`, `publishedAt`. `livestream` rows also carry `isLive`, `isUpcoming`, `concurrentViewersText`.

`post` rows additionally carry `postId`, `postType` (`text` / `image` / `poll` / `video` / `playlist` / `shared`), `textContent`, `imageUrls`, `pollOptions`, `pollTotalVotesText`, `attachedVideoId`, `likesCount`, `likesCountText`, `commentsCountText`.

```json
[
  {
    "rowType": "channel",
    "channelId": "UCa_JUG9hl8D7S6jH7nWTURA",
    "channelName": "GetFit Studio",
    "channelUsername": "@getfitstudio",
    "channelUrl": "https://www.youtube.com/@getfitstudio",
    "numberOfSubscribers": "2.81M subscribers",
    "channelDescription": "Home workouts for every fitness level.",
    "channelJoinedDate": "May 21, 2019",
    "channelLocation": "Australia",
    "channelTotalVideos": 929,
    "channelTotalViews": 404183563,
    "isChannelVerified": true,
    "availableTabs": ["Home", "Videos", "Shorts", "Playlists", "Posts"],
    "discoverySource": "keyword",
    "discoveryQuery": "home workout",
    "discoveryRank": 3,
    "discoveryPage": 1,
    "scrapedAt": "2026-08-04T09:12:04+00:00"
  },
  {
    "rowType": "video",
    "order": 1,
    "channelId": "UCa_JUG9hl8D7S6jH7nWTURA",
    "channelName": "GetFit Studio",
    "videoId": "g3_nLhSxFHE",
    "title": "10 Min Full-Body Home Workout - No Equipment",
    "url": "https://www.youtube.com/watch?v=g3_nLhSxFHE",
    "duration": "10:36",
    "viewCount": 594231,
    "viewCountText": "594K views",
    "likesCount": 15420,
    "publishedTimeText": "3 days ago",
    "publishedAt": "2026-08-01T08:34:22+00:00",
    "discoveryQuery": "home workout",
    "discoveryRank": 3,
    "scrapedAt": "2026-08-04T09:12:11+00:00"
  },
  {
    "rowType": "short",
    "order": 1,
    "channelId": "UCa_JUG9hl8D7S6jH7nWTURA",
    "channelName": "GetFit Studio",
    "videoId": "b7Yq2FghT0",
    "title": "30-second ab burner",
    "url": "https://www.youtube.com/shorts/b7Yq2FghT0",
    "duration": "0:31",
    "viewCount": 1203400,
    "viewCountText": "1.2M views",
    "publishedTimeText": "1 week ago",
    "scrapedAt": "2026-08-04T09:12:22+00:00"
  },
  {
    "rowType": "post",
    "order": 1,
    "channelId": "UCa_JUG9hl8D7S6jH7nWTURA",
    "channelName": "GetFit Studio",
    "postId": "UgkxAbCdEfGhIjKl",
    "postType": "poll",
    "title": "What should next week's program focus on?",
    "url": "https://www.youtube.com/post/UgkxAbCdEfGhIjKl",
    "pollOptions": ["Upper body", "Core", "Mobility"],
    "pollTotalVotesText": "4.2K votes",
    "likesCount": 812,
    "commentsCountText": "96 comments",
    "publishedTimeText": "2 days ago",
    "scrapedAt": "2026-08-04T09:12:30+00:00"
  }
]
```

***

### 💡 How can I use the data extracted with this Actor?

- 🎯 **Creator outreach and influencer discovery teams:** run a keyword against your niche, filter by `minSubscribers`, and build a shortlist from `channelName`, `channelUsername`, `numberOfSubscribers`, and `channelDescriptionLinks` without opening a single channel by hand.
- 📈 **YouTube SEO and content strategists:** pull `video`/`short` titles and `publishedTimeText` from competing channels found by keyword to spot content gaps and upload cadence in a niche.
- 🤖 **AI engineers and LLM developers:** issue a keyword query, get back structured JSON, and pass `channel` and `video` rows directly to a model as grounded context for a research or outreach agent.
- 📊 **Market and competitor researchers:** track `numberOfSubscribers`, `channelTotalViews`, and `discoveryRank` across a keyword set over time to measure share-of-voice shifts in a category.

***

### 📉 How do you monitor YouTube channels and their growth over time?

Monitoring here means running the same keyword set (or the same `startUrls`) on a schedule and diffing the results — a single snapshot only tells you where a channel stood the moment it was scraped. Because every row carries `scrapedAt`, and channel rows carry `numberOfSubscribers`, `channelTotalViews`, and `channelTotalVideos`, two runs of the same channel a week apart give you a growth delta without any extra tooling.

The fields worth diffing between runs: `numberOfSubscribers` and `channelTotalViews` for growth, `channelTotalVideos` for upload volume, and — specific to keyword discovery — `discoveryRank` and `discoveryFoundByQueries`, since YouTube's own channel search is not a stable ranking and a channel's position (or presence at all) in a keyword's results can shift between runs. `video`/`short` rows let you diff `viewCount` and `likesCount` on the same `videoId` to see how individual uploads are performing over time.

A typical workflow: schedule a run across your keyword set (via Apify's built-in Scheduler), compare each new dataset against the previous run's export keyed on `channelId` (and `videoId` for item-level rows), and alert when a subscriber count crosses a threshold or a channel drops out of a keyword's top results entirely.

***

### 🔌 Integrate this Actor and automate your workflow

This Actor works with any language or tool that can send an HTTP request, through the Apify API and the `apify-client` SDKs.

#### REST API with Python

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_API_TOKEN>")

run = client.actor("<username>/youtube-channel-scraper-channel-search").call(
    run_input={
        "searchQueries": ["home workout"],
        "maxChannelsPerQuery": 10,
        "minSubscribers": 100000,
    }
)

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["rowType"], item.get("channelName") or item.get("title"))
```

#### Scheduled monitoring and delivery

There is no built-in webhook push for individual events, but the Apify platform's **Schedules** let you run this Actor on a recurring cadence, and **webhooks** can fire on run completion (e.g. to notify a pipeline that a new dataset is ready). For continuous monitoring, schedule the run and pull the dataset with `apify-client` after each run finishes, or use a webhook to trigger that pull automatically.

***

### ⚖️ Is it legal to scrape YouTube channel data?

Yes — scraping publicly visible YouTube pages is generally lawful when you're only reading data any visitor can see without logging in, and this Actor returns only that: public channel, video, and community-post information. In *hiQ Labs v. LinkedIn Corp* (9th Cir. 2019), the court held that scraping publicly accessible data does not violate the Computer Fraud and Abuse Act, a precedent frequently cited for public-web scraping generally.

That said, channel names, handles, subscriber counts, and community-post text can identify a real person, so this data can qualify as personal data under GDPR (EU/UK) and the CCPA (California) even though it's public — those regimes govern how personal data is collected, stored, and reused, not just whether it was public when collected. YouTube's own Terms of Service also restrict automated access, which is a separate, contractual risk from the public-data legal question. Scraping for operational monitoring (outreach lists, competitor tracking) and scraping to build an AI training set carry different risk profiles under both regimes. Consult your legal team for commercial use cases involving bulk data storage.

***

### ❓ Frequently asked questions

#### Does the search market (`searchMarket`) change which channels I find?

Yes. `searchMarket` (e.g. `en-US`, `de-DE`, `pt-BR`) controls the language and country YouTube's channel search runs in, and different markets return different channels for the same keyword. Channel pages themselves are always fetched in English (`hl=en&gl=US`) regardless of `searchMarket`, so tab names and sort chips stay readable.

#### What does `sortOrderVideos` actually control?

It picks which of the Videos tab's own sort chips to request: `date` (newest first, the default), `viewCount` (most viewed first), or `oldest` (first uploads first). If a channel doesn't offer the Popular or Oldest chip, the Actor falls back to newest-first and the run log says so. Shorts, Live, and Community tabs have no sort chips on YouTube at all, so those three are always returned in YouTube's own order — there's no equivalent setting for them.

#### How does this Actor handle YouTube's anti-bot and consent walls?

Every request goes through Apify Proxy (residential by default, since datacenter IPs are more likely to trigger a consent or bot interstitial), and consent cookies are set automatically. If a channel page or an exact-count lookup comes back bot-walled and no rows have been pushed yet for that channel, the Actor retries once on a fresh proxy session before giving up and logging the channel as failed.

#### Does this Actor extract community posts?

Yes — `scrapePosts` (on by default) emits one `post` row per Community-tab post, including `postType`, `textContent`, `imageUrls`, `pollOptions`, and `attachedVideoId` for video/playlist shares. This is data the official YouTube Data API v3 does not expose at all, since it has no resource for the Community tab.

#### How many channels can one keyword return?

`maxChannelsPerQuery` caps channels kept per keyword (default 5, max 500), and `maxChannels` caps the total across all keywords in the run (default 10, max 1000). Internally, discovery pages through up to 25 of YouTube's own search-result pages per keyword (about 20 channels per page), which is the practical ceiling behind the 500 maximum.

#### How many videos, Shorts, streams, and posts does it return per channel?

`maxResults` (default 10, max 1000) applies independently to each enabled section — videos, Shorts, live streams, and posts — per channel. A channel with no Shorts, Live, or Community tab returns zero rows for that section rather than substituting anything.

#### How do I monitor a channel's subscriber count or ranking over time?

Schedule the same keyword set or channel list to run repeatedly (via Apify's Scheduler), and diff `numberOfSubscribers`, `channelTotalViews`, and — for keyword-discovered channels — `discoveryRank` between runs, keyed on `channelId`. YouTube's channel search ranking is not fixed, so `discoveryRank` reflects that run only, not a permanent position.

#### Does this Actor work with Claude, ChatGPT, or AI agent frameworks?

It's callable as an HTTP endpoint through the Apify API using any language or agent framework's HTTP client, or via the `apify-client` Python/JS SDKs — an agent can issue a keyword query, receive the structured JSON dataset, and use it as grounded context before generating an answer.

#### Can I use this Actor without managing proxies or a YouTube login?

Yes. Apify Proxy is configured by default (US residential) and attached to every request the Actor makes, and no YouTube account or login is required since everything scraped is public data. You only need an Apify account to run the Actor.

#### What happens if I don't provide any keywords or channel links?

The run fails immediately with an error telling you to provide at least one keyword in `searchQueries` or at least one channel in `startUrls` — the input schema marks both optional, but the Actor needs at least one of them to have anything to scrape.

***

### 💬 Your feedback

Found a bug, or a field that's missing or wrong? Open an issue on this Actor's Apify Store page, or reach out through Apify Console — reports help keep the scraper aligned with YouTube's current page structure.

# Actor input Schema

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

Search terms, one per line. Each one is sent to YouTube's channel search and the channels it returns are scraped. Leave empty to scrape only the channels listed below.

## `maxChannelsPerQuery` (type: `integer`):

How many channels to keep from each keyword's result list. The Actor pages through YouTube's channel search (about 20 results per page) until it has this many.

## `maxChannels` (type: `integer`):

Hard cap on how many channels the keywords may discover in total, across every keyword. Channels supplied in “YouTube channels” do not count against it.

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

Drop discovered channels whose subscriber label is below this number. YouTube publishes a rounded label (“9.06M subscribers”), so the floor is applied to that rounded figure; a channel whose label cannot be read is dropped too, and the run log counts both cases separately. 0 keeps every channel.

## `searchSortBy` (type: `string`):

Which of YouTube's own channel-search orderings to ask for.

## `searchMarket` (type: `string`):

Language and country the channel search runs in, written as language-COUNTRY (en-US, de-DE, pt-BR …). Different markets return different channels for the same keyword. Channel pages themselves are always read in English so tab names and sort chips stay readable.

## `discoveryOnly` (type: `boolean`):

Emit one channel row per discovered channel straight from the search response — id, name, @handle, subscriber label, avatar, verification, keyword and rank — and fetch nothing else. Fast and cheap for building a shortlist. About-tab fields (description, join date, country, banner, lifetime views) stay empty because the search response does not carry them.

## `deepItemDetails` (type: `boolean`):

Open each video, Short and stream through YouTube's own player endpoint to read its exact view count, like count and publish date. Costs one extra request per item. Turn it off for a fast, cheap listing — rows then carry only the abbreviated view text YouTube prints on the grid, and viewCount / likesCount / publishedAt stay empty.

## `startUrls` (type: `array`):

Channel URLs (https://www.youtube.com/@Handle or .../channel/UC…), bare @handles, or bare UC… channel IDs. One per line. These are scraped exactly like the keyword results, next to or instead of them.

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

Upper limit for each enabled section (videos, Shorts, live streams, posts) on each channel. With exact counts on, every video-type item costs one extra request, so keep this small for quick runs.

## `sortOrderVideos` (type: `string`):

Which of YouTube's own Videos-tab sort chips to use. Channels that do not offer Popular/Oldest chips fall back to newest-first, and the run log says so. YouTube publishes no sort chips at all on the Shorts, Live and Community tabs, so those three are always returned in YouTube's own order.

## `scrapeChannelInfo` (type: `boolean`):

Emit one channel row per channel: description, join date, country, subscriber text, lifetime view count, video count, avatar, banner, external links and verification badge — plus the keyword, page and rank that discovered it.

## `scrapeVideos` (type: `boolean`):

Emit one video row per upload from the Videos tab.

## `scrapeShorts` (type: `boolean`):

Emit one short row per Short. Channels without a Shorts tab return no Shorts rows.

## `scrapeLiveStreams` (type: `boolean`):

Emit one livestream row per item on the Live tab. Channels without a Live tab return no livestream rows — YouTube silently serves the Home tab there, and this Actor detects that instead of mislabelling home videos as streams.

## `scrapePosts` (type: `boolean`):

Emit one post row per community post, including image, poll and video attachments.

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

Apify Proxy is attached to every request the Actor makes. US residential is the most reliable option for YouTube; datacenter IPs are more likely to hit consent or bot interstitials.

## Actor input object example

```json
{
  "searchQueries": [
    "home workout"
  ],
  "maxChannelsPerQuery": 5,
  "maxChannels": 10,
  "minSubscribers": 0,
  "searchSortBy": "relevance",
  "searchMarket": "en-US",
  "discoveryOnly": false,
  "deepItemDetails": true,
  "startUrls": [
    "https://www.youtube.com/@MrBeast"
  ],
  "maxResults": 10,
  "sortOrderVideos": "date",
  "scrapeChannelInfo": true,
  "scrapeVideos": true,
  "scrapeShorts": true,
  "scrapeLiveStreams": true,
  "scrapePosts": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

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

All scraped items in the Actor's default dataset.

# 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 workout"
    ],
    "maxChannelsPerQuery": 5,
    "maxChannels": 10,
    "minSubscribers": 0,
    "searchSortBy": "relevance",
    "searchMarket": "en-US",
    "startUrls": [
        "https://www.youtube.com/@MrBeast"
    ],
    "maxResults": 10,
    "sortOrderVideos": "date",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapier/youtube-channel-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 workout"],
    "maxChannelsPerQuery": 5,
    "maxChannels": 10,
    "minSubscribers": 0,
    "searchSortBy": "relevance",
    "searchMarket": "en-US",
    "startUrls": ["https://www.youtube.com/@MrBeast"],
    "maxResults": 10,
    "sortOrderVideos": "date",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapier/youtube-channel-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 workout"
  ],
  "maxChannelsPerQuery": 5,
  "maxChannels": 10,
  "minSubscribers": 0,
  "searchSortBy": "relevance",
  "searchMarket": "en-US",
  "startUrls": [
    "https://www.youtube.com/@MrBeast"
  ],
  "maxResults": 10,
  "sortOrderVideos": "date",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call scrapier/youtube-channel-scraper --silent --output-dataset

```

## MCP server setup

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