# Telegram Channel Scraper: Posts, Views & Subscribers (`yugenox/telegram-channel-scraper`) Actor

Scrape public Telegram channels: every post with text, date, views, reactions, media, links, hashtags, forwards and replies, plus exact subscriber counts. Keyword search inside channels, date range, only-new monitoring. No login, no API key, no phone number.

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

## Pricing

from $0.80 / 1,000 posts

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

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

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

## How to integrate an Actor?

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

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

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

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

# README

## Telegram Channel Scraper: Posts, Views & Subscribers

Scrape **public Telegram channels** into clean rows: every post with its **text, date, views, reactions, media, links,
hashtags, mentions, forwards and replies**, plus one row per channel with the **exact subscriber count** and the photo /
video / link counters. Search **inside a channel by keyword**, limit to a **date range**, or run it on a schedule with
**Only new posts** to monitor hundreds of channels hourly or daily.

No login, no API key, no phone number, no Telegram account.

### What you can do with it

- **Crypto and finance monitoring** — watch signal, exchange and news channels; track views and reactions per post.
- **News and media tracking** — collect every post of news channels for a day, a week or a date range.
- **Threat intelligence and brand monitoring** — search channels for a keyword, a ticker, a domain or a hashtag.
- **Marketing and competitor research** — compare channels by subscribers, posting frequency, views and engagement.
- **Archiving and research** — page back through a channel's history, or jump straight to a past period.

### How it works

1. Add channels: a name (`durov`, `@durov`) or a link (`https://t.me/durov`, `https://t.me/s/durov`).
   A link to a single post (`https://t.me/durov/532`) saves just that post.
2. Choose how many posts per channel (newest first), and optionally a keyword, a date range or **Only new posts**.
3. Run it. Each post is one row; each channel adds one channel row (turn **Include channel info** off if you only want
   posts).

Telegram has no search across all channels, so the keyword search runs inside the channels you list.

### Input

| Field | What it does |
|---|---|
| **Channels** | Public channel names or links, or links to single posts. |
| **Max posts per channel** | Newest first, going back until this many are saved (default 100). `0` = channel info only. |
| **Keyword in channel** | Only posts that match, using Telegram's own search inside each channel (words or a `#hashtag`). |
| **Posts from / Posts up to** | Date range: `YYYY-MM-DD` or relative (`7 days`, `1 month`). Far-back dates are found by jumping, not by reading every newer post. |
| **Only new posts** | Skip posts an earlier run with the same channels and keyword already saved. Ideal for schedules. |
| **Include channel info** | One row per channel with the exact subscriber count, title, description, labels and counters (default on). |
| **Include media URLs** | Photo, video, voice, sticker and thumbnail links (default on). |
| **Max posts in total** | Optional cap for the whole run. |

Example:

```json
{
  "channels": ["durov", "https://t.me/cointelegraph"],
  "maxPostsPerChannel": 50,
  "keyword": "bitcoin",
  "dateFrom": "30 days",
  "includeChannelInfo": true
}
```

### Output

A post row (real, trimmed):

```json
{
  "type": "post",
  "channelUsername": "durov",
  "channelTitle": "Pavel Durov",
  "channelId": "1006503122",
  "channelSubscribers": 10210963,
  "postId": 548,
  "url": "https://t.me/durov/548",
  "date": "2026-09-11T16:04:02+00:00",
  "isEdited": false,
  "authorSignature": "Pavel Durov",
  "text": "🤝 Telegram has become the sponsor of Codeforces — the largest competitive programming platform in the world. …",
  "views": 3810000,
  "viewsText": "3.81M",
  "reactionsTotal": 79300,
  "reactions": [
    { "emoji": "⭐", "emojiId": null, "count": 21900, "isPaid": true },
    { "emoji": null, "emojiId": "5399847211989246390", "count": 36600, "isPaid": false }
  ],
  "mediaType": "link",
  "mediaCount": 0,
  "media": [],
  "linkPreview": {
    "url": "https://codeforces.com/blog/entry/156620",
    "siteName": "Codeforces",
    "title": "Telegram Returns as Title Sponsor of Codeforces!",
    "description": "Hi, Codeforces!"
  },
  "links": ["https://codeforces.com/blog/entry/156620"],
  "hashtags": [],
  "mentions": [],
  "forwardedFromName": null,
  "forwardedFromType": null,
  "replyToPostId": null,
  "keyword": null,
  "scrapedAt": "2026-10-29T04:43:55.669Z"
}
```

A channel row (real):

```json
{
  "type": "channel",
  "channelUsername": "durov",
  "channelTitle": "Pavel Durov",
  "channelId": "1006503122",
  "channelSubscribers": 10210963,
  "subscribersText": "10 210 963",
  "url": "https://t.me/durov",
  "description": "Founder of Telegram.",
  "isVerified": true,
  "isScam": false,
  "isFake": false,
  "photosCount": 102,
  "videosCount": 47,
  "linksCount": 200,
  "previewEnabled": true
}
```

All fields: `type, channelUsername, channelTitle, channelId, channelSubscribers, postId, url, date, isEdited,
authorSignature, text, views, viewsText, reactionsTotal, reactions[], mediaType, mediaCount, media[], imageUrl,
linkPreview, links[], hashtags[], mentions[], buttons[], poll, location, forwardedFromName, forwardedFromUrl,
forwardedFromType, replyToPostId, replyToUrl, replyToText, keyword, description, photoUrl, isVerified, isScam, isFake,
subscribersText, photosCount, videosCount, filesCount, linksCount, previewEnabled, scrapedAt`. Every row has every
field (empty ones are `null` or `[]`), so the table and CSV exports line up.

`mediaType` is one of `text, photo, video, gif, round_video, voice, audio, document, sticker, poll, location, contact,
link, album, unsupported, other` (`unsupported` = content Telegram only shows in its apps, such as stories or paid
media). `media[]` lists each item with its type, duration (videos, voice) and URL.

### Pricing

Pay per event — you only pay for what you get:

| Event | Price |
|---|---|
| Post saved | **$0.80 per 1,000 posts** |
| Channel info row | $2 per 1,000 channels ($0.002 each) |
| Page checked with no new posts (keyword without hits, nothing in the date range, nothing new since the last run) | $0.10 per 1,000 pages |
| Run start | $0.002 per run at the default memory (charged per GB of run memory) |

Example: 10 channels × 100 posts with channel info = 1,000 posts + 10 channel rows ≈ **$0.82**.
A "nothing new" check of 100 channels with **Only new posts** ≈ $0.012.

### Tips

- **Monitoring:** schedule the actor hourly or daily with **Only new posts** on and **Include channel info** off — each
  run then saves only the posts published since the last one.
- **Big histories:** raise **Max posts per channel**; the run pages back about 20 posts at a time.
- **A past period:** set **Posts up to** (and **Posts from**) — the actor jumps to that period instead of reading
  everything newer.
- **Media links** expire after a while; download what you need soon after the run, or turn **Include media URLs** off.
- Channels whose owners turned off the web preview, groups, bots and private (invite-only) channels have no public posts;
  they are listed in the run's status message, and preview-disabled channels still get their channel row.

### FAQ

**Is it legal to scrape Telegram?**
This actor reads only what Telegram itself publishes openly on the web for public channels — the same pages anyone can
open in a browser without an account. Scraping publicly available data is generally considered legal, but how you use
it matters: personal data in posts can fall under laws such as the GDPR or CCPA. If you are unsure about your use case,
consult a lawyer.

**Does it access any private data?**
No. It never logs in and cannot see private channels, groups, members, phone numbers or messages between people. It
reads public channel posts only. When a post was forwarded from a person's account, that person's name is not saved —
only channel sources are named. Shared contact cards are reported as the type "contact" without their details.

**Why does a channel return no posts?**
Either it is not a public channel (a group, a bot, a person or a private invite link), or its owner turned off the web
preview. The status message names each such input. Preview-disabled channels still get their channel row.

**Can I search all of Telegram for a keyword?**
Telegram offers no public search across all channels. List the channels you care about and use **Keyword in channel** —
the search runs inside each of them.

**Why does a keyword return fewer posts than I asked for?**
The keyword uses Telegram's own search, and the run saves every match Telegram returns. On very large, busy channels
Telegram's web search only reaches back over the channel's more recent posts, so older matches may not appear. To
collect older posts, leave **Keyword in channel** empty, set a date range, and filter the results yourself.

**How fresh is the data?**
Live: every run reads the channels at that moment, including current view and reaction counts.

**What happens if Telegram slows down or refuses requests?**
The run waits and retries on new IP addresses. If Telegram keeps refusing, it stops cleanly with the posts already saved
and a plain status message — you are never charged for posts you did not get.

# Actor input Schema

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

Public Telegram channels: a name (durov, @durov) or a link (https://t.me/durov, https://t.me/s/durov). A link to one post (https://t.me/durov/532) saves just that post. Private invite links (t.me/+…) and groups have no public web preview and are skipped with a note.

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

Newest posts first, going back in time until this many are saved for each channel (or the date range / the start of the channel is reached). 0 = channel info only, no posts.

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

Optional: only posts that match these words, using Telegram's own search inside each channel, e.g. "bitcoin", "privacy" or a hashtag like "#crypto". Telegram has no search across all channels, so list the channels to search in.

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

Optional: only posts published on or after this date (YYYY-MM-DD) or within a relative period like "7 days" or "1 month". The run stops paging back once it passes this date.

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

Optional: only posts published on or before this date (YYYY-MM-DD, the whole day counts) or relative like "30 days" (older than 30 days). Far-back dates are found by jumping, not by reading every newer post.

## `onlyNew` (type: `boolean`):

Skip posts that an earlier run with the same channels and keyword already saved — ideal for hourly or daily monitoring schedules. The first run saves the newest posts as usual.

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

Add one row per channel with its exact subscriber count, title, description, verified / scam / fake labels and photo, video, file and link counters (one extra request per channel, charged as "Channel"). Turn off for frequent monitoring runs if you only need posts.

## `includeMediaUrls` (type: `boolean`):

Add photo, video, voice, sticker and thumbnail URLs to each post. Telegram's media links are signed and expire after a while, so download what you need soon after the run. Off = media types and durations only.

## `maxItems` (type: `integer`):

Optional cap on post rows for the whole run, across all channels. 0 = no cap (only "Max posts per channel" applies). Channel rows are not counted.

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

Apify Proxy (datacenter) is the default, comes with every Apify plan, and is all Telegram's public pages need.

## Actor input object example

```json
{
  "channels": [
    "durov"
  ],
  "maxPostsPerChannel": 10,
  "onlyNew": false,
  "includeChannelInfo": true,
  "includeMediaUrls": true,
  "maxItems": 0,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

All scraped posts and channel rows.

## `run` (type: `string`):

Status and statistics for this run.

## `report` (type: `string`):

RUN_REPORT: how the run ended, counts per channel, requests and proxy use.

# 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"
    ],
    "maxPostsPerChannel": 10,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("yugenox/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"],
    "maxPostsPerChannel": 10,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("yugenox/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"
  ],
  "maxPostsPerChannel": 10,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call yugenox/telegram-channel-scraper --silent --output-dataset

```

## MCP server setup

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