Telegram Channel Scraper - Posts, Views, Media
Pricing
from $1.00 / 1,000 run start fees
Telegram Channel Scraper - Posts, Views, Media
Scrape posts from any public Telegram channel: full text, publish time, view counts, media flags, forwards and outbound links, plus channel title and subscriber count. No login, no bot token.
Pricing
from $1.00 / 1,000 run start fees
Rating
0.0
(0)
Developer
SR
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
1
Monthly active users
7 days ago
Last modified
Categories
Share
Read the posts of any public Telegram channel: the full text, when it was published, how many people saw it, whether it carries a photo or a video, what it forwards, and every external link in it. Every row also carries the channel's display name and subscriber count, so a post's reach can be read against the size of its audience.
No login, no bot token, no API registration. Give it channel names, get back one row per post.
What you get
- One row per post, newest first within each channel, with a stable
postidentifier (durov/546) you can join runs on. - Text with its shape intact. Line breaks and emoji survive, so a fifteen-paragraph post does not arrive as one line.
- View counts twice:
views_textexactly as Telegram shows it (2.76M) andviewsas an integer (2760000) that sorts and sums correctly. - Attribution: the channel owner name, per-post signatures where a channel uses them, and
forwarded_fromwhen a post came from elsewhere. - Media and link flags:
has_photo,has_video,has_link_preview,is_reply, andoutbound_linkswith Telegram's own domains removed. - Channel context on every row:
channel_titleandsubscribersat the time of the run.
What a row looks like
A row from a run of the default input on the current build, unchanged:
{"channel": "durov","message_id": 546,"post": "durov/546","url": "https://t.me/durov/546","text": "🏳️ Gram Wallet in Telegram is ready and is now accessible to a select group of users.\n\n🤩 We'll be gradually rolling it out to our billion+ users over the next couple of weeks.\n\n⚡️ Thanks to the validators who approved the smart contract that powers it, future upgrades of the wallet won't require painful migrations.\n\nThis is just one of the innovations we've built to make non-custodial wallets usable ✨","text_length": 404,"posted_at": "2026-08-31 16:30:15","views_text": "2.76M","views": 2760000,"author": "Pavel Durov","signature": null,"forwarded_from": null,"is_forwarded": false,"has_photo": false,"has_video": true,"has_link_preview": false,"is_reply": false,"outbound_links": null,"channel_title": "Pavel Durov","subscribers": 10700000,"position": 3}
That run read two channels and returned 100 posts in 26 seconds: 99 with text, 100 with a view count, 57 with a photo or video.
Input
| Field | Type | Default | Example | What it controls |
|---|---|---|---|---|
channels | array of strings | ["durov", "telegram"] | ["durov", "t.me/telegram", "@bloomberg"] | The public channels to read. A t.me/name link or an @name is reduced to the bare name. Names must be 4 to 32 characters of letters, digits and underscores; anything else is reported as bad_input and skipped. |
limit | integer, 1 to 5000 | 100 | 500 | The total number of posts across all channels, divided evenly, so 100 over two channels is 50 each. A higher limit means more pages and a longer run; the Actor waits one to two seconds between pages. |
retries | integer, 1 to 8 | 4 | 6 | How often a page is retried with backoff before it is reported as fetch_failed. Raise it for a long unattended run; lower it if a fast answer matters more than a complete one. |
Paging walks backwards through message ids: each request asks for the posts older than the lowest id seen so far. There is no page count and no total, so a channel is finished when a page returns nothing new, when its share of limit is reached, or after 40 pages.
Output
One row per post in the default dataset.
| Field | Meaning |
|---|---|
channel | Channel username |
message_id | Message number within the channel |
post | channel/message_id. Stable, join runs on it |
url | Direct link to the post |
text | Post text with line breaks preserved, capped at 8,000 characters |
text_length | Characters of text |
posted_at | When it was published, UTC |
views_text | View count as Telegram shows it, for example 1.2K |
views | View count as an integer, which is what sorts correctly |
author | Channel owner name shown on the post |
signature | Per-post author signature, when the channel uses them |
forwarded_from | Original channel, when the post is a forward |
is_forwarded | Whether the post is forwarded from elsewhere |
has_photo | Whether the post carries a photo |
has_video | Whether the post carries a video |
has_link_preview | Whether a link preview card is attached |
is_reply | Whether the post replies to another |
outbound_links | External links in the post, Telegram's own excluded, at most 20 |
channel_title | Display name of the channel |
subscribers | Subscriber count at the time of the run |
position | Row position in this run |
The key-value store holds two more records. summary carries postsReturned, channelsRead, withText, withViews, withMedia, forwarded, fetchedInSeconds and errorCount. errors lists each failure with a code and a message, and is only written when something went wrong.
Pricing
Pay per event, no subscription.
- $0.001 per post returned (
post). - $0.001 per run start fee (
run_start).
A run that returns 100 posts costs $0.101. A run that returns 1,000 posts costs $1.001. Posts are billed only when they land in the dataset, so a channel that turns out to be private costs nothing beyond the start fee.
Free Apify plans receive at most 10 rows per run and are billed for those 10, not for the posts fetched and discarded. A paid Apify plan removes the cap.
Limits and failure modes
- Public channels only. A private channel, a group or a user account has no public preview. Telegram answers with a valid page that contains no posts, which is easy to mistake for a channel that never posted. The Actor recognises that page and reports
not_a_public_channelnaming the channel, while every other channel in the run continues. - A run with no rows still succeeds. The dataset is empty,
summary.errorCountis set, anderrorsholds the reason:bad_inputfor names that fail validation,not_a_public_channel,fetch_failedwhen a page failed after all retries, orno_resultswhen a channel exists and has nothing to show. - Per-channel ceiling. The walk stops after 40 pages per channel. For a deep archive, run the channel on its own with a high
limitrather than sharing the limit across many channels. - Timeouts. Each page request is given 40 seconds. The default run timeout is 300 seconds; a
limitof several thousand across many channels can need more, so raise the run timeout in the run options for large jobs. viewscan be null. Some channels hide view counts. That is the channel's setting, and it comes back asnullrather than zero.- Rounded subscriber counts. Telegram rounds them: a channel of 10.7M is reported as 10,700,000, not to the person.
- No comments, reactions or media files. These live in the app rather than the web preview.
has_photoandhas_videotell you a post carries media; the files are not downloaded. - No historical view counts.
viewsis the count at the moment of the run. Schedule the Actor and join onpostto watch a number grow.
Use cases
Competitor and market monitoring. "I want to know what the channels in my sector publish and which posts actually travel." Read them weekly, divide views by subscribers for an engagement rate, and rank posts across channels on one scale.
News and signal collection. "Announcements in my niche land on Telegram before anywhere else." Schedule the Actor, join each run on post, and pipe only the new rows into a feed or an alert.
Content research. "Which topics does this audience respond to?" text next to views shows which subjects a channel's readers open. Filter on has_video to compare formats.
Link and campaign tracking. "Where does this channel send its readers, and who amplifies it?" outbound_links shows the destinations; is_forwarded with forwarded_from maps how a message moves between channels.
Run it from the API
Start a run with the default input and read the dataset when it finishes:
curl -X POST "https://api.apify.com/v2/acts/s-r~telegram-channel-scraper/runs?token=YOUR_APIFY_TOKEN&waitForFinish=120" \-H "Content-Type: application/json" \-d '{"channels": ["durov", "telegram"], "limit": 100, "retries": 4}'
The response carries data.defaultDatasetId. Fetch the rows with:
$curl "https://api.apify.com/v2/datasets/DATASET_ID/items?token=YOUR_APIFY_TOKEN&format=json"
Use format=csv or format=xlsx for a spreadsheet.