# Telegram Channel Scraper — Public Posts, Views, Media, No Login (`charliemorrisondev/telegram-channel-scraper`) Actor

Scrape public Telegram channels with no account: messages with text, timestamp, views, media URLs, forwards. Search channels, fetch single posts, or pass a post-ID cursor to track a channel and pay only for new updates. Public channels only — member lists need a login, this Actor never signs in.

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

## Pricing

from $5.00 / 1,000 posts

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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: public posts, views and media, no login

Scrape public Telegram channels without an account. Hand it channel handles or `t.me` links,
get back every recent post as a clean row: full text, exact timestamp, view count, media URLs,
forward attribution and a reply flag.

No API key. No login. No captcha. No proxy required.

Channel messages come back as structured rows you can send straight to a sheet, a database or an
LLM pipeline.

### What you get per post

| field | what it is |
|---|---|
| `channel` / `channelTitle` | handle and display name |
| `postId` / `url` | post number and direct `t.me` link |
| `datetime` | exact publish time, ISO 8601 with timezone |
| `text` | full post text |
| `views` | view count, expanded from Telegram's `1.2M` shorthand to `1200000` |
| `media` | photo / video / document / voice URLs attached to the post |
| `forwardedFrom` | original channel when the post is a forward |
| `isReply` | whether the post replies to another |
| `scrapedAt` | when this run collected it |
| `channelSubscribers` / `channelDescription` | with `includeChannelMeta` on |

### Input

```json
{
  "channels": ["durov", "https://t.me/telegram"],
  "maxItemsPerChannel": 100,
  "since": "2026-07-01",
  "includeMedia": true,
  "minViews": 1000,
  "excludeForwarded": false,
  "mediaOnly": false,
  "includeChannelMeta": false
}
```

`channels` takes `durov`, `@durov` or `https://t.me/durov`. All three work.

`since` skips anything older than the date you give, so a daily run costs you the day, not the
archive. `maxItemsPerChannel` caps posts per channel, which caps what you pay: billing is per
post returned.

You are billed per post returned, and nothing else: a channel with no new posts costs nothing, a
private or nonexistent one is skipped rather than charged, and the same channel listed twice —
`durov`, `@durov`, `https://t.me/durov` — is scraped once, not billed twice.

`minViews`, `excludeForwarded` and `mediaOnly` filter posts out *before* they are returned, so
they cut the bill and not just the file.

### Running it on a schedule

A date is a blunt cursor. Two runs on the same day either hand you the boundary day twice or
lose whatever landed between them, and you pay for the overlap either way. So every run reports
the last post ID it saw per channel — in the log, and in the key-value store as `LAST_POST_IDS`:

```
Next incremental run: pass afterPostIds={"durov": 536}
```

Feed that back as `afterPostIds` (or `afterPostId` for a single cursor across all channels) and
the next run returns only what is genuinely new. A quiet channel returns nothing and costs
nothing.

### Two other ways to ask

**Search inside the channels.** `searchQuery` runs Telegram's own in-channel search over the same
public page, so you can watch twenty channels for one word and pay only for the posts that
mention it. A channel with no match returns nothing rather than an error.

**Fetch specific posts.** `postUrls` takes links like `https://t.me/durov/536` and returns those
posts alone — useful when something else in your pipeline already found the link. Works with or
without `channels`.

### What people use it for

- Channel monitoring: run it on a schedule, keep only what is newer than the last run.
- Narrative tracking: `forwardedFrom` shows how a post spreads between channels.
- Engagement research: `views` is on every post, so you can rank content without opening
  Telegram.
- Archiving: text plus media URLs for the channels you care about.

### Scope: public channels only, on purpose

This Actor reads Telegram's public channel preview, which anyone can open in a browser without
an account. That is the whole design.

It will not scrape member lists, phone numbers or private groups. Those need a logged-in
Telegram session, and session-based scrapers break the moment the account behind them gets
flagged. Nothing here signs in, so there is no session to lose. A private, invite-only or
nonexistent channel is skipped with a clear log line and an empty result rather than a
mysterious error.

Fewer moving parts is the point: an anonymous HTML page keeps working.

### Notes

- Posts come back newest first.
- Channels are processed one at a time, with a short delay between page requests, so Telegram
  is not being hammered and neither is your run.
- A channel with no posts returns nothing and says so in the log.

# Actor input Schema

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

Public Telegram channels to scrape. Optional if you pass `postUrls` instead. Accepts a handle (durov), an @handle (@durov) or a full link (https://t.me/durov). Private, invite-only and nonexistent channels are skipped with a log line — this Actor never signs in.

## `maxItemsPerChannel` (type: `integer`):

How many of the most recent posts to return per channel. Lower it to bound your spend — you are charged per post returned.

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

ISO date, e.g. 2026-07-01. Older posts are skipped and not charged. Leave empty for the most recent posts.

## `includeMedia` (type: `boolean`):

Return photo, video, document and voice URLs attached to each post.

## `afterPostId` (type: `integer`):

Incremental cursor for scheduled runs: return only posts newer than this ID. Each run saves the latest IDs it saw to the key-value store as LAST\_POST\_IDS and prints them in the log, so the next run can pick up exactly where this one stopped. More precise than a date — two runs on the same day will not re-charge you for the same posts.

## `afterPostIds` (type: `object`):

Per-channel version of the cursor above, e.g. {"durov": 536, "telegram": 412}. Overrides it for the channels listed. Copy the LAST\_POST\_IDS value from your previous run.

## `minViews` (type: `integer`):

Skip posts below this view count. Filtered posts are never returned and never charged.

## `excludeForwarded` (type: `boolean`):

Drop posts that were forwarded from another channel, keeping only original content.

## `mediaOnly` (type: `boolean`):

Keep only posts that have a photo, video, document or voice message attached.

## `includeChannelMeta` (type: `boolean`):

Add the channel's subscriber count and description to every row.

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

Return only posts matching this phrase, using Telegram's own in-channel search on the public preview. Combines with the date, cursor and view filters.

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

Scrape individual posts by link, e.g. https://t.me/durov/536. Works on its own or alongside `channels`.

## Actor input object example

```json
{
  "channels": [
    "durov",
    "telegram"
  ],
  "maxItemsPerChannel": 100,
  "since": "2026-07-01",
  "includeMedia": true,
  "afterPostId": 536,
  "afterPostIds": {
    "durov": 536
  },
  "minViews": 1000,
  "excludeForwarded": false,
  "mediaOnly": false,
  "includeChannelMeta": false,
  "searchQuery": "airdrop",
  "postUrls": [
    "https://t.me/durov/536"
  ]
}
```

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

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

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

```

## MCP server setup

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