# Telegram Channel Messages Scraper (`memo23/telegram-channel-messages-scraper`) Actor

Scrape public Telegram channel messages from t.me links — text, dates, view counts, photos, videos, forwards, replies & link previews. Paste channel URLs, @handles, or direct post URLs; filter by date window. No Telegram account, API key, or login needed. Clean JSON or CSV out.

- **URL**: https://apify.com/memo23/telegram-channel-messages-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** Social media, AI, Agents
- **Stats:** 3 total users, 2 monthly users, 50.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.25 / 1,000 telegram messages

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/platform/actors/running/actors-in-store#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 Messages Scraper — posts, views & media from t.me links

Scrape **public Telegram channel messages** straight from t.me links — full text, post date, view counts, author signature, photos, videos, documents, polls, forwards, replies, and link previews. Works with **channel URLs, bare @handles, and direct post URLs**, needs **no Telegram account, no API key, and no login**, and returns clean JSON/CSV you can plug into any workflow.

![How Telegram Channel Messages Scraper works](https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-telegram-messages.png)

### Why use this scraper?

- **Zero setup** — no Telegram API credentials, no phone number, no session files. Paste links, press Start.
- **Full message payload** — text (plain AND original HTML), ISO dates, exact view counts, media URLs, "Forwarded from" sources, quoted replies, and link-preview cards in every row.
- **Deep history** — pages backwards through the channel feed automatically; grab the latest 20 posts or the last 20,000.
- **Date-window filters** — `oldestDate` / `newestDate` extract an exact time slice and stop pagination early, so you don't pay for pages you don't need.
- **Single-post mode** — paste `t.me/channel/123` and get exactly that message as one row.
- **Fast and parallel** — channels are scraped concurrently in a sliding window; feeds page as fast as t.me serves them.

### Overview

Give the actor any list of public channel links or usernames. For each channel it walks the t.me web-preview feed newest-first, parses every message widget into one structured row, and stops when it hits your `maxItems` cap, your date window, or the beginning of the channel's history. Direct post URLs return exactly one row. One message in, one row out — no padding.

### Supported inputs

| Input | Example | Result |
|---|---|---|
| Public channel | `https://t.me/telegram` | the channel's message feed, newest first |
| Web preview | `https://t.me/s/bloomberg` | same feed — normalized automatically |
| Direct post URL | `https://t.me/durov/513` | that single message as one row |
| `telegram.me` alias | `https://telegram.me/telegram` | normalized to `t.me` |
| Bare handle | `@durov` or `durov` (via the *Usernames* field) | same as the full channel URL |

**Not supported:** private invite links (`t.me/+hash`, `t.me/joinchat/...`) and private-channel post links (`t.me/c/...`). Telegram does not expose private channels' message history on the web preview, so these inputs are reported and skipped — no silent failures. Groups and channels that disable the web preview also return no messages.

### Use cases

- **Crypto / trading signal monitoring** — pull the latest posts from signal channels into your own pipeline, with dates and views.
- **News & media monitoring** — track what large news channels publish, when, and how many views each post gets.
- **Content research & trend analysis** — which posts in a niche get the most views; what formats (video/photo/poll) perform.
- **OSINT & trust-and-safety research** — archive public channel history with forward-chains ("Forwarded from") preserved.
- **Competitor channel analysis** — posting frequency, view velocity, and outbound links of competing channels.
- **Dataset building for AI/LLM** — clean message text at scale for classification, summarization, or fine-tuning corpora.

### How it works

1. **Normalize** — every input (channel URL, `t.me/s/` preview, post URL, `@handle`) is normalized and deduplicated; unsupported private links are reported up front.
2. **Fetch** — the channel's t.me/s web-preview feed is fetched with browser-grade TLS fingerprints (impit), 20 posts per page. t.me has no anti-bot, so no proxy is needed.
3. **Parse** — each message widget yields text, HTML, date, views, author, photos, videos, documents, polls, forwards, replies, link previews, and outbound links.
4. **Paginate** — the feed walks backwards (`?before=` cursor) until `maxItems`, your `oldestDate`, or the start of the channel history is reached.
5. **Push** — one dataset row per message, newest first, JSON or CSV. Channels run in parallel.

### Input configuration

```json
{
    "startUrls": [
        "https://t.me/telegram",
        "https://t.me/durov/513"
    ],
    "usernames": ["@durov"],
    "maxItems": 200,
    "oldestDate": "2026-01-01"
}
```

| Field | Type | Default | Description |
|---|---|---|---|
| `startUrls` | array | — | Public channel URLs, `t.me/s/` previews, or direct post URLs |
| `usernames` | array | `[]` | Bare handles as an alternative to full URLs |
| `maxItems` | integer | `100` | Hard cap on message rows across all channels (newest kept first) |
| `oldestDate` | string | — | Skip messages before this ISO date; also stops pagination early |
| `newestDate` | string | — | Skip messages after this ISO date |
| `maxConcurrency` | integer | `10` | Channels scraped in parallel (pages within one channel are sequential) |
| `proxy` | object | none | Optional — t.me needs no proxy; direct is fastest |

### Output overview

One row per message. Text comes in two forms — `text` (plain, line breaks preserved) and `textHtml` (original markup with bold/italic/links). Media, forwards, replies, and link previews are `null` or empty arrays when the message doesn't have them.

```json
{
    "messageId": 513,
    "messageUrl": "https://t.me/durov/513",
    "channelUsername": "durov",
    "channelName": "Pavel Durov",
    "channelUrl": "https://t.me/durov",
    "text": "$100,400 just went to the 15 winners of the TON Consensus Challenge.\n\n🧠 The smartest engineers on the planet — some armed with loyal AI sidekicks — helped us maximize the security of the new consensus mechanism.\n\n🏆 Telegram contests offer generous prize money — and the only path to joining our engineering team.",
    "textHtml": "<b>$100,400</b> just went to the <b>15 winners</b> of the <a href=\"https://t.me/contest/452\">TON Consensus Challenge</a>. …",
    "date": "2026-05-15T18:14:32+00:00",
    "views": 3980000,
    "author": "Pavel Durov",
    "photos": [],
    "videos": [],
    "documents": [],
    "voice": null,
    "sticker": null,
    "poll": null,
    "forwardedFrom": null,
    "replyTo": null,
    "linkPreview": {
        "url": "https://t.me/contest/452",
        "siteName": "Telegram",
        "title": "Telegram Contests",
        "description": "💎 Blockchain Contest, Round 2: Results…",
        "imageUrl": null
    },
    "outboundLinks": ["https://t.me/contest/452", "https://t.me/contest"],
    "scrapedAt": "2026-07-22T20:09:36.075Z"
}
```

A message with media instead looks like:

```json
{
    "messageId": 453,
    "channelUsername": "telegram",
    "photos": ["https://cdn4.telesco.pe/file/…jpg"],
    "videos": [
        {
            "videoUrl": "https://cdn4.telesco.pe/file/….mp4?token=…",
            "thumbUrl": "https://cdn4.telesco.pe/file/…",
            "duration": "0:39",
            "kind": "video"
        }
    ]
}
```

### Key output fields

| Group | Fields |
|---|---|
| Identity | `messageId`, `messageUrl`, `channelUsername`, `channelName`, `channelUrl` |
| Content | `text` (plain), `textHtml` (original markup), `author` (post signature, if the channel signs posts) |
| Engagement | `date` (ISO 8601), `views` (exact integer, e.g. `3980000`) |
| Media | `photos[]` (CDN URLs), `videos[]` (`videoUrl`/`thumbUrl`/`duration`/`kind`), `documents[]` (`title`/`extra`), `voice`, `sticker`, `poll` (question + options + percentages) |
| Context | `forwardedFrom` (`name`/`url`), `replyTo` (`url`/`authorName`/`text` excerpt), `linkPreview` (`url`/`siteName`/`title`/`description`/`imageUrl`) |
| Links | `outboundLinks[]` — every `http(s)` link found in the message text |

### FAQ

**Do I need a Telegram account or API key?**
No. The actor reads Telegram's public t.me web-preview pages — no account, no `api_id`/`api_hash`, no phone number, no session string.

**Can it scrape private channels or groups?**
No. Private invite links (`t.me/+hash`) and `t.me/c/` post links are not supported — Telegram does not expose private message history on the web preview. These inputs are reported and skipped at the start of the run. If you need channel *metadata* (name, bio, member count) from an invite link, use my Telegram Channel Scraper instead.

**Can it scrape comments and reactions?**
No. The t.me web preview shows posts only — comment threads and reaction counts are not rendered there, so this actor doesn't return them.

**How far back can it go?**
All the way to the channel's first post, budget permitting. Set `maxItems` and/or `oldestDate` to control depth — the feed pages backwards 20 posts at a time.

**Are the video URLs permanent?**
No — `videoUrl` links are tokenized CDN URLs that expire after some time, and t.me doesn't embed them at all for very large files (you still get `thumbUrl` and `duration`). Photo and avatar URLs are stable. Download media promptly if you need to keep it.

**Why do some rows have `text: null`?**
Pure media posts (a photo/video with no caption) have no text — you still get the media, date, and views.

**Are view counts exact?**
They're Telegram's own display values — small posts show exact numbers, popular posts show abbreviations like `3.98M`, which the actor converts to integers (`3980000`).

**What happens with dead or invalid links?**
Unrecognizable inputs are reported and skipped at the start; deleted posts and channels without a web preview are logged and skipped without producing a row. You only pay for actual results.

### Support

- **Issues tab** — fastest way to report a problem or request a field; I typically respond within 24 hours.
- The actor sends me automatic failure alerts, so broken runs are usually being fixed before you write in.

### Additional services

Need a custom pipeline (scheduled channel monitoring with diffing, keyword alerting, media downloading at scale, CRM integration), a private variant of this actor, or a scraper for another platform? Reach out via the Issues tab or my Apify profile — custom work is available.

### Explore more scrapers

Check my Apify profile for more scrapers — my **Telegram Channel Scraper** (channel/group/bot metadata, member counts, invite-link resolution), jobs (LinkedIn, Indeed, Glassdoor, Upwork), real estate (Zillow, Realtor, Rightmove, ImmoScout24, Idealista), reviews (Trustpilot, Glassdoor), travel (Expedia), and many more directory and listing sites.

### 🤖 For AI Agents & LLM Apps

Quick-reference card for calling this actor via the Apify API or MCP:

- **Purpose:** extract public Telegram channel messages (text, date, views, media, forwards, link previews) as one JSON row per message.
- **Minimal input that runs:**

```json
{ "usernames": ["telegram"], "maxItems": 20 }
```

- **Key output fields (flat):** `messageId`, `messageUrl`, `channelUsername`, `channelName`, `text`, `textHtml`, `date`, `views`, `author`, `photos`, `videos`, `documents`, `poll`, `forwardedFrom`, `replyTo`, `linkPreview`, `outboundLinks`, `scrapedAt`.
- **Billing:** pay-per-event — a small actor-start fee plus one charge per message row saved. `maxItems` is the cost ceiling.
- **Behaviors agents should know:**
  - Channels are scraped newest-first; `maxItems` keeps the most recent posts.
  - `oldestDate` / `newestDate` (ISO `YYYY-MM-DD`) slice an exact window and reduce cost.
  - A direct post URL in `startUrls` (e.g. `https://t.me/durov/513`) returns exactly that message.
  - Private links (`t.me/+…`, `t.me/c/…`) are skipped with a warning — don't retry them.
  - `text` can be `null` for caption-less media posts; check `photos`/`videos` too.

### ⚠️ Disclaimer

This actor collects only **publicly available data** from Telegram's public t.me web-preview pages — the same information any logged-out visitor sees in a browser. It does not access private channels, does not join anything, does not read direct messages, and does not bypass any authentication. You are responsible for how you use the data: comply with Telegram's Terms of Service, applicable data-protection laws (GDPR, CCPA), and platform policies in your jurisdiction. This actor is not affiliated with or endorsed by Telegram FZ-LLC. All trademarks are the property of their respective owners.

### SEO Keywords

telegram message scraper, telegram channel messages scraper, scrape telegram messages, telegram post scraper, telegram channel posts export, telegram channel history scraper, t.me scraper, telegram views scraper, telegram media scraper, telegram channel monitoring, telegram osint, telegram news scraper, telegram crypto signals scraper, extract telegram posts, telegram channel archive, telegram post views count, telegram forwarded from tracker, telegram channel data extraction, scrape t.me channel, telegram feed scraper, telegram message export csv, telegram channel api alternative

# Actor input Schema

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

Full t.me URLs — a channel URL scrapes its message feed (newest first), a direct post URL (t.me/channel/123) scrapes that single message.

## `usernames` (type: `array`):

Bare Telegram channel handles, with or without the @ — e.g. `@telegram` or `durov`. Merged with the URL list above and deduplicated.

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

Hard cap on the number of message rows saved in this run, across all channels. Each channel is scraped newest-first, so the cap keeps the most recent posts. Use it to limit billing.

## `oldestDate` (type: `string`):

Skip messages posted before this date (ISO format, e.g. `2026-01-01`). Also stops pagination early once the feed reaches older posts, saving run time on large channels.

## `newestDate` (type: `string`):

Skip messages posted after this date (ISO format, e.g. `2026-06-30`). Combine with the oldest-date filter to extract an exact date window.

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

Maximum number of channels processed in parallel. Pages within one channel are always fetched sequentially (each page's cursor comes from the previous one).

## `minConcurrency` (type: `integer`):

Minimum number of channels processed in parallel.

## `maxRequestRetries` (type: `integer`):

Number of retries before a failed request is given up.

## `proxy` (type: `object`):

t.me is served from Telegram's edge with no anti-bot, so no proxy is needed — direct connections are fastest. Configure a proxy here only if your infrastructure requires one.

## Actor input object example

```json
{
  "startUrls": [
    "https://t.me/telegram"
  ],
  "usernames": [],
  "maxItems": 100,
  "maxConcurrency": 10,
  "minConcurrency": 1,
  "maxRequestRetries": 5,
  "proxy": {
    "useApifyProxy": false
  }
}
```

# 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": [
        "https://t.me/telegram"
    ],
    "usernames": [],
    "proxy": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/telegram-channel-messages-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": ["https://t.me/telegram"],
    "usernames": [],
    "proxy": { "useApifyProxy": False },
}

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

```

## MCP server setup

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