Telegram Channel Scraper - Posts, Views, Media
Pricing
from $1.00 / 1,000 run start fees
Telegram Channel Scraper - Posts, Views, Media
Scrape posts from any public Telegram channel: full text, publish time, view counts, media flags, forwards and outbound links, plus channel title and subscriber count. No login, no bot token. For channel monitoring, news tracking and content research.
Pricing
from $1.00 / 1,000 run start fees
Rating
0.0
(0)
Developer
SR
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
1
Monthly active users
20 days ago
Last modified
Categories
Share
This Telegram scraper reads the posts of any public Telegram channel for monitoring, news tracking and content research: the full text, when it was published, how many people saw it, whether it carries a photo or a video, what it forwards, and every external link in it. Every row also carries the channel's display name and subscriber count, so a post's reach can be read against the size of its audience.
No login, no bot token, no API registration. Give it channel names, get back one row per post.
What this Telegram scraper returns
- One row per post, newest first within each channel, with a stable
postidentifier (durov/548) you can join runs on. - Text with its shape intact. Line breaks and emoji survive, so a fifteen-paragraph post does not arrive as one line.
- View counts twice:
views_textexactly as Telegram shows it (1.97M) andviewsas an integer (1970000) that sorts and sums correctly. - Attribution: the channel owner name, per-post signatures where a channel uses them, and
forwarded_fromwhen a post came from elsewhere. - Media and link flags:
has_photo,has_video,has_link_preview,is_reply, andoutbound_linkswith Telegram's own domains removed. - Channel context on every row:
channel_titleandsubscribersat the time of the run.
What a row looks like
An actual row collected from the public channel durov on 4 October 2026:
{"channel": "durov","message_id": 548,"post": "durov/548","url": "https://t.me/durov/548","text": "🤝 Telegram has become the sponsor of Codeforces — the largest competitive programming platform in the world. \n\n ⚡CodeForces organizes over 100 coding contests every year and has 4 million contestants signed up.\n\n🌱 Since 2010, I've supported Codeforces through the companies I started.\n\n🆙 But this year will be special. Its founder Mike wants to bring Codeforces to the next level — and we'll be happy to help him do that 🤝","text_length": 422,"posted_at": "2026-09-11 16:04:02","views_text": "1.97M","views": 1970000,"author": "Pavel Durov","signature": "Pavel Durov","forwarded_from": null,"is_forwarded": false,"has_photo": false,"has_video": false,"has_link_preview": true,"is_reply": false,"outbound_links": ["https://codeforces.com/blog/entry/156620","https://codeforces.com/blog/entry/156620"],"channel_title": "Pavel Durov","subscribers": 10500000,"position": 1,"source": "telegram-channel-scraper","platform": "telegram","fetched_at": "2026-10-04T04:13:26.285Z","post_url": "https://t.me/durov/548","post_id": "durov/548","author_handle": "durov","posted_at_iso": "2026-09-11T16:04:02.000Z"}
The same collection returned 100 posts from two channels: 99 with text, 100 with a view count, and 57 with a photo or video. View and subscriber counts change over time.
Input
| Field | Type | Default | Example | What it controls |
|---|---|---|---|---|
channels | array of strings | No default; the editor pre-fills ["durov", "telegram"] | ["durov", "https://t.me/telegram"] | The public channels to read. An https://t.me/name link or an @name is reduced to the bare name. Names must be 4 to 32 characters of letters, digits and underscores; anything else is reported as bad_input and skipped. |
limit | integer, 1 to 5000 | 100 | 500 | The total number of posts across all channels. Each channel gets a share, but complete source pages can overshoot it. The combined results are trimmed to the total before sorting; the example with 100 posts returns 59 from durov and 41 from telegram. A higher limit means more pages and a longer run. |
retries | integer, 1 to 8 | 4 | 6 | Maximum attempts per page, including the first attempt, before it is reported as fetch_failed. Attempts remain bounded even when a large collection is resumed. |
Paging walks backwards through message ids: each request asks for the posts older than the lowest id seen so far. There is no page count and no total, so a channel is finished when a page returns nothing new, when its share of limit is reached, or after 40 pages.
Output
One row per post in the default dataset.
| Field | Meaning |
|---|---|
channel | Channel username |
message_id | Message number within the channel |
post | channel/message_id. Stable, join runs on it |
url | Direct link to the post |
text | Post text with line breaks preserved, capped at 8,000 characters |
text_length | Characters of text |
posted_at | When it was published, UTC |
views_text | View count as Telegram shows it, for example 1.2K |
views | View count as an integer, which is what sorts correctly |
author | Channel owner name shown on the post |
signature | Per-post author signature, when the channel uses them |
forwarded_from | Original channel, when the post is a forward |
is_forwarded | Whether the post is forwarded from elsewhere |
has_photo | Whether the post carries a photo |
has_video | Whether the post carries a video |
has_link_preview | Whether a link preview card is attached |
is_reply | Whether the post replies to another |
outbound_links | External links in the post, Telegram's own excluded, at most 20 |
channel_title | Display name of the channel |
subscribers | Subscriber count at the time of the run |
position | Row position in this run |
source, platform | Product identifier and social platform |
fetched_at | Fetch time in ISO 8601 UTC |
post_url, post_id, author_handle | Standard aliases of url, post, and channel |
posted_at_iso | Publish time in ISO 8601 UTC; the original posted_at format remains available |
The key-value store holds two more records. summary carries postsReturned, channelsRead, withText, withViews, withMedia, forwarded, fetchedInSeconds and errorCount. errors lists each failure with a code and a message, and is only written when something went wrong.
Pricing
Pay per event, no subscription.
- $0.001 per post returned (
post). - $0.001 per run start fee (
run_start).
A run that returns 100 posts costs $0.101. A run that returns 1,000 posts costs $1.001. Posts are billed only when they land in the dataset, so a channel that turns out to be private costs nothing beyond the start fee.
Free Apify plans receive at most 10 rows per run and are billed for those 10, not for the posts fetched and discarded. A paid Apify plan removes the cap.
Limits and failure modes
- Public channels only. A private channel, a group or a user account has no public preview. Telegram answers with a valid page that contains no posts, which is easy to mistake for a channel that never posted. The Actor recognises that page and reports
not_a_public_channelnaming the channel, while every other channel in the run continues. - A run with no rows still succeeds. The dataset is empty,
summary.errorCountis set, anderrorsholds the reason:bad_inputfor names that fail validation,not_a_public_channel,fetch_failedwhen a page failed after all retries, orno_resultswhen a channel exists and has nothing to show. - Per-channel ceiling. The walk stops after 40 pages per channel. For a deep archive, run the channel on its own with a high
limitrather than sharing the limit across many channels. - Timeouts. The default run timeout is 300 seconds; a
limitof several thousand across many channels can need more, so raise the run timeout in the run options for large jobs. viewscan be null. Some channels hide view counts. That is the channel's setting, and it comes back asnullrather than zero.- Rounded subscriber counts. Telegram rounds them: a channel of 10.7M is reported as 10,700,000, not to the person.
- No comments, reactions or media files. These live in the app rather than the web preview.
has_photoandhas_videotell you a post carries media; the files are not downloaded. - No historical view counts.
viewsis the count at the moment of the run. Schedule the Actor and join onpostto watch a number grow.
Use cases
Competitor and market monitoring. "I want to know what the channels in my sector publish and which posts actually travel." Read them weekly, divide views by subscribers for an engagement rate, and rank posts across channels on one scale.
News and signal collection. "Announcements in my niche land on Telegram before anywhere else." Schedule the Actor, join each run on post, and pipe only the new rows into a feed or an alert.
Content research. "Which topics does this audience respond to?" text next to views shows which subjects a channel's readers open. Filter on has_video to compare formats.
Link and campaign tracking. "Where does this channel send its readers, and who amplifies it?" outbound_links shows the destinations; is_forwarded with forwarded_from maps how a message moves between channels.
How it compares to other ways to scrape Telegram
| Approach | What it takes | What you get |
|---|---|---|
| This Actor | A list of channel names | One row per post with text, views, media flags, forwards, links and subscriber count, as JSON, CSV or Excel |
| Official Telegram API (Telethon, Pyrogram) | A phone number, an api_id, a logged-in session and your own code | Full access to what that account can see, but you maintain the script, the session and the parsing |
| Bot API | A bot token and admin rights in the channel | Only new messages in channels where the bot was added; no history of someone else's channel |
| Browser extensions | A logged-in Telegram Web tab and manual clicks per channel | A one-off export; no schedule, no API and no join key across runs |
The trade-off is scope: this Telegram scraper reads what a public channel shows to anyone without an account. That covers posts, views and attribution for public channels, and nothing behind a login (private channels, groups, member lists, comments).
FAQ
Do I need a Telegram account, a bot token or an API key?
No. The Actor reads the public web preview of a channel, the same page anyone sees at https://t.me/s/<channel>. You only need an Apify account to run it.
Can this Telegram scraper read private channels, groups or member lists?
No. Only public channels have a web preview. A private channel or group is reported as not_a_public_channel in the errors record, the rest of the run continues, and you are not billed for posts that were never returned.
How many posts can I get from one channel?
Up to 40 pages per channel, and at most 5,000 posts per run across all channels. For a deep archive, run a single channel with a high limit.
How do I track Telegram post views over time?
views is the count at the moment of the run. Schedule the Actor (daily or hourly), keep each dataset, and join the runs on post; the difference between runs is the growth of a post's reach.
Can I export the results to CSV, Excel or Google Sheets?
Yes. Every Apify dataset downloads as JSON, CSV, Excel, XML or RSS, and the Apify integrations push it to Google Sheets, a webhook, Make, Zapier or n8n.
Is scraping public Telegram channels legal?
The Actor only collects content that the channel owner published openly on the web, without logging in. How you store and use that data, especially text that names people, is your responsibility under the laws that apply to you, such as the GDPR.
Run it from the API
Start a run with the default input and read the dataset when it finishes:
response="$(curl --fail --silent --show-error --request POST \'https://api.apify.com/v2/acts/s-r~telegram-channel-scraper/runs?waitForFinish=120' \--header "Authorization: Bearer $APIFY_TOKEN" \--header 'Content-Type: application/json' \--data '{"channels":["durov","telegram"],"limit":100,"retries":4}')"dataset="$(python3 -c 'import json,sys;print(json.load(sys.stdin)["data"]["defaultDatasetId"])' <<< "$response")"curl --fail --silent --show-error \"https://api.apify.com/v2/datasets/$dataset/items?format=json" \--header "Authorization: Bearer $APIFY_TOKEN"
Set APIFY_TOKEN in your environment before running these commands. For longer runs,
poll the run until it finishes before reading the dataset. The response provides the run ID.
Use format=csv or format=xlsx for a spreadsheet.