# Telegram Scraper – Channel Posts, Views & Reactions (`ninhothedev/telegram-scraper`) Actor

Scrape public Telegram channels: posts, text, views, reactions, media, links, hashtags + channel info (subscribers, verified). Date range, keyword filter & search. No login or API key.

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

## Pricing

from $0.80 / 1,000 results

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?

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 Scraper – Channel Posts, Views & Reactions

**Telegram Scraper** extracts posts from any public Telegram channel – full text, date, views, reactions, photos, videos, files, links, hashtags, mentions, forwards and replies – plus channel info such as subscribers, description and verified badge. It reads Telegram's public web preview (`t.me/s/<channel>`), so you need **no Telegram account, no phone number and no API key**. Scrape one channel or hundreds, go back through the whole history, filter by date range or keywords and export to JSON, CSV, Excel or Google Sheets.

### What data can you extract?

| Field | Example | Description |
|---|---|---|
| `channel` | `durov` | Channel username |
| `post_id` / `id` | `552` / `durov/552` | Message ID and stable unique ID |
| `url` | `https://t.me/durov/552` | Direct link to the post |
| `date` | `2026-10-11T14:52:40+00:00` | Publish time (ISO 8601, UTC) |
| `text` | `Designers × AI …` | Full post text with line breaks and emoji |
| `views` | `912000` | View count |
| `reactions_total` / `reactions` | `25330` / `[{"emoji":"👍","count":74900}]` | Total and per-emoji reactions (incl. paid ⭐ stars) |
| `media_type` | `photo`, `video`, `album`, `file`, `audio`, `poll` | Type of attached media |
| `photos`, `videos`, `video_thumbnails`, `documents` | URLs / objects | Media URLs, video thumbnails & durations, file titles |
| `links`, `hashtags`, `mentions` | `["https://telegram.org/blog"]`, `["news"]`, `["durov"]` | Everything extracted from the text |
| `link_preview_*` | title, description, site, image | Rich link preview of the post |
| `is_forwarded`, `forwarded_from_name`, `forwarded_from_url` | `true`, `Telegram` | Forward source |
| `is_reply`, `reply_to_post_id` | `true`, `78557` | Reply-to message |
| `is_edited` | `true` | Post was edited |
| `author_signature` | `Pavel Durov` | Admin signature (if the channel signs posts) |
| `channel_title`, `channel_subscribers` | `Pavel Durov`, `10400000` | Channel context on every post |

**Channel info row** (`type = "channel"`): title, description, subscribers, photos / videos / files / links counts, verified, scam / fake flags, avatar URL. Public **groups** return title, member count, online count and description.

### How to use

1. Add one or more channels – `durov`, `@telegram`, `https://t.me/durov` or `https://t.me/s/durov` all work.
2. Set **Max posts per channel** (0 = full history) and optional filters: date range (`since` / `until`, e.g. `"7 days"` or `"2026-01-01"`), keyword filter or Telegram search query.
3. Click **Start** and download the results as JSON, CSV, Excel, XML or HTML – or fetch them via API.

```json
{
  "channels": ["durov", "https://t.me/telegram", "@breakingmash"],
  "maxPostsPerChannel": 200,
  "since": "30 days",
  "keywords": ["ai", "crypto"],
  "includeChannelInfo": true
}
```

#### Modes

- **Channel history** – newest posts first, paging backwards 20 posts per request until your limit or date is reached.
- **Date range** – `since` / `until` with absolute or relative dates; scraping stops as soon as older posts appear.
- **Keyword filter** – whole-word, case-insensitive, any/all match; matched words in `matched_keywords`.
- **Search across channels** – `searchQuery` runs Telegram's own in-channel search on every channel in your list.
- **Media only** – skip text-only posts.

### Output example

Real post from a test run:

```json
{
  "type": "post",
  "id": "durov/552",
  "channel": "durov",
  "post_id": 552,
  "url": "https://t.me/durov/552",
  "date": "2026-10-11T14:52:40+00:00",
  "text": "✨ Designers × AI\n\n🆕 The mockup era is over. Today’s AI tools give every designer a superpower: you can now craft your ideas, not just draw them. \n\nThe gap between “what if…?” and “look, it works!” has never been smaller,",
  "views": 913000,
  "reactions_total": 25350,
  "reactions": [
    {
      "emoji": "⭐",
      "count": 5410,
      "paid": true
    },
    {
      "emoji": "custom:5936157098181135162",
      "count": 11300,
      "paid": false
    },
    {
      "emoji": "custom:5399847211989246390",
      "count": 4940,
      "paid": false
    }
  ],
  "media_type": "video",
  "media_count": 1,
  "photos": [],
  "videos": [
    {
      "url": null,
      "thumbnail": "https://cdn4.telesco.pe/file/OiJcyAExOKYdCDKh5YmkrTUa3_9poHu…",
      "duration": "1:15",
      "post_url": "https://t.me/durov/552"
    }
  ],
  "video_thumbnails": [
    "https://cdn4.telesco.pe/file/OiJcyAExOKYdCDKh5YmkrTUa3_9poHu…"
  ],
  "documents": [],
  "poll": null,
  "links": [],
  "hashtags": [],
  "mentions": [
    "designers"
  ],
  "link_preview_url": null,
  "link_preview_title": null,
  "link_preview_description": null,
  "link_preview_site_name": null,
  "link_preview_image": null,
  "is_forwarded": true,
  "forwarded_from_name": null,
  "forwarded_from_url": null,
  "is_reply": false,
  "reply_to_post_id": null,
  "reply_to_url": null,
  "is_edited": false,
  "is_service_message": false,
  "author_signature": "Pavel Durov",
  "sender_name": "Pavel Durov",
  "sender_url": "https://t.me/durov",
  "channel_title": "Pavel Durov",
  "channel_subscribers": 10400000,
  "channel_url": "https://t.me/durov",
  "matched_keywords": [],
  "search_query": null,
  "input": "durov",
  "scraped_at": "2026-10-15T19:58:14+00:00"
}
```

Channel info row:

```json
{
  "type": "channel",
  "id": "channel:durov",
  "channel": "durov",
  "url": "https://t.me/durov",
  "title": "Pavel Durov",
  "description": "Founder of Telegram.",
  "subscribers": 10400000,
  "photos_count": 102,
  "videos_count": 47,
  "files_count": null,
  "links_count": 200,
  "verified": true,
  "scam": false,
  "fake": false,
  "avatar_url": "https://cdn4.telesco.pe/file/MULwMfcgvO-kscu6vwzjmymEfId90Vg…",
  "online_count": null,
  "entity_type": "channel",
  "preview_available": true,
  "input": "durov",
  "scraped_at": "2026-10-15T19:58:14+00:00"
}
```

A run summary (posts per channel, errors, requests) is stored in the key-value store record `RUN_SUMMARY`.

### Pricing

Pay per result: **$0.80 per 1,000 results** ($0.0008 per post or channel row) plus a tiny start fee. Scraping **1,000 posts costs about $0.80**, 10,000 posts about $8. No monthly rental, no proxy needed for normal use. The scraper is HTTP-only and very fast (≈ 20 posts per request), so runs finish in seconds.

### Use cases

- **Media monitoring & news tracking** – follow news, crypto and politics channels and pipe new posts into dashboards or alerts.
- **Lead generation** – find active channels in your niche, extract links, @mentions and contact handles from posts and descriptions.
- **Market & competitor research** – benchmark views, reactions and posting frequency of competitor channels.
- **Influencer & ad sales** – compare subscribers, average views and engagement before buying ads.
- **OSINT & research** – archive public channel history with dates, forwards and edits for analysis.
- **AI / NLP datasets** – build clean text corpora with hashtags and metadata for sentiment or topic models.

### Integrations

Use the scraper from the Apify API, the Apify Python/JS clients, **Make**, **Zapier**, **n8n**, **Google Sheets**, Slack, webhooks or schedule it to run hourly/daily.

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("ninhothedev/telegram-scraper").call(run_input={
    "channels": ["durov", "telegram"],
    "maxPostsPerChannel": 100,
    "since": "7 days",
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["url"], item.get("views"), item.get("text", "")[:80])
```

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });
const run = await client.actor('ninhothedev/telegram-scraper').call({ channels: ['durov'], maxPostsPerChannel: 50 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

### FAQ

**Is it legal to scrape Telegram?** The scraper only reads public channels that anyone can view in a browser at t.me/s/… without logging in. It does not access private chats or groups. Make sure your use of personal data complies with GDPR and applicable laws.

**Do I need a Telegram account or API key?** No. No login, no phone number, no api_id/api_hash.

**Can I scrape groups or private channels?** Private channels and invite links (t.me/+…) are not supported. Public groups (and channels with the web preview disabled) return their profile info (title, members, online, description) – Telegram does not publish their messages on the web.

**Are comment counts and forward counts available?** Telegram's public web preview does not show comment or forward counts, so they are not included. Views, reactions, forward source and reply-to are included.

**How far back can I go?** The entire public history – set *Max posts per channel* to 0. Use *Start before post ID* to resume big scrapes.

**Do I need a proxy?** Normally not. For very large scrapes (thousands of pages) enable Apify Proxy to avoid rate limits; the scraper retries with backoff and rotates sessions automatically.

**Media URLs expire?** Telegram CDN links (cdn\*.telesco.pe) are signed and can expire after some time – download media soon after the run if you need it.

**Support** – open an issue on the actor's Issues tab; feature requests are welcome.

### Related scrapers by ninhothedev

- [YouTube Channel Scraper](https://apify.com/ninhothedev/youtube-channel-scraper) – channel stats & videos
- [Website Contact Extractor](https://apify.com/ninhothedev/website-contact-extractor) – emails & socials from websites linked in posts
- [Email Finder Scraper](https://apify.com/ninhothedev/email-finder-scraper) – find business emails

# Actor input Schema

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

Public Telegram channels to scrape. Accepts usernames (durov), @handles (@durov) and links (https://t.me/durov, https://t.me/s/durov, t.me/durov/123). Public groups, bots and channels without web preview return their profile info (title, members, description) only. Private invite links (t.me/+…) are not supported.

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

Maximum number of posts per channel (newest first). The scraper pages backwards through the channel history 20 posts at a time. 0 = all posts (whole history).

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

Hard cap on the total number of posts across all channels (channel info rows are not counted). 0 = no limit.

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

Only posts published on/after this date. Absolute (2026-01-31 or ISO date-time) or relative ('7 days', '24 hours', '2 weeks', '3 months'). Scraping stops as soon as older posts are reached.

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

Only posts published on/before this date (2026-06-30, ISO date-time or relative like '30 days'). Combine with 'Posts newer than' for a date range.

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

Only keep posts whose text (or link preview / file name) contains these words. Case-insensitive, whole-word match. Matching keywords are returned in 'matched_keywords'. The whole channel history is scanned until 'Max posts per channel' matches are found.

## `keywordMatch` (type: `string`):

'Any' keeps posts containing at least one keyword, 'All' requires every keyword.

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

Uses Telegram's own in-channel search (t.me/s/<channel>?q=…) on every channel – fast way to find posts about a topic across many channels. Telegram's search may return fewer results than a full scan with the keyword filter.

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

Skip text-only posts; keep posts with photos, videos, files, voice, polls etc.

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

Add one row per channel (type = 'channel') with title, description, subscribers, photo/video/file/link counts, verified flag and avatar.

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

Also return service messages such as 'Channel photo updated' or 'Channel created'.

## `beforePostId` (type: `integer`):

Advanced: start paging from posts older than this post ID (useful to resume a large history scrape). 0 = start with the newest post.

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

How many channels are scraped in parallel (1–10).

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

Optional. Telegram's web preview usually works without a proxy. Enable Apify Proxy for very large scrapes (thousands of pages) to avoid rate limits.

## Actor input object example

```json
{
  "channels": [
    "durov",
    "telegram"
  ],
  "maxPostsPerChannel": 20,
  "maxItems": 0,
  "keywordMatch": "any",
  "onlyWithMedia": false,
  "includeChannelInfo": true,
  "includeServiceMessages": false,
  "beforePostId": 0,
  "maxConcurrency": 4,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

No description

## `channels` (type: `string`):

No description

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

No description

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

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

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

```

## MCP server setup

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