Telegram Channel Scraper: Posts, Search, Profiles
Pricing
$0.50 / 1,000 result rows
Telegram Channel Scraper: Posts, Search, Profiles
68 fields per message against 16 from the top Telegram Actor: reactions per emoji, polls, forwards, replies, buttons, media links. Channel posts, in-channel search and profile cards in one Actor. No API key, no account.
Pricing
$0.50 / 1,000 result rows
Rating
0.0
(0)
Developer
Snow Leo Data
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 hours ago
Last modified
Categories
Share
Telegram Channel Scraper: posts, in-channel search and profile cards
Read any public Telegram channel without an account, without a bot token and
without the MTProto API. This Actor opens Telegram's own public web preview —
the same pages you see at t.me/s/durov — walks the history backwards page by
page and returns 68 fields per message, including the things every other
Telegram Actor on the store leaves on the floor: reactions with a count per
emoji, the channel's numeric id, the author signature, poll results, forwards,
replies, inline buttons and direct CDN links to every attachment.
Three jobs live in one Actor here, and on Apify they are usually three separate paid Actors: reading a channel's posts, searching inside channels by keyword, and resolving a handle into its profile card.
What you get, in numbers
Everything below was measured against live Telegram on 2026-09-13 and can be
reproduced with python3 tools/measure.py.
| Measured | Result |
|---|---|
| Fields per message row | 68 (69 with the HTML body) |
| Fields per profile row | 21 |
| Fields in the strongest rival's documented row | 16 |
| Live messages sampled across 6 channels | 356 |
| Of those, carrying reactions this Actor returns and the rival does not | 239 (67.1%) |
| Carrying a view count | 356 (100%) |
| Carrying the numeric channel id | 356 (100%) |
| Carrying an attachment | 250 (70.2%) |
| Carrying a link preview | 55 (15.4%) |
| Carrying an author signature | 119 (33.4%) |
| Distinct reaction emoji seen in that sample | 34 |
| Messages per page returned by Telegram | 20 |
| Throughput, one address, no proxy | 190 messages over 10 pages, about 38 per second |
There is no history ceiling
The usual complaint about Telegram scrapers is that they hand you the latest
page and stop. This one walks all the way down. Measured on meduzalive, a
channel whose newest message id is 147827:
before=1000returned messages 980 to 999;before=50000returned 49980 to 49999;before=20returned 1 to 19 — the very first messages the channel ever published.
So the reachable depth is the whole channel, and the test suite proves the
walk really terminates at message 1 rather than looping on the same page.
tests/test_live.py also checks the opposite failure, the one that is easy to
miss: that page two is genuinely older than page one and does not overlap
with it. A source that answers 200 and silently repeats the first page looks
perfectly healthy until you count the rows.
Three Actors in one
On the store today you buy these separately:
- channel posts —
tri_angle/telegram-scraper, 4502 users, store rating 2.81 on 13 reviews at the time of writing; - profile cards —
truefetch/telegram-profile, 1158 users; - keyword search —
lofomachines/telegram-keyword-search-scraper, 1031 users.
Here it is one input form and one dataset. Pick the mode:
Channel posts. Give one or more channels and walk the history, newest
message first. A link to a single post (https://t.me/durov/536) starts the
walk at that exact message, so pasting a post link and asking for one row is a
perfectly good way to fetch one post.
Search inside channels. Telegram's own in-channel search, the same one behind the magnifier in the web preview. Every term is searched separately in every channel, because the source accepts one query at a time. A term that matches nothing returns an explicit "no posts found" marker, and this Actor reports it as such rather than pretending the channel is empty.
Profile cards. Resolve a handle into title, description, photo, verified badge, subscriber or member count and the entity type. This is the mode that works where no post feed exists at all. Measured on a sample of 8 handles it resolved 2 channels, 1 group with its member count, 2 bots, 2 user accounts and correctly reported 1 handle as not found.
Fields you will not find in the other Telegram Actors
reactions— a list of{emoji, custom_emoji_id, is_paid_stars, count}, plusreactions_totalandreaction_types. Telegram Stars paid reactions are flagged separately. Two thirds of live messages carry them.channel_id— the numeric internal id, decoded from the page's owndata-viewpayload. Stable across renames, unlike the @handle.author_signature— the name of the admin who signed the post, when the channel has signatures on.poll_question,poll_type,poll_options(text and percentage per option) andpoll_voters.forwarded_from_name/forwarded_from_url,via_bot,is_album,is_service_message.reply_to_url,reply_to_author,reply_to_text— the quoted message, not just a flag saying a reply happened.inline_buttons— the text and URL of every button under the post.photos,videoswith direct CDN URLs, thumbnails, aspect ratio andvideo_duration_seconds;document_title,document_extra,document_kind;sticker_url;voice_urlandvoice_duration_seconds.links,hashtags,mentionsextracted from the message body.link_preview_url,_site,_title,_description,_image_url.
Counts that Telegram abbreviates arrive twice: views_text is exactly what the
page said (5.87M), views is the integer (5870000). Dropping the string
would pass an abbreviation off as an exact figure; dropping the number would
make you parse it yourself. An absent value is null, never zero and never an
empty string — an empty string in a numeric column breaks Excel, BigQuery and
pandas alike.
Monitor mode: pay once for each message
Turn on Only posts new since the last run and the Actor keeps, in its own named key-value store, the highest message id it actually delivered per channel. The next run stops the walk at that border. That saves you two things at once: the rows you would be charged for a second time, and the requests needed to fetch them. The lifecycle suite proves both — after a first run over 200 messages, a run that finds 2 new ones fetches at most 2 pages.
The order of operations matters here and it is deliberate: rows are pushed to your dataset first and only then marked as delivered. Marking first is how a scraper silently loses data — after an interrupted run its memory claims messages were sent that never arrived, and they never arrive later either. The test suite kills a run mid-push and asserts that the remembered id never runs ahead of what reached the dataset, then resumes and asserts nothing was lost.
Filters run before you are charged
Every filter removes rows before they reach the dataset, so a filtered-out message costs nothing. You can filter by date range or last N days, by text (contains any of / contains none of), by attachment type, by minimum and maximum views, by minimum total reactions, and you can skip service messages (on by default) or forwarded messages.
Two rules are wired into the filters on purpose:
- Missing data is not a mismatch. If Telegram publishes no view counter for a message, a minimum-views filter keeps that message instead of dropping it. A naive filter on a sometimes-absent field is the single most common way a green run ends with an empty dataset.
- Limits count delivered rows, not messages read. Both
maxPostsPerChannelandmaxItemsare counted in rows that actually reach your dataset. A limit that stops the crawl instead would combine with a narrow filter to hand you nothing at all while the log happily reports hundreds of messages collected.
There is also maxScannedPerChannel, a safety stop at 2000 messages read per
channel by default, so a filter that matches nothing ends the walk instead of
reading a 147827-message history to the bottom. The REPORT record tells you
scanned and delivered per channel, so you can always see which of the two
ended the run.
An empty dataset always says why
A successful run that returns nothing is the worst outcome for a buyer: paid
for, and unexplained. This Actor never does that silently. It knows 14
named reasons and writes one of them into empty_reason in the REPORT record,
plus a plain-English warning in the log.
7 of them are decided from your input alone, before a single byte is
fetched, so you are never charged the waiting time for a run whose result was
already determined: no-channel-given, all-channel-names-invalid,
search-mode-without-search-terms, date-range-is-empty,
views-range-is-empty, media-types-not-recognised, charge-limit-reached.
The other 7 can only be seen after the walk: filtered-out,
nothing-new-since-last-run, no-search-match, no-public-preview,
source-returned-nothing, all-channels-unreachable, rate-limited.
nothing-new-since-last-run is the legitimate one: a scheduled monitor run
with nothing to report is working correctly, and naming it is what lets a
watchdog tell it apart from a real breakage.
How the source lies, and what is done about it
Telegram's web preview answers HTTP 200 to almost everything. Ask for a handle that does not exist and you get 200 with a ten-kilobyte "If you have Telegram, you can contact @… right away" page. Ask for a private channel, or a group with no public preview, and you get exactly the same thing. Nothing in the status code distinguishes it from a healthy feed.
So this Actor never decides by status code. It looks for the markers of a real
feed and reports no-public-preview when they are missing, while Profile cards
mode still returns the card for those handles, because the card is real even
when the feed is not. Private invite links (t.me/+…, /joinchat/…,
t.me/c/…) are rejected at the input stage with the reason spelled out, rather
than fetched and silently returned empty.
One more real-world quirk worth naming: Telegram serves at least one live post
(durov/406) with a stray double quote inside an HTML attribute. A parser that
tries to respect quoting swallows the next block whole and silently loses the
reply information on every quoted post. The parser here scans attributes up to
the next > for exactly that reason, and a test pins it.
Runs from Apify with no proxy
Verified from Apify's own cloud, not from a laptop: probe run
cCOERBkgmO36p2Pu4 fetched 12 different t.me addresses — channel feed, two
pagination pages, in-channel search, a profile card, a non-existent handle, a
single-post embed — and every one answered HTTP 200. No proxy, no browser, no
API key, no Telegram account. The Actor uses the Python standard library only
and runs in 1024 MB.
Input in short
channels accepts usernames, @handles, t.me links, t.me/s/ links and
single-post links. mode picks posts, search or profiles. searchTerms feeds
search mode. maxPostsPerChannel (default 100) caps delivered rows per
channel, maxScannedPerChannel (default 2000) caps messages read per channel,
maxItems caps the whole run. postedAfter, postedBefore and lastNDays
bound the dates — and a date bound also stops the walk early, which makes it
the cheapest way to keep a run short. mustContain, mustNotContain,
mediaTypes, minViews, maxViews, minReactions, skipServiceMessages
and skipForwarded narrow the result. onlyNewPosts turns on monitor mode.
includeChannelCard, includeTextHtml and compact shape the row.
inputDatasetId plus channelField take the channel list from another
Actor's dataset, so this one can sit downstream of any other scraper.
Honest limitations
These are things the source genuinely does not offer, named here rather than discovered after purchase.
- Only public channels with the web preview switched on. Private channels,
invite-only channels and most groups have no
t.me/s/feed at all. Profile cards mode still resolves them; posts do not exist to be read. A channel owner can also turn the preview off, and then the feed disappears even though the channel is public inside the app. - Group messages and member lists are out of reach. Some rival Actors sell group member scraping; that needs a logged-in Telegram session, which this Actor deliberately does not have. Group cards, including the member count, do work.
- Comments and discussion threads are not included. The web preview shows a channel's own messages, not the linked discussion group's replies.
is_albummarks an album, but Telegram serves an album as one block. You get every photo of it inphotos, and no separate row per image.- Bot versus user is a guess. Telegram's public page renders a bot exactly
like a person.
entity_typesaysbotwhen the handle or title ends in "bot", otherwiseuser. On our 8-handle sample that gotQuizBotandstickersright and calledBotFathera user. - View and reaction counts are the abbreviated ones Telegram shows.
5.87Mbecomes 5870000; the exact figure behind the rounding is not published anywhere public. - No media download. You get direct CDN URLs for photos, videos, voice messages and stickers; fetching the bytes is left to you, because a scraper that downloads video for you is a scraper that charges you for bandwidth.
- Message edits are not detectable. The preview does not expose an edited flag, so monitor mode reports genuinely new messages, not changed ones.
- No search across all of Telegram. The search mode is Telegram's in-channel search: it needs the channel list you give it. Actors that advertise global keyword discovery reach it through a web search engine, which is a different source with different failure modes.
FAQ
Do I need a Telegram account, a bot token or API credentials? No. Nothing is logged in anywhere. The Actor reads the same public pages a logged-out browser sees.
Will it get my account banned? There is no account involved, so there is nothing to ban.
How far back can it go? To the first message of the channel. Measured on a 147827-message channel: the walk reaches messages 1 to 19.
How fast is it? About 38 messages per second, measured as 190 messages over 10 consecutive pages from a single address, with a small deliberate pause between requests so that a long history walk does not get the address throttled.
Why do I sometimes get fewer rows than I asked for?
Either the channel does not have that many messages, or your filters removed
them, or maxScannedPerChannel ended the walk. The REPORT record at the end of
the dataset names which, with a count per filter.
Why is the dataset empty?
Read empty_reason in the REPORT record. One of 14 named reasons is always
there, along with a sentence in the log explaining it.
Can I run it on a schedule and only get new posts?
Yes — that is what onlyNewPosts is for. The first run gives you the history
you asked for, each later run gives you only what appeared since.
Can I feed it a list of channels from another Actor?
Yes. Put that run's dataset id into inputDatasetId and name the field holding
the handle in channelField.
What does a row look like for a message with no text, like a photo post?
text is an empty string, text_length is 0, and the media fields carry the
content. Nothing is invented to fill the gap.
Is the output good for AI pipelines?
Turn on compact to drop every null and empty field, which cuts the row down
to what a model actually needs. Keep it off for CSV or Excel, where stable
columns matter more than size.
What happens if Telegram rate-limits the address?
The Actor slows down and retries; if not one page came back it stops and
reports rate-limited rather than pretending the channels are empty.