# Telegram Channel Scraper · Posts, Views, Date Range (`automation_craft/telegram-channel-scraper`) Actor

Scrape public Telegram channels without an account or API key: post text, views, photos, videos, documents, forwards, replies. True server-side date filtering, a typed status for every channel (never a silent empty run), and $0 when nothing is returned.

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

## Pricing

Pay per event

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 Scraper · Posts, Views, Date Range

Scrape **public Telegram channels** — post text, views, photos, videos, documents, forwards, replies and channel stats — with **no Telegram account, no API keys, no phone number**. Pure HTTP against Telegram's own web preview, so it's fast and cheap.

Built specifically to fix the three things users of existing Telegram scrapers complain about the most:

### Why this scraper

#### 1. A typed status for every channel — never a silent empty run

Other scrapers "didn't scrape some of the publicly accessible channels I tested" and returned nothing without telling you why. Here, **every channel you request gets a free `channel-summary` item** with a typed status:

| Status                  | Meaning                                                                                  |
| ----------------------- | ---------------------------------------------------------------------------------------- |
| `ok`                    | Posts scraped successfully                                                               |
| `not_found`             | No such public username (banned/deleted channels look identical on the web)              |
| `bot`                   | The username is a bot, not a channel                                                     |
| `user`                  | The username is a personal account                                                       |
| `group`                 | The username is a group/supergroup — Telegram exposes no web preview for message history |
| `private_or_no_preview` | The channel exists, but its owner disabled the web preview                               |
| `no_posts_in_range`     | The channel is fine — it just has no posts inside your date window                       |
| `error`                 | Network/HTTP failure or invalid input reference (message included)                       |

You always know **what** happened and **why** — and none of these cost you anything.

#### 2. True server-side date filtering — you don't pay for out-of-range posts

Other scrapers' date filters are "totally not right" and charge you for piles of non-relevant messages. This actor **seeks your date window directly** in the channel history using Telegram's own `?before=` pagination (an interpolated jump, typically 2–5 pages even for a years-old window in a huge channel), walks only the pages inside the window, and **emits and charges only in-range posts**. It never downloads everything and filters afterwards.

Each channel summary also tells you `dateWindowFullyCovered: true/false` — so you know whether you got the complete window or hit your cap first.

#### 3. Zero results = $0

If a channel yields nothing — wrong name, bot, private, empty date window — **you are charged nothing** for it. Status items and run summaries are always free. The channel-info item is only charged when the channel also returned at least one post.

### What you get

One dataset item per post:

```json
{
  "type": "post",
  "requestedChannel": "durov",
  "channel": "durov",
  "id": "519",
  "postUrl": "https://t.me/durov/519",
  "date": "2026-05-20T15:38:10+00:00",
  "text": "🤖 Your Telegram inbox can now run itself.\n\nAssign a bot to read and reply for you — with granular control over its rights and chat access. 🧠\n\n⚙️ Start building!",
  "links": ["https://core.telegram.org/bots/features#secretary-bots"],
  "views": 8620000,
  "viewsRaw": "8.62M",
  "photos": [],
  "videos": [
    { "thumbUrl": "https://cdn4.telesco.pe/file/…", "duration": "0:09" }
  ],
  "documents": false,
  "poll": null,
  "forwardedFrom": null,
  "replyTo": null,
  "authorSignature": "Pavel Durov",
  "isAlbum": false,
  "isServiceMessage": false,
  "edited": false
}
```

(Real output from a live run against t.me/durov. `textHtml`, `documentFiles`, `linkPreview`, `roundVideo`, `audio` fields also included.)

Plus, per channel:

- **channel-info** item (optional): title, description, verified badge, subscriber/photo/video/link counts — as numbers **and** the raw strings (`9910000` and `"9.91M"`).
- **channel-summary** item (always, free): typed status, posts returned, date range covered, `dateWindowFullyCovered`, pages fetched.

#### Field reliability (measured across 278 posts from 15 real channels)

Always populated: `id`, `date` (100%), `views` (99.6%), `text` (95%; empty for pure-media posts). Populated **when the post contains that content**: `photos`, `videos`, `links`, `forwardedFrom`, `replyTo`, `documentFiles`, `authorSignature`, `edited`, `isAlbum`. Best-effort: `poll` (parser follows Telegram's widget markup but no live poll appeared in probed channels), `roundVideo`, `audio`.

Media albums: Telegram's web preview renders an album as **one post** with multiple entries in `photos`/`videos` (`isAlbum: true`) — you're charged one post, not one per photo.

### ✅ CAN / ❌ CANNOT

✅ Public channels with web preview enabled (the vast majority)
✅ Post text (plain + HTML), dates, view counts, links
✅ Photos, video thumbnails + durations, documents, audio flags
✅ Forward headers, reply quotes, author signatures, edited markers, service messages
✅ Channel metadata: title, description, subscribers, verified badge
✅ Date-range scraping that only bills in-range posts
✅ Works with usernames, @names, or any t.me URL form

❌ Private channels or invite links (`t.me/+…`) — no web preview exists
❌ Channels whose owners disabled the web preview (reported as `private_or_no_preview`)
❌ Groups/supergroups — Telegram exposes no message history on the web (reported as `group`)
❌ Member lists, subscriber identities, or any personal user data
❌ Posting, joining, reacting — this is read-only public data
❌ Comment/reply threads under posts (the discussion group is separate)
❌ Full video/file downloads — you get URLs, thumbnails and metadata

Telegram may hide some channels from the web preview entirely — when that happens you get a typed status telling you so, not a silent empty run and not a bill.

### Input

| Field                | Type    | Default        | Notes                                                    |
| -------------------- | ------- | -------------- | -------------------------------------------------------- |
| `channels`           | array   | `["telegram"]` | Usernames, `@names`, or t.me URLs — all forms normalized |
| `postsFrom`          | date    | —              | Oldest post date, inclusive, UTC                         |
| `postsTo`            | date    | —              | Newest post date, inclusive, UTC                         |
| `maxPostsPerChannel` | integer | `50`           | Exact cap on charged posts per channel                   |
| `includeChannelInfo` | boolean | `true`         | One channel-info item per channel                        |
| `oldestFirst`        | boolean | `false`        | Output order within a channel                            |
| `proxyConfiguration` | object  | Apify proxy    | Datacenter proxy is sufficient                           |

### Use cases

- **OSINT & research** — archive and analyze public channel activity over exact date ranges
- **Brand & crypto monitoring** — track mentions, announcements and sentiment sources
- **News aggregation** — pull breaking-news channels into your pipeline with views as a signal
- **Trend analysis** — post frequency, view velocity, forward patterns across channels

### Fair pricing model

- `post` — charged per delivered in-range post
- `channel-info` — charged per info item, only when the channel also returned posts
- Everything else — statuses, summaries, empty runs, failures — **free**

Up to 3 channels are scraped concurrently; pages within a channel are fetched sequentially and politely. 429/5xx responses are retried 3× with backoff.

# Actor input Schema

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

Public channels to scrape — as usernames (<code>durov</code>), @-names (<code>@durov</code>), or any t.me link (<code>https://t.me/durov</code>, <code>t.me/s/durov</code>). Case and trailing slashes don't matter. Private invite links (<code>t.me/+…</code>) have no web preview and are reported as errors, not silently skipped.

## `postsFrom` (type: `string`):

Oldest post date to include, as <code>YYYY-MM-DD</code> (inclusive, UTC). The scraper walks the channel history server-side and STOPS at this date — you are never charged for older posts. Leave empty for no lower bound.

## `postsTo` (type: `string`):

Newest post date to include, as <code>YYYY-MM-DD</code> (inclusive, UTC). The scraper seeks this point in the history directly with <code>?before=</code> jumps instead of downloading everything — posts newer than this are never fetched in bulk, never returned, never charged. Leave empty for no upper bound.

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

Hard cap on the number of CHARGED posts returned per channel — honored exactly (a cap of 7 returns exactly 7). Defaults to 50 when omitted.

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

Also emit one channel-info item per channel: title, description, verified badge, and subscriber/photo/video/link counts (as numbers AND the raw strings like <code>9.91M</code>). Only charged when the channel also returned at least one post — on a zero-post run it is free.

## `oldestFirst` (type: `boolean`):

Output order within each channel. Off (default) = newest post first. On = oldest first — handy for chronological processing.

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

Proxy to use for t.me requests. Apify datacenter proxy works fine (verified) and is the cheapest option; t.me currently has no anti-bot protection.

## Actor input object example

```json
{
  "channels": [
    "telegram"
  ],
  "maxPostsPerChannel": 50,
  "includeChannelInfo": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

No description

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

No description

## `channelSummaries` (type: `string`):

No description

## `resultsCsv` (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": [
        "telegram"
    ],
    "maxPostsPerChannel": 50,
    "includeChannelInfo": true,
    "oldestFirst": false,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation_craft/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": 50,
    "includeChannelInfo": True,
    "oldestFirst": False,
    "proxyConfiguration": { "useApifyProxy": True },
}

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

```

## MCP server setup

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