# Telegram Channel Scraper - Full History & Media (`monkey.d.scraping/telegram-channel-scraper`) Actor

Export the complete post history of any public Telegram channel: text, direct media URLs, reactions, polls, replies and forwards. No account, phone number or API key, and no cap on messages per channel.

- **URL**: https://apify.com/monkey.d.scraping/telegram-channel-scraper.md
- **Developed by:** [Monkey D. Scraping](https://apify.com/monkey.d.scraping) (community)
- **Categories:** Social media, News, Developer tools
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.20 / 1,000 telegram 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 — export a full channel history without an API key

Export everything a public Telegram channel has ever posted, as clean structured data: the text
of every message, direct media URLs, view counts, reactions, polls, replies, forwards and link
previews, already parsed into one row per post.

No Telegram account. No phone number. No API key. No bot token. Paste a channel name and run it.

**$0.20 per 1,000 posts. No start fee, no per-channel fee, no run minimum, platform usage included.**

***

### Export a Telegram channel to CSV in four steps

1. Put the channel name in **Channels** — `cointelegraph`, `@cointelegraph` and `https://t.me/cointelegraph` all work.
2. Set **Max messages per channel**, or clear the field to export the channel's entire history.
3. Run the Actor.
4. Open the **Dataset** tab and export as CSV, Excel, JSON, XML or RSS.

For a spreadsheet, set **Output format** to `Compact` first: it returns flat columns with no nested
objects, which is what Excel and Google Sheets expect.

### What you get

- **The complete history.** There is no cap on messages per channel. Ask for 100 posts, or for
  every post a channel has published since its first one.
- **Direct media URLs**, including the actual video file rather than only a thumbnail. Photos,
  videos, round videos, documents with file name and size, voice notes, stickers and GIFs are
  each typed and listed, and a photo album stays on a single row.
- **Polls** with the question, the quiz type, every option and its percentage.
- **Replies and forwards** resolved to the source channel and message id, not just a display name.
- **Link previews** with site name, title, description and image.
- **Structured reactions** — one object per emoji with its count — and view counts as real
  integers rather than `"26.2K"` strings.
- **Rich text three ways**: plain text, Markdown, and typed entities with offsets, so bold runs,
  links and code blocks survive into whatever you build next.
- **Channel details** — subscribers, verification, description, avatar and the channel's own
  photo, video, file and link counters — included at no charge.

### Every field in the export

| Field | What it holds |
| --- | --- |
| `channelUsername`, `channelName`, `channelUrl` | Which channel the post came from |
| `messageId`, `messageUrl` | Canonical identity of the post; also used to deduplicate |
| `publishedAt` | Publication time in ISO 8601 UTC |
| `text`, `textLength`, `textMarkdown`, `entities` | Message text as plain, Markdown, and typed offsets |
| `views`, `viewsText` | View count as an integer, plus the abbreviation as shown |
| `reactions`, `reactionsTotal` | One object per emoji with its count, plus the sum |
| `media` | Typed array: photo, video, roundVideo, document, voice, audio, sticker — with URL, thumbnail, file name, size and duration |
| `isAlbum` | Whether the row carries a multi-photo album |
| `poll` | Question, quiz type, options and percentages |
| `linkPreview` | Site name, title, description and image of the linked page |
| `forwardedFrom`, `replyTo` | Source channel, message id and quoted preview |
| `author`, `isEdited`, `isServiceMessage` | Post signature and status flags |
| `links`, `hashtags`, `mentions`, `cashtags` | Everything extracted from the text |
| `channel` | Subscribers, verification, description, avatar and channel counters |
| `scrapedAt` | When the row was collected |

Fields a channel does not publish are returned as `null`. Nothing is inferred or filled in.

### Why runs finish fast

Long histories are collected in parallel rather than one page after another, and every run reports
the exact range of message IDs it covered, so you can see the export is complete instead of taking
it on trust.

Measured on a public news channel with roughly 340,000 subscribers:

| Request | Time |
| --- | --- |
| 1,000 posts | 5.9 seconds |
| 10,000 posts | 38.6 seconds |
| First row in the dataset | under 1 second |

A date cutoff is located directly rather than by paging through everything newer first, so
*"only posts since January"* on a channel with tens of thousands of messages costs a handful of
requests instead of thousands.

### Typical uses

- **Research and OSINT** — pull a channel's full archive into a spreadsheet or a database.
- **Market and crypto monitoring** — track what announcement channels publish and how their
  audience reacts.
- **Brand and reputation** — export mentions from the channels that matter to you and narrow them
  by phrase or regular expression.
- **AI and RAG pipelines** — the `LLM` output format returns one ready-to-embed Markdown document
  per post, with source URL, date, poll results and media summary already inlined.
- **Content and social analysis** — compare posting cadence, reactions and views over time.

### Input

Only the channel list is needed. Everything else has a sensible default.

```json
{
  "channels": ["cointelegraph", "@durov", "https://t.me/telegram"],
  "maxMessagesPerChannel": 500,
  "afterDate": "30 days",
  "outputPreset": "full",
  "includeChannelInfo": true
}
```

Other options: `beforeDate` for an exact window, `fromMessageId` to pull only what is new since
your last run, `postUrls` to export a specific list of posts, `includeKeywords`, `excludeKeywords`
and `regexFilters` to narrow the result, `sortOrder` for chronological output, and `downloadMedia`
to also save the files themselves.

### Output

One row per post. In the `full` format:

```json
{
  "channelUsername": "cointelegraph",
  "channelName": "Cointelegraph",
  "messageId": 71830,
  "messageUrl": "https://t.me/cointelegraph/71830",
  "publishedAt": "2026-08-28T16:41:01.000Z",
  "text": "Bitcoin reclaims its weekly range after the latest ETF inflows",
  "textMarkdown": "**Bitcoin reclaims its weekly range** after the latest [ETF inflows](https://example.com/etf)",
  "entities": [{ "type": "bold", "offset": 0, "length": 29, "url": null }],
  "views": 26200,
  "reactions": [{ "emoji": "🔥", "count": 184, "countText": "184" }],
  "reactionsTotal": 512,
  "media": [{ "type": "video", "url": "https://cdn4.telesco.pe/file/...mp4", "durationSeconds": 30 }],
  "poll": null,
  "linkPreview": { "siteName": "Cointelegraph", "title": "ETF inflows hit a record" },
  "forwardedFrom": null,
  "replyTo": null,
  "hashtags": ["#bitcoin"],
  "channel": { "subscribers": 339000, "isVerified": true }
}
```

Three output formats over the same underlying data:

- **Full** — every field, nested objects. Best for APIs and pipelines.
- **Compact** — flat columns only. Best for Excel, Google Sheets and CSV export.
- **LLM** — one Markdown document per post. Best for vector databases and AI agents.

Every run also writes a **run summary** and a **per-channel coverage report**: how many posts came
back, the message-ID range actually read, whether the beginning of the channel was reached, and
any range that could not be read.

### Pricing

| Event | Price |
| --- | --- |
| Post returned | $0.0002 — that is **$0.20 per 1,000 posts** |
| Channel details record | Free |
| Starting the run | Free |

You pay for results and nothing else. A run that returns no posts costs nothing. Platform usage is
included in that price rather than billed separately, and you can cap the maximum spend of a single
run — the Actor stops when that cap is reached instead of running up costs.

### Integrations

The dataset is available over the Apify REST API in JSON, CSV, Excel, XML and RSS, and works with
n8n, Make, Zapier, LangChain and any HTTP client. Schedule the Actor to run daily and use
`fromMessageId` to fetch only what has appeared since the previous run, or attach a webhook to push
each finished run into your own system.

### Frequently asked questions

#### Do I need a Telegram account, phone number or API key?

No. The Actor reads publicly available channel pages. There is nothing to authorise, no bot token
to create, and no phone number to hand over.

#### How many messages can I export from a single channel?

There is no cap. Clear **Max messages per channel** and the Actor walks the channel back to its
first post. A verified run has exported a channel's entire history from message 1.

#### How long does a large export take?

10,000 posts from one channel completes in about 39 seconds, and 1,000 posts in about 6 seconds.
The first row reaches the dataset in under a second, so you can start consuming results while the
run is still going.

#### Can it read private channels or groups?

No. Private channels, invite-only channels, groups and anything behind a login cannot be read —
not by this Actor, and not by any tool that does not sign in as you. A channel with no public
preview is reported as unreadable rather than silently skipped.

#### How do I export Telegram messages to Excel or CSV?

Set **Output format** to `Compact`, run the Actor, then use the Export button on the Dataset tab
and pick CSV or Excel. The compact format has no nested objects, so every field becomes one column.

#### How do I fetch only new posts since my last run?

Keep the highest `messageId` you have already stored and pass it as `fromMessageId` on the next
run. Only newer posts are returned, and you are billed only for those.

#### Does it download photos and videos?

Direct media URLs are always included, including the real video file rather than only a thumbnail.
Turn on **Download media files** and the files themselves are also saved to the run's key-value
store. It is off by default because the URLs are usually enough and keep runs fast.

#### Can I export only a specific date range?

Yes. **Published after** and **Published before** accept an absolute date or a relative expression
such as `7 days`, `1 month` or `1 year`. A deep cutoff is located directly, so restricting a long
history to a recent window does not cost a full crawl.

#### What does it cost to export 10,000 messages?

$2.00, at $0.20 per 1,000 posts. There is no start fee, no per-channel fee and no run minimum.

#### Is scraping public Telegram channels legal?

This Actor accesses only pages that Telegram publishes openly, without signing in or circumventing
any access control, which is why it needs no credentials. What you may then do with the data is a
separate question: personal data is regulated in the EU by the GDPR and elsewhere by comparable
laws, so collect only what you have a legitimate reason to collect, and take your own legal advice
for your specific use. This is not legal advice.

### What to check when comparing Telegram scrapers

| Question to ask | Why it matters |
| --- | --- |
| Is there a cap on messages per channel? | Many tools stop at 5,000 or 10,000, which silently truncates an archive |
| Does it return media, polls, replies and forwards? | The cheapest tools return text and links only |
| Is the video URL the real file or a thumbnail? | A thumbnail cannot be analysed, transcribed or archived |
| Does it tell you what it actually covered? | Without a coverage report you cannot tell a complete export from a partial one |
| Is platform usage included in the quoted price? | A low headline price can hide separately billed compute |
| Does it respect your maximum spend per run? | If it does not, an unlucky run keeps costing after it stops earning |
| What is the published success rate? | A rich field set is worth little if a run in seven fails |

### Limitations, stated plainly

- **Public channels only.** Private channels, invite-only channels and groups are out of reach.
- A channel that has disabled its public preview is reported as unreadable, with the reason.
- Fields a channel does not publish stay `null`. Nothing is inferred.
- Comment threads under posts live in a separate discussion group and are not part of this export.
- View and reaction counts are exactly what the channel shows publicly, including abbreviations,
  which are also kept in their original text form.

### Related Actors

- **Telegram Keyword Search & Channel Scraper** — when you do not yet know which channels to read,
  and want to find posts by keyword across public channels.
- **Telegram New Posts Monitor & Channel Discovery** — when you want a scheduled run that returns
  only what is new since last time.

# Actor input Schema

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

Public Telegram channels to export. The `@` prefix is optional and full links work too — `durov`, `@durov` and `https://t.me/durov` are all accepted. Add one channel per line.

## `maxMessagesPerChannel` (type: `integer`):

How many messages to export from each channel. Clear the field to export the **complete history** of every channel, all the way back to its first post.

## `afterDate` (type: `string`):

Export only messages published **on or after** this date. Pick a date or type a relative expression — e.g. `7 days`, `2 weeks`, `1 month`, `1 year`. Deep date cutoffs are located directly, without paging through the newer history first. *(Optional)*

## `beforeDate` (type: `string`):

Export only messages published **on or before** this date. Combine with *Published after* to pull one exact window out of a long history. *(Optional)*

## `outputPreset` (type: `string`):

Pick the shape that matches where the data is going next.

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

Adds subscriber count, verification badge, description, avatar and the channel's photo, video, file and link counters. Free — channel details are never charged as results.

## `downloadMedia` (type: `boolean`):

Off by default, because direct media URLs are always included and keep runs fast. When on, available public photos and videos are also saved to this run's key-value store.

## `includeKeywords` (type: `array`):

Keep a message only when its text contains at least one of these phrases. *(Optional)*

## `excludeKeywords` (type: `array`):

Drop a message when its text contains any of these phrases. *(Optional)*

## `regexFilters` (type: `array`):

Keep a message only when its text matches at least one JavaScript-compatible pattern. *(Optional)*

## `postUrls` (type: `array`):

Export only these exact posts, e.g. `https://t.me/durov/342`. Useful for enriching a list of links you already have. Works on its own or alongside the channels above. *(Optional)*

## `fromMessageId` (type: `integer`):

Export only messages newer than this ID. Store the highest `messageId` from your last run here to pull just what has been posted since. *(Optional)*

## `sortOrder` (type: `string`):

Newest first streams results as they are found. Oldest first waits for each channel to finish before writing it out.

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

A single ceiling across every channel, for predictable cost on large runs. *(Optional)*

## Actor input object example

```json
{
  "channels": [
    "cointelegraph"
  ],
  "maxMessagesPerChannel": 100,
  "outputPreset": "full",
  "includeChannelInfo": true,
  "downloadMedia": false,
  "sortOrder": "newestFirst"
}
```

# Actor output Schema

## `overview` (type: `string`):

Every public post collected in this run, deduplicated by canonical message URL.

## `runSummary` (type: `string`):

Totals for posts returned, pages read, duplicates removed, filters applied and any warnings.

## `channelReport` (type: `string`):

For each channel: posts returned, the message-ID ranges actually covered, whether the start of the channel was reached, and the reason collection stopped.

# 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": [
        "cointelegraph"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("monkey.d.scraping/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": ["cointelegraph"] }

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

```

## MCP server setup

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