# Telegram Channel Scraper — Messages, Views & Media (`bovi/telegram-channel-scraper`) Actor

Scrape public Telegram channel messages from the web preview. Get fields: text, views, timestamps, media type, links, forwards, reply IDs, and parse-quality fields. Run with public channel inputs; no account or API key required.

- **URL**: https://apify.com/bovi/telegram-channel-scraper.md
- **Developed by:** [Vitalii Bondarev](https://apify.com/bovi) (community)
- **Categories:** Social media, Marketing, News
- **Features:** Limited permissions
- **Stats:** 19 total users, 3 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## 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

Scrape public Telegram channel messages from the web preview at `https://t.me/s/{channel}` and extract text, views, timestamps, media type, links, forwards, reply-to IDs, and parse-quality fields. No Telegram account, bot token, API key, login, or required proxy is needed.

- Collect one row per message with structured fields and a direct message URL.
- Track brands and competitors using public channel history.
- Aggregate crypto and news signals with timestamps and view counts.
- Feed channel records into LLM pipelines.
- Run with optional Apify Proxy configuration; residential proxy is recommended for best reliability.

### What you get

Fields are `null` when the source does not provide a value. `links` and `warnings` are empty arrays when none are found; boolean fields are `false` when the condition is absent.

| Field | Type | Description |
|---|---|---|
| `channel` | string | Normalized public channel username |
| `message_id` | integer | Telegram message ID; `null` when it cannot be read |
| `text` | string | Message text content; `null` when the source has no text |
| `views` | string | View count such as `2.3M` or `42K`; `null` when unavailable |
| `datetime` | ISO 8601 string | Message timestamp; `null` when no timestamp is found |
| `has_media` | boolean | Whether the message contains media; `false` when no supported media is found |
| `media_type` | string | `photo`, `video`, `document`, `sticker`, `audio`, or `poll`; `null` when no media is detected |
| `links` | array of strings | HTTP/HTTPS links found in message text; empty when none are found |
| `is_forwarded` | boolean | Whether the message was forwarded; `false` when it was not forwarded |
| `forward_from` | string | Forwarded-from channel or user name; `null` when no source name is found |
| `reply_to` | integer | Message ID being replied to; `null` when no reply ID is found |
| `message_url` | string | Full `https://t.me/{channel}/{id}` URL; `null` when the message ID cannot be read |
| `parse_confidence` | number | Parse quality score from `0.0` to `1.0` |
| `warnings` | array of strings | Parse quality warnings; empty when no warnings are generated |
| `scraped_at` | ISO 8601 string | UTC timestamp when the record was scraped |

The scraper uses the public Telegram web preview and structural message attributes such as `data-post`. It paginates backwards through the public history and emits one dataset record for each message pushed.

### Sample output

The shortened sample omits `is_forwarded`, `forward_from`, `reply_to`, and `scraped_at`.

```json
{
  "channel": "telegram",
  "message_id": 460,
  "text": "For all the info from today's update and a look back at 13 years of Telegram features — check out our blog: https://telegram.org/blog/welcome-messages-buttons-TG-13 August Features 1 • 2 • 3 • 4 • 5 • More",
  "views": "2.3M",
  "datetime": "2026-08-26T19:12:34+00:00",
  "has_media": false,
  "media_type": null,
  "links": [
    "https://telegram.org/blog/welcome-messages-buttons-TG-13",
    "https://t.me/telegram/455",
    "https://t.me/telegram/456",
    "https://t.me/telegram/457",
    "https://t.me/telegram/458",
    "https://t.me/telegram/459"
  ],
  "message_url": "https://t.me/telegram/460",
  "parse_confidence": 1,
  "warnings": []
}
```

### Pricing

**Today's model: FREE.** Since 2026-11-10 the Actor charges no pay-per-event fees on any plan (the Free plan included). Apify platform usage (compute, storage, Apify Proxy traffic, residential by default) still counts against your plan; the Apify Free plan includes $5 per month of platform usage credit. Check the Pricing tab on the Actor page before a large run.

**Earlier pricing, pay-per-event until 2026-11-10 (shown for reference only):**

- Actor Start (`apify-actor-start`): $0.00005 per event on the Free plan (one event per GB of memory, minimum one).
- Dataset item (`apify-default-dataset-item`): $0.003 per message on the Free plan.
- Free plan, 1,000 messages: 1,000 x $0.003 = $3.00 in dataset-item events.
- Free plan, 10,000 messages: 10,000 x $0.003 = $30.00.

The `message-scraped` event has no price, so it was never charged.

### How to use

1. **Enter** one or more public Telegram channel usernames, `@` handles, or full `t.me` URLs in `channels`.
2. **Set** `maxMessages` to control the number of messages to collect per channel, or leave it at the default of 100.
3. **Set** optional `beforeId` to collect only messages with IDs lower than this value.
4. **Run** the Actor. It reads `https://t.me/s/{channel}` and paginates backwards until the requested number of messages is collected or the available public history is exhausted.
5. **Inspect** `parse_confidence` and `warnings` in each record.
6. **Repeat** the run through Apify Schedules; daily is a suitable starting point for brand, competitor, crypto, or news monitoring.

Export the dataset as JSON or CSV, or read it through the Apify dataset API.

```json
{
  "channels": [
    "telegram",
    "@durov",
    "https://t.me/cointelegraph"
  ],
  "maxMessages": 200
}
```

### Input parameters

| Parameter | What it does | Default |
|---|---|---|
| `channels` | Required list of public Telegram usernames, `@` handles, or `t.me` URLs to scrape | Required; no default |
| `maxMessages` | Maximum number of messages to collect per channel; maximum `50000` | `100` |
| `beforeId` | Collect only messages with IDs lower than this value | No default; starts with recent messages when empty |
| `proxyConfiguration` | Configures Apify Proxy. The default uses the Apify Proxy with the `RESIDENTIAL` group | {"useApifyProxy":true,"apifyProxyGroups":\["RESIDENTIAL"]} |
| `disableUsageStats` | Set to `true` to turn off the run summary described in Usage statistics. | `false` |

### Limits and edge cases

| Condition | What you see | What to do |
|---|---|---|
| The channel is private, is a private invite link, or does not have a public web preview | No message rows; the run status explains that only public channels with a public preview can be read | Use a public channel username or public `t.me` URL |
| The channel does not exist or is not a valid public channel input | No message rows and a status explaining that the channel could not be matched or reached | Check the username, handle, or URL |
| The channel is public but has no public posts | No message rows and a status indicating that no public posts were found | Check the channel or try another public channel |
| `beforeId` is older than the available messages | No message rows for that channel because there are no messages older than the supplied ID | Lower `beforeId` or leave it empty |
| A channel fetch raises a transport or access problem during pagination | That channel produces no pushed rows from that scrape; processing can continue for other channels in the input | Check the channel status and run again later when appropriate |
| The source contains comments or replies inside a post | The output contains the channel message and may include a `reply_to` message ID, but not the comment or reply thread contents | Use the message-level fields provided |
| A message contains media | `has_media` and `media_type` identify the media type | Use the returned type; media files and media URLs are not downloaded or returned |
| The run reaches the end of the public preview history | Pagination stops when no further message blocks are available | Use another run with a suitable `beforeId` if you need to continue from a known checkpoint |

Only public channel messages available through Telegram’s web preview are collected. Private channels, groups, direct messages, real-time streaming, comments, and media downloads are not supported.

### FAQ

#### How much does it cost to scrape a Telegram channel?

The Actor is free to run since 2026-11-10, so no event fees apply on any plan. Apify platform usage still applies to your plan. See Pricing for the earlier pay-per-event prices.

#### Why did the run return an empty dataset?

The channel may be private, invalid, unavailable, empty, restricted, or missing a public web preview. An empty result can also occur when `beforeId` is lower than the available message IDs. Check the run status, use a public channel username or URL, and remove or adjust `beforeId`.

#### Why are some fields missing or flagged?

A field is `null` when the source does not provide a value (see What you get). Review any record whose `warnings` array is not empty.

#### Does this Actor download Telegram media?

No. It detects `photo`, `video`, `document`, `sticker`, `audio`, and `poll` media types and returns the type flag, but it does not download media files or return media URLs.

#### Is it legal to scrape public Telegram channel data?

The Actor reads the publicly accessible Telegram web preview at `t.me/s/*`, the same public source available in a browser. Whether a particular use is legal or allowed depends on the source terms and the applicable law for your use case; assess those requirements before using the collected data.

### Related actors

- [Telegram Channel Monitor — Virality & Keyword Alerts](https://apify.com/bovi/telegram-channel-monitor) — related Telegram workflow for channel monitoring and alerts.
- [Dzen.ru Scraper - Articles & Channels](https://apify.com/bovi/dzen-scraper) — extracts articles and channel content from another publishing platform.
- [Truth Social Scraper — Posts, Profiles & Engagement API](https://apify.com/bovi/truth-social-scraper) — related social-media data collection for posts, profiles, and engagement.

### Usage statistics

This Actor creates a small, content-free summary at the end of each run, used only to monitor reliability and improve this Actor. No input values, credentials or scraped data are sent.

A copy of the exact record for your run is saved as `USAGE_STATS` in your own Apify key-value store.

To opt out, set `disableUsageStats` to `true` in the input. Nothing is sent then.

[Full list of collected fields](https://api.apify.com/v2/key-value-stores/v9rgHjMKLDGtHf8tD/records/index.html)

# Actor input Schema

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

Public Telegram channel usernames to scrape. Each item can be a bare username (e.g. 'telegram'), an @handle (e.g. '@durov'), or a full t.me URL (e.g. 'https://t.me/telegram'). Only public channels are supported — private channels are not accessible via the web preview.

## `maxMessages` (type: `integer`):

Maximum number of messages to collect per channel. The actor paginates backwards from the most recent message. Keep low (20–100) for quick sampling; set higher (500+) for bulk research or archive exports. Each message costs one PPE charge.

## `beforeId` (type: `integer`):

Optional: only collect messages with ID lower than this value. Use to scrape a specific historical window or to resume a previous crawl from a known checkpoint. Leave empty to start from the most recent messages.

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

Apify Proxy settings. RESIDENTIAL proxy is recommended for best reliability. Leave default (useApifyProxy: true) unless you have a reason to change.

## `disableUsageStats` (type: `boolean`):

See the README Usage statistics section for details.

## Actor input object example

```json
{
  "channels": [
    "telegram"
  ],
  "maxMessages": 100,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "disableUsageStats": false
}
```

# Actor output Schema

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

Dataset containing Telegram Channel Scraper records (channel, message_id, datetime, views, text, media_type, links, message_url, parse_confidence).

# 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"
    ],
    "maxMessages": 100,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("bovi/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"],
    "maxMessages": 100,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

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

```

## MCP server setup

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