Telegram Channel Scraper & API - Posts, Views, Reactions avatar

Telegram Channel Scraper & API - Posts, Views, Reactions

Pricing

from $0.90 / 1,000 results

Go to Apify Store
Telegram Channel Scraper & API - Posts, Views, Reactions

Telegram Channel Scraper & API - Posts, Views, Reactions

Telegram channel scraper: get posts and messages from public Telegram channels, no login or API key. Input: channel usernames or t.me links, max posts, date cutoff or search keyword. One result = one post: text, date, views, reactions, media URLs, forwards; plus channel info. $0.90 per 1,000.

Pricing

from $0.90 / 1,000 results

Rating

0.0

(0)

Developer

Giovanni Rich

Giovanni Rich

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

2

Monthly active users

3 hours ago

Last modified

Categories

Share

Telegram channel scraper and simple Telegram API alternative: scrape Telegram messages and posts from any public channel with no login, phone number or API key. Send channel names and get back each post's text, date, views, reactions, photos, videos, files, polls, forwards and replies, plus channel info (subscribers, description), as JSON, CSV or Excel. A built-in Telegram search finds posts by keyword inside each channel.

The scraper reads Telegram's own public web preview (t.me/s/<channel>) over plain HTTP, with no headless browser. In our tests 250 posts took 7 seconds, and it saves one channel-info row per channel with subscribers, description, media counters and the verified badge.

What you get

  • Post content: the full text with line breaks and emoji, plus the links, hashtags and @mentions found in it.
  • Engagement: view count (e.g. 18.8M becomes 18800000), every reaction with its count (including paid ⭐ Star reactions), and the total reaction count.
  • Media: photo URLs, video URLs with thumbnails and duration, albums, voice notes and audio, round videos, stickers, file names and sizes, polls with option percentages and voter counts, locations, and link previews (title, site, description, image).
  • Context: forwarded-from (channel name, username, original post), reply-to (post ID and quoted text), author signature, and whether the post was edited.
  • IDs and links: post ID, post URL and the numeric Telegram channel ID.
  • Channel info: title, description, subscriber count, photo/video/file/link counters, the verified badge and the avatar URL.
  • Full history: the scraper pages back from the newest post to the oldest. You can stop it at a post count, at a date, or let it read the whole channel.
  • In-channel search: pass a keyword to get only the matching posts, using Telegram's own search.

Sample output

Real rows from a run on 24 Sep 2026 (text shortened):

channelpostIddateviewsreactionsTotalmediaTypetext
bloomberg32252026-09-22T03:26:17.000Z13400173photoAlibaba unveils new AI chip it says is China's most powerful...
bloomberg32242026-09-03T03:10:11.000Z42900478textEXCLUSIVE: TikTok developer ByteDance secures a $30 billion...
bloomberg32232026-09-02T10:17:56.000Z44200340photoThe Nepal disaster is the latest example of how climate chan...

Use cases

  • Media monitoring and news tracking across dozens of channels
  • Crypto, trading and signals research: what got posted when, and how much attention it got
  • Brand and competitor monitoring, influencer and channel vetting (subscribers, views per post, engagement)
  • Keyword monitoring inside channels with Telegram's own search
  • Datasets for NLP, sentiment analysis and LLM training
  • Archiving a channel's history, with links to every media file

Pricing

$0.90 per 1,000 results (pay per result; failed requests are never charged). A result is a post or a channel-info row; most Telegram channel scrapers on the Store charge $1.00-$2.50 per 1,000.

FAQ

Is it legal to scrape Telegram? The scraper collects only content that channel owners have published publicly on the open web. You're responsible for how you use the data, including complying with Telegram's terms of service and with privacy laws such as GDPR for any personal data that appears in posts. This is not legal advice; if you're unsure, check with a lawyer.

Do I need a Telegram account, phone number or API ID? No. The scraper reads the same public page you see when you open https://t.me/s/durov in a browser. Only public channels with a web preview work: private channels, invite links (t.me/+...), groups and comments are not available.

How fast is it, and how far back can it go? In our tests 250 posts took 7 seconds and 480 posts took 12 seconds. It can go all the way to the first post: set maxPostsPerChannel: 0 to read the whole history.

Can I schedule it to get only new posts, or call it via API? Yes. Schedule the actor with onlyPostsNewerThan: "1 day" and deduplicate on url (or channel + postId). Start runs from the Apify API, the JavaScript/Python clients, Make, Zapier, n8n or the Apify MCP server.

Which proxy should I use? How does it avoid blocks? The default Apify Proxy works; Telegram rarely blocks the preview pages. It sends realistic desktop Chrome headers and keeps a session pool: a 401, 403 or 429 response, or a page it doesn't recognise, retires that session so the next attempt goes out on a new IP, up to 6 retries by default (maxRequestRetries).

Why did a channel return 0 posts? Check the RUN_SUMMARY record in the key-value store. It gives a stop reason for each channel, such as "channel not found" or "web preview not available (group)".

How to use

  1. Enter one or more public channels in Channels (durov, @durov or https://t.me/durov). Optionally set Max posts per channel, a date limit or a search keyword.
  2. Click Start. Small runs finish in seconds.
  3. Download the results as JSON, CSV or Excel from the Output tab, or call the actor from your own code through the Apify API.

Input

FieldDefaultDescription
channelsdurovUsernames or links, any form: durov, @durov, https://t.me/durov, https://t.me/s/durov
maxPostsPerChannel100Newest posts first. 0 = the whole history
onlyPostsNewerThan-A date (2026-09-01) or a relative period (7 days, 2 weeks, 3 months). The scraper stops when posts get older than this.
searchQuery-Returns only the posts that match this keyword
includeChannelInfotrueAdds one channel row per channel
maxConcurrency5Channels scraped in parallel (1-20)
maxRequestRetries6Retries per failed page (0-20)
proxyConfigurationApify ProxyDatacenter proxy is enough

Input example

{
"channels": ["durov", "bloomberg", "https://t.me/telegram"],
"maxPostsPerChannel": 500,
"onlyPostsNewerThan": "30 days"
}

Output example

Each post is one dataset item with type: "post". The example below is a real item from a run on 24 Sep 2026. The long CDN URL is shortened, and 5 of its 8 reactions are left out.

{
"type": "post",
"channel": "bloomberg",
"channelId": "1488156064",
"channelTitle": "Bloomberg",
"postId": 3225,
"url": "https://t.me/bloomberg/3225",
"date": "2026-09-22T03:26:17.000Z",
"timestamp": 1790047577,
"edited": false,
"text": "Alibaba unveils new AI chip it says is China's most powerful and targets 20 gigawatts of data center capacity by 2032. https://bloom.bg/4ydroys\n\n📷: Qilai Shen/Bloomberg",
"textLength": 169,
"views": 13400,
"viewsText": "13.4K",
"reactionsTotal": 173,
"reactions": [
{ "emoji": "🔥", "customEmojiId": null, "isPaid": false, "count": 62, "countText": "62" },
{ "emoji": "❤", "customEmojiId": null, "isPaid": false, "count": 33, "countText": "33" },
{ "emoji": "👏", "customEmojiId": null, "isPaid": false, "count": 27, "countText": "27" }
],
"mediaType": "photo",
"mediaUrls": ["https://cdn5.telesco.pe/file/B-Vjm-tufLB1ZSrW02mb...R7w.jpg"],
"isAlbum": false,
"photos": [{ "url": "https://cdn5.telesco.pe/file/B-Vjm-tufLB1ZSrW02mb...R7w.jpg", "postUrl": "https://t.me/bloomberg/3225" }],
"videos": [],
"roundVideos": [],
"audio": [],
"documents": [],
"sticker": null,
"poll": null,
"location": null,
"linkPreview": null,
"links": ["https://bloom.bg/4ydroys"],
"hashtags": [],
"mentions": [],
"forwardedFrom": null,
"replyTo": null,
"author": null,
"unsupportedInPreview": false,
"input": "bloomberg",
"searchQuery": null,
"scrapedAt": "2026-09-24T05:15:24.280Z"
}

When a post has them, the fields look like this:

"forwardedFrom": { "name": "Mash Money", "url": "https://t.me/mashmoneymash/2732", "username": "mashmoneymash", "postId": 2732 },
"replyTo": { "postId": 78053, "url": "https://t.me/mash/78053", "author": "Mash", "text": "..." },
"videos": [{ "url": "https://cdn4.telesco.pe/file/7451201fa5.mp4?token=...", "thumbnail": "https://cdn4.telesco.pe/file/...", "duration": "0:25", "isGif": false, "tooLargeForPreview": false }],
"documents": [{ "title": "Features 1.png", "size": "3.9 MB", "postUrl": "https://t.me/designers/243?single" }],
"poll": { "question": "...", "type": "Anonymous Poll", "voters": 1720, "options": [{ "text": "Yes", "percent": 42 }, { "text": "No", "percent": 34 }] }

Each channel also gets one item with type: "channel":

{
"type": "channel",
"username": "bloomberg",
"title": "Bloomberg",
"description": "The first word in business news.",
"subscribers": 175000,
"photos": 800,
"videos": 49,
"files": null,
"links": 3180,
"verified": true,
"photoUrl": "https://cdn5.telesco.pe/file/TczlHBZov8...jpg",
"url": "https://t.me/bloomberg",
"previewUrl": "https://t.me/s/bloomberg",
"previewAvailable": true
}

The dataset has three ready-made views: Overview (date, text, views, reactions, media), Engagement (views and reactions for each post) and Channels (the channel-info rows).

Field fill rates (real test run: 900 posts across 6 channels)

FieldFill rate
postId, url, date, channelId, channelTitle100%
views100%
text99% (the rest are media-only posts)
links in text77%
media (photo / video / file / poll)66%
reactions62% (100% on channels that have reactions turned on; some channels switch them off)
linkPreview17%
author signature17% (only channels that sign their posts)
hashtags15%
edited14%
forwardedFrom / replyTo3% / 2%

Integrations

  • Make, Zapier and n8n: start runs and pass new posts into your workflows.
  • Google Sheets: export the dataset straight to a spreadsheet.
  • Apify API: run the actor and fetch results over REST, or with the JavaScript and Python API clients.
  • Webhooks: get a call when a run finishes.
  • MCP for AI agents: through the Apify MCP server (https://mcp.apify.com), Claude, ChatGPT and Cursor agents can call this actor as a tool.
  • Schedules: run it daily or hourly to track channels over time.

Limits (read before large runs)

  • Only public channels that have a web preview. Private channels, invite links (t.me/+...), groups and bots have no public post history. For groups and channels with the preview turned off, the scraper returns only the channel-info row (previewAvailable: false), and the log says why.
  • Comments are not included. Telegram doesn't show discussion-group replies in the web preview.
  • Custom (premium) emoji reactions come back as a customEmojiId with no emoji character, because Telegram shows them as images. Their counts are still correct.
  • Counts are rounded the way Telegram shows them, e.g. 13.4K is saved as 13400. The raw text is kept in viewsText and countText.
  • Very large videos are listed without a direct file URL, because Telegram doesn't stream them in the preview. You still get the thumbnail, the duration and a link to the post (tooLargeForPreview: true).
  • Media URLs are signed CDN links that expire after a while. Download the files soon after the run if you need to keep them.
  • A few service messages and deleted posts leave gaps in the post IDs. That's normal.

How it works (for developers)

t.me/s/<channel> returns server-rendered HTML with up to 20 posts per page, plus a <link rel="prev" href="?before=N"> cursor. The scraper parses each tgme_widget_message block with cheerio. It reads every field defensively, so one malformed post can never break a page. Channels run in parallel, and the pages of one channel run in order. Everything the scraper has already seen is kept in the actor state, so a migrated run resumes without creating duplicates.

Run it locally:

npm install
npm test # parser tests on saved fixtures
APIFY_LOCAL_STORAGE_DIR=./storage node src/main.js # input in storage/key_value_stores/default/INPUT.json