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

Scrape public Telegram channels without an account: channel info with subscriber count, and every message with text, date, views, reactions, media, links, hashtags and forwards. Search inside a channel, stop at a date or message id. Numbers come back as real numbers.

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

## Pricing

from $0.44 / 1,000 messages

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

<img src="https://apify-image-uploads-prod.s3.us-east-1.amazonaws.com/wCP1WauwRX2Gr3Gir-actor-vTMCHzaBNNqMFh153-vao3Ao74FB-telegram-channel-scraper.png" alt="Telegram Channel Scraper logo" width="120">

## Telegram Channel Scraper

Scrape public Telegram channels without a Telegram account. Get the channel's title, description and **subscriber count as a number**, and every message with text, date, views, reactions, photos, videos, link previews, outbound links, hashtags, mentions and forwards.

| At a glance | |
|---|---|
| **You give it** | Public channel names or t.me links, plus optional search, date or message-id limits |
| **You get** | One row per channel (subscriber count as a number) and one row per message with views, reactions, media and links |
| **Price** | $0.005 per run + $0.0017 per channel + $0.00085 per message (Free plan, lower on paid plans) |
| **Speed** | 1 channel and 20 messages in about 7 seconds |
| **Needs** | Nothing: no login, no API key, no proxy |

### What you get

- **Channel info:** title, description, subscribers, photo/video/link counters, verified badge, avatar.
- **Messages, newest first,** back through the whole public history if you want it.
- **Real numbers:** `1.63M` views becomes `1630000`, so you can sort, filter and chart.
- **Search inside channels** with Telegram's own channel search (recent posts only; see the FAQ).
- **Only new messages, automatically.** Turn on `onlyNewMessages` and each run picks up where the last one stopped. No ids to copy between runs, no duplicates, no double charges.
- **Stop points:** a date, or a message id. Only messages after it are fetched and charged.
- **Minimum views filter.** Skipped messages are not charged.
- **Engagement built in:** every message has `engagementRate` (reactions per view) and `viewRate` (views per subscriber), ready to rank posts and compare channels.
- **Extracted for you:** outbound links, hashtags, @mentions, link preview title and site, reactions with counts (including paid star reactions) and a total.
- **Light:** 1 channel and 20 messages used $0.0003 of platform usage in one request.

### Use cases

- **Crypto and trading signals.** Watch channels for tickers and links the moment they post.
- **News and OSINT monitoring.** Track what public channels say about a topic, with dates and view counts.
- **Influencer and channel research.** Compare subscriber counts and engagement across many channels.
- **Link and lead extraction.** Every outbound link a channel shares.
- **AI summaries and sentiment.** Clean message text with dates and reach.

### Example inputs

#### Latest 100 messages from two channels

```json
{ "channels": ["durov", "https://t.me/telegram"], "maxMessagesPerChannel": 100 }
```

#### Everything since a date

```json
{ "channels": ["telegram"], "maxMessagesPerChannel": 0, "oldestMessageDate": "2026-01-01" }
```

#### Only new messages, for a schedule

```json
{ "channels": ["durov", "telegram"], "onlyNewMessages": true, "includeChannelInfo": false }
```

The first run saves the latest 100 messages. Every run after that saves only what was posted since.

#### Search inside a channel

```json
{ "channels": ["durov"], "searchQuery": "TON", "maxMessagesPerChannel": 50 }
```

#### Subscriber counts for a list of channels

```json
{ "channels": ["durov", "telegram"], "channelInfoOnly": true }
```

### Input

| Field | What it does |
|---|---|
| `channels` | Usernames or links: `durov`, `@durov`, `https://t.me/durov`. |
| `maxMessagesPerChannel` | Newest first. `0` means all public history. Default 100. |
| `searchQuery` | Only messages matching this text (Telegram returns about the 20 most recent matches). |
| `oldestMessageDate` | Stop at messages older than this date. |
| `afterMessageId` | Stop at this message id. |
| `onlyNewMessages` | Remember the newest saved message per channel; the next run starts after it. |
| `stateName` | Optional. A separate memory per schedule when two schedules watch the same channel. |
| `minViews` | Skip messages with fewer views (not charged). |
| `includeChannelInfo` | One channel row per channel. Default on. |
| `channelInfoOnly` | Channel rows only, no messages. |

### Output

Each row has a `type`: `channel` or `message`. Real rows from a test run on 2026-09-26 (long text shortened here):

#### Channel

```json
{
  "type": "channel",
  "channel": "durov",
  "title": "Pavel Durov",
  "description": "Founder of Telegram.",
  "subscribers": 10600000,
  "subscribersText": "10.6M",
  "photos": 102,
  "videos": 46,
  "links": 200,
  "files": null,
  "isVerified": true,
  "avatarUrl": "https://cdn4.telesco.pe/file/PwaBtFuo9UBudJwMKXgYeWSHFhZ1wTCA-wbjfFspV08LLTYiNUdq5ZKgVnBzmW7JXpgszn2y2bWDVCdjZHpaejEJpBeXozIiSN28g0x9smQcPGtDd4gvTPcRcjyp6Iig9cRtYbGowCBalvStcy-ExGW6hv0XLL_VQoN0zCUDSEVA1L4uNKuBVN39trixlnObeO3ptQRxDs44wIzdxy5NAZe-zu4xiejgJYWH8axLd1c6zC66Qw4EHiZUdp8jsSd7k0wyM1bh8o7q3fx16W1hr8RSuYQYatgs2Nim8zi1NTaStvFnH1fdI6DvLoYbKh4qlajW4vlWSJ3CKkUM1q5qQA.jpg",
  "url": "https://t.me/durov"
}
```

#### Message

```json
{
  "type": "message",
  "channel": "durov",
  "messageId": 548,
  "url": "https://t.me/durov/548",
  "date": "2026-09-11T16:04:02+00:00",
  "text": "🤝 Telegram has become the sponsor of Codeforces , the largest competitive programming platform in the world. \n\n ⚡CodeForces organizes over 100 coding contests every year and has 4 million contestants signed up.\n\n🌱 Since 2010, I've supported Codeforces through ...",
  "views": 1630000,
  "author": "Pavel Durov",
  "isEdited": false,
  "isForwarded": false,
  "forwardedFrom": null,
  "forwardedFromUrl": null,
  "replyToMessageId": null,
  "mediaType": "text",
  "photoUrls": [],
  "videoUrls": [],
  "videoThumbnailUrls": [],
  "videoDuration": null,
  "documents": [],
  "pollQuestion": null,
  "linkPreview": {
    "url": "https://codeforces.com/blog/entry/156620",
    "site": "Codeforces",
    "title": "Telegram Returns as Title Sponsor of Codeforces!",
    "description": "Hi, Codeforces!"
  },
  "links": [
    "https://codeforces.com/blog/entry/156620"
  ],
  "hashtags": [],
  "mentions": [],
  "reactions": [
    {
      "emoji": "⭐",
      "emojiId": null,
      "count": 13500,
      "isPaid": true
    },
    {
      "emoji": null,
      "emojiId": "5399847211989246390",
      "count": 28100,
      "isPaid": false
    },
    {
      "emoji": null,
      "emojiId": "5936157098181135162",
      "count": 8080,
      "isPaid": false
    },
    {
      "emoji": null,
      "emojiId": "5373223594484587136",
      "count": 7580,
      "isPaid": false
    }
  ],
  "totalReactions": 57260,
  "channelTitle": "Pavel Durov",
  "channelSubscribers": 10600000,
  "engagementRate": 0.0351,
  "viewRate": 0.1538,
  "searchQuery": null
}
```

Standard reactions come with the emoji. Custom emoji reactions come back as `emojiId` only, with `emoji` empty: Telegram's public preview does not name them. The id is stable, so you can still group and count them.

`engagementRate` is `totalReactions` divided by `views`. `viewRate` is `views` divided by `channelSubscribers`. Both are fractions (0.0351 means 3.51%) and empty when the numbers behind them are missing.

`mediaType` is one of `text`, `photo`, `video`, `document`, `poll`, `sticker`, `voice`, `unsupported` (media the web preview can't show; open `url` in Telegram) or `other`. `links` holds outbound links from the text; @mentions and #hashtags are in their own fields, and links to the channel's own posts are left out. Service notices such as "live stream started" are skipped and not charged.

### Pricing

Pay per event:

- **$0.005** per run start
- **$0.0017** per channel info row
- **$0.00085** per message

Lower on higher Apify plans. Apify platform usage is billed separately to your account and is tiny: 1 channel and 20 messages used $0.0003.

| What you scrape | Actor cost |
|---|---|
| 1 channel + 100 messages | about $0.09 |
| 10 channels + 1,000 messages | about $0.87 |
| 50 channels, info only | about $0.09 |

### Automate it with n8n

Each workflow uses n8n's official **Apify** node, operation **Run actor and get dataset**, actor `labrat011/telegram-channel-scraper`. Paste the input into **Input JSON**; switch it to **Expression** when it contains `{{ }}`.

#### 1. New messages to Slack or Discord every 15 minutes

```
Schedule Trigger (every 15 minutes)
  > Apify: Run actor and get dataset
      { "channels": ["channel_one", "channel_two"], "onlyNewMessages": true, "includeChannelInfo": false }
  > Discord / Slack: "{{ $json.channel }}: {{ $json.text }} {{ $json.url }}"
```

#### 2. Keyword alerts across channels

```
Schedule Trigger (hourly)
  > Apify: Run actor and get dataset   { "channels": [...], "searchQuery": "airdrop", "oldestMessageDate": "<1 hour ago>" }
  > Telegram / Email: forward each hit with its link
```

#### 3. Channel leaderboard in Google Sheets

```
Schedule Trigger (weekly)
  > Apify: Run actor and get dataset   { "channels": [ ...50 channels... ], "channelInfoOnly": true }
  > Google Sheets: Append rows (date, channel, subscribers)
```

Week-over-week subscriber growth per channel, charted from the sheet.

#### 4. Link harvesting

```
Schedule Trigger (daily)
  > Apify: Run actor and get dataset   (your channels, last 24 hours)
  > Split Out: links
  > Remove Duplicates: on the link
  > Airtable: store link, channel, date, views
```

#### 5. Daily AI digest of a busy channel

```
Schedule Trigger (daily 18:00)
  > Apify: Run actor and get dataset   (one channel, last 24 hours, minViews 1000)
  > Aggregate: text, views, url
  > OpenAI / Anthropic: "Summarize today's most-viewed posts in 5 bullets with links"
  > Email: send the digest
```

### For AI agents

- **Actor:** `labrat011/telegram-channel-scraper`
- **Smallest input:** `{ "channels": ["durov"] }`
- **One row = one channel (`type: "channel"`) or one message (`type: "message"`).** Key fields: channels: `channel`, `title`, `subscribers`; messages: `messageId`, `url`, `date`, `text`, `views`, `engagementRate`, `viewRate`, `links`.
- **Scheduled runs:** set `onlyNewMessages: true`; each run returns only messages posted since the last one.
- **Billing:** `apify-actor-start` once per run, `channel-info` per channel row, `message` per message row. Cap spend with `maxMessagesPerChannel` or a maximum cost per run.
- **Run it:** `POST https://api.apify.com/v2/acts/labrat011~telegram-channel-scraper/run-sync-get-dataset-items` with the input as the JSON body, or call it from the Apify MCP server.
- **Done signal:** the run's status message reads `Saved N channels and M messages in R requests.`

### FAQ

**Why does a channel return nothing?** Only public channels with Telegram's web preview turned on can be read without an account. The run log says which channels had no preview. Some large brands turn it off.

**Can it read groups or private channels?** No. Public channels only, and no login is used.

**Why does search return fewer posts than I expected?** Search uses Telegram's public web search, which returns about the 20 most recent matches per channel and does not reach far back. For a full keyword scan of older posts, fetch the history (with `oldestMessageDate`) and filter the `text` yourself.

**How does "only new messages" remember?** It keeps the newest saved message id per channel in a key-value store named `telegram-channel-scraper-state` in your Apify account. A search keeps its own memory, separate from plain runs. To start over, delete that store or use a new `stateName`. If a run stops early at `maxMessagesPerChannel` or your maximum cost per run, the next run starts after the newest message saved, so older unread messages from that gap are skipped. Keep the limit above what a channel posts between runs.

**Do I need a proxy?** No.

### Support

Open an issue on the actor's Issues tab with the run ID and it will be looked at.

# Changelog

This Actor's version history is a separate document: https://apify.com/labrat011/telegram-channel-scraper/changelog.md

# Actor input Schema

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

Usernames or links: durov, @durov, https://t.me/durov. Public channels with the web preview turned on.

## `maxMessagesPerChannel` (type: `integer`):

Newest first. 0 means the whole public history.

## `searchQuery` (type: `string`):

Only messages matching this word or phrase, using Telegram's own channel search.

## `oldestMessageDate` (type: `string`):

Stop at messages older than this, e.g. 2026-09-01.

## `afterMessageId` (type: `integer`):

Stop at this message id. Use the highest messageId from your last run.

## `onlyNewMessages` (type: `boolean`):

Remember the newest message saved from each channel. The next run fetches and charges only messages posted after it. Built for schedules.

## `stateName` (type: `string`):

Give each schedule its own name when two schedules watch the same channel, so they do not share one memory. Leave empty for one schedule.

## `minViews` (type: `integer`):

Skip messages with fewer views. Skipped messages are not charged.

## `includeChannelInfo` (type: `boolean`):

One row per channel with title, description, subscribers and counters.

## `channelInfoOnly` (type: `boolean`):

Skip messages. Useful for checking subscriber counts across many channels.

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

Not needed normally.

## Actor input object example

```json
{
  "channels": [
    "durov",
    "telegram"
  ],
  "maxMessagesPerChannel": 100,
  "onlyNewMessages": false,
  "minViews": 0,
  "includeChannelInfo": true,
  "channelInfoOnly": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (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"
    ],
    "maxMessagesPerChannel": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("labrat011/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",
    ],
    "maxMessagesPerChannel": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("labrat011/telegram-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 '{
  "channels": [
    "durov",
    "telegram"
  ],
  "maxMessagesPerChannel": 100
}' |
apify call labrat011/telegram-channel-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,labrat011/telegram-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/vTMCHzaBNNqMFh153/builds/302CH7kFfJPsgI8qR/openapi.json
