# Telegram Channel Scraper — Posts, Views, Reactions & Monitor (`crawloop/telegram-channel-scraper`) Actor

Scrape public Telegram channels into JSON: posts, views, reactions, media, forwards, and channel profiles. No login. Incremental monitor for scheduled runs. A Telegram API alternative for Python, Node.js, and MCP.

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

## Pricing

from $0.99 / 1,000 telegram 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?

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 — Public Posts, Views & Monitor

> Unofficial tool for publicly accessible Telegram web preview data. Telegram and related trademarks belong to their respective owners. Not affiliated with, sponsored by, or endorsed by Telegram FZ-LLC. Provided for informational use only; users must comply with applicable terms and laws. Public channels with web preview only — no private chats, members, or phone numbers.

| Social listening | Product launches |
| :--- | :--- |
| **Telegram Channel Scraper** ◄── you are here | [Bluesky Scraper](https://apify.com/crawloop/bluesky-scraper) |
| Public posts, views, reactions, profiles, keyword filter, monitor | Posts, profiles, search, threads, graph, monitor |

**Telegram scraper** for Apify — extract **public channel posts, views, reactions, media URLs, forwards, link previews, and channel profiles** from the public web preview, with **no login, phone number, or bot token**. Use it as a **Telegram API alternative** from **Python**, **Node.js**, or **MCP**.

**Monitor mode** stores per-channel watermarks in a named key-value store and returns **only posts newer than the last run** — built for hourly/daily schedules, brand listening, and competitor tracking.

### When to use this Actor

- Archive public channel posts (news, crypto, government, brand, OSINT)
- Pull view counts, reaction tallies, and media CDN URLs without an MTProto session
- Resolve channel profiles (title, bio, subscriber count, verified)
- Keyword / hashtag filter across a list of channels you already follow
- Scheduled incremental monitoring of new posts (optional webhook / bot alerts)

### When not to use

- Private groups, invite links (`t.me/+`, `joinchat`), or `t.me/c/…` numeric chats
- Member lists, phone numbers, last-seen, or adding users
- Channels that disabled the public web preview
- Comments from a linked discussion group (not exposed on the public preview)

### Key features

- **No login** — HTTP parse of the public preview (`t.me/s/{channel}` + embed), not a shared user session
- **4 modes** — posts, profiles, keyword search inside given channels, incremental **monitor**
- **Full public post contract** — text + HTML, views (`1.59M` → integer), reactions when present, photos/videos, documents, polls, forwards, replies, link previews, hashtags, mentions
- **Multi-channel input** — usernames, `@handles`, `t.me` / `t.me/s` URLs, or a single post URL
- **Date bounds** — ISO dates or relative lookback (`7 days`, `2 weeks`)
- **Stable pagination** — `rel=prev` + `?before=` with per-channel caps
- **Monitor watermarks** — `lastPostId` in a named KV store; optional baseline-only first run
- **Preview-disabled channels** — one `error` row, the rest of the run continues
- **RateGate** — client RPS budget + 429/403 backoff; proxy optional

### Input

| Parameter | Description |
|-----------|-------------|
| `mode` | `posts`, `profiles`, `search`, `monitor` |
| `startUrls` | `t.me` / `t.me/s` URLs or a single post URL |
| `channels` | Usernames or `@handles` |
| `searchQuery` / `searchQueries` | Keyword filter (required for `search`) |
| `sinceDate` / `untilDate` | ISO or relative lookback |
| `maxItems` | Dataset row cap (`0` = unlimited) |
| `maxPostsPerChannel` | Per-channel cap |
| `includeChannelInfo` | Copy profile fields onto every post (default true) |
| `includeReactions` | Parse reaction tallies when the preview includes them |
| `onlyWithMedia` / `messageTypes` | Optional filters |
| `monitorStoreName` | Named KV store for watermarks |
| `monitorBaselineOnly` | First run: set watermark, push nothing |
| `webhookUrl` / `telegramToken` / `telegramChatId` | Monitor alerts |
| `concurrency` / `maxRequestsPerSecond` | Throughput (keep modest) |
| `proxyConfiguration` | Optional; enable if you see HTTP 403 |

Channel archive:

```json
{
  "mode": "posts",
  "channels": ["telegram", "durov"],
  "maxItems": 100,
  "includeChannelInfo": true,
  "includeReactions": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

Keyword search inside channels:

```json
{
  "mode": "search",
  "channels": ["telegram"],
  "searchQuery": "polls",
  "maxItems": 50,
  "sinceDate": "7 days"
}
```

Monitor (schedule after a baseline run):

```json
{
  "mode": "monitor",
  "channels": ["telegram"],
  "monitorStoreName": "telegram-monitor-brand-x",
  "monitorBaselineOnly": true,
  "maxItems": 200
}
```

After the baseline run, set `monitorBaselineOnly` to `false` (or omit it) so later runs push only new posts. Use a distinct `monitorStoreName` per schedule.

### Output

One dataset row per post (or one profile / error row). Channel fields are copied onto each post so you do not need a join.

| Field | Description |
|-------|-------------|
| `type` | `post`, `profile`, or `error` |
| `channelUsername` / `channelTitle` / `channelSubscriberCount` | Channel identity |
| `postId` / `url` / `date` | Message identity |
| `text` / `textHtml` | Body |
| `views` | Integer view count |
| `reactions` / `totalReactions` | Tallies when the preview exposes them |
| `mediaType` / `photos` / `videos` | Media CDN URLs (time-limited) |
| `linkPreview` / `forwardedFrom` / `replyToUrl` | Graph |
| `hashtags` / `mentions` / `urls` | Extracted entities |
| `error` | `preview_disabled`, `not_found`, `private_invite`, … |
| `monitorTarget` | Present on monitor rows |

```json
{
  "type": "post",
  "channelUsername": "examplechannel",
  "channelTitle": "Example Channel",
  "channelVerified": true,
  "channelSubscriberCount": 125000,
  "postId": 123,
  "url": "https://t.me/examplechannel/123",
  "date": "2025-01-15T12:00:00Z",
  "text": "This is an example channel post.",
  "views": 1590000,
  "mediaType": "photo",
  "photos": [{ "url": "https://cdn4.telesco.pe/file/example.jpg" }],
  "reactions": [{ "emoji": "⭐", "isPaid": true, "count": 12 }],
  "totalReactions": 12,
  "hashtags": ["#example"]
}
```

Export JSON, JSONL, CSV, Excel, or XML from the default dataset.

### Use cases

- **Brand / OSINT listening** — schedule monitor on public announcement channels
- **Crypto / news research** — archive posts with views and reaction totals
- **Content pipelines / RAG** — structured post text + source URLs for search indexes
- **Competitor tracking** — profile subscriber counts plus new posts only
- **MCP / agents** — call the Actor from an AI assistant without writing a Telethon client

### Integration examples

#### Node.js

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('crawloop/telegram-channel-scraper').call({
  mode: 'posts',
  channels: ['telegram'],
  maxItems: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.slice(0, 5));
```

#### Python

```python
from apify_client import ApifyClient

client = ApifyClient(token)
run = client.actor("crawloop/telegram-channel-scraper").call(
    run_input={"mode": "posts", "channels": ["telegram"], "maxItems": 20}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item.get("type"), item.get("channelUsername"), item.get("views"), item.get("text"))
```

#### cURL

```bash
curl "https://api.apify.com/v2/acts/crawloop~telegram-channel-scraper/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mode":"posts","channels":["telegram"],"maxItems":20}'
```

### MCP and AI assistants

Use this Actor from AI tools via [Apify MCP](https://docs.apify.com/platform/integrations/mcp). Connect your Apify account, then call `crawloop/telegram-channel-scraper`.

Example prompts:

- "Run Telegram Channel Scraper posts for telegram max 20 and return text, views, and reactions"
- "Search the telegram channel for posts about polls in the last 7 days"
- "Chain Telegram Channel Scraper then Bluesky Scraper for cross-network brand listening"

### Suite next step

For public social search and incremental author feeds, use [Bluesky Scraper](https://apify.com/crawloop/bluesky-scraper).

### FAQ

**Do I need a phone number, bot token, or API id?**\
No. The Actor reads the public web preview. Private data is out of scope.

**Why are reactions missing on some channels?**\
The public preview does not always include reaction HTML. The Actor returns them when present and omits a fake MTProto contract.

**Do media URLs last forever?**\
CDN links from the preview are time-limited. Re-run the Actor when you need a fresh URL. Original file download is not part of this Actor.

**What happens if a channel disabled preview?**\
You get one `type: error` row with `error: preview_disabled`. Other channels in the same run continue.

**Can I scrape group members?**\
No. Member lists are not on the public preview and are not collected.

### Related Actors

| Actor | Role |
| :--- | :--- |
| Telegram Channel Scraper ◄── you are here | Public Telegram posts, profiles, search, monitor |
| [Bluesky Scraper](https://apify.com/crawloop/bluesky-scraper) | Bluesky / AT Protocol public data |
| [Product Hunt Scraper](https://apify.com/crawloop/producthunt-scraper) | Product launches, reviews, comments |

# Actor input Schema

## `mode` (type: `string`):

posts = public channel history; profiles = channel/group metadata only; search = keyword filter inside the given channels; monitor = incremental new posts (named KV watermark).

## `startUrls` (type: `array`):

Public t.me / t.me/s URLs, or a single post such as https://t.me/telegram/454. Private invite links (t.me/+ and joinchat) are rejected.

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

Public usernames or @handles (telegram, @durov). Same as Start URLs; merged and de-duplicated.

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

Keyword or hashtag to keep (search / monitor, and optional filter in posts). Uses the public preview search plus a text match.

## `searchQueries` (type: `array`):

Optional extra keywords. Merged with Search query.

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

Lower bound. ISO date (2026-01-01) or relative lookback (7 days, 2 weeks, 1 month).

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

Upper bound. ISO date/datetime. Posts newer than this are skipped.

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

Maximum dataset rows to push. 0 = unlimited.

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

Per-channel cap. 0 = no per-channel cap (still bounded by Max items).

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

Copy title, description, subscriber count, verified flag, and avatar onto every post row.

## `includeReactions` (type: `boolean`):

Parse reaction tallies when the public preview exposes them (not all channels do).

## `onlyWithMedia` (type: `boolean`):

Skip text-only posts.

## `messageTypes` (type: `array`):

Optional allow-list. Leave empty for all types.

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

Named key-value store for per-channel lastPostId watermarks. Use a distinct name per schedule.

## `monitorBaselineOnly` (type: `boolean`):

If true and no watermark exists yet, record the newest post ids without pushing rows. Later runs emit only new posts.

## `resetMonitorState` (type: `boolean`):

Clear watermarks in the monitor store before this run.

## `webhookUrl` (type: `string`):

Optional. Monitor mode POSTs each new post JSON to this URL (Slack/Discord-style text+content).

## `telegramToken` (type: `string`):

Optional bot token for monitor alerts (new posts).

## `telegramChatId` (type: `string`):

Chat/user ID that receives monitor alerts (requires token).

## `concurrency` (type: `integer`):

Parallel channel fetches. Keep low; Telegram rate-limits aggressive clients.

## `maxRequestsPerSecond` (type: `number`):

Client-side rate limit for t.me requests.

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

Optional. Enable Apify Proxy if you see HTTP 403; switch to RESIDENTIAL for stubborn blocks.

## Actor input object example

```json
{
  "mode": "posts",
  "startUrls": [
    {
      "url": "https://t.me/s/telegram"
    }
  ],
  "channels": [
    "telegram"
  ],
  "searchQuery": "",
  "searchQueries": [],
  "sinceDate": "",
  "untilDate": "",
  "maxItems": 100,
  "maxPostsPerChannel": 0,
  "includeChannelInfo": true,
  "includeReactions": true,
  "onlyWithMedia": false,
  "messageTypes": [],
  "monitorStoreName": "telegram-channel-monitor",
  "monitorBaselineOnly": false,
  "resetMonitorState": false,
  "concurrency": 3,
  "maxRequestsPerSecond": 4,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Default dataset items (posts, profiles, and error rows).

# 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 = {
    "startUrls": [
        {
            "url": "https://t.me/s/telegram"
        }
    ],
    "channels": [
        "telegram"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawloop/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 = {
    "startUrls": [{ "url": "https://t.me/s/telegram" }],
    "channels": ["telegram"],
}

# Run the Actor and wait for it to finish
run = client.actor("crawloop/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 '{
  "startUrls": [
    {
      "url": "https://t.me/s/telegram"
    }
  ],
  "channels": [
    "telegram"
  ]
}' |
apify call crawloop/telegram-channel-scraper --silent --output-dataset

```

## MCP server setup

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