# Telegram Channel Scraper + New Posts Monitor (`ninoventures/telegram-channel-scraper`) Actor

Scrape posts from public Telegram channels without a login or API key: text, date, views, reactions, links, media URLs, replies and forwards. Keyword search inside a channel, date filters, and a monitor that returns only new posts. $0.60 per 1,000 posts.

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

## Pricing

Pay per event

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 + New Posts Monitor

Get posts from public Telegram channels as clean JSON, CSV or Excel. No Telegram account, no phone number, no API key. It reads the same public web preview you see at `https://t.me/s/<channel>`.

**Price: $0.60 per 1,000 posts.** No start fee. Channels that don't exist or have no public preview cost nothing.

### What you get per post

| Field | Example |
|---|---|
| `channel`, `postId`, `url` | `durov`, `527`, `https://t.me/durov/527` |
| `date` | `2026-08-14T09:12:03+00:00` (UTC) |
| `text` | Full post text, line breaks kept, emoji as characters |
| `views` | `24400000` (Telegram shows "24.4M"; you get a number) |
| `reactions`, `reactionsTotal`, `paidStars` | Per-reaction counts, their sum, and Telegram Stars |
| `links`, `hashtags`, `mentions` | Extracted from the post body |
| `media` | Photos (image URL), videos (mp4 URL when Telegram serves it on the web, thumbnail, duration), documents, voice, stickers |
| `linkPreview` | URL, site name, title, description, image |
| `replyTo` | Link, author and text of the post it replies to |
| `forwardedFrom` | Source name and link, when public |
| `poll` | Question, type, options with percentages |
| `channelTitle`, `channelSubscribers`, `channelVerified` | Channel context on every row |
| `isNewSinceLastRun` | `true`/`false` once the monitor has state for this channel |

A `SUMMARY` record in the key-value store lists every channel with its post count, subscriber count, latest post ID, and the reason when a channel was skipped.

### Input

- **Channels**: `durov`, `@durov`, `https://t.me/durov` or `https://t.me/s/durov`. A post link like `https://t.me/durov/300` starts from that post and reads back in time.
- **Max posts per channel**: newest first. Default 100.
- **Only posts from the last N days** / **Only posts after this date**: stops reading once posts get older.
- **Keep only posts containing**: keywords matched at the start of a word, case-insensitive (`eth` matches "Ethereum" but not "together"). Non-matching posts are skipped and not charged. Each saved post lists `matchedKeywords`.
- **Search inside channels**: Telegram's own search. Its web version only returns the latest ~20 matches per channel; use the keyword filter above for more.
- **Only new posts since last run**: monitor mode, see below.
- **Channel info only**: one row per channel (title, description, subscribers, photo/video/link counters, avatar URL). $1 per 1,000 channels.

Example:

```json
{
  "channels": ["cointelegraph", "whale_alert", "https://t.me/binance_announcements"],
  "maxPostsPerChannel": 200,
  "postedWithinDays": 7,
  "keywords": ["ETF", "listing"]
}
```

### Monitoring new posts

Turn on **Only new posts since last run** and schedule the actor (every hour, every day). The first run saves the newest post ID per channel. Every later run returns only posts published after it, so you pay only for new posts. A run with nothing new finishes in a few seconds and costs nothing in events.

Use **Monitor name** to keep separate state for separate schedules, for example one per client or per keyword set. Connect the run to Slack, Google Sheets, a webhook, Make or n8n through Apify integrations.

### Test run numbers

From a run on 22 inputs on 31 October 2026, 150 posts max per channel:

- 20 channels had a public preview, 2 did not (`reuters`, `cnn`: no web preview on Telegram). Those two were reported in `SUMMARY` and not charged.
- 2,767 posts in 52 seconds on the default 512 MB memory.
- Coverage: date and views on 100% of posts, text on 99%, at least one link on 90%, link preview on 744, photos 1,108, videos 233, reactions on 57%, replies 92, forwards 49, polls 14.
- Re-running the same 22 channels in monitor mode a few minutes later: 0 new posts, 3 seconds, $0 in events.

### Limits

- **Public channels only.** Private channels, groups, bots and user accounts have no web preview. They're listed in `SUMMARY` as skipped, never charged.
- Some channels turn off the web preview in their settings. Same result: skipped, free.
- Large videos show "Media is too big" on the web. You still get the thumbnail and duration, and `tooBigForWeb: true`, but no mp4 URL.
- Media URLs from Telegram's CDN are signed and expire after a while. Download what you need soon after the run.
- Custom (premium) emoji reactions come with an `customEmojiId` instead of a character.
- Comments under posts and member lists are not on the public preview, so this actor doesn't return them.
- Telegram rate-limits heavy traffic from one IP. The actor retries with back-off. For very large runs, turn on Apify Proxy in the input.

### Use the data responsibly

The actor only reads what Telegram publishes openly for anyone without logging in. Posts can still contain personal data. If you store or process it, you're responsible for having a lawful reason under GDPR or your local law, and for following Telegram's terms.

### Questions or bugs

Open an issue on the actor's Issues tab. Fixes usually ship within a few days.

# Actor input Schema

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

Public channel usernames or links: durov, @durov, https://t.me/durov, https://t.me/s/durov. A post link (https://t.me/durov/300) starts from that post and goes back in time.

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

Newest posts first. The web preview serves about 20 posts per page.

## `postedWithinDays` (type: `integer`):

0 = no date limit.

## `postedAfter` (type: `string`):

ISO date, e.g. 2026-10-01. Leave empty for no limit.

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

Uses Telegram's own in-channel search. Telegram's web preview only returns the most recent ~20 matches per channel, so for deeper history use 'Keep only posts containing' instead.

## `keywords` (type: `array`):

Case-insensitive. A post is kept if its text, links or link preview title contains any of these. Posts that don't match are not saved and not charged.

## `maxPagesPerChannel` (type: `integer`):

Safety cap on how far back to read (about 20 posts per page). Matters mostly with keyword or date filters.

## `onlyNewPosts` (type: `boolean`):

Monitor mode. The first run saves the latest post ID per channel; later runs return only posts after it. Schedule the actor to get a feed of new posts.

## `monitorName` (type: `string`):

Keeps separate monitor state for different schedules. Optional.

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

Return one row per channel (title, description, subscribers, counters) instead of posts. Charged per channel.

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

Not needed for most runs. Turn on Apify Proxy if you scrape thousands of posts and see HTTP 429 warnings.

## Actor input object example

```json
{
  "channels": [
    "durov",
    "telegram"
  ],
  "maxPostsPerChannel": 100,
  "postedWithinDays": 0,
  "maxPagesPerChannel": 50,
  "onlyNewPosts": false,
  "channelInfoOnly": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `posts` (type: `string`):

No description

## `summary` (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"
    ]
};

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

# Run the Actor and wait for it to finish
run = client.actor("ninoventures/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"
  ]
}' |
apify call ninoventures/telegram-channel-scraper --silent --output-dataset

```

## MCP server setup

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