Telegram Channel Message Scraper avatar

Telegram Channel Message Scraper

Pricing

from $6.00 / 1,000 message returneds

Go to Apify Store
Telegram Channel Message Scraper

Telegram Channel Message Scraper

Telegram Channel Scraper that returns message text, views, publish dates, media types, forwards and link previews. 36 typed fields per message. Read a whole channel, search inside it, or turn on onlyNewSinceLastRun so a repeat run bills only for new posts. JSON, CSV, Excel.

Pricing

from $6.00 / 1,000 message returneds

Rating

0.0

(0)

Developer

angel nguyen

angel nguyen

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

2 days ago

Last modified

Share

Changelog

Telegram Channel Message Scraper extracts messages from public Telegram channels, and as of 2026-09-02 it no longer charges you a second time for messages it has already delivered to you.

2026-09-02 — onlyNewSinceLastRun now defaults to true

What changed. The onlyNewSinceLastRun switch used to be off unless you turned it on. It is now on unless you turn it off.

Why. With it off, every run read each channel from its newest message backwards, so running this Actor twice over the same channel delivered the same messages twice — and charged you for them twice. You were paying again for data you already had. A default should not do that, so it no longer does.

What it costs you, on the numbers in the Pricing section. One channel with 100 messages of history, polled 8 times in a day, publishing 2 new posts that day:

Old default (false)New default (true)
First run$0.602$0.602
The other seven runs$4.214 — the same 100 messages, seven more times$0.026 — 7 channel reads plus the 2 new posts
That day$4.816$0.628

Same messages delivered. 87% less spent.

How to get the old behaviour back. Set one field:

{
"channels": ["durov"],
"onlyNewSinceLastRun": false
}

That restores the full re-read on every run, exactly as before. It is a legitimate choice and it is not going away — a full re-read is what you want if you need messages that were edited after you first collected them, if you want current view counts on older posts rather than the counts at the moment they were first scraped, or if your own pipeline de-duplicates downstream and would rather have everything.

Nothing else changed. Same fields, same output records, same prices — message-returned is still $0.006 and channel-resolved is still $0.002. Only the starting position of one switch moved.

What this Actor does

Telegram Channel Message Scraper extracts every message a public Telegram channel has published and hands you each one as a typed record — the text, the view count, the publish date, the media attached to it, who it was forwarded from, and the link preview Telegram itself resolved. You give it a channel username; you get back rows you can sort, filter, diff and export.

Actor icon

It reads the same public web preview a logged-out visitor sees at https://t.me/s/<channel>. No account, no phone number, no API key, no session string. If a channel is readable in a browser without signing in, this Actor can read it; if it is not, the Actor says so instead of returning an empty list that looks like an empty channel.

If you run this more than once, onlyNewSinceLastRun is already doing the right thing

It is on by default, and it is what stops a repeat run billing you twice for the same message.

With it off, each run reads every channel from the newest message backwards, so a run you repeat an hour later returns the same messages again — and charges for them again. Poll a channel eight times a day and you are billed eight times for the posts that were already delivered on the first run. That used to be the default. Since 2026-09-02 it is not.

With it on, the Actor records the newest message ID it delivered for each channel in a key-value store inside your own Apify account, and the next run returns — and bills for — only what has been published since. Nothing else about the output changes.

{
"channels": ["durov"],
"onlyNewSinceLastRun": true
}

Worked at the prices in the Pricing section below: one channel with 100 messages of history, polled eight times in a day, publishing two new posts that day.

onlyNewSinceLastRun: falseDefault (true)
First run$0.602 — 100 messages + 1 channel read$0.602 — 100 messages + 1 channel read
The other seven runs$4.214 — the same 100 messages, seven more times$0.026 — 7 channel reads plus the 2 new posts
That day$4.816$0.628

Same messages delivered, 87% less spent.

When turning it off is the right call. A full re-read is what you want if you need messages that were edited after you first collected them, if your own pipeline de-duplicates downstream, or if you want the current view counts on older posts rather than the counts at the moment they were first scraped. Those are real reasons, which is why this Actor does not decide for you and does not change your input behind your back. It just makes sure you know the switch is there.

Key features

FeatureDetail
InputChannel usernames, @usernames or https://t.me/<name> links — mixed freely in one list
Fields per message36 typed fields, including views, media types, forwards, replies and link previews
SearchRuns Telegram's own in-channel search with searchQuery and returns only what matched
IncrementalonlyNewSinceLastRunon by default. A repeat run returns, and bills for, only messages published since the last run. Set it to false for a full re-read every time
Date and ID floorsminDate and sinceMessageId stop the walk early, so you neither fetch nor pay for old messages
Channel recordOne optional summary row per channel: title, description, subscriber count, media counters
Unreadable channelsReported as unavailable with a reason, and never charged for
OutputFlat rows, one per message — no nested blobs, exports cleanly to CSV and Excel

How it works

  1. Normalise every reference. durov, @durov and https://t.me/durov all resolve to the same channel, and duplicates in your list are collapsed before anything is fetched.
  2. Read the public preview. The Actor requests https://t.me/s/<channel>. That page carries the most recent messages plus Telegram's own before= cursor for the page before it.
  3. Walk backwards. It follows that cursor page by page until it has reached your maxMessagesPerChannel cap, crossed your minDate or sinceMessageId floor, or run out of channel. A cursor that stops advancing ends the walk rather than looping.
  4. Charge, then deliver. Records are charged before they are pushed and delivery is truncated to the number actually charged, so the row count you receive and the row count you are billed for are the same number.

A single channel at the default cap of 100 messages typically finishes in well under a minute.

Two things this Actor deliberately does not do

  • It does not guess. A field the page does not carry comes back null, not 0 and not "". A message with no view counter and a message with zero views are different facts and stay different.
  • It does not turn a failure into an empty result. A channel that is private, deleted, or has its web preview switched off is written to the run summary as unavailable with the reason, is excluded from the dataset, and is not billed.

Input

The minimum input is a single channel name. Everything else has a working default.

{
"channels": ["durov", "@telegram", "https://t.me/durov"],
"maxMessagesPerChannel": 100,
"searchQuery": "",
"minDate": "2026-01-01",
"sinceMessageId": 0,
"onlyNewSinceLastRun": true,
"includeChannelRecord": true
}
FieldTypeDefaultMeaning
channelsarray of stringPublic channel usernames, @usernames or t.me links. Required.
maxMessagesPerChannelinteger100Hard cap per channel. Bounds runtime and spend.
searchQuerystringRuns Telegram's in-channel search; only matches are returned.
minDatestringISO date. Messages published before it are neither returned nor charged.
sinceMessageIdintegerOnly messages with a higher ID. Every message URL ends in its ID.
onlyNewSinceLastRunbooleantrueStores the newest delivered ID per channel and resumes from it, so a repeat run is not charged for messages already delivered. Set false for a full re-read every run.
includeChannelRecordbooleantrueAdds one summary row per channel alongside the messages.

Output

Every run writes one row per message to the default dataset. The record below is a real row from a real run against t.me/s/telegram, copied unchanged:

{
"recordType": "message",
"channelUsername": "telegram",
"channelTitle": "Telegram News",
"messageId": 459,
"url": "https://t.me/telegram/459",
"datePublished": "2026-08-26T19:12:24+00:00",
"text": "Signed Gifts. You can now add your signature and a comment to gifts you buy via the gift marketplace.\n\nAugust Features\n1 • 2 • 3 • 4 • 5 • More",
"textLength": 143,
"views": 590000,
"authorSignature": null,
"isEdited": false,
"isForwarded": false,
"forwardedFrom": null,
"forwardedFromUrl": null,
"isReply": false,
"replyToUrl": null,
"replyToText": null,
"mediaTypes": ["video"],
"photoUrls": [],
"videoThumbUrl": "https://cdn1.telesco.pe/file/NTKaupuR9Z_bBp0...",
"videoDurationSeconds": 12,
"voiceDurationSeconds": null,
"documentTitle": null,
"documentExtra": null,
"pollQuestion": null,
"pollType": null,
"pollOptions": [],
"linkPreviewTitle": null,
"linkPreviewDescription": null,
"linkPreviewSiteName": null,
"linkPreviewUrl": null,
"linkPreviewImageUrl": null,
"outboundLinks": [
"https://t.me/telegram/382",
"https://t.me/telegram/455",
"https://t.me/telegram/456"
],
"hashtags": [],
"mentions": [],
"scrapedAt": "2026-08-30T03:57:46.459Z"
}

Only videoThumbUrl and the outboundLinks list are abbreviated above — the CDN token is several hundred characters long and the message links to six sibling posts. Everything else is verbatim.

The channel summary record

With includeChannelRecord left on, each channel also produces one row of type channel:

{
"recordType": "channel",
"username": "durov",
"url": "https://t.me/durov",
"title": "Pavel Durov",
"description": "Founder of Telegram.",
"isVerified": false,
"subscribers": 11000000,
"photosCount": 102,
"videosCount": 45,
"filesCount": null,
"linksCount": 197,
"scrapedAt": "2026-08-30T03:57:32.785Z"
}

filesCount is null there because that channel's page shows no file counter at all. That is the tri-state the Actor keeps: present-and-zero and not-present are not the same answer.

The dataset exports to JSON, CSV, Excel and XML from the Storage tab, and the same rows are readable through the dataset API.

Pricing

This Actor is priced per event, so you pay for what it delivers rather than for how long it runs.

EventPriceCharged when
message-returned$0.006Once per message record delivered to the dataset
channel-resolved$0.002Once per channel whose public preview was successfully read

There is no per-run start fee and no monthly minimum. A run over one channel returning 100 messages costs $0.602. A channel that turns out to be unreadable costs nothing. A sinceMessageId or minDate filter that matches nothing costs only the $0.002 channel read.

The setting that moves your bill the most is onlyNewSinceLastRun, and it is on by default. Left on, a repeat run charges only for what was published since the last one. Turned off, a repeat run over the same channel returns the same messages and charges for them again — in the worked example above, $4.816 a day instead of $0.628 for the same delivered messages. If you turn it off, read the section near the top of this page first so the bill is not a surprise.

How that price was set. The two public Telegram message Actors on the Store that charge per message price it at $0.005 and $0.0025 per record. This Actor sits slightly above both because its record is wider than a plain text-and-date dump — 36 fields including view counts, forward provenance, media typing and the resolved link preview. Deliberately, there is room to move the price down and none is needed to move it up.

Charges are applied before records are handed over, and delivery is truncated to whatever your own budget cap allowed, so you are never billed for a row you did not receive.

FAQ

Do I need a Telegram account, a phone number or an API key? No. The Actor reads the public web preview that Telegram serves to logged-out visitors. It holds no credentials and asks you for none.

Can it read private channels or group chats? No, and it will not pretend to. A private channel, a group chat and a deleted username are all unreadable from outside, and all three are reported as unavailable rather than as empty.

Why did a public channel come back as unavailable? Channel owners can switch the web preview off. When they do, t.me/s/<name> serves no messages even though the channel is public inside the app. There is no way around that from outside, so the Actor reports it instead of guessing.

How far back can it go? As far as the channel's own preview goes. The walk follows Telegram's before= cursor until the cursor stops, which is the start of the channel. Use maxMessagesPerChannel to stop earlier.

Does it get comments or reactions? No. The public preview does not carry the comment thread or the reaction counts, so neither is in the schema. A field this Actor does not serve is a field it does not advertise.

Can I run it on a schedule and only get what is new? Yes — that is what onlyNewSinceLastRun is for, and since 2026-09-02 it is on by default, so you get that behaviour without asking for it. It keeps the newest delivered message ID per channel in a key-value store inside your own account, resumes from there, and bills you only for what is new. Set it to false if you would rather re-read the whole window every run.

Are view counts exact? They are exactly what the page shows, which Telegram rounds above a thousand. 590000 in a record means the page said 590K. The rounding is Telegram's, not this Actor's.

Do you keep my results? The dataset lives in your own Apify account under your own storage. Nothing is copied anywhere else and nothing is retained by the publisher.

Limits and troubleshooting

  • maxMessagesPerChannel is a cap, not a target. A channel with fewer messages returns fewer rows, and you are charged for the rows you got.
  • A search query narrows before it charges. searchQuery is Telegram's own search, so a term that matches nothing produces no message rows and no message charges.
  • Rate limiting. The Actor retries a 429 or a 5xx three times with a growing pause. A page that stays unreachable ends that channel's walk and is reported, never treated as an empty page.
  • Views can be missing. Some channels hide the counter. Those records carry views: null.
  • The walk is bounded. It stops after 200 pages per channel even if the cap has not been reached, so a pathological cursor cannot spend your budget in a loop.

Integrations and API

The Actor is callable from anywhere the Apify API reaches.

curl -X POST "https://api.apify.com/v2/acts/praise-most-high~telegram-channel-message-scraper/runs?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"channels":["telegram"],"maxMessagesPerChannel":50}'
  • Scheduling — run it nightly from the Apify scheduler and, with onlyNewSinceLastRun on by default, the dataset only ever grows by what was actually posted.
  • Webhooks — fire on ACTOR.RUN.SUCCEEDED to push new messages into your own store the moment they land.
  • Python and JavaScript SDKs — both call it the same way; see the Apify client documentation.
  • MCP — reachable as a tool from any MCP-speaking agent through Apify's own MCP server, with LIMITED_PERMISSIONS so it can be paid for per call.

Unofficial. This Actor is not affiliated with, endorsed by, or sponsored by Telegram Messenger Inc. or Telegram FZ-LLC. "Telegram" is a trademark of its owner and is used here only to identify the service being read.

The Actor reads a public, logged-out web page that a channel's owner chose to publish, and it collects no personal data beyond what that owner put on it, nothing behind an authentication boundary, and no member lists. It stores no credentials because it needs none.

Whether your own use of the output is permitted is a question about your jurisdiction and your purpose, and it is yours to answer — this Actor makes no representation about it. Telegram's own Terms of Service govern your relationship with Telegram.

Support and feedback

Open an issue on the Actor's Issues tab. Bug reports that include the run ID and the input that produced the problem are answered fastest. Feature requests are read, and they are the main source of what gets built next.