# Telegram Channel Scraper: Public Posts, Views & Reactions (`stippled_journey/telegram-channels`) Actor

Posts from public Telegram channels: text, date, views, reactions, media links, polls, link previews, replies and forwards, with channel title and subscriber count on every row. Date and keyword filters, new-posts-only monitoring. No login, no API key, no start fee.

- **URL**: https://apify.com/stippled_journey/telegram-channels.md
- **Developed by:** [Navista Multani](https://apify.com/stippled_journey) (community)
- **Categories:** Social media, Developer tools
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.35 / 1,000 post or channel rows

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: Public Posts, Views & Reactions

A Telegram channel scraper for public channels: get every post with its text, date,
views, reactions and media links as clean rows, search channels by keyword, and monitor
them for new posts. No Telegram account, phone number, bot token or API key.

### Telegram public channel scraper: what you can use it for

- **Scrape data from Telegram channels into a spreadsheet or database.** Posts, views,
  reactions, links and media come out as columns, not screenshots.
- **Track news, crypto and market channels.** Run it hourly with "only new posts" on and
  send each new post to Slack, Google Sheets or your own app.
- **Search Telegram channels by keyword.** Find every post that mentions a coin, a brand
  or a hashtag across the channels you follow.
- **Measure channels.** Subscriber counts, post views and reactions over time, or a quick
  summary row per channel with its media counts.
- **Feed an AI agent or research pipeline.** Each row carries its channel's name, title
  and subscriber count, so it drops into an LLM context without joins.

### Telegram channel data: sample output

A channel row and a post row from the same run (CDN links shortened):

```json
{
  "type": "channel",
  "id": "telegram:channel/telegram",
  "source": "telegram",
  "channel": "telegram",
  "url": "https://t.me/telegram",
  "channel_title": "Telegram News",
  "channel_subscribers": 9240000,
  "channel_verified": true,
  "channel_description": "The official Telegram on Telegram. Much recursion. Very Telegram. Wow.",
  "channel_avatar_url": "https://cdn1.telesco.pe/file/...jpg",
  "photos_count": 16,
  "videos_count": 228,
  "files_count": null,
  "links_count": 378
}
```

```json
{
  "type": "post",
  "id": "telegram:telegram/459",
  "source": "telegram",
  "channel": "telegram",
  "channel_title": "Telegram News",
  "channel_subscribers": 9240000,
  "channel_verified": true,
  "post_id": 459,
  "url": "https://t.me/telegram/459",
  "posted_at": "2026-08-26T19:12:24Z",
  "edited": false,
  "text": "Signed Gifts. You can now add your signature and a comment to gifts you buy via the gift marketplace. ...",
  "links": ["https://t.me/telegram/455", "https://t.me/telegram/460"],
  "views": 1880000,
  "views_text": "1.88M",
  "reactions": [],
  "reactions_total": 0,
  "media": [{"type": "video", "url": "https://cdn1.telesco.pe/file/...mp4?token=...", "thumbnail_url": "https://cdn1.telesco.pe/file/...", "duration": "0:12", "duration_seconds": 12, "title": null}],
  "poll": null,
  "link_preview": null,
  "forwarded": false,
  "forwarded_from": null,
  "reply_to_post_id": null
}
```

Use the dataset's **Posts** or **Channels** view, or filter on `type`.

### Telegram channel post views, reactions and media

One row per post (`"type": "post"`):

- `text` (plain text, contact details removed), `posted_at`, `edited`, `url`, `post_id`
- `views` as a number and `views_text` as Telegram shows it ("1.88M")
- `reactions` with emoji, custom emoji ID, paid Star reactions and counts, plus `reactions_total`
- `media`: photos, videos (link, thumbnail, `duration` and `duration_seconds`), round
  videos, documents, voice, audio, stickers, locations
- `links` in the post, `link_preview` (URL, site, title, description, image)
- `poll` (question, type, voters, options with percentages)
- `reply_to_post_id`, `forwarded` and `forwarded_from` (the source channel)
- the channel's `channel_title`, `channel_subscribers` and `channel_verified` on every row
- `id`, `source` and `channel` on every row; `change` in monitoring mode

Plus one summary row per channel (`"type": "channel"`, on by default):

- `channel_title`, `channel_description`, `channel_subscribers`, `channel_verified`
- `channel_avatar_url`, `photos_count`, `videos_count`, `files_count`, `links_count`

Notes:

- Counts are as Telegram shows them, so large numbers are rounded ("157K" becomes 157000;
  `views_text` keeps the original).
- `media.type` is `photo`, `video`, `round_video`, `document`, `voice`, `audio`, `sticker`,
  `location`, or `other` for media the web preview cannot show. Video links carry a token
  and expire; download them soon after the run.
- `forwarded_from` names the source channel when a post was forwarded from a public
  channel; a post forwarded from a person shows `"kind": "user_or_hidden"` and no name.
- In monitoring mode each post row has `"change": "new"` and posts come oldest first.

### Telegram keyword search

With `keywords` set, the Actor asks Telegram's in-channel search (the search box on
`t.me/s/<channel>`) for each keyword in each channel you list, merges the results and
keeps posts that contain a keyword as a whole word. Telegram returns only the newest 22
results per search; when you ask for more matches than that, the Actor reads the
channel's history below the oldest result, so you still get every match within your
limits.

Measured on 2026-10-27, same request, history-only method versus search:

| Channel and keyword | Matches | Pages read | Time |
|---|---|---|---|
| telegram, "Fragment" | 3 (all there are) | 22 → 1 | 19.5 s → 0.6 s |
| cointelegraph, "bitcoin" | 20 | 9 → 2 | 15.0 s → 2.5 s |
| durov, "TON" | 20 | 7 → 1 | 6.4 s → 0.9 s |
| cointelegraph, "bitcoin" | 50 | 30 → 24 | 43.2 s → 24.2 s |

Both methods returned the same posts in every case.

Telegram's search also matches other word forms ("fragments" for "Fragment"); those
extra results are dropped by the whole-word check, so results match the keyword exactly
as typed.

### Telegram channel posts history and new-post monitoring

For history, set `maxPostsPerChannel` to 0 to read back to your date limit (up to 10,000
posts per channel per run), and use `sinceDate` or `postedWithinDays` to stop earlier.

For monitoring:

1. Save your input as a task with `"onlyChanges": true` (and a `stateName` if you run
   several tasks).
2. Add a schedule in Apify Console (Schedules, then Create), for example hourly.
3. Connect the output: a webhook, an integration (Google Sheets, Slack, Make, Zapier, n8n),
   or read the dataset from the API.

The first run returns everything within your limits; later runs return only newer posts.
If a run hits your spending limit, the next run picks up where it stopped. Set `minRows`
if you want a run to fail (and alert you) when it finds fewer posts than expected.

### Ready-made Telegram channel scraper examples

Open one, press Start, and change the channels to your own:

- [Crypto news from Telegram channels](https://apify.com/stippled_journey/telegram-channels/examples/crypto-telegram-channels)
- [Search Telegram channels by keyword](https://apify.com/stippled_journey/telegram-channels/examples/search-telegram-channels-keyword)
- [Telegram channel stats: subscribers and media counts](https://apify.com/stippled_journey/telegram-channels/examples/telegram-channel-stats)

### How it compares

- **No start fee and no minimum.** You pay per row returned, so a scheduled check that finds
  nothing new costs nothing.
- **Fast keyword search.** Keywords use Telegram's own in-channel search, so a rare word in
  a long channel is found in one request instead of reading the whole history (measured
  above).
- **Built-in monitoring.** "Only new posts" remembers what each channel already delivered;
  no cursor bookkeeping on your side.
- **Rows stand alone.** Every post row carries the channel's name, title and subscriber
  count, so it drops into a spreadsheet or an AI agent's context without joins.

### Pricing

From **$0.50 per 1,000 posts** ($0.0005 per post). The optional channel summary row is
$0.0005 per channel. No start fee, no per-run fee. The same price on every Apify plan.

| Run | Rows | Cost |
|---|---|---|
| Last 100 posts of one channel, with its summary row | 100 posts + 1 channel | $0.0505 |
| "bitcoin" in 5 crypto channels, 20 matches each, no summary rows | 100 posts | $0.05 |
| 10 channels checked every hour for a month, about 30 new posts a day in total | about 900 posts + 10 channel rows (first run only) | about $0.46 a month |

Runs stop cleanly at your spending limit. In monitoring mode, posts not yet delivered
stay pending for the next run.

### Input examples and settings

The last 20 posts of two channels:

```json
{
  "channels": ["telegram", "nytimes"],
  "maxPostsPerChannel": 20
}
```

Keyword search across channels, last 30 days:

```json
{
  "channels": ["cointelegraph", "durov", "binance_announcements"],
  "keywords": ["bitcoin", "TON", "#AI"],
  "postedWithinDays": 30,
  "maxPostsPerChannel": 50
}
```

Channel details only (one row per channel, no posts):

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

Monitoring, for a schedule:

```json
{
  "channels": ["tass_agency", "bbcrussian"],
  "onlyChanges": true,
  "maxPostsPerChannel": 200,
  "stateName": "news-hourly"
}
```

| Field | What it does |
|---|---|
| `channels` | Usernames or links: `telegram`, `@nytimes`, `t.me/cointelegraph`, `https://t.me/s/bbcrussian`, or a post link. |
| `keywords` | Keep posts containing any of these as a whole word, case-insensitive. |
| `postedWithinDays` / `sinceDate` | Only recent posts. Reading stops at the first older post. |
| `afterPostId` | Only posts with a higher ID than this. |
| `maxPostsPerChannel` | Newest first; default 100, 0 for the whole history back to the date limit (up to 10,000). |
| `maxPosts` | Cap across all channels. |
| `includeMedia` | Photo, video and thumbnail links (on by default). |
| `includeChannelInfo` | One summary row per channel (on by default). |
| `channelInfoOnly` | Only the summary rows, no posts. |
| `onlyChanges` | Monitoring mode: only posts newer than the last run delivered. |
| `stateName` | Name for the monitoring state, to keep several schedules apart. |
| `minRows` | Fail the run when fewer posts come back, so your alerts fire. |

### FAQ

**How do I scrape data from a Telegram channel?**
Put the channel's username or link in `channels` and start the run. You get one row per
post, newest first (100 per channel by default), plus a summary row for the channel.
Export the dataset as JSON, CSV or Excel, or read it from the API.

**Do I need a Telegram account, API key or bot?**
No. The Actor reads Telegram's public web preview, the pages anyone can open without
logging in.

**How do I get a Telegram channel's full posts history?**
Set `maxPostsPerChannel` to 0. The run reads back to your date limit, up to 10,000 posts
per channel per run.

**Can I get Telegram channel post views?**
Yes. Every post row has `views` as a number and `views_text` as Telegram shows it. Large
counts are rounded by Telegram ("157K"), so exact numbers are not available.

**Can I search Telegram by keyword?**
Within the channels you list, yes: set `keywords` and the Actor uses Telegram's own
in-channel search. It does not discover new channels; you choose which channels to search.

**How do I use this Telegram channel scraper from Python?**
Install the Apify API client (`pip install apify-client`) and call the Actor:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("stippled_journey/telegram-channels").call(
    run_input={"channels": ["durov"], "keywords": ["TON"], "maxPostsPerChannel": 50}
)
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["type"], row.get("posted_at"), row.get("views"), row.get("text"))
```

The same works from JavaScript, the Apify MCP server, or integrations such as Make,
Zapier, n8n and LangChain.

**Does it download photos and videos?**
It returns media links (photos, videos, thumbnails) on Telegram's CDN, not the files.
Video links expire, so download them soon after the run. Turn `includeMedia` off for
smaller rows.

**Which channels work?**
Public channels with Telegram's web preview on (`t.me/s/<channel>` shows posts). Private
channels, invite links, groups and user profiles are not supported. Channels with the
preview off are listed in the run log and not charged.

**Can it scrape a channel's members or subscriber list?**
No. It returns the subscriber count only. Member lists, comments and phone numbers are
personal data and out of scope by design.

**Are author names or contact details collected?**
Author signatures are not collected. Email addresses and phone numbers in post text and
channel descriptions are replaced with `[email removed]` / `[phone removed]`.

### Data source and terms

The Actor reads Telegram's public web preview (`t.me/s/<channel>`) and its search, the
pages Telegram serves to anyone without logging in, 20 posts per request, politely paced.
Telegram's terms of service (telegram.org/tos, checked 2026-10-27) set rules for using
Telegram (no spam, scams, illegal content or copyright violation) and say nothing about
automated access; t.me has no robots.txt. You are responsible for how you use the data,
including copyright in the posts you collect.

# Actor input Schema

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

Public channel usernames or links, e.g. 'telegram', '@nytimes', 't.me/cointelegraph' or a post link. Invite links, private channels and groups are not supported.

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

Keep only posts that contain any of these as a whole word, case-insensitive ('bitcoin', '#AI', '$TON'). Uses Telegram's own channel search, so matches deep in a channel's history are found fast. Leave empty for every post.

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

Only posts from the last N days. Leave empty for no date limit.

## `sinceDate` (type: `string`):

Only posts from this date (UTC) on. If both date options are set, the later date wins.

## `afterPostId` (type: `integer`):

Only posts with a higher ID than this (the number at the end of a post link, t.me/channel/1234). Most useful with one channel. For repeated runs, 'Only new posts' does this for you.

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

Newest posts first. 0 means no limit: the whole history back to the date limit (up to 10,000 posts per channel per run).

## `maxPosts` (type: `integer`):

Cap across all channels, newest first. 0 means no cap.

## `includeMedia` (type: `boolean`):

Photo, video and thumbnail links (Telegram CDN; video links expire, so download them promptly). Off gives smaller rows.

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

One extra row per channel (type 'channel') with title, description, subscriber count, verified flag, avatar and photo/video/file/link counts. In monitoring mode it is sent once per channel, with the first new posts; every post row also carries the channel's title and subscriber count.

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

Return only the channel summary rows, no posts: one page read per channel.

## `onlyChanges` (type: `boolean`):

Return only posts newer than the newest one the previous run delivered (same channels and keywords, or the same state key), oldest first. The first run returns everything within the limits.

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

Optional name for the monitoring state, to keep several schedules apart or keep one state across input changes.

## `minRows` (type: `integer`):

Mark the run failed when it returns fewer posts than this, so your alerts fire. 0 turns the check off.

## Actor input object example

```json
{
  "channels": [
    "telegram",
    "nytimes"
  ],
  "keywords": [],
  "maxPostsPerChannel": 20,
  "maxPosts": 0,
  "includeMedia": true,
  "includeChannelInfo": true,
  "channelInfoOnly": false,
  "onlyChanges": false,
  "minRows": 0
}
```

# Actor output Schema

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

Channel summary rows first (type 'channel'), then the posts (type 'post'), one normalized row each.

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

Counts for this run: rows pushed, what was read, and anything that failed or hit a cap.

# 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",
        "nytimes"
    ],
    "maxPostsPerChannel": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("stippled_journey/telegram-channels").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",
        "nytimes",
    ],
    "maxPostsPerChannel": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("stippled_journey/telegram-channels").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",
    "nytimes"
  ],
  "maxPostsPerChannel": 20
}' |
apify call stippled_journey/telegram-channels --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,stippled_journey/telegram-channels"
        }
    }
}
```

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/lujnOlcgTwpedFDwo/builds/mpughjQLpqwMH8MUi/openapi.json
