# YouTube Community Posts & Comments Scraper (`api-empire/youtube-community-posts-scraper`) Actor

YouTube Community Posts Scraper: Extract community posts and comments with authors, text, timestamps, likes, replies, media, and engagement data. Analyze audience feedback, discussions, content trends, and community activity for creator research, competitor analysis, and YouTube marketing.

- **URL**: https://apify.com/api-empire/youtube-community-posts-scraper.md
- **Developed by:** [API Empire](https://apify.com/api-empire) (community)
- **Categories:** Videos, Social media, Lead generation
- **Stats:** 4 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.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 Community Posts Scraper — Extract Posts, Comments & Polls

Scrape every public post on a YouTube channel's Community (Posts) tab — text updates, polls, images, and video shares — then optionally pull the comments on each post and detect the language of every post and comment. The scraper returns typed, normalized JSON: no HTML, no DOM selectors, no `ytInitialData` blobs to parse yourself. Point it at one or more channel URLs and it pages the whole feed for you, links every comment to its parent post, and hands back a dataset you can filter, join, or feed straight into an LLM pipeline. After reading this page you'll know exactly which fields ship on a post row versus a comment row, how billing works when comments are enabled, and where the scraper's real limits sit.

### What is the YouTube Community Posts & Comments Scraper?

It's an Apify Actor that reads the Community/Posts tab of any public YouTube channel and returns every post as a structured JSON row — then, if you turn it on, walks the comment thread under each post and returns those as linked child rows. No YouTube account, no Google Cloud project, and no API key are required to run it; it reads the same public HTML and internal InnerTube endpoints your browser uses when you visit a channel's Posts tab while logged out. It is a child-entity variant of a base community-post extractor: every base post field is preserved, and comment fetching plus offline language detection are layered on top.

- Scrape **community posts** — text updates, polls, image posts, and shared-video posts
- Scrape **comments** on any post, sorted newest-first or top-first
- Detect the **language** of post and comment text (ISO 639-1 code)
- Query **multiple channels** in a single run, each with its own post/comment pagination
- Export straight to **JSON, CSV, Excel, or XML** from the Apify dataset — no parsing step

### ⚙️ How the scraper reads the Community tab

The engine is hybrid: it first fetches `https://www.youtube.com/<handle>/posts` as plain HTML and extracts the `ytInitialData` JSON blob YouTube embeds in the page — the same payload the browser uses to render the first screen of posts. From there it reads `ytcfg` for the page's `INNERTUBE_API_KEY` and `INNERTUBE_CONTEXT`, and switches to YouTube's internal **InnerTube** `youtubei/v1/browse` endpoint to walk continuation tokens for every post past the first batch, until `maxPosts` is reached or the feed runs out. Comments work the same way one level down: fetching `https://www.youtube.com/post/<postId>` seeds a comment continuation token, which is then walked through the same `browse` endpoint, optionally switching to the "Newest first" or "Top" sort token if `sortCommentsBy` asks for the non-default order.

Every request — the initial HTML fetch and every `browse` call — goes through **curl\_cffi** with Chrome 131 TLS impersonation rather than a stock HTTP client, so the TLS fingerprint matches a real Chrome browser rather than a typical Python `requests` session. A response missing `ytInitialData` is treated as a soft-block signal (a data-less shell page) and triggers a retry with exponential backoff (starting at a short delay and doubling on each attempt) before the Actor escalates to the next proxy tier. This combination — HTML bootstrap, InnerTube continuation, browser TLS impersonation, and a soft-block guard — is what lets the Actor page through large post and comment feeds without a headless browser.

Resolving `channelId` reliably is harder than it looks, because YouTube renders channel pages with several different header layouts depending on the channel. The Actor tries, in order: the canonical `channelMetadataRenderer.externalId` field, then the legacy `c4TabbedHeaderRenderer.channelId`, then the newer `pageHeaderViewModel.channelId`, then the `urlCanonical` field inside `microformatDataRenderer`, and finally a scan of the playlist sidebar's browse endpoint for anything starting with `UC`. If every one of those is absent, `channelId` on the resulting rows falls back to whatever the post's own `authorEndpoint.browseId` reports, and only comes back empty if none of these six sources resolve.

### 📊 What data does the scraper collect?

The Actor returns two structurally different row types from the same run — community posts and their comments — plus the poll/image/video attachment data embedded in each post. That's the breadth this scraper covers in one pass:

| Data Type | Key Fields | JSON Field Names |
| --- | --- | --- |
| Community posts (text, poll, image, video-share) | author, post text, published time, like count, comment count, attachment type | `author`, `text`, `publishedTime`, `likeCount`, `commentCount`, `attachmentType` |
| Poll posts | poll question, answer choices | `text`, `pollChoices` |
| Image & video-share posts | image URLs, shared-video thumbnail | `images`, `videoThumbnail` |
| Comments (child rows) | commenter handle, comment text, like count, reply count, creator-heart flag | `author`, `text`, `likeCount`, `replyCount`, `isHearted` |
| Language tags (optional, on posts and comments) | ISO 639-1 language code | `detectedLanguage` |

#### Need more YouTube data?

If you need video-level rather than Community-tab data, API Empire publishes other YouTube Actors worth pairing with this one: a keyword-rank search scraper, a playlist scraper, a Shorts scraper that surfaces sponsor segments and sentiment/topic breakdowns, a video-details scraper for chapters and replay-heatmap peaks, and an influencer-tier finder for channel scoring. Run this Actor for the Community feed and one of those for the channel's actual video catalogue.

### How does it differ from the official YouTube Data API?

The YouTube Data API v3 has no resource for Community-tab posts at all. Its current reference (developers.google.com/youtube/v3/docs, checked 2026-07-25) lists Activities, Captions, Channels, ChannelSections, Comments, CommentThreads, PlaylistItems, Playlists, Search, Subscriptions, Videos, and a handful of others — there is no `communityPosts` or `posts` resource anywhere in it. `commentThreads.list`, the closest thing to a comments endpoint, is documented to cover comments on a **video or a channel**, not on a community post.

| Feature | YouTube Data API v3 | This Scraper |
| --- | --- | --- |
| Community-post access | Not available — no documented resource covers the Posts/Community tab | Reads any public Community/Posts tab directly |
| Comments on a community post | Not available — `commentThreads` only covers video/channel comments | Fetches comments per post, linked via `parentId` |
| Setup | Requires a Google Cloud project, an API key, and OAuth for write operations (per the API's getting-started guide) | No API key, no Google account — run it directly on Apify |
| Default rate budget | 10,000 units/day combined quota for most endpoints, 100 calls/day for `search.list` (per the API's getting-started guide, checked 2026-07-25) | No per-day unit quota built into the Actor itself; throughput depends on your Apify plan and proxy setup |
| Poll / image / video-share attachment data | Not exposed for community posts | Returned per post via `pollChoices`, `images`, `videoThumbnail`, `attachmentType` |
| Output shape | Per-resource JSON, varies by endpoint | One normalized row shape per post, one per comment, across every channel in the run |

Use the official API when you're building inside Google's own quota system and only need video, channel, or playlist data it actually exposes. Use this scraper when you need the Community tab specifically — the official API cannot reach it at any quota level.

### Why do developers and teams scrape YouTube community posts?

#### For AI engineers and agent builders

Community posts are unstructured, high-signal creator communication — announcements, polls testing audience sentiment, behind-the-scenes updates — that rarely shows up in a channel's video metadata. Pull `text`, `pollChoices`, and `detectedLanguage` from a channel's recent posts and comments and you have a clean corpus to index into a vector store for a fan-facing Q\&A agent, or to feed an agent tool that answers "what has this creator announced recently" without re-scraping HTML on every call.

#### For marketers and brand teams

Community posts are where creators test messaging before it goes into a video — polls in particular double as informal audience research. Track a shortlist of creator or brand channels, pull `pollChoices` and comment `likeCount`/`replyCount` on each post, and you get a lightweight signal of what resonates with an audience before committing budget to a sponsorship or a co-branded video.

#### For researchers and analysts

Because the scraper only returns publicly accessible posts and comments, it's suited to studying public discourse patterns — how creators use polls versus text updates, comment sentiment on announcements, or engagement trends across a cohort of channels — without needing platform-side data-sharing agreements. `publishedTime`, `likeCount`, and `commentCount` on posts give you a time-ordered engagement series per channel.

#### For developers building data products

Because output is one stable JSON shape per post and per comment across every channel you query, it's straightforward to run this Actor on a schedule and layer a dashboard, alerting system, or creator-monitoring product on top — new post detection, comment-volume spikes, or poll-result tracking — without maintaining your own YouTube HTML parser.

### How to scrape YouTube community posts (step by step)

1. Open the Actor's page in the Apify Store and click **Try for free** (or find it in your Apify Console under Actors if you've already added it).
2. Provide the required input: `channelUrls` — one or more channel URLs (`youtube.com/@Handle` or `youtube.com/channel/UC…`), one per line.
3. Set `maxPosts` for how many posts to pull per channel, and turn on `includeComments` and/or `detectPostLanguage` if you want comment threads and language tags.
4. Click **Start** and watch results land in the dataset in real time as each channel is processed.
5. Download the finished dataset as JSON, CSV, Excel, or XML — or pull it programmatically via the Apify API / `apify-client`.

#### What to do when YouTube changes its Community tab structure

The Actor is maintained against YouTube's current markup and InnerTube payload shapes, and the output schema — field names and types — stays stable on your end even when the underlying page structure changes. The code already handles one real structural change: YouTube renamed the "Community" tab to "Posts," and the extractor checks for both tab titles so existing integrations kept working through the rename. No specific turnaround time is promised for future changes.

### What changed in YouTube community-post scraping recently?

Two verifiable shifts matter here. First, YouTube renamed the Community tab to "Posts" as part of its Communities rollout (reported in trade coverage around February 2025), which broke any scraper hard-coded to look for a tab literally titled "Community" — this Actor checks both titles. Second, since 2024 YouTube has progressively rolled out BotGuard-issued Proof-of-Origin (PO) token enforcement across InnerTube endpoints, primarily targeting video-playback requests; unauthenticated automated clients that don't attest a token risk 403 responses or IP-level blocks on the endpoints where it's enforced (documented in yt-dlp's PO Token Guide). This Actor's browse/continuation calls for the Community feed and comment threads are not player-format requests, but the direction of travel across InnerTube generally is tighter anti-bot enforcement — which is why this Actor ships TLS impersonation, retries, and a multi-tier proxy fallback rather than a single bare HTTP client. Ongoing maintenance tracks these changes; no numeric SLA is attached to it.

### ⬇️ Input

The Actor accepts seven parameters, all defined in its input schema. `channelUrls` is the only required one.

| Parameter | Required | Type | Default | Constraints |
| --- | --- | --- | --- | --- |
| `channelUrls` | Yes | array of strings | — | `stringList` editor. One or more channel URLs, one per line (a comma-separated string is also accepted). Both `youtube.com/@Handle` and `youtube.com/channel/UC…` formats work. |
| `maxPosts` | No | integer | `10` | `minimum: 1`, `maximum: 10000`. Community posts to collect **per channel**. Comments, if enabled, are fetched in addition and do not count against this — only parent posts are counted. |
| `includeComments` | No | boolean | `false` | Fetches comments for every scraped post and pushes each as a child row linked via `parentId`. |
| `maxCommentsPerPost` | No | integer | `10` | `minimum: 1`, `maximum: 200`. How many comments to fetch per post when `includeComments` is on. Applied per post; the post count is unaffected. |
| `sortCommentsBy` | No | string (enum) | `"mostRecent"` | One of `"mostRecent"` (newest first) or `"mostPopular"` (top/most-liked first). |
| `detectPostLanguage` | No | boolean | `false` | Runs offline language detection on every post's (and comment's) text and adds `detectedLanguage`. Empty or emoji-only text yields `null`. |
| `proxy` | No | object | `{"useApifyProxy": false, "apifyProxyGroups": []}` | `proxy` editor. Optional — the Actor runs on a direct connection by default. Enable Apify Proxy or set a custom proxy URL for higher volume; see the Input pitfall note below for what happens on a block. |

No credential, API key, or login field exists anywhere in this schema — the Actor doesn't need one, and there's nothing here that requires masking as a secret.

**How the `proxy` fallback order actually works:** if you supply a `customProxyUrl`, it's tried first. If `useApifyProxy` is on with specific `apifyProxyGroups` selected, those groups are tried next, in the order listed (e.g. `DATACENTER` before `RESIDENTIAL` if you list both); if `useApifyProxy` is on with no group specified, the Actor tries Datacenter then Residential automatically. A direct connection (no proxy at all) is always appended as the final fallback, even if you configured a proxy — so a run never hard-fails purely because every configured proxy tier was rejected, though it may still fail for other reasons (e.g. a channel with no Community tab).

#### Example JSON input

```json
{
  "channelUrls": [
    "https://www.youtube.com/@MrBeast"
  ],
  "maxPosts": 25,
  "includeComments": true,
  "maxCommentsPerPost": 20,
  "sortCommentsBy": "mostPopular",
  "detectPostLanguage": true,
  "proxy": {
    "useApifyProxy": false,
    "apifyProxyGroups": []
  }
}
```

**Common pitfall:** pasting a URL that includes `/community`, `/about`, `/videos`, or `/posts` already appended is fine — the Actor strips those suffixes and rebuilds the Posts-tab URL itself — but pasting a video URL or a search-results URL instead of a channel URL will return zero posts, since there's no Community tab to read at that address.

### ⬆️ Output

Every row lands in the default dataset as flat, typed JSON — no nested HTML, no selectors. Post rows and comment rows are interleaved: each post is immediately followed by its comments (when `includeComments` is on), and every row carries `type` and `isChild` so you can tell them apart programmatically. **Post rows and comment rows carry different key sets** — a post row never has `commentId`, `authorChannelId`, `replyCount`, or `isHearted`; a comment row never has `authorUrl`, `commentCount`, `pollChoices`, `videoThumbnail`, or `attachmentType`. Filter on `type` before assuming a field exists.

When `includeComments` is on, comment rows are additionally mirrored to a per-run dataset named `<runId>-comments`, so you can pull just the comments for a given run without filtering the combined dataset.

#### Scraped community post (row shape)

```json
{
  "type": "post",
  "isChild": false,
  "parentId": null,
  "channelId": "UCX6OQ3DkcsbYNE6H8uQQuVA",
  "postId": "Ugkx7RvKumu_Ilo0oXu6kwVvtPmQsL5miLae",
  "author": "MrBeast",
  "authorUrl": "/@MrBeast",
  "text": "James Patterson and I wrote a book! Pre-order now…",
  "publishedTime": "3 weeks ago",
  "likeCount": "271K",
  "commentCount": "7K",
  "pollChoices": [],
  "videoThumbnail": null,
  "images": ["https://yt3.ggpht.com/example-image.jpg"],
  "attachmentType": "image",
  "sourceUrl": "https://www.youtube.com/@MrBeast",
  "detectedLanguage": "en"
}
```

- `type` (string) — always `"post"` for a community-post row.
- `isChild` (boolean) — always `false` for posts.
- `parentId` (null) — always `null` for posts; only comment rows carry a parent.
- `channelId` (string) — the channel's `UC…` ID, resolved from the page metadata.
- `postId` (string) — the unique community-post ID.
- `author` (string) — the channel's display name.
- `authorUrl` (string) — canonical channel path of the poster, e.g. `/@MrBeast`.
- `text` (string) — the full post text, including a poll's question text.
- `publishedTime` (string) — YouTube's relative timestamp as rendered, e.g. `"3 weeks ago"`.
- `likeCount` (string or null) — the like count **as YouTube's own abbreviated string** (e.g. `"271K"`), not a parsed integer; `null` when no likes are shown.
- `commentCount` (string or null) — the comment count as YouTube's abbreviated string (e.g. `"7K"`); `null` when absent.
- `pollChoices` (array of strings) — poll answer text; `[]` for non-poll posts.
- `videoThumbnail` (string or null) — largest available thumbnail URL for a shared video; `null` when the post has no shared video.
- `images` (array of strings) — attached image URLs; `[]` when the post has no images.
- `attachmentType` (string or null) — one of `"video"`, `"image"`, `"poll"`, or `null` for a plain text post.
- `sourceUrl` (string) — the exact channel URL you passed as input for that post.
- `detectedLanguage` (string or null) — present **only** when `detectPostLanguage` is `true`; the key is omitted entirely otherwise, not set to `null`.

#### A poll post looks like this

Poll posts use the same row shape as any other post — only `attachmentType`, `text`, and `pollChoices` change:

```json
{
  "type": "post",
  "isChild": false,
  "parentId": null,
  "channelId": "UCX6OQ3DkcsbYNE6H8uQQuVA",
  "postId": "UgkxAbCdEf1234567890ghijk",
  "author": "MrBeast",
  "authorUrl": "/@MrBeast",
  "text": "Which video should I post next?",
  "publishedTime": "5 days ago",
  "likeCount": "48K",
  "commentCount": "2.1K",
  "pollChoices": ["100 Girls vs 100 Guys", "Extreme Hide and Seek", "Squid Game IRL"],
  "videoThumbnail": null,
  "images": [],
  "attachmentType": "poll",
  "sourceUrl": "https://www.youtube.com/@MrBeast"
}
```

Individual vote percentages per choice are not returned — YouTube only reveals per-choice percentages to a signed-in viewer who has already voted, and this Actor runs without authentication, so only the poll question and its answer options are available.

#### Scraped comment (row shape, only when `includeComments` is on)

```json
{
  "type": "comment",
  "isChild": true,
  "parentId": "Ugkx7RvKumu_Ilo0oXu6kwVvtPmQsL5miLae",
  "channelId": "UCX6OQ3DkcsbYNE6H8uQQuVA",
  "postId": "Ugkx7RvKumu_Ilo0oXu6kwVvtPmQsL5miLae",
  "commentId": "UgzVEw6TwJFuNHsHOzh4AaABAg",
  "text": "MrBeast making books now? The side quests are getting crazy",
  "author": "@KarionJohnson_3",
  "authorChannelId": "UC5m7OG-AMr2QubKW5V3H5-Q",
  "likeCount": 2000,
  "replyCount": 17,
  "publishedTime": "3 weeks ago",
  "isHearted": true,
  "sourceUrl": "https://www.youtube.com/post/Ugkx7RvKumu_Ilo0oXu6kwVvtPmQsL5miLae",
  "detectedLanguage": "en"
}
```

- `type` (string) — always `"comment"`.
- `isChild` (boolean) — always `true`.
- `parentId` (string) — the `postId` of the post this comment belongs to.
- `channelId` (string or null) — the channel ID the parent post belongs to.
- `postId` (string) — same value as `parentId`, repeated for convenience when you're not joining on `parentId`.
- `commentId` (string) — the unique comment ID.
- `text` (string) — comment text.
- `author` (string) — the commenter's handle, e.g. `"@name"`.
- `authorChannelId` (string or null) — the commenter's channel ID, when YouTube exposes it.
- `likeCount` (integer) — comment like count, **parsed to a number** — note this differs in type from a post's `likeCount`, which stays as YouTube's raw abbreviated string.
- `replyCount` (integer) — number of replies under the comment.
- `publishedTime` (string) — the comment's relative timestamp.
- `isHearted` (boolean) — whether the channel owner hearted the comment.
- `sourceUrl` (string) — the `youtube.com/post/<postId>` page the comment was read from.
- `detectedLanguage` (string or null) — present only when `detectPostLanguage` is `true`.

### How does this scraper compare to other YouTube community post scrapers?

| Feature | This Scraper | Generic alternative |
| --- | --- | --- |
| Output format | Typed JSON, stable field names, ready for direct ingestion | Often raw HTML or inconsistent field naming that requires post-processing |
| Comment support | Comments are a first-class linked child entity (`parentId`, mirrored per-run dataset) | Frequently post-only, or comments bolted on as nested arrays inside the post row |
| Entity coverage per run | Posts and comments across multiple channels in a single run | Usually single-entity or single-channel per run |
| Anti-bot handling | Chrome-TLS-impersonated requests, retry/backoff, and an automatic custom → datacenter → residential → direct proxy escalation chain | Varies widely; some tools ship no fallback and fail outright on a block |
| Schema stability across a platform change | Actor already tolerates the Community → Posts tab rename by checking both titles | Depends entirely on the individual scraper's maintenance cadence |

If you're building an AI agent or RAG pipeline, the output-format row is the decision-maker — parsing HTML inside an agent loop is a reliability failure mode, not a feature, and every field this scraper returns is already typed and named consistently across post and comment rows.

### How many posts and comments can you scrape?

`maxPosts` is schema-capped at 10,000 per channel per run (default `10`) — there's no separate hard ceiling elsewhere in the code, so the practical limit is however many posts YouTube's Community feed actually serves for that channel. Pagination is automatic: the Actor reads the first page from the channel's HTML, then walks YouTube's InnerTube `browse` continuation endpoint until `maxPosts` is reached or the feed runs out of tokens. `maxCommentsPerPost` is capped at 200 (default `10`); internally the comment fetch stops after 12 continuation pages per post as a safety bound, reached well before 200 comments on most posts. lurkapi's YouTube Community Posts Scraper README (Apify Store, checked 2026-07-25) states YouTube itself serves "approximately 200 community posts" per channel with "no way to access older posts" — a platform-side ceiling reported by a third party, not measured by this Actor, but a useful expectation-setter if you set `maxPosts` well above what a channel's feed actually holds.

Both limits apply **per channel**, not per run: five `channelUrls` with `maxPosts: 100` targets up to 100 posts on each channel independently, not 100 split across all five. Channels process sequentially, and one channel failing doesn't stop the rest.

### Integrate the scraper and automate your workflow

This Actor works with any language or tool that can send an HTTP request — it runs on Apify's standard Actor API, so anything that can call `apify-client` or a REST endpoint can drive it.

#### REST API integration

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_API_TOKEN>")

run = client.actor("<YOUR_USERNAME>/youtube-community-posts-comments-scraper").call(
    run_input={
        "channelUrls": ["https://www.youtube.com/@MrBeast"],
        "maxPosts": 25,
        "includeComments": True,
        "detectPostLanguage": True,
    }
)

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["type"], item.get("postId") or item.get("commentId"), item["text"][:60])
```

Works in Python, Node.js, Go, Ruby, and cURL — same Actor ID, same input shape, same JSON rows back.

#### Automation platforms (n8n, Make, LangChain)

**n8n** — Apify's official n8n node can run any Actor on your account, including this one: configure it with your Apify API token and this Actor's ID, set the JSON input, and wire the output dataset into downstream nodes without writing a scraper yourself. **Make (Integromat)** — the Apify app module works the same way: pick "Run an Actor," select this Actor, map `channelUrls` and the other input fields to upstream data, and consume the dataset items in the next module. **LangChain** — since output is already typed JSON per post/comment, you can load a finished dataset straight into a `Document` loader or a custom tool without an HTML-parsing step in between; the agent gets clean `text`, `author`, and engagement fields instead of raw markup.

### Is it legal to scrape YouTube community posts and comments?

Scraping publicly accessible YouTube content is generally permissible; this Actor only returns posts and comments that are visible on a channel's public Community/Posts tab without logging in — it cannot and does not access private, members-only, or age-gated content. Comment rows do include personal identifiers — a commenter's public handle (`author`) and, when exposed, their channel ID (`authorChannelId`) — so if you store or process this data at scale, GDPR (for EU/UK data subjects) and CCPA (for California residents) considerations apply to how you retain and use that information, separate from whether the scrape itself is permitted. YouTube's own Terms of Service govern automated access to the platform. Consult legal counsel for commercial use cases involving bulk collection of personal data such as commenter identities.

### ❓ Frequently asked questions

#### Does the scraper work without a YouTube account?

Yes. There's no login field, API key, or OAuth step anywhere in the input schema — the Actor reads the same public HTML and InnerTube endpoints a logged-out browser sees on a channel's Posts tab.

#### How often is the scraped data updated?

Every run performs a live fetch against YouTube at the time you start it — there's no caching layer in the Actor, so the data reflects whatever posts, comments, and counts YouTube is serving at that moment, not a stored snapshot from a previous run.

#### What happens if a channel has no Community/Posts tab, or its posts aren't public?

You get zero rows for that channel, and the run log records a warning that no posts were found and to check the URL and that community posts are public — no charge is incurred for a channel that returns no posts, since billing is tied to each post row actually pushed.

#### What happens when comments are disabled on a specific post?

That post row is still returned in full; the Actor simply logs that no comment continuation token was found for that post (YouTube's own signal that comments may be off) and returns zero comment rows for it, without failing the rest of the run.

#### Can I scrape private or members-only community posts?

No. Only content visible on the public Posts tab without signing in is returned. Members-only posts and posts on channels with the Community tab disabled are not accessible to this Actor, the same way they aren't visible to a logged-out browser.

#### What does it cost to run, and is there a free trial?

The Actor runs on Apify's pay-per-event pricing, charging for each community post row it successfully returns; comment rows fetched via `includeComments` are pushed at no additional charge. Check the Actor's Pricing tab on the Apify Store for current rates — Apify's own account terms determine any trial credit, not this Actor's configuration.

#### Does it work for AI agent workflows and LLM pipelines?

Yes. Every response is typed, normalized JSON with stable field names — no HTML and no parsing step before you pass it into an LLM context window, a vector store, or an agent tool call. It's callable as a standard HTTP endpoint via `apify-client` from any agent framework.

#### How does it handle YouTube's anti-bot defenses, and do I need to manage proxies myself?

Requests go through `curl_cffi` with Chrome TLS impersonation rather than a plain HTTP client, with retry and exponential backoff on failure, plus a soft-block guard that detects a data-less response (missing `ytInitialData`) and escalates rather than returning empty data silently. You don't have to manage proxies — the Actor runs on a direct connection by default and only escalates through your configured proxy tiers (custom → Apify Datacenter → Apify Residential → direct) if a request is blocked.

#### How does it compare to other YouTube community post scrapers?

Scrapestorm's YouTube Community Posts Extractor (Apify Store listing, checked 2026-07-25) returns posts only, on a flat monthly subscription, with no comment-fetching or language-detection feature documented. lurkapi's YouTube Community Posts Scraper (same, checked 2026-07-25) offers comments, image download, and language detection as separately priced paid add-ons on top of a base per-post charge. This Actor returns posts and comments from the same run, with language detection as a plain input toggle rather than a metered add-on, and interleaves comment rows with their parent post automatically.

#### What happens when YouTube changes its structure or blocks the scraper?

The Actor is maintained, and the output schema — field names and types — doesn't change on your end even when YouTube's page markup does. It's already proven that in practice: when YouTube renamed the Community tab to "Posts," the extraction logic was updated to check both titles rather than breaking silently. No specific turnaround time is promised for future changes.

#### Can I filter posts by type (poll, image, video, text-only) before they're scraped?

No — there's no `postType` filter parameter in the input schema. Every post the channel's Community feed serves within your `maxPosts` limit is returned, regardless of type; use `attachmentType` client-side after the run to split posts into `"poll"`, `"image"`, `"video"`, or plain text (`null`). If your workflow only cares about one post type, filter the dataset on `attachmentType` rather than trying to restrict it at scrape time.

### 💬 Your feedback

Found a bug, or a field that doesn't match what YouTube is actually showing? We want to know. Reach out through the Actor's page on the Apify Store or open an issue via Apify's support channel — reports like this go straight into what gets fixed next.

# Actor input Schema

## `channelUrls` (type: `array`):

One or more YouTube channel URLs. Both formats work: youtube.com/@Handle or youtube.com/channel/UC…. One per line (a comma-separated string is also accepted). The channel's Community/Posts tab is read for each URL.

## `maxPosts` (type: `integer`):

Maximum number of community posts to collect per channel (1–10,000). Comments (if enabled) are fetched in addition to this and do NOT count against it — only parent posts are counted.

## `includeComments` (type: `boolean`):

Turn on to fetch comments for every scraped community post. Each comment becomes a child row linked to its post via parentId.

## `maxCommentsPerPost` (type: `integer`):

How many comments to fetch per post when comment fetching is on (1–200). Applied per post; the top-level post count is unaffected.

## `sortCommentsBy` (type: `string`):

Order in which comments are fetched: newest first, or the most-liked/top comments first.

## `detectPostLanguage` (type: `boolean`):

Run offline language detection on each post's (and comment's) text and add a detectedLanguage ISO code (e.g. en, es, fr). Empty or emoji-only text yields null.

## `proxy` (type: `object`):

Optional. Runs directly by default. Enable Apify Proxy or set a custom URL for higher volume. On a block the actor auto-escalates custom → datacenter → residential → direct.

## Actor input object example

```json
{
  "channelUrls": [
    "https://www.youtube.com/@MrBeast"
  ],
  "maxPosts": 10,
  "includeComments": false,
  "maxCommentsPerPost": 10,
  "sortCommentsBy": "mostRecent",
  "detectPostLanguage": false,
  "proxy": {
    "useApifyProxy": false,
    "apifyProxyGroups": []
  }
}
```

# Actor output Schema

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

All scraped items in the Actor's default dataset.

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

Aggregate run counters (total posts, total comments, successful/failed channels, last-processed URL, timestamp), refreshed after each channel.

# 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 = {
    "channelUrls": [
        "https://www.youtube.com/@MrBeast"
    ],
    "proxy": {
        "useApifyProxy": false,
        "apifyProxyGroups": []
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("api-empire/youtube-community-posts-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 = {
    "channelUrls": ["https://www.youtube.com/@MrBeast"],
    "proxy": {
        "useApifyProxy": False,
        "apifyProxyGroups": [],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("api-empire/youtube-community-posts-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 '{
  "channelUrls": [
    "https://www.youtube.com/@MrBeast"
  ],
  "proxy": {
    "useApifyProxy": false,
    "apifyProxyGroups": []
  }
}' |
apify call api-empire/youtube-community-posts-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,api-empire/youtube-community-posts-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/HXT81GHwbZOVM8Uwn/builds/IS65ga4xfzD6msjcL/openapi.json
