# Telegram Channel Scraper - Posts, Views, Reactions, No Login (`maksym_kotenko/telegram-channel-scraper`) Actor

Scrape posts of public Telegram channels without login or API keys: text, date, views, reactions, media links, forwards, replies and polls. Incremental mode returns only posts published since the last run. Pay only per post.

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

## Pricing

from $0.50 / 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.
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

## Telegram Channel Scraper - Posts, Views, Reactions, No Login

Export posts from any public Telegram channel: text, publication date, views, reactions, photos, videos, files, forwards, replies and polls. No Telegram account, phone number or API key. Schedule it with **incremental mode** and every run returns only the posts published since the previous one.

### What it does

- **Every post of a public channel** read from its official web preview at `t.me/s/<channel>`, newest first, as far back as you need.
- **Rich fields**: full text with line breaks, date, edited flag, author signature, view count, each reaction with its count (emoji, custom emoji and paid stars), forwarded-from source, the post it replies to, link previews, polls with options and percentages, hashtags, mentions and links.
- **Media links**: photos, videos with thumbnail and duration, voice notes, round videos, stickers, file names and sizes.
- **Channel details on every row**: title and subscriber count, plus a run summary per channel.
- **Incremental mode** for monitoring: the last returned post is remembered per channel, the next run reads forward from it and returns only newer posts, oldest first. A run cut short by **Max posts per channel** or your spending limit is continued exactly where it stopped: no gaps, no duplicates. Clean feeds into Google Sheets, Slack, a database or an AI pipeline.
- **Date window**: posts from a date, until a date, or both. An old window is reached with a binary search over post ids instead of paging through years of newer posts.
- **Many channels per run**, read in parallel, duplicates and invalid entries reported as rows.
- **Honest billing**: channels that do not exist, are private or fail to load are listed with an error code and **not charged**. The run stops at your maximum cost per run.

### Use cases

- Monitor news, crypto, job or deal channels and get each new post once.
- Media and brand monitoring: track mentions, links and reactions over time.
- Research datasets: full channel history with dates and engagement for analysis or AI training.
- Competitor and market analysis: posting frequency, views per post, which formats get reactions.
- Archive a channel to JSON, CSV, Excel or a database.

### How to use

1. Add channels to **Channels**: a username like `durov`, `@durov`, or a link like `https://t.me/durov`.
2. Set **Max posts per channel**, and optionally **Posts published from** and **until**.
3. Press **Start**. Posts appear in the **Output** tab; export them as JSON, CSV, Excel or via the API.

For monitoring, turn on **Only posts that are new since the last run** and create a schedule. The first run returns the newest posts by your limits, newest first; each next run only the new ones, oldest first, so you can append them in order.

#### Input example

```json
{
  "channels": ["telegram", "https://t.me/durov"],
  "maxPostsPerChannel": 200,
  "dateFrom": "30 days",
  "incremental": true
}
```

#### Output example

One dataset row per post:

```json
{
  "channel": "telegram",
  "channelTitle": "Telegram News",
  "channelSubscribers": 9300000,
  "postId": 441,
  "url": "https://t.me/telegram/441",
  "date": "2026-05-14T16:08:31.000Z",
  "edited": false,
  "author": null,
  "text": "Bot-to-Bot Communication. Bots are now able to respond to other bots, not just users.\n\nAI enthusiasts can utilize this to create custom workflows with autonomous agents.",
  "views": 1580000,
  "reactionsCount": 0,
  "reactions": [],
  "forwardedFrom": null,
  "replyTo": null,
  "mediaType": "video",
  "mediaCount": 1,
  "media": [
    {
      "type": "video",
      "url": "https://cdn4.telesco.pe/file/<file>.mp4?token=<token>",
      "thumbnailUrl": "https://cdn4.telesco.pe/file/<file>.jpg",
      "durationSeconds": 6,
      "title": null,
      "size": null,
      "postUrl": "https://t.me/telegram/441"
    }
  ],
  "linkPreview": null,
  "poll": null,
  "links": ["https://t.me/telegram/440", "https://t.me/telegram/442"],
  "hashtags": [],
  "mentions": [],
  "isServiceMessage": false,
  "unsupportedContent": false,
  "scrapedAt": "2026-10-18T13:00:00.000Z",
  "error": null,
  "errorMessage": null
}
```

Reactions look like `{ "type": "emoji", "emoji": "👍", "customEmojiId": null, "count": 862 }`; `type` is `custom_emoji` for premium emoji and `paid` for Telegram Stars, which are not added to `reactionsCount`. A channel that could not be read keeps the same columns with an `error` code: `INVALID_CHANNEL`, `NO_PUBLIC_PREVIEW` (not found, private, or the owner disabled the web preview), `FETCH_FAILED` or `STORAGE_FAILED`.

The key-value store record `SUMMARY` lists per channel: title, subscribers, reading direction, posts returned, pages read, why paging stopped and the incremental position before and after the run.

### Pricing

Pay per event: one **Post** event per returned post. Error rows, skipped service messages and pages read only to find your date window are free. Current prices are on the **Pricing** tab. Set a maximum cost per run in the run options and the Actor stops there.

### Good to know

- Counters are rounded by Telegram: `1.58M` views become `1580000`.
- Media links come from Telegram's CDN and expire after some time; download files you want to keep.
- Videos larger than the web preview allows have a thumbnail and duration but no file link. Posts with content the web preview cannot show, such as stories or giveaways, are flagged with `unsupportedContent: true`.
- Incremental positions live in the named key-value store `telegram-channel-scraper-state` in your account and are saved after every stored batch, so even an aborted or timed-out run does not return the same posts twice. Use **Incremental state name** to keep separate positions for different schedules; delete a record there to start a channel over.
- Telegram rate-limits very large runs from one IP; if you see `FETCH_FAILED`, enable **Proxy**.

### Integrations

Use the Apify API, scheduler, webhooks and integrations (Zapier, Make, n8n, Google Sheets, Slack and others) to send new posts where you need them.

### Limits and fair use

The Actor reads only public channels through Telegram's own public web preview and does not log in. It does not collect members, phone numbers or private messages. You are responsible for using the data in line with Telegram's terms and the laws that apply to you.

### Support

Missing a field or found a post that is parsed wrong? Open an issue on the **Issues** tab with the channel and post link. Fixes ship quickly.

# Actor input Schema

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

Public channel usernames or links, one per line: telegram, @durov, https://t.me/durov, https://t.me/s/durov. Private channels, groups and invite links are not supported because they have no public web preview.

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

Stop after this many posts per channel, newest first. Each returned post is one charged event.

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

Only posts published on or after this date: YYYY-MM-DD, an ISO timestamp or a relative value like 7 days. Paging stops at the first older post, so the rest of the history is never fetched.

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

Only posts published on or before this date (the whole day is included). For an old window the actor jumps straight to it instead of paging through newer posts.

## `incremental` (type: `boolean`):

Remembers the last returned post per channel in the named key-value store telegram-channel-scraper-state in your account. The next run reads forward from it and returns only newer posts, oldest first; a run cut short by the post limit or your spending limit is continued where it stopped. The first run returns the newest posts by the limits above.

## `stateKey` (type: `string`):

Separate name for the remembered positions, so two schedules over the same channel do not steal each other's posts. Letters, digits, - and \_.

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

Also return service messages such as Channel created, Channel photo updated or pinned message notices.

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

How many channels are read at the same time. Pages of one channel are always read one after another.

## `maxRetries` (type: `integer`):

How many times a page is retried when Telegram answers with an error or rate limit.

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

t.me usually answers without a proxy. Turn on Apify Proxy if Telegram starts rate limiting your runs.

## Actor input object example

```json
{
  "channels": [
    "telegram",
    "durov"
  ],
  "maxPostsPerChannel": 20,
  "incremental": false,
  "stateKey": "default",
  "includeServiceMessages": false,
  "maxConcurrency": 5,
  "maxRetries": 3,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

One row per post: text, date, views, reactions, media links, forwards, replies, polls; error rows for channels that could not be read.

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

Per channel: title, subscribers, posts returned, why paging stopped and the incremental position.

# 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": [
        "telegram",
        "durov"
    ],
    "maxPostsPerChannel": 20,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("maksym_kotenko/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": [
        "telegram",
        "durov",
    ],
    "maxPostsPerChannel": 20,
    "proxyConfiguration": { "useApifyProxy": False },
}

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

```

## MCP server setup

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