Telegram Channel Scraper: Posts, Search, Profiles avatar

Telegram Channel Scraper: Posts, Search, Profiles

Pricing

$0.50 / 1,000 result rows

Go to Apify Store
Telegram Channel Scraper: Posts, Search, Profiles

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

Snow Leo Data

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 hours ago

Last modified

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.

MeasuredResult
Fields per message row68 (69 with the HTML body)
Fields per profile row21
Fields in the strongest rival's documented row16
Live messages sampled across 6 channels356
Of those, carrying reactions this Actor returns and the rival does not239 (67.1%)
Carrying a view count356 (100%)
Carrying the numeric channel id356 (100%)
Carrying an attachment250 (70.2%)
Carrying a link preview55 (15.4%)
Carrying an author signature119 (33.4%)
Distinct reaction emoji seen in that sample34
Messages per page returned by Telegram20
Throughput, one address, no proxy190 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=1000 returned messages 980 to 999;
  • before=50000 returned 49980 to 49999;
  • before=20 returned 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}, plus reactions_total and reaction_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 own data-view payload. 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) and poll_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, videos with direct CDN URLs, thumbnails, aspect ratio and video_duration_seconds; document_title, document_extra, document_kind; sticker_url; voice_url and voice_duration_seconds.
  • links, hashtags, mentions extracted 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:

  1. 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.
  2. Limits count delivered rows, not messages read. Both maxPostsPerChannel and maxItems are 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_album marks an album, but Telegram serves an album as one block. You get every photo of it in photos, and no separate row per image.
  • Bot versus user is a guess. Telegram's public page renders a bot exactly like a person. entity_type says bot when the handle or title ends in "bot", otherwise user. On our 8-handle sample that got QuizBot and stickers right and called BotFather a user.
  • View and reaction counts are the abbreviated ones Telegram shows. 5.87M becomes 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.