# Telegram Media Scraper - Photo & Video URLs (`scrapersdelight/telegram-channel-media-scraper`) Actor

From $0.80 per 1,000 media files, no start fee. Every photo and video in a public Telegram channel as its own row: direct CDN file URL, thumbnail, video duration in seconds, post id, date and view count. Filter by media type or date, and pay only for files delivered. No login.

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

## Pricing

$0.80 / 1,000 per media file 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?

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 Media Scraper — Photo & Video URLs

Every photo and video in a public Telegram channel as **its own row**, with a direct CDN file URL,
a thumbnail, the video's duration in seconds, and the post it came from.

No login. No phone number. No API key. No Telegram account of any kind.

***

### What one row looks like

```json
{
  "channel": "varlamov",
  "channelTitle": "Илья Варламов",
  "subscribers": 237000,
  "postId": 10175,
  "postUrl": "https://t.me/varlamov/10175",
  "datetime": "2026-09-13T14:20:15+00:00",
  "views": 283000,
  "mediaIndexInPost": 0,
  "mediaType": "video",
  "fileUrl": "https://cdn4.telesco.pe/file/69c5229dcb.mp4?token=fapL8sIW…",
  "thumbnailUrl": "https://cdn4.telesco.pe/file/Jw5aBpVWHcCgb1ZUt9ygXm5…",
  "hasDirectUrl": true,
  "durationSeconds": 29,
  "durationText": "0:29",
  "width": null
}
```

| field | what it is |
|---|---|
| `mediaType` | `photo`, `video`, `voice` or `document` |
| `fileUrl` | The direct CDN URL — download it straight away |
| `hasDirectUrl` | **Read this before a bulk download.** See below |
| `thumbnailUrl` | Video preview image, always present for a video |
| `durationSeconds` | Video length as a number. `durationText` keeps Telegram's own `"0:29"` |
| `width` | Photo display width where Telegram publishes it |
| `mediaIndexInPost` | Position within its post, so an album keeps its order |
| `postId` · `postUrl` · `datetime` · `views` | The post the file came from |

### Not every video has a direct file URL

Some videos are served as a **thumbnail plus a player link**, with no inline file to download.
Measured on a real 3-channel run: **38 of 40** items carried a direct URL, and the other 2 did not.

Those rows are still delivered — they have the thumbnail, duration, post and view count — but
`hasDirectUrl` is `false` and `fileUrl` is `null`. An extractor that pretended otherwise would hand
you a download list with silent holes in it. Set **`onlyWithDirectUrl: true`** to deliver — and pay
for — only rows you can fetch immediately.

### One row per file, not per post

A post can carry an album of a dozen photos. Here each one is its own row with its own URL, so the
output feeds a downloader directly instead of needing an array unpacked first.

Durations and file URLs are read **per post block**, never by scanning the whole page and pairing
results up by position. On a real channel those counts differ — one page carried 3 video durations
but only 2 inline video tags — and a page-wide pairing would silently attach one video's duration to
a different video's file.

### Input

| input | default | what it does |
|---|---|---|
| `channels` | `["telegram"]` | Handles or links — all t.me forms work |
| `mediaTypes` | `["photo","video"]` | Also `voice` and `document`, parsed when a channel has them |
| `maxMediaItems` | `500` | Cap across all channels on files delivered — and charged |
| `maxMediaPerChannel` | `0` | Per-channel cap, for an even sample across many channels |
| `maxPagesPerChannel` | `0` | Telegram serves 20 posts per page; a safety stop for long channels |
| `sinceDate` · `untilDate` | — | ISO dates. Paging stops once a page is entirely older than `sinceDate` |
| `onlyWithDirectUrl` | `false` | Deliver only immediately-downloadable files |
| `maxConcurrency` | `5` | Channels read at once — raising it cuts run time and run cost |

### Pricing

Pay per event: **$0.0008 per media file returned**, and **no run-start fee**.

500 files cost **$0.40**. The closest media Actor in this category charges **$0.01 per post** — on a
channel carrying roughly one media file per post, that is about **$6.25** for the same 500 files.

Files excluded by your type or date filters are never delivered and never charged, and a run that
finds nothing costs nothing.

### Public channels only

This reads `t.me/s/<channel>`, the preview Telegram serves to anyone with a browser. If a channel has
no public preview — private, or preview disabled by its owner — the run says so and charges nothing
for it. Groups, direct messages and member lists are **not** accessible this way and are not attempted.

A page that isn't HTTP 200 is retried three times on three separate proxy sessions before the channel
is reported as unreachable — a transport hiccup is never treated as the end of a channel, because
that is how a scraper ships a third of a history and bills you for it as though it were complete.

### Source

`t.me/s/<channel>` is Telegram's own public web preview, served to any visitor. The host publishes no
`robots.txt` at all. Media in a public channel is published by the channel owner to be seen publicly;
copyright in it stays with whoever owns it, and what you may do with a downloaded file is your
responsibility, not a permission this Actor grants.

# Actor input Schema

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

Handles or links. `telegram`, `@telegram` and `https://t.me/telegram` all work.

## `mediaTypes` (type: `array`):

Which media to deliver. Photos and videos are what public channels mostly carry; voice notes and documents are parsed when a channel has them.

## `maxMediaItems` (type: `integer`):

Cap across all channels on files delivered - and charged. 0 means no cap.

## `maxMediaPerChannel` (type: `integer`):

Per-channel cap, for an even sample across many channels. 0 means no per-channel cap.

## `maxPagesPerChannel` (type: `integer`):

Telegram serves 20 posts per page. A safety stop for very long channels. 0 means no limit.

## `sinceDate` (type: `string`):

ISO date. Paging stops once a page is entirely older than this, so a narrow window is cheap.

## `untilDate` (type: `string`):

ISO date.

## `onlyWithDirectUrl` (type: `boolean`):

Some videos are served as a thumbnail plus a player link with no inline file URL. Turn this on to deliver - and pay for - only rows you can download straight away.

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

How many channels to read at once. Raising this cuts run time and therefore what the run costs you.

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

Apify Proxy. Datacenter is sufficient.

## Actor input object example

```json
{
  "channels": [
    "telegram"
  ],
  "mediaTypes": [
    "photo",
    "video"
  ],
  "maxMediaItems": 500,
  "maxMediaPerChannel": 0,
  "maxPagesPerChannel": 0,
  "onlyWithDirectUrl": false,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per media file: type, direct CDN URL, thumbnail, video duration in seconds, photo width, and the post it came from with its date and view count.

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

RUN\_SUMMARY: per channel, pages read, media delivered, how many carry a direct download URL, and exactly why paging 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/telegram-channel-media-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 = {}

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

```

## MCP server setup

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