# Telegram Channel Scraper (`smolevich90/telegram-channel-scraper`) Actor

Scrape posts from public Telegram channels by keyword, with views, reactions and reposts deduplicated. No login, no API key, no Telethon.

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

## Pricing

from $2.50 / 1,000 post returneds

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?

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

**Scrape any public Telegram channel by keyword — no login, no phone number, no API key.** Point this Actor at one or more public channels and it returns their posts as clean structured rows: text, views, reactions, media, post date and a direct `t.me` link. Add a keyword and it uses Telegram's own built-in per-channel search to return only matching posts, scored by relevance.

It reads Telegram's public web preview (`https://t.me/s/<channel>`) — the same page Telegram serves to a logged-out browser. No Telegram account, no Telethon, no MTProto, no headless browser. Channel names in, structured posts out.

### What you can do with it

- **Keyword monitoring** — track a brand, ticker, product or topic across dozens of channels and get back only the posts that mention it, newest and most relevant first.
- **Lead and signal generation** — scrape channels where people post offers, jobs or "looking for X", then filter by views and reactions to keep only the posts that got traction.
- **Content and trend research** — pull a channel's recent posts with engagement numbers attached and feed them straight into your own analysis, LLM or spreadsheet.
- **OSINT and news monitoring** — collect public channel output at scale without an account tied to your phone number, with reposts collapsed so you read each story once.
- **Dataset building** — export thousands of posts as JSON, CSV or Excel to train, benchmark or archive.

### Output fields

One dataset row is one post. Every row carries:

| Field | Type | What it is |
|---|---|---|
| `channel` | string | Channel username the post came from |
| `message_id` | integer | Telegram message id |
| `url` | string | Direct `t.me/<channel>/<message_id>` link |
| `date` | string | When it was posted, ISO 8601 UTC |
| `text` | string | The post text, HTML stripped and unescaped |
| `views` | integer | View count as a number (`1.38M` → `1380000`), or `null` if hidden |
| `reactions` | integer | Total reactions across every emoji, including paid star reactions |
| `reactions_by_emoji` | object | Per-emoji breakdown, e.g. `{"❤": 231, "🔥": 88, "⭐": 4}` |
| `media` | array | Media on the post: `type` (`voice`, `round_video`, `video`, `document`), `url`, `duration_sec`, `title` |
| `is_repost` | boolean | `true` on a post detected as a near-identical repost of another |
| `duplicate_of` | string | On a repost, the `t.me` link of the original it copies |
| `relevance_score` | number | Keyword relevance (BM25) when a search query is set, otherwise `null` |

### Example output

```json
{
  "channel": "telegram",
  "message_id": 441,
  "url": "https://t.me/telegram/441",
  "date": "2026-05-14T16:08:31+00:00",
  "text": "Bot-to-Bot Communication is here — bots can now talk to each other.",
  "views": 1380000,
  "reactions": 342,
  "reactions_by_emoji": { "❤": 231, "🔥": 88, "⭐": 23 },
  "media": [],
  "is_repost": false,
  "duplicate_of": null,
  "relevance_score": null
}
```

### How to scrape a Telegram channel

1. Paste one or more public channels into **Telegram channels** — `@durov`, `t.me/durov` and `https://t.me/durov` all point at the same channel and all work.
2. Optionally type a **keyword search** to scrape only posts that match it, using Telegram's own in-channel search.
3. Optionally set a date range, a minimum view count, or a minimum number of reactions under **Filters**.
4. Run it. Rows stream into the dataset, then near-identical reposts are collapsed at the end.

### Input options

| Field | What it does | Default |
|---|---|---|
| `channels` | Public channels to scrape (`@name`, `t.me/name` or a full link) | required |
| `query` | Keyword — scrape only matching posts via Telegram's per-channel search | none |
| `since` / `until` | Only posts inside this date range (`YYYY-MM-DD`) | no limit |
| `minViews` | Drop posts below this view count (posts hiding views are dropped) | none |
| `minReactions` | Drop posts below this total reaction count | none |
| `maxPostsPerChannel` | How many recent (or matching) posts to scan per channel | 100 |
| `maxResults` | Hard cap on unique posts across all channels | no cap |
| `dedupeReposts` | Collapse near-identical reposts into one original | true |

### Pricing: pay per event

You are charged for results, not for runtime:

| Event | Price |
|---|---|
| `channel-searched` — one channel fetched and scanned | $0.001 |
| `post-returned` — one unique post returned | $0.0025 |

Scanning three channels and getting 200 unique posts back costs `3 × $0.001 + 200 × $0.0025 = $0.503`. **Reposts are free** — you pay once for the original, whether the repost is collapsed away or kept as a flagged row. **A dead or private channel, a search that returns nothing, and posts removed by your filters are not charged.**

If you set a maximum spend on the run, the Actor stops as soon as your limit is reached and finishes successfully with everything collected up to that point — you keep the partial dataset and pay nothing extra.

### How it works

- **It reads the public web preview**, `https://t.me/s/<channel>` — the same page a logged-out browser gets. No account, no phone number, no Telegram API, no headless browser. That is why only public channels are reachable.
- **Keyword search is Telegram's own.** When you set a query, the Actor uses the channel's built-in `?q=` search and pages through its results, so you get exactly what Telegram's own search would show — not a client-side text match on a handful of recent posts.
- **Reposts are detected by text, not by luck.** Posts are shingled and compared (MinHash-style Jaccard) so copy-paste reposts across channels cluster together; the earliest post (or the most-viewed on a date tie) is kept as the original and the rest are collapsed or flagged.
- **Relevance is ranked with BM25** over the run's own posts when a query is set — a cheap, CPU-only score, no LLM and no embeddings — so the most on-topic posts come first.
- **Reactions are parsed in full**, including custom and paid star reactions, into a total and a per-emoji breakdown.

### Limits worth knowing before you run it

- **Public channels only.** Private channels, groups and DMs are not reachable without a Telegram account, and this Actor deliberately uses none.
- **A channel that is missing, private or briefly unreachable is skipped, not fatal.** The run logs a warning, moves on and finishes successfully with whatever it did collect — and you are not charged for it.
- **Only what the preview shows.** The web preview carries text, views, reactions, post date and media links. Comment threads, the full subscriber list and members are not part of it.
- **Media links on Telegram's CDN are signed and expire**, so download any media URLs during the same run.
- **Reposts are matched on text.** A repost that only shares an image or a forwarded media file, with different or no caption, is not treated as a text duplicate.

***

*Keywords: telegram channel scraper, telegram scraper, telegram keyword search, scrape telegram posts, telegram channel export, telegram no login, telegram data scraper, telegram reactions, telegram views.*

# Actor input Schema

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

Public channels to scrape. Use @name, t.me/name or a full https://t.me/name link. Private channels, groups and DMs are not reachable without a Telegram account, and this Actor deliberately uses none.

## `query` (type: `string`):

Optional. Scrape only posts matching this term using Telegram's own built-in per-channel search — the same search box you get inside a channel. Leave empty to scrape the channel's recent posts. When set, every row also gets a `relevance_score`.

## `since` (type: `string`):

Only posts on or after this date (YYYY-MM-DD). Leave empty for no lower bound.

## `until` (type: `string`):

Only posts on or before this date (YYYY-MM-DD). Leave empty for no upper bound.

## `minViews` (type: `integer`):

Drop posts with fewer views than this. Posts that hide their view count are dropped when this is set. Leave empty for no minimum.

## `minReactions` (type: `integer`):

Drop posts whose total reaction count (all emoji, including paid star reactions) is below this. Leave empty for no minimum.

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

How many recent posts to scan in each channel before filtering and deduplication. With a keyword search set, this is how many matching posts to scan.

## `maxResults` (type: `integer`):

Optional hard cap on how many unique posts to output across all channels. Leave empty to return everything collected.

## `dedupeReposts` (type: `boolean`):

On: near-identical copy-paste reposts across channels collapse into a single original (the earliest, or the most-viewed on a tie). Off: reposts are kept as their own rows, flagged with `is_repost` and `duplicate_of`. Either way you are charged only for unique originals — reposts are free.

## Actor input object example

```json
{
  "channels": [
    "telegram"
  ],
  "query": "airdrop",
  "maxPostsPerChannel": 20,
  "dedupeReposts": true
}
```

# Actor output Schema

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

One row per unique post across the scraped channels.

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

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

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

```

## MCP server setup

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