Telegram Channel Scraper: Posts, Comments & Media
Pricing
from $0.07 / 1,000 posts
Telegram Channel Scraper: Posts, Comments & Media
Scrape public Telegram channels without login: posts, views, reactions, media, links, comments, channel details. Search inside a channel and get only new posts for n8n or Make. Export to JSON, CSV or Excel.
Pricing
from $0.07 / 1,000 posts
Rating
0.0
(0)
Developer
Nice Dev
Maintained by CommunityActor stats
0
Bookmarked
12
Total users
7
Monthly active users
16 hours ago
Last modified
Categories
Features
Share
🔎 What is Telegram Channel Scraper?
Telegram Channel Scraper extracts posts from public Telegram channels (t.me): text, date, views, reactions, photos, videos, links, forwards, polls, plus the comments of each post and the details of the channel when you ask for them. It works as a Telegram API alternative for public channels: no account, no API key, no phone number. Run it on a schedule, call it from your code, or plug it into Make, Zapier or n8n.
Type the channel names (durov, telegram), click Start, and download the posts in JSON, CSV or Excel. Add a keyword to search inside each channel, or tick Only new posts to get, at each scheduled run, only the posts published since the last one. Speed: about 1,000 posts of one channel in 4 minutes with the included proxy (several channels are read side by side), and about 5 times faster without a proxy.
📋 What data can you extract from Telegram?
One item per post, comment or channel, 100 fields:
| Category | What you get |
|---|---|
| 📝 Post | text (plain, HTML and Markdown), publication date, edited mark, link, post number |
| 👀 Reach | views, every reaction with its emoji and count, reactions paid with Stars |
| 🖼️ Media | photo links, videos (thumbnail, duration, size, GIF or not), documents, audio files, stickers, polls (quiz, anonymous, votes per option), link cards, albums as one row, every media listed in page order with its size |
| 🔗 Links and mentions | every link, hashtag, @mention, e-mail address and inline button of a post; the links hidden behind words, the invitation links and the domains linked |
| 🧩 Text signals | spoilers, quotes, code blocks, custom emoji, text length and alphabet (Latin, Cyrillic, Arabic, Chinese…), forwarded / reply / album / media / link flags |
| 📊 Rates | engagement rate (reactions over views), view rate (views over subscribers), publication time as a Unix timestamp |
| ↪️ Forwards and replies | where a post was forwarded from, which message it replies to |
| 📣 Channel | title, username, numeric id, subscribers, verified badge, SCAM / FAKE label |
| 💬 Comments | author, text, date, reactions and reply-to of each public comment, with the commented post |
| 🧾 Channel details | exact subscriber count, description with its links and @names, photo, counts of photos, videos, files and links, last post date, creation date, posting rhythm and average / median views of the recent posts |
| 🔍 Search | the keyword that found a post |
Every field, with an example, is listed in the Output section below. Comments, channel details, e-mail addresses and saved media files are options: they are empty (or absent) unless you tick them.
✅ Why use Telegram Channel Scraper?
- 🔍 Search inside the channels: the keyword is searched by Telegram in each channel, older posts included, not only the latest ones.
- 🔔 Monitoring built in: tick Only new posts, schedule the Actor, and each run reads each channel only from the last post it delivered. Posts already delivered are not read again and not charged.
- 💬 Comments without an account: the public comments of each post, one row per comment, up to 1,000 per post (author, text, date, reactions, the comment it answers).
- 🧾 A row for every channel it cannot read: a closed, hidden, unknown, group, bot, user account or private-invite channel gives a row that says why, never an empty run with no explanation.
- 🗂️ Many channels in one run: up to 1 000 channels, with a cap per channel so that a big one cannot use up the whole budget.
- 🔌 API, scheduling, monitoring, integrations (Make, Zapier, n8n, Google Sheets…), proxy and JSON/CSV/Excel export via the Apify platform.
🚀 How to scrape Telegram channels
- Create a free Apify account.
- Open Telegram Channel Scraper and list the Channels (
durov,@telegram,t.me/telegram, or a full link). - Optional: a Search inside the channels keyword, a date range, the options you want (comments, channel details…).
- Set Max posts (100 by default, 0 = no limit), then click Start.
- Download the dataset in JSON, CSV, Excel or via API.
💰 How much does it cost to scrape Telegram?
This Actor uses pay per event pricing: you pay for each post saved, plus a small fee per run start. The options are charged only when you tick them: each comment, each channel detail row, each page of comments read, each e-mail address found and each media file saved. The exact prices are in the Pricing tab of the Actor. A run that saves nothing costs only the run start, plus the small fee for each post a filter drops if you use filters (see Pricing).
⚙️ Input
Channels, a cap, and nothing else:
{"channels": ["durov", "telegram"],"maxItems": 100}
Search a keyword inside a channel, from a date:
{"channels": ["durov"],"query": "privacy","postedAfter": "2026-01-01","maxItems": 50}
Monitoring: only the posts published since the last run, one memory per schedule:
{"channels": ["durov", "telegram"],"onlyNew": true,"stateKey": "crypto-news","maxItems": 0}
With links, comments and channel details:
{"startUrls": [{ "url": "https://t.me/s/durov?q=privacy" }, { "url": "https://t.me/durov/427" }],"includeChannelInfo": true,"includeComments": true,"maxCommentsPerPost": 20,"maxItems": 20}
| Field | Notes |
|---|---|
channels | Public channels, in any form: durov, @durov, t.me/durov, https://t.me/s/durov. A link of a channel page keeps its keyword and its page (https://t.me/s/durov?q=privacy), as in startUrls. Max 1 000 channels. A public group gives its details only. A private invite link (t.me/+AbCdEfGhIjKlMnOp, t.me/joinchat/AbCdEfGhIjKlMnOp, tg://join?invite=AbCdEfGhIjKlMnOp), the name of a bot or of a user account gives one details row, never posts. |
startUrls | Channel pages, searches inside a channel (https://t.me/s/durov?q=privacy), single posts (https://t.me/durov/427), a channel read from a post number (https://t.me/s/durov?before=1000), or a private invite link (https://t.me/+AbCdEfGhIjKlMnOp: one details row, no posts). Max 1 000 URLs. |
query, searchQueries | Keyword searched inside each channel, as the channel's own search box does (privacy); searchQueries adds more keywords: channels × keywords = one search each (max 500 searches per run). |
readOrder, fromPostId, toPostId | newest (default) reads each channel from its latest post back; oldest reads it from its very first post up, so the rows come oldest first and Max posts keeps the oldest posts of the channel. A page address pasted with a before / after number keeps its own direction. fromPostId / toPostId keep only the posts numbered from / up to that number (the number at the end of a post link; 0 = no bound): the posts outside are never read, so never charged. Same price in both orders. |
maxItems, maxItemsPerQuery | Posts saved for the whole run (100 by default, 0 = unlimited) and for each channel and keyword (0 = no cap). Comments and channel rows are not counted. |
includeChannelInfo, channelInfoOnly | One row per channel with its exact subscribers, description and counts; the second one returns only these rows, no post. Paid options, off by default. |
includeComments, maxCommentsPerPost | The most recent public comments of each post whose discussion is open, one row per comment (the newest ones, written in the order they were posted), up to 1,000 per post (Telegram shows 100 per page, more are loaded 50 at a time). Paid option, off by default. |
includeEmails, downloadMedia | E-mail addresses written in the posts, comments and channel descriptions; photos and video thumbnails saved in the run's key-value store, their links in mediaFiles (they work as long as the run's storage is kept: the data retention of your plan). Paid options, off by default. |
postedAfter, postedBefore | Publication date range: 2026-09-01, or a period before now (7 days, 2 weeks, 30 days; via the API also 24 hours or a full ISO date-time). A channel is read from the newest post back and stops at the first one too old. |
minViews, maxViews, minReactions, maxReactions | Keep the posts seen at least / at most so many times, or with at least / at most so many reactions (0 = no bound). |
minTextLength, maxTextLength | Keep the posts whose text has at least / at most so many characters (a post without text counts 0; 0 = no bound). |
mediaOnly, withTextOnly, withLinksOnly, includeForwarded, includeServiceMessages | Keep only the posts with any media (photo, video, file, audio, poll, sticker, location, or a post Telegram shows only in the app; use Only these media types to keep only some), with text, with a link; drop the forwarded posts; add the service messages (channel created, post pinned…). |
mediaTypes, excludeMediaTypes | Keep only the posts holding one of these media kinds (photo, video, document, audio, poll, sticker, location), or drop the posts that hold one. |
withoutMediaOnly | Keep only the posts with no media at all (pure text). Cannot go with Only posts with media or Only these media types. |
forwardedOnly, forwardedFrom | Keep only the posts forwarded from another channel or user, or only those forwarded from the public channels you list (durov, @durov or a t.me link). Cannot go with Include forwarded posts off. |
includeReplies | Untick to drop the posts that reply to another post of the channel. |
includeKeywords | Keep only the posts containing one of these words (sale); case and accents ignored. |
includeKeywordsMode | any (default): at least one of the words; all: every one of them. |
excludeKeywords | Drop the posts whose text contains one of these WHOLE words (case and accents ignored): "art" drops "Art deco", not "Smart watch". Several words in one entry ("hand made") must appear together. |
includeHashtags, excludeHashtags, includeMentions | Keep only the posts carrying one of these hashtags (#ton or ton), drop the posts carrying one, or keep only the posts that mention one of these @names. Case ignored. A keyword that starts with # in query is a hashtag search: the word is searched, only the posts that carry the tag are kept. |
linkDomains, excludeLinkDomains | Keep only the posts that link to one of these websites (youtube.com; its subdomains match too), or drop them. |
authors, excludeAuthors | Keep only the posts signed by one of these author names (the signature some channels add), or drop them. Case and accents ignored. |
excludeAds, adsOnly | Drop the posts Telegram marks as sponsored ads, or keep only those. |
editedOnly | Keep only the posts the channel edited after publishing. |
textScripts | Keep only the posts written mainly in these alphabets: latin, cyrillic, arabic, han, hebrew, devanagari, thai, hangul, kana, greek. |
commentsPostedAfter, commentKeywords, minCommentReactions | Filters of the comments read with Comments: only the comments from a date (2026-09-01, 7 days), containing one of these words, or with at least so many reactions. A comment left out is not written and not charged as a comment (the page of comments read is still charged); the comment count of the post stays the total Telegram shows, and only the newest Max comments per post comments are read before the filters apply (no extra page to make up for the ones left out). |
onlyNew, stateKey, resetState | Monitoring: each channel is read from the last post a previous run delivered under this memory key (crypto-news); resetState forgets the memory. |
| Advanced | proxyConfiguration (Apify proxy by default, included in the price; the residential proxy is not available; for a proxy country, US works), maxConcurrency, maxRequestsPerMinute, minRequestIntervalMs, maxRequestRetries, debugLog. |
📦 Output
A post (shortened: the full row has 100 fields, the ones that do not apply are null or empty):
{"type": "post","id": "durov/427","url": "https://t.me/durov/427","channel": "durov","channelTitle": "Pavel Durov","channelUrl": "https://t.me/durov","channelId": 1006503122,"channelSubscribers": 10600000,"channelVerified": true,"channelLabels": [],"postId": 427,"publishedAt": "2026-09-30T14:02:11.000Z","edited": false,"text": "Telegram now has 1 billion monthly users.","views": 18900000,"reactions": [{ "emoji": "👍", "emojiId": null, "count": 15400, "paid": false }],"reactionsCount": 52300,"photos": ["https://cdn4.telesco.pe/file/abc.jpg"],"mediaTypes": ["photo"],"mediaCount": 1,"links": ["https://telegram.org/blog"],"hashtags": [],"mentions": [],"searchQuery": null,"scrapedAt": "2026-10-07T12:00:00.000Z"}
You can download the dataset in various formats such as JSON, HTML, CSV or Excel. A comment row and a channel row have the same columns; type tells them apart.
All 100 fields
| Fields | What you get |
|---|---|
type, id, url | post, comment or channel; a unique row id (durov/427); the public link |
channel, channelTitle, channelUrl, channelId | username, title, link, numeric id of the channel |
channelSubscribers, channelVerified, channelLabels | subscribers (rounded by Telegram on a post, exact on a channel row, empty on a post pasted by its link), verified badge, SCAM / FAKE label |
postId, publishedAt, edited, serviceAction | post number, ISO date, edited mark, what a service message says |
text, textHtml, textMarkdown | the text, with its links and formatting |
views, reactions, reactionsCount, paidStars | views, each reaction with its count, their total, reactions paid with Stars |
author, authorUrl, authorPhoto | signature of a signed post, or the comment author and profile |
forwardedFrom, replyTo | origin of a forwarded post, the message a post or comment replies to (with isQuote when it quotes a passage, and the small picture of that message) |
photos, videos, documents, audio | media of the post; each video also gives its size and whether it is a GIF (a silent looping animation); comments carry their videos too |
mediaItems, stickers | every photo, video, GIF, sticker, document and audio file in the order of the page, each with its type, link, thumbnail, size and duration; stickers with their link and kind (static, animated, video) |
hasAudio, musicAuthor | whether the post's media has sound (a video with sound, a music file, a voice message; false for a muted animation), the artist printed under a music file |
poll, linkPreview, buttons | poll question, quiz and anonymous flags, results with the votes of each option (no results on a post pasted by its link: its page shows none), link card, inline buttons |
mediaTypes, mediaCount, albumPostIds, mediaFiles | kinds of media, how many, post numbers of an album, links of the files saved by Download media |
links, hashtags, mentions, emails | every link, hashtag, @mention and e-mail address of the text |
cashtags, cryptoAddresses | $BTC-style tickers and wallet addresses written in the text (Bitcoin, Ethereum, Tron, TON; checksum checked), also on comments |
hasSpoiler, spoilers, quotes, codeBlocks, customEmojiIds | what the text hides or marks: the words behind a spoiler, quoted passages, code blocks as written, ids of the custom emoji; also on comments |
linkAnchors, linkDomains, inviteLinks | links whose shown words are not their address (text and url), the domains of the links, the Telegram invitation links (https://t.me/+…) |
isForwarded, isReply, isAlbum, hasMedia, hasLinks | yes / no flags of the message (empty on channel rows) |
textLength, textScript | number of characters of the text; its main alphabet (latin, cyrillic, arabic, han, hebrew, devanagari, thai, hangul, kana, greek, other) |
engagementRate, viewRate, publishedTimestamp | reactions over views and views over subscribers in percent; publication date in seconds since 1970 |
authorUsername, discussionChatId | comments: the @name of the commenter, the number of the discussion group (a post shows no link to its signer: authorUsername is empty on a post) |
isAd | true when the post carries an ad mark the author wrote (erid token, #реклама, #ad, #sponsored); Telegram's page has no sponsored flag |
commentsCount, relatedPostUrl | comments of a post (0 = discussion closed); for a comment, the post it belongs to |
phones | phone numbers written in the text of a post or a comment, or in a tel: link: +digits when written in international form, else the digits as written; empty for now |
aiLabelMatches | AI label results: the labels you wrote that apply to the post, each with a score; null for now (the AI label option is not available yet) |
foundVia, foundInPostUrl | rows of related channels only (channels a post forwards from or mentions): how a post led to the channel, and the first such post; empty on every other row |
channelDescription, descriptionLinks, descriptionMentions, channelPhoto, isGroup, membersOnline | channel and group rows: description, the web links and @names written in it, photo (also given on each post), group or channel, members online |
photosCount, videosCount, filesCount, linksCount | channel rows: counts of what the channel published |
webPreview, entityType, lastPostId, lastPostAt | channel rows: open, closed, not_found, group or private (an invite link); what the name is (channel, group, closed, bot, user, private_channel, private_group, not_found); the newest post and its date |
isPrivate, inviteLink, botMonthlyUsers | channel rows: true for a private invite link (false on other channel rows), the link in its short form https://t.me/+…, the « monthly users » figure of a bot's page |
channelCreatedAt | channel rows (Channel info): the date the channel was created, read from its first message; empty when the channel has none |
recentPostsCount, recentPostsPerDay, recentAvgViews, recentMedianViews, recentAvgReactions, recentEngagementRate | channel rows (Channel info): worked out from the posts of the newest page: how many, posts per day, average and median views, average reactions, average views over subscribers in percent |
searchQuery, scrapedAt | the keyword that found the post; ISO timestamp of the extraction |
💡 Tips
How to get more results
Set maxItems to 0 and maxItemsPerQuery to the number of posts you want from each channel. A channel is read from its newest post back (or from its first post up with readOrder set to oldest), page after page, with no limit on its history.
How to reduce costs
The price is per post, so the levers are maxItems, maxItemsPerQuery, the date range (postedAfter stops the reading of a channel at the first post too old), a slice of post numbers (fromPostId, toPostId: the posts outside it are never read), the filters (a post a filter drops is charged the filter fee, lower than a post), and onlyNew for recurring runs (you never pay twice for the same post). Leave the options you do not need unticked.
Search inside a channel
Fill query (and searchQueries for several keywords): Telegram searches each channel and the Actor reads only the matching posts, however old. Each post carries the keyword that found it in searchQuery.
Several searches in one run
Fill channels and searchQueries: the Actor runs one search per channel × keyword (3 keywords × 4 channels = 12 searches, up to 500 per run). A post found by several keywords is saved — and charged — once. Set maxItemsPerQuery to give every channel its own cap: without it the first channels can use up the whole maxItems.
Monitoring: only the new posts
Tick Only new posts (onlyNew) and schedule the Actor. The first run returns the posts allowed by the caps and dates; each later run reads each channel only from the last post it delivered, so a delivered post is never read, saved or charged twice. The memory lives in a named key-value store of your account (telegram-channel-scraper-seen, up to 150,000 posts per key) and is only updated with posts that really reached the dataset: a post you paste by its link, or a page you paste with ?before= / ?after=, does not move where its channel stopped, and a run that was stopped before its end (its maximum cost, a site that changed) leaves the memory under the first post it did not deliver, so the next run delivers it. A post that minViews or minReactions dropped while it was among the newest (its counters can still grow) is checked again by the next run, with the filter fee, until it passes or a newer post is delivered. Give each schedule its own stateKey.
Comments
Tick Comments to get the public comments of each post whose discussion is open. A channel without comments is detected after a few posts and no more comment pages are asked for it.
🔌 Integrations and API
Call the Actor via the Apify API, the JavaScript or Python clients, or connect it with integrations and webhooks (Make, Zapier, n8n, Google Sheets, Slack, Airtable…). The dataset can be fetched as JSON or CSV from any tool.
🤖 Use with AI agents (MCP)
AI agents (Claude, ChatGPT, Cursor…) can find and run this Actor through the Apify MCP server, billed to their Apify account like any run. It returns one item per Telegram post. Actor id: nice_dev/telegram-channel-scraper; MCP server with this Actor only: https://mcp.apify.com/?tools=fetch-actor-details,nice_dev/telegram-channel-scraper.
Smallest input, for a cheap first call:
{"channels": ["durov"],"maxItems": 10}
Key output fields: channel, postId, publishedAt, text, views, reactionsCount, url.
Cost: see the pricing section above. Cap each call with maxItems and, through the API, with the run option maxTotalChargeUsd.
❓ FAQ
Is it legal to scrape Telegram?
The Actor only reads what Telegram shows publicly to any anonymous visitor on the web: the posts of public channels. It logs in to nothing. Posts and comments can contain personal data, which is protected by GDPR: do not store it without a legitimate reason. You are responsible for using the data in compliance with Telegram's Terms of Service and applicable law. This Actor is not affiliated with Telegram.
Does it need a login or a proxy?
No login. The proxy is included in the price: leave the default setting (the residential proxy is not available). Some channels are hidden on the web in some countries: pick another proxy country for them — US works; a country where Apify Proxy has no IP fails the run, with a message that says so. About 5 times faster without a proxy (untick Apify Proxy), but then a request Telegram turns away is asked again from the same address only, after a pause. A request Telegram turns away is retried at once on a new proxy session, up to 10 times on top of the retries (without a proxy, after a pause of 5 seconds, doubled at each retry up to 150 seconds).
Why does a channel give a row and no post?
A channel row with webPreview set to closed means the channel hides its posts on the web (some channels only show them in the app), not_found that the name belongs to nobody, group that it is a public group (its messages are not published on the web, only its details), private that it is a private invite link (title, members and description of its page, no posts). A bot or a user account shows its title, description and photo with entityType set to bot or user; a bot also gives its monthly users. An expired invite link gives not_found. These rows are free unless you ticked Channel details. If Telegram does not answer for the channel's own page or for its newest post, the row is still delivered with what was read but is not charged as channel details.
Is the data safe to open in Excel or to show on a web page?
Texts are the channels' own words, copied as they are. A text can begin with -, +, = or @: Excel and Google Sheets may read such a cell of a CSV file as a formula or as a number. The Actor leaves the text as it is, so that the JSON and the API give the real value: when you open a CSV, import these columns as text. On a web page, escape the text: textHtml is Telegram's own markup and the Actor does not sanitize it.
Known limitations
- Only public channels whose web preview is open give posts; public groups give their details only.
- Views, reactions and subscribers of a post are rounded by Telegram (
10.6M); the exact subscriber count is on the channel row. - Photo and video links of Telegram expire: tick Download media to save the files in the run's key-value store, where their links work as long as the run's storage is kept (the data retention of your plan): copy the files elsewhere to keep them for good.
onlyNewremembers post numbers, not their content: a post that was edited is not returned again.- Two runs sharing the same
stateKeyat the same time may both return the same new post. - Comments: the most recent ones of each post, up to Max comments per post (1,000 at most).
A run that reaches its timeout stops itself about 45 seconds before it: no new page is asked, what it read is saved and, with onlyNew, remembered, and the run ends Succeeded with "Stopped before the run's timeout". Resurrect it to go on from there, or give the next run a longer timeout (Run options).
A run the platform stops without warning (out of memory)
- Resurrect it: it goes on from where it stood at most a minute before the stop. What it had read since is read again, and the posts already saved are skipped: none is delivered or charged twice, and
maxItemsstill counts them. - With
onlyNew, the memory is saved after each write (at most once every 2 seconds): resurrect the stopped run and the posts it had saved meanwhile join the memory; leave it stopped for good, and the next run may return the posts of its last 2 seconds once more.
Something doesn't work?
The last line of the log counts the posts saved, those filtered out and the requests that failed after every retry. Those requests are listed, with the reason, in the FAILED_REQUESTS record of the run's key-value store. A run that saved nothing and had failed requests fails, and its last message gives the reason.
If Telegram changes its pages, you are told instead of paying for blank rows. If the first 20 posts read all lack their date, channel title or view count, the run saves nothing more, stops and fails, and its last message names the missing field: at most those first posts are charged. A post that postedAfter / postedBefore drops because it has no date at all counts among those 20. A run of fewer posts (a scheduled onlyNew run that finds a few new ones, a small Max posts) is checked at its end, from 2 posts (the view count from 5): its posts are already saved, and the run fails with the missing field instead of ending green.
🛟 Support
Open an issue in the Issues tab with a link to your run: the run log and the FAILED_REQUESTS record of the key-value store show exactly which URLs failed and why.