Telegram Channel Scraper: Messages, Views & Channel Info
Pricing
Pay per event
Telegram Channel Scraper: Messages, Views & Channel Info
Export public Telegram channel messages — text, view counts, media type, links, hashtags, reply and edit info — plus channel title, description and subscriber count. No Telegram login, no phone number, no API ID: reads the public t.me/s preview.
Pricing
Pay per event
Rating
0.0
(0)
Developer
Changefeeds Tools
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
Give it a list of public Telegram channels and it exports their messages —
text, views, hashtags, links, media type, reply and edit info — plus one row
per channel with title, description, subscriber count and photo. Built for
"telegram scraper" / "telegram channel messages export" jobs: research
digests, competitor monitoring, dataset building, archiving a public channel's
posts. No Telegram login, no API ID, no phone number — it reads the same
public t.me/s/<channel> preview a logged-out browser sees.
This is a plain, one-shot export. If you need a run-over-run change feed (only what's new, edited, deleted or view-changed since the last check), see our companion actor Telegram Channel Monitor – Change Feed.
Input
| Field | Default | Notes |
|---|---|---|
channels | required | Channel usernames: durov, @durov, t.me/durov, https://t.me/s/durov all work. Up to 1,000 per run. Invalid entries become an error row; the run continues. |
maxMessagesPerChannel | 200 | 1 to 10,000. Pages backwards through the preview using its ?before=<id> cursor. |
since | none | ISO date/time. Only messages on or after it are exported; pagination stops once it reaches older ones. |
until | none | ISO date/time. Only messages on or before it are exported. |
keywords | none | Keep only messages whose text contains at least one keyword (case-insensitive). Leave empty to keep everything. |
includeChannelInfo | true | Also emit one row per channel with title, description, subscribers, photo, verified badge. |
{"channels": ["durov", "telegram"],"maxMessagesPerChannel": 20,"includeChannelInfo": true}
Output
One message row per exported message:
{"type": "message","channel": "durov","message_id": 549,"url": "https://t.me/durov/549","date": "2026-09-05T10:00:00+00:00","text": "Something new happened today.","views": 812000,"forwarded_from": null,"reply_to_id": null,"has_media": false,"media_type": null,"media_urls": [],"links": ["https://telegram.org"],"hashtags": [],"edited": false,"author": null}
One channel row per channel, when includeChannelInfo is on:
{"type": "channel","channel": "durov","title": "Pavel Durov","username": "durov","description": "Founder of Telegram.","subscribers": 10600000,"verified": true,"photo": "https://cdn4.telesco.pe/file/....jpg","messages_returned": 20,"checked_at": "2026-09-29T12:00:00.000Z"}
Channels that fail get a free channel status row instead of message rows —
the run never charges for a channel it couldn't read:
{"type": "channel","input": "thischanneldoesnotexist9x7q","channel": "thischanneldoesnotexist9x7q","status": "not_found","error": null,"checked_at": "2026-09-29T12:00:00.000Z"}
status is one of:
not_found— the username does not resolve to a channel.private_or_no_preview— the page shows Telegram's contact card but with channel content on it (see Limits below) — likely a channel that exists but whose message preview isn't public.error— the page failed to load after retries.invalid— the input entry is not a valid channel username.
The key-value store record OUTPUT holds a per-channel and per-run summary
(counts, HTTP stats, stopped_reason, charged_events). Every run leaves at
least one dataset row, even a run that matches nothing, so a quiet run is
never mistaken for a broken one.
Pricing
Pay per event, nothing else:
- $0.002 per channel checked (only charged when
includeChannelInfois on and the channel loaded successfully). - $0.001 per message returned ($1 per 1,000 messages).
Example: 3 channels, 200 messages each, with channel info = 3 × $0.002 +
600 × $0.001 = $0.61. If you set a maximum total charge for the
run, the actor stops cleanly once it's reached, delivering only what it
already charged for, and says so in OUTPUT.stopped_reason
("max_total_charge_reached").
Limits, stated plainly
- Public channels only. The actor reads
https://t.me/s/<channel>— exactly what a browser without a Telegram account sees. Private groups, member lists and phone numbers are never available this way. not_foundvsprivate_or_no_previewis a best-effort guess. Telegram's logged-out contact-card page looks nearly identical whether a username is simply unregistered or belongs to a channel whose preview isn't public; the actor infers which from whether the page carries any channel content. Don't treat the distinction as authoritative.- No media download. Photos, videos and documents only set
media_typeand, when the page's HTML happens to expose one (a photo/video thumbnail URL, a document link),media_urls. No files are fetched. reply_to_idandauthorare best effort. They come from the reply preview block and the "Sign messages" signature the page shows, when present; many channels show neither.- View counts are approximate by nature. Telegram rounds the public
counter (
2.67M,1.2K); a re-run may show a slightly different number. - It stays polite: at most 2 channels fetched at a time, at least 1.5 s between page requests of the same channel, HTTP 429 honoured (Retry-After, capped at 60 s, max 3 retries), a normal browser-like User-Agent, 20 s timeouts. A channel needs roughly 1 page per 20 messages, so exporting 10 channels × 200 messages takes a couple of minutes.
FAQ
Can this scrape private channels or groups? No. Only channels with a public username and a public web preview.
Does it get member lists or phone numbers? No, never — the public preview never exposes those.
Can I get only messages from the last week? Yes, set since to an ISO
date; pagination stops once it reaches older messages, so this is also
cheaper than exporting everything and filtering afterward.
Can I filter by topic? Yes, use keywords — case-insensitive substring
match against the message text.
I need a daily digest of only what changed, not a full re-export. Use our companion actor, Telegram Channel Monitor – Change Feed, instead.
Local development
pnpm --filter @mmnm/tgexport test # unit tests, no networkpnpm --filter @mmnm/tgexport build
node src/main.ts runs the actor locally with Apify's local storage
(./storage); ACTOR_TEST_PAY_PER_EVENT=true ACTOR_MAX_TOTAL_CHARGE_USD=1
exercises the charging path with the SDK's $1 local test price.