# Telegram Channel Scraper & Keyword Search: posts, views, media (`brenton8907/telegram-channel-scraper`) Actor

Scrape any public Telegram channel with no login or API key: messages with text, dates, views, reactions, media URLs, forwards, polls and link previews, plus subscriber counts. Keyword search inside channels, date windows, and a changes-only monitor for scheduled Slack/email alerts. Pay per message.

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

## Pricing

from $0.70 / 1,000 messages

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Telegram Channel Scraper & Keyword Search: posts, views, reactions, media, subscriber counts

Scrape any **public Telegram channel** without a login, a phone number, or API keys, and **search channels by keyword** using Telegram's own in-channel search. Reads Telegram's own public web preview (`t.me/s/<channel>`), so it needs no browser, no proxies and no session, and it does not break when the Telegram app changes. Returns the full post history as flat rows, plus one channel-info row with the subscriber count. Includes a **changes-only monitor mode** so a scheduled run gives you only what was posted since last time.

**Try it free:** every Apify account includes free monthly usage; 1,000 messages cost $1, so a first run costs nothing.

### Keyword search across channels

Search many channels for the terms you care about without downloading their full history. The actor uses Telegram's in-channel search (`t.me/s/<channel>?q=`), one request per keyword per page of 20 results, so 20 channels × 3 keywords is about 60 requests and you pay only for the posts that match.

```json
{
  "channels": ["bloomberg", "cryptosignals", "telegram", "durov"],
  "keywords": ["airdrop", "listing", "ETF"],
  "maxMessagesPerChannel": 50,
  "includeChannelInfo": false
}
```

Every row has `matched_keyword`, so you know which term found it. Combine with `sinceDate` to search only recent posts. Search is per channel (Telegram has no public cross-channel search); give the actor the channel list you want covered.

### Channel monitor: schedule it, get new posts in Slack or email

Turn any set of channels into an alert feed. Three steps, no code:

1. **Create a task** from this actor with the input below and run it once. The first run seeds the newest message ID per channel.
2. **Schedule it** (task → *Schedule*, or Console → Schedules): every 15 minutes, hourly or daily. A quiet channel costs one request and no result charges, so frequent polling is cheap.
3. **Alerts**: in the task's *Integrations* tab add **Slack** or **Gmail** on "run succeeded". The run's status message is written for this: `12 NEW post(s) since last run across 3/3 channel(s)` or `No new posts since last run`. Slack template (Handlebars):

   ```
   *Telegram monitor:* {{resource.statusMessage}}
   <https://console.apify.com/storage/datasets/{{resource.defaultDatasetId}}|Open the new posts>
   ```

   To notify only when something is new, or to forward the post text itself, route the run through **n8n / Make / Zapier** (Apify triggers exist for all three) and filter on the dataset items.

```json
{
  "channels": ["cryptosignals", "telegram", "bloomberg"],
  "changesOnly": true,
  "monitorStoreName": "signals-watch",
  "includeChannelInfo": false
}
```

Add `keywords` to the same input to get alerts only for posts that mention your terms. Cost at scale: 20 channels posting 30 new messages a day in total = **$0.03/day, under $1/month**.

### What it does

- Pulls messages from one or many public channels, newest first, up to a limit you set.
- Every message row has: text (plain and optional HTML), date, views, reactions (emoji + counts, custom emoji IDs, paid star reactions), author signature, edited flag, forwarded-from, reply-to (with quoted text), media (photos, videos, GIFs, round videos, documents, stickers, voice, audio) with direct URLs and thumbnails, polls with options and vote counts, link previews, extracted links, hashtags and mentions.
- Channel info row: title, description, subscriber count, photo/video/file/link counts, avatar URL.
- **Windows:** since/until date, since message ID. Paging stops as soon as the window is left, so tight windows cost a request or two.
- **Keyword search:** uses Telegram's own in-channel search, one request per keyword per page, instead of downloading the whole history.
- **Monitor mode:** remembers the newest message ID per channel in a named key-value store and outputs only new messages. A quiet channel costs one request and zero result charges.
- Channels without a public preview (private channels, groups, users, bots, invite links) are reported as error rows, never charged, and the run still succeeds for the other channels.

### Who it's for

- Analysts, journalists and OSINT researchers archiving or monitoring channels (news, official announcements, regional sources).
- Crypto and trading teams tracking signal and announcement channels, with views and reactions as engagement signals.
- Brand and community teams measuring reach (views, reactions, subscriber growth over repeated runs).
- AI agents and alerting workflows: schedule a changes-only run and act on the rows that come back.

### Input examples

**Latest 100 posts from two channels, with channel info (default):**

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

**Everything a channel posted in January 2026:**

```json
{ "channels": ["telegram"], "sinceDate": "2026-01-01", "untilDate": "2026-01-31", "maxMessagesPerChannel": 5000 }
```

**Only posts mentioning specific terms, newest 50 per term:**

```json
{ "channels": ["bloomberg"], "keywords": ["oil", "Fed"], "maxMessagesPerChannel": 50, "includeChannelInfo": false }
```

**Monitor** (see the section above): the first run with `changesOnly: true` outputs the latest `maxMessagesPerChannel` posts and seeds the store; later runs output only newer posts. Use a different `monitorStoreName` per watchlist.

**Fetch only what is newer than a message you already have:**

```json
{ "channels": ["durov"], "sinceMessageId": 540 }
```

### Output

One row per message; with `includeChannelInfo` also one row per channel.

| Field | Description |
|---|---|
| `channel`, `channel_title` | Channel username and display title |
| `message_id`, `url` | Numeric post ID (increasing) and `https://t.me/<channel>/<id>` |
| `date` | Posted time, ISO 8601 UTC |
| `text` | Plain text with line breaks; `text_html` when `includeHtml` is on |
| `author` | Author signature, when the channel shows one |
| `views` | View count as an integer (Telegram shows "1.2M"; we return 1200000) |
| `reactions`, `reactions_total` | `[{ "emoji", "count", "custom_emoji_id"?, "paid"? }]` and the sum |
| `is_edited`, `is_service` | Edited flag; service messages (pinned, channel created, etc.) |
| `forwarded_from` | `{ "name", "url", "author" }` or null |
| `reply_to` | `{ "message_id", "url", "author", "text", "thumbnail_url" }` or null |
| `media`, `media_types` | `[{ "type", "url", "thumbnail_url", "duration", "title", ... }]`; types: photo, video, gif, round\_video, document, sticker, voice, audio |
| `link_preview` | `{ "url", "site_name", "title", "description", "image_url" }` or null |
| `poll` | `{ "question", "type", "options": [{ "text", "percent" }], "votes" }` or null |
| `links`, `hashtags`, `mentions` | Extracted from the text |
| `matched_keyword` | Only with `keywords`: which search term returned this post |
| `is_new` | Only in monitor mode |
| `scraped_at` | When the row was fetched (UTC) |

Channel row: `username`, `url`, `title`, `description`, `subscribers`, `photos_count`, `videos_count`, `files_count`, `links_count`, `photo_url`, `messages_scraped`, `scraped_at`.

Media URLs point at Telegram's CDN. Video URLs carry a token and expire after some hours; download them in the same workflow if you need to keep them.

### Pricing

Pay per result. You are charged only for rows written to the dataset; error rows are free.

| Event | Price | When |
|---|---|---|
| Message | $1.00 per 1,000 messages | Every message row |
| Channel info | $2.00 per 1,000 channels | One row per channel when `includeChannelInfo` is on |

Examples: the latest 1,000 posts from a channel cost $1.00. A monitor run over 20 channels that finds 12 new posts costs $0.012. The first monitor run charges every post it seeds with, like any other run.

Set **Maximum total charge** on the run to cap spend; the actor stops cleanly at the cap.

### Limits and notes

- Public channels only. The preview does not exist for private channels, groups, supergroups, users, bots or invite links (`t.me/+...`); those come back as error rows.
- Some channels turn the web preview off in their settings; they are reported the same way.
- 20 posts per request. 10,000 posts is about 500 requests, roughly 5 minutes at the default pacing.
- Comments (discussion group replies) are not part of the channel preview and are not returned. Reply-to refers to replies inside the channel itself.
- Views and reaction counts are rounded the way Telegram shows them (1.2K, 45.8K, 23M).
- Custom emoji reactions carry `custom_emoji_id`; their glyph is not always in the HTML, so `emoji` can be null for them.
- Telegram serves the preview generously, but very large jobs at zero delay can get HTTP 429; the actor retries with backoff and reports rate-limit hits in the status message. Raise `requestDelaySeconds` or enable Apify Proxy if you see them.
- Monitor state is per `monitorStoreName`. Do not run two monitor runs against the same store at the same time.
- Data is Telegram's public content as displayed at t.me. Respect the channel owners' rights and local law when reusing it.

### Use from an AI agent (MCP)

Every Apify actor is available as a tool through the [Apify MCP server](https://mcp.apify.com). Add it to Claude, Cursor, or any MCP client and call this actor by name with the JSON input above. Suggested agent pattern: keep a watchlist, run with `changesOnly: true` on a schedule, and act only on the rows returned.

### Support

Open an issue on the actor page with the run ID and input. If Telegram changes its preview markup, affected fields go null rather than failing the run; report which field and we will patch it. If the actor saved you time, a review helps other people find it.

# Actor input Schema

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

Public channel usernames or t.me links, one per line: durov, @bloomberg, https://t.me/telegram, https://t.me/s/cryptosignals. Private channels, groups, users, bots and invite links have no public preview and are reported as errors, not charged.

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

Newest first. 20 messages per request, so 1,000 messages is about 50 requests. Hard cap 50,000.

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

Only messages posted on or after this date (UTC). YYYY-MM-DD or ISO 8601 datetime. Paging stops as soon as an older message is reached, so tight windows are cheap.

## `untilDate` (type: `string`):

Only messages posted on or before this date (UTC). YYYY-MM-DD or ISO 8601 datetime.

## `sinceMessageId` (type: `integer`):

Only messages with an ID greater than this. Use the highest message\_id from your last run to fetch just what is new (or turn on changes-only mode, which does this for you).

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

Optional. Search each channel for these terms using Telegram's own in-channel search (server side, one search per keyword) instead of reading the full history. Results carry a matched\_keyword field.

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

Add one channel row per channel: title, description, subscriber count, photo/video/file/link counts, avatar URL. Charged as a channel record.

## `includeHtml` (type: `boolean`):

Also return the message body as HTML (text\_html) with Telegram's formatting, links and custom emoji markup.

## `changesOnly` (type: `boolean`):

Remember the newest message ID per channel in a key-value store and output only messages posted since the last run. The first run outputs everything (all messages are new) and seeds the store. Ideal for scheduled runs that feed alerts or agents.

## `monitorStoreName` (type: `string`):

Key-value store that holds the per-channel last message IDs for changes-only mode. Use a different name per watchlist.

## `requestDelaySeconds` (type: `number`):

Polite pacing toward t.me. 0.5 is safe; lower it for large jobs at your own risk.

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

Optional. t.me serves the public preview without a proxy; enable Apify Proxy only if your runs get HTTP 429.

## Actor input object example

```json
{
  "channels": [
    "durov",
    "bloomberg"
  ],
  "maxMessagesPerChannel": 100,
  "includeChannelInfo": true,
  "includeHtml": false,
  "changesOnly": false,
  "monitorStoreName": "telegram-channel-monitor",
  "requestDelaySeconds": 0.5,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per message, plus one channel-info row per channel when enabled, as JSON from the default dataset.

## `csv` (type: `string`):

The same rows as CSV, for spreadsheets.

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

The run page with the Overview table, status message and log.

# 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",
        "bloomberg"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

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

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

```

## MCP server setup

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