DEV Community

Ken-Mutisya
Ken-Mutisya

Posted on

"Scrape Public Telegram Channels Without an API Key, Bot Token or Phone Number"

Telegram's official APIs are great, but both come with strings attached: the Bot API needs a bot that channel owners must add, and the MTProto client API wants your phone number and an app registration. If all you need is the public message history of public channels, there is a third door with no strings at all.

The t.me preview pages

Every public channel has a web preview:

GET https://t.me/s/durov
Enter fullscreen mode Exit fullscreen mode

That returns the latest ~20 messages as server-rendered HTML. No key, no cookie, no login. And it paginates:

GET https://t.me/s/durov?before=510
Enter fullscreen mode Exit fullscreen mode

gives you the 20 messages older than message id 510. Walk before= down and you can read a channel's history as far back as you care to go.

What is in the HTML

Each message sits in a .tgme_widget_message block with a data-post="channel/id" attribute. Inside:

  • .tgme_widget_message_text holds the message body, links included
  • time[datetime] gives a clean ISO 8601 timestamp
  • .tgme_widget_message_views shows the view count, abbreviated (3.4M)
  • .tgme_widget_message_photo_wrap hides photo URLs in its inline background-image style
  • .tgme_widget_message_forwarded_from_name names the source channel of a forward, with link

The channel card at t.me/<channel> (without /s/) adds the title, bio and subscriber count via ordinary OpenGraph meta tags plus a .tgme_page_extra element.

Two parsing gotchas

Abbreviated and spaced numbers. View counts come as 3.4M or 12.3K, and subscriber counts as 11 862 837 with narrow no-break spaces (U+202F) between groups, which will break a naive parseInt. Normalize both:

function parseCount(raw) {
  const s = raw.replace(/[\s  ]/g, '').toLowerCase();
  const m = s.match(/^([\d.]+)([km]?)$/);
  if (!m) return null;
  return Math.round(parseFloat(m[1]) * (m[2] === 'm' ? 1e6 : m[2] === 'k' ? 1e3 : 1));
}
Enter fullscreen mode Exit fullscreen mode

Not every channel has a preview. Private channels, most groups, and channels that disabled previews return a page with no message blocks. Detect the empty case and report it honestly instead of retrying.

What the preview does not have

Reactions, comment counts and poll results are not in the public preview, so no keyless scraper can offer them truthfully. If a tool claims them without asking for API credentials, be suspicious.

Be polite and it just works

The preview pages are served to every browser and link previewer on the internet, so they are tolerant, even from datacenter IPs. Pace your requests a few hundred milliseconds apart and paginate only as deep as you need.

If you want it as a service

I packaged this as a pay per use actor: public channel usernames in, one JSON row per message out (text, date, parsed views, links, media, forward source), plus a free channel info row with the subscriber count: https://apify.com/scrapemint/telegram-channel-scraper

Same theme as the rest of my actors: find the public endpoint a platform already serves, skip the browser, and the data costs almost nothing.

Top comments (0)