# Telegram Channel Scraper – Posts, Search & Monitoring (`bitofacoder/telegram-channel-scraper`) Actor

Scrape public Telegram channels via the open t.me/s/ web preview — posts, in-channel keyword search, channel info, and an incremental monitor mode that returns only new messages each run. No login, no bot token.

- **URL**: https://apify.com/bitofacoder/telegram-channel-scraper.md
- **Developed by:** [Bobby](https://apify.com/bitofacoder) (community)
- **Categories:** Social media, News, AI
- **Features:** Limited permissions
- **Stats:** 6 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $1.00 / 1,000 results

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?

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 – Posts, Search & Monitoring

Pull posts from any **public** Telegram channel — text, views, dates, forwards, links, and media — as clean CSV/JSON. Then keep it fresh: **monitor mode** returns only the messages posted since your last run, so a scheduled task becomes a live feed instead of a re-scrape.

No login, no bot token, no phone number. Reads Telegram's public `t.me/s/<channel>` web preview.

### What you can do

| Mode | What it does | Fields it uses |
|---|---|---|
| **Channel posts** | Messages from one or more public channels, newest-first. | `channels`, `maxItems` |
| **Search** | In-channel keyword search across each channel. | `channels`, `searchQueries`, `maxItems` |
| **Channel info** | Title, subscribers, description, post/photo/video counts. | `channels` |
| **Monitor** *(incremental)* | Only messages posted **since the last run**, per channel. State persists between runs. | `channels`, `maxItems`, `monitorStoreName` |

### Example input

Latest 100 posts from two channels:

```json
{
  "mode": "channel",
  "channels": ["telegram", "https://t.me/durov"],
  "maxItems": 100
}
```

Track new posts on a schedule (each run returns only what's new):

```json
{
  "mode": "monitor",
  "channels": ["durov", "telegram"],
  "maxItems": 50
}
```

Watch crypto channels for announcement keywords:

```json
{
  "mode": "search",
  "channels": ["binance", "coingecko"],
  "searchQueries": ["listing", "partnership"],
  "maxItems": 50
}
```

`@name`, `name`, `t.me/name`, and `https://t.me/s/name` all work (case-insensitive).

### Scheduled monitoring

To turn `monitor` mode into a live feed: in the Apify Console go to **Schedules → Create**, set an interval (e.g. every 15 minutes), and point it at this Actor with `mode: "monitor"`. Each run stores only the messages posted since the last one. Keep the **same `monitorStoreName`** across runs so the watermark persists, and give a **different** name to each separate monitor task. Pipe the dataset to a webhook/Slack/Sheet via Apify integrations to get alerts.

### Output (per message)

```json
{
  "type": "message",
  "channel": "telegram",
  "id": 123,
  "url": "https://t.me/telegram/123",
  "text": "…",
  "date": "2026-06-22T15:01:34+00:00",
  "views": 1200000,
  "author": null,
  "isForwarded": false,
  "forwardedFrom": null,
  "tags": ["update"],
  "tagList": "update",
  "mentions": [],
  "links": ["https://…"],
  "firstLink": "https://…",
  "media": [{ "type": "photo", "url": "https://…" }],
  "mediaCount": 1,
  "linkPreview": { "url": "…", "siteName": "…", "title": "…", "description": "…" }
}
```

`tagList`, `firstLink`, and `mediaCount` are flat mirrors of the array fields, so CSV/Excel exports are usable without unpacking JSON.

### What this does — and doesn't — return

To keep expectations honest:

- ✅ **Public channel content**: text, date, view count, author signature, forwards, replies, hashtags, mentions, outbound links, media URLs, and link-preview cards. Plus in-channel search, channel info, and incremental monitoring.
- ❌ **Public channels only** — those visible at `t.me/s/<channel>`. Private channels, groups, and user accounts are **not** accessible (the Actor rejects invite links with a clear message).
- ❌ **No member lists, DMs, poll voters, or per-user reactions.** Those require a logged-in account / bot token, which this Actor deliberately does not use. View counts are included; reaction counts are not exposed by the public preview.
- ⚠️ **History depth depends on the public preview.** The Actor pages backward via `?before=`, but coverage is whatever Telegram serves to its web preview — not guaranteed to reach a channel's very first post.

### Notes

- **Proxy is off by default.** The preview pages are public; enable Apify Proxy (or set `requestDelayMs` to ~300–1000) only for large drains or many channels, to spread the per-IP rate limit.
- **`maxTotalItems`** caps results (and charges) across the whole run, independent of the per-channel `maxItems`.
- One bad/private/typo'd channel is logged and skipped — it won't abort the run or lose the other channels' data.
- **How it works / longevity:** this reads Telegram's public `t.me/s/` HTML preview (no API). That preview has been stable for years, but it is an undocumented endpoint — a Telegram markup change can require a parser update (you'll see a "no posts parsed / markup may have changed" warning), and if Telegram ever added JavaScript bot-protection to it, an HTTP-only scraper like this would need a browser-based rework. No reactions or poll-voter data is exposed by the preview.

### Pricing

Pay per result — you're charged once per message (or channel-info record) stored to the dataset. Cap any run with `maxTotalItems`.

# Actor input Schema

## `mode` (type: `string`):

What to scrape. Each mode uses a different set of fields below.

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

Public channel usernames or t.me links, e.g. \['telegram', 'https://t.me/durov']. The @ and URL parts are optional — 'telegram', '@telegram', and 't.me/telegram' all work.

## `searchQueries` (type: `array`):

Used by 'search' mode. Keywords or phrases to search for inside each channel above.

## `monitorStoreName` (type: `string`):

Named key-value store that holds the per-channel watermark (the last message ID seen) across runs. Use a DISTINCT name per monitor — two monitors sharing one name will overwrite each other's state. Don't run two monitors with the same name at the same time.

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

Upper limit of messages per channel (per query, in search mode). Kept low by default for a fast, cheap first run — raise it to pull more history (the public preview reaches roughly the last few hundred to ~1,000 messages).

## `maxTotalItems` (type: `integer`):

Hard ceiling on the total number of results — and pay-per-result charges — across ALL channels in one run. Leave empty for no overall cap.

## `requestDelayMs` (type: `integer`):

Optional pause before each page fetch. Leave at 0 for normal runs. Raise to ~300–1000 for large drains or many channels without a proxy, to avoid rate limiting.

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

Off by default — t.me/s/ preview pages are public. Enable Apify Proxy to rotate IPs and spread the rate limit on large runs.

## Actor input object example

```json
{
  "mode": "channel",
  "channels": [
    "telegram"
  ],
  "searchQueries": [
    "update"
  ],
  "monitorStoreName": "telegram-monitor-state",
  "maxItems": 20,
  "requestDelayMs": 0,
  "proxyConfiguration": {
    "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 = {
    "channels": [
        "telegram"
    ],
    "searchQueries": [
        "update"
    ]
};

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

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

```

## MCP server setup

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