# Telegram Channel Scraper (`apt_marble/telegram-channel-scraper`) Actor

Turn any public Telegram channel into clean, structured data. Every post with its full text, photos, videos, views, reactions, forwards and replies — the newest posts, a date range, or the whole archive. No Telegram account and no login. Export to CSV, Excel or JSON.

- **URL**: https://apify.com/apt\_marble/telegram-channel-scraper.md
- **Developed by:** [Hamza](https://apify.com/apt_marble) (community)
- **Categories:** Social media, News
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 post scrapeds

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/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

## Telegram Channel Scraper — Posts, Views & Reactions

Paste one or more public Telegram channels and get every post as a clean, structured row: the full text, the photos and videos attached to it, how many people saw it, every reaction with its count, whether it was forwarded or was a reply, and exactly when it was published. Collect just the newest posts, a date range, or the entire archive back to a channel's very first post. Nothing to install, no Telegram account, no login — paste, run, download.

### What you can do with it

- **Build a full archive of a channel** — every public post it has ever published, in one dataset you own.
- **Track competitors and industry channels** — see what they post, how often, and which posts actually land.
- **Measure engagement** — views, reaction counts and reaction mix per post, ready to average, rank or chart.
- **Feed a monitoring or research pipeline** — schedule a daily run and keep a rolling record of what a market is saying.
- **Collect media at scale** — photo and video links per post, with photo albums arriving as a single post carrying every image.
- **Filter to what matters** — restrict to a date range, or to posts in a channel that mention one keyword.

### What you get

One row per post. Abridged real example:

```json
{
  "channel": "durov",
  "channelTitle": "Pavel Durov",
  "channelId": -1006503122,
  "subscriberCount": 11400000,
  "subscriberCountIsExact": false,
  "messageId": 538,
  "date": "2026-08-04T15:20:04.000Z",
  "text": "Last night, Apple briefly removed Telegram from the App Store because a single user had planted illegal pornographic content in a public group chat. …",
  "textLength": 2151,
  "isEdited": true,
  "authorSignature": "Pavel Durov",
  "viewCount": 862000,
  "viewCountIsExact": false,
  "viewCountPublished": "862K",
  "reactionCount": 47860,
  "reactionKinds": 4,
  "reactions": [
    { "emoji": null, "customEmojiId": null, "isPaid": true, "count": 3750, "countIsExact": false },
    { "emoji": null, "customEmojiId": "5373223594484587136", "isPaid": false, "count": 25900, "countIsExact": false }
  ],
  "isReply": false,
  "replyToMessageId": null,
  "isForwarded": false,
  "mediaType": "text",
  "isAlbum": false,
  "photoCount": 0,
  "videoCount": 0,
  "hasUnviewableMedia": false,
  "hashtags": [],
  "mentions": [],
  "isServiceMessage": false,
  "matchedKeyword": null,
  "scrapedAt": "2026-08-05T09:14:22.000Z"
}
```

Export as JSON, CSV or Excel, or pull the dataset straight into your own tools.

### Input reference

Every setting below appears in the actor's form, labelled exactly as shown.

| Setting | Type | Default | What it does |
| --- | --- | --- | --- |
| Telegram channels | list of strings | — | The public channels to collect. A channel link or an `@username`, one per line. **Required.** |
| Posts per channel | integer | `100` | How many posts to take from each channel, newest first. Raise it to build a full archive; the actor keeps going back until a channel runs out of published history. Maximum 20,000. |
| Keyword filter | string | empty | Only keep posts in each channel that mention this word. Leave it empty for the full feed. |
| Posted after | date | empty | Only keep posts published on or after this date. |
| Posted before | date | empty | Only keep posts published on or before this date. |
| Newest posts first | boolean | `true` | Write the newest post first. Turn it off for oldest-first. |
| Include channel notices | boolean | `false` | Include Telegram's own channel notices, such as "Channel created" or a pinned-post announcement. |
| Fast mode for deep archives | boolean | `true` | Collect large archives faster. Switched off automatically when a keyword or a "posted after" date is set. |
| Browse from | select | `United States` | The country the run browses from. A few channels are restricted in some countries, so this can change what a channel returns. |
| Parallel workers | integer | `16` | How much work runs at the same time. Maximum 32. |

### Output fields

| Field | Type | Description |
| --- | --- | --- |
| `channel` | string | The channel's `@username`, without the `@`. |
| `channelTitle` | string | The channel's display name. |
| `channelId` | number | Telegram's own numeric id for the channel. One channel can answer on more than one name, so this is the reliable way to group rows. |
| `channelUrl` | string | Public link to the channel. |
| `subscriberCount` | number | null | Subscribers at the moment of collection. |
| `subscriberCountIsExact` | boolean | null | `false` when Telegram published the figure in rounded form. |
| `messageId` | number | The post's own number inside the channel. |
| `url` | string | Public link to the post. |
| `date` | string | null | When the post was published, in UTC. |
| `text` | string | null | The post body, in full. `null` when the post is media-only. |
| `textHtml` | string | null | The same body with its original formatting and links. |
| `textLength` | number | null | Characters in `text`. |
| `isEdited` | boolean | Whether the post was edited after publishing. |
| `authorSignature` | string | null | The signature on the post, when the channel signs its posts. |
| `viewCount` | number | null | Views at the moment of collection. `null` when the channel does not publish views. |
| `viewCountIsExact` | boolean | null | `false` when the figure was published in rounded form. |
| `viewCountPublished` | string | null | Exactly what Telegram displayed, e.g. `"862K"`. |
| `reactionCount` | number | null | All reactions added together. `null` when the channel publishes no reactions at all. |
| `reactionKinds` | number | null | How many different reactions the post received. |
| `reactions` | array | null | One entry per reaction: `emoji`, `customEmojiId`, `isPaid`, `count`, `countIsExact`. |
| `isReply` | boolean | Whether the post replies to another post. |
| `replyToMessageId` | number | null | The post it replies to. |
| `replyTo` | object | null | The quoted post: its number, link, author and text. |
| `isForwarded` | boolean | Whether the post was forwarded from somewhere else. |
| `forwardedFrom` | object | null | Where it came from: name, link and `@username`. |
| `linkPreview` | object | null | The preview card attached to the post: link, site, title, description and image. |
| `mediaType` | string | `text`, `photo`, `video`, `album`, `round_video`, `sticker`, `audio`, `voice` or `file`. |
| `isAlbum` | boolean | Whether the post is a photo album. |
| `photos` / `photoCount` | array / number | Direct image links, one per image in the post. |
| `videos` / `videoCount` | array / number | Video links with poster frame and duration in seconds. |
| `documents` | array | Files, audio and voice notes: title, performer or size, and kind. |
| `stickers` | array | Sticker image links. |
| `hasUnviewableMedia` | boolean | `true` when the post carries media Telegram does not make viewable outside its own apps. |
| `outboundLinks` | array | Every link the author put in the post. |
| `hashtags` | array | Hashtags used in the post, without the `#`. |
| `mentions` | array | `@usernames` mentioned in the post. |
| `isServiceMessage` | boolean | `true` for Telegram's own channel notices. |
| `matchedKeyword` | string | null | The keyword that selected this post, when a keyword was used. |
| `scrapedAt` | string | When this row was collected, in UTC. |

### Pricing

You pay per result, not for time.

| What you pay for | Price |
| --- | --- |
| Every post collected | **$1.00 per 1,000 posts** |

Nothing else is billed. A channel that turns out to be unavailable produces no rows, so it costs nothing.

**Worked example.** You want the latest 500 posts from 10 competitor channels. That is up to 5,000 posts, so up to **$5.00** for the run. Set **Posts per channel** to 50 for a daily check on the same 10 channels and each run is at most 500 posts — **$0.50 a day**.

Set a maximum spend on the run and the actor stops cleanly when it is reached, keeping everything already collected.

### Limits & what this actor cannot do

- Only **public** Telegram channels are covered. Private channels, invite-only groups and one-to-one chats are not publicly viewable, and are reported as unavailable rather than returned empty.
- **Groups are not channels.** A public group's messages are not published for reading, so a group handle is reported as unavailable rather than returned empty.
- View, reaction and subscriber counts are a **snapshot** at the moment of collection and keep changing afterwards.
- Telegram publishes very large counts in **rounded** form, so the biggest numbers are approximate. Every row says which of its numbers were rounded, so you never have to guess.
- **Absent is not zero.** Some channels publish no view counts and some publish no reactions at all. Those fields arrive as `null`, which means "Telegram never published this" — not "this post got none".
- The **keyword filter** returns at most 22 posts per channel per keyword, and that is a limit of Telegram's own keyword matching, not of this actor. It matches whole words, so a partial word finds nothing. For broad keyword work, run the keyword across many channels instead of digging deep into one. A keyword identical to the channel's own name cannot be used as a filter there, and that channel is reported as skipped rather than returned unfiltered.
- Files, audio and voice notes arrive with their **title and kind but no downloadable link** — Telegram does not publish one. Photos and videos do have direct links.
- Some posts carry media Telegram does not make viewable outside its own apps. Those posts are returned with their text and marked with `hasUnviewableMedia`.
- Some channels are **restricted in some countries**, so the same channel can return a different result depending on the country the run browses from.
- Speed depends on the size of the job and on Telegram's own response times; no fixed throughput is promised.
- Telegram's terms govern automated access. You are responsible for using the data lawfully, in line with the source site's terms, and for handling any personal data in line with applicable privacy law.

### FAQ

**Do I need a Telegram account?**
No. Nothing is connected to any account of yours.

**Does it need my phone number or a login?**
No. There is nothing to sign in to and nothing to authorise. You paste channel names and press start.

**Can I schedule it?**
Yes. Schedule it on Apify to run hourly, daily or weekly, and each run writes a fresh dataset you can export or send onward.

**Is the data complete?**
It is complete for what Telegram publishes publicly. For an ordinary public channel the actor goes all the way back to the first post. It cannot return anything from private channels, invite-only groups or deleted posts, and posts that were edited show their current text rather than the original.

**How do I get the whole history of a channel?**
Raise **Posts per channel**. The actor stops on its own when a channel runs out of published history and tells you so in the log, so setting a number larger than the channel is safe.

**Why do some posts have `null` views or reactions?**
Because that channel does not publish them. A `null` is deliberate: it means the number was never made public, which is a different fact from a genuine zero.

**Can I get the comments underneath a post?**
Not with this actor — it returns the channel's own posts. Comment threads are a separate job.

**What happens if I paste a channel that does not exist?**
It is reported as unavailable, with the reason, and the rest of the run continues. You are never charged for it.

# Actor input Schema

## `channels` (type: `array`):

The public channels to collect. Paste a channel link or an @username — one per line. Private channels, invite links and groups have no public post history and are reported as unavailable.

## `maxPostsPerChannel` (type: `integer`):

How many posts to collect from each channel, newest first. Raise it to build a full archive — the actor keeps going back until it runs out of published history.

## `keyword` (type: `string`):

Only return posts from each channel that mention this word. Telegram matches whole words and publishes at most 22 keyword matches per channel, so leave this empty when you want the full feed. A keyword that is the same as the channel name cannot be used as a filter and that channel is reported as skipped.

## `dateFrom` (type: `string`):

Only keep posts published on or after this date (YYYY-MM-DD). Leave empty for no lower bound.

## `dateTo` (type: `string`):

Only keep posts published on or before this date (YYYY-MM-DD). Leave empty for no upper bound.

## `newestFirst` (type: `boolean`):

Write the newest post first. Turn it off to get each channel in the order it was published, oldest first.

## `includeServiceMessages` (type: `boolean`):

Include Telegram's own notices such as "Channel created" or a pinned-post announcement. They carry no views, reactions or media and are left out by default.

## `turbo` (type: `boolean`):

Collect deep history in parallel instead of one step at a time. Much faster on big archive jobs and returns the same posts. It is automatically switched off when a keyword or a "Posted after" date is set.

## `country` (type: `string`):

The country the run should browse from. A few channels are restricted in some countries, so this can change what a channel returns.

## `maxConcurrency` (type: `integer`):

How much work to run at the same time. 16 is a good balance of speed and politeness; raise it for very large jobs.

## Actor input object example

```json
{
  "channels": [
    "@durov",
    "https://t.me/telegram"
  ],
  "maxPostsPerChannel": 50,
  "keyword": "bitcoin",
  "dateFrom": "2026-01-01",
  "dateTo": "2026-12-31",
  "newestFirst": true,
  "includeServiceMessages": false,
  "turbo": true,
  "country": "us",
  "maxConcurrency": 5
}
```

# Actor output Schema

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

No description

# 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 = {
    "channels": [
        "@durov",
        "@telegram"
    ],
    "maxPostsPerChannel": 50,
    "country": "us",
    "maxConcurrency": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("apt_marble/telegram-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 = {
    "channels": [
        "@durov",
        "@telegram",
    ],
    "maxPostsPerChannel": 50,
    "country": "us",
    "maxConcurrency": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("apt_marble/telegram-channel-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 '{
  "channels": [
    "@durov",
    "@telegram"
  ],
  "maxPostsPerChannel": 50,
  "country": "us",
  "maxConcurrency": 5
}' |
apify call apt_marble/telegram-channel-scraper --silent --output-dataset

```

## MCP server setup

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

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/qQRmRNjpLXLWsoEnA/builds/1Yci00SCQcZHNCVfk/openapi.json
