Public Telegram channels are where a lot of news breaks first: crypto projects, newsrooms, brands and communities post there before anywhere else. Getting those posts into a spreadsheet usually starts with registering an app, a phone number, an api_id and a session file, all for data any browser can already open at t.me/s/<channel>. For monitoring a list of channels, comparing their reach or keeping an archive, that public preview is enough.
This post reads public Telegram channels into Python, compares channels by subscribers and engagement, pulls a date window, and sets up a daily feed of new posts.
Disclosure: I'm Aurelix, and I built the Telegram Subscriber Count Scraper. It's a paid tool (prices below).
What each row holds
One row per post, 31 fields:
-
post_id,post_url,channel_username,date(ISO 8601 UTC),edited,author_signature. -
text,links,hashtags,mentionsandemailsfound in it. -
views,reactions({emoji, count}) andreactions_total. -
media(type, URL, thumbnail, duration),poll,link_preview. - Forwards and replies:
is_forwarded,forwarded_from_channel,forwarded_from_title,forwarded_from_post_url,reply_to_post_id. - The channel on every row:
channel_title,channel_description,channel_subscribers(exact),channel_verified,channel_url.
A few channels, as a sheet
pip install apify-client
import csv
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("aurelix/telegram-channel-scraper").call(
run_input={
"channels": ["bitcoin", "@cointelegraph", "https://t.me/s/tginfo"],
"maxPostsPerChannel": 200,
}
)
posts = list(client.dataset(run["defaultDatasetId"]).iterate_items())
fields = ["channel_username", "date", "views", "reactions_total", "is_forwarded", "text", "post_url"]
with open("telegram_posts.csv", "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=fields, extrasaction="ignore")
w.writeheader()
w.writerows(posts)
Channels can be a username, an @username or a t.me link. Posts come newest first; older ones are read page by page, about 20 posts a page, and maxPostsPerChannel: 0 reads the whole history the preview shows.
Which channels get the most attention
Subscriber counts say how big a channel is; views per post and reactions say how many people actually read it:
from collections import defaultdict
from statistics import median
by_channel = defaultdict(list)
for p in posts:
by_channel[p["channel_username"]].append(p)
for name, ps in by_channel.items():
subs = ps[0]["channel_subscribers"]
views = median(p["views"] or 0 for p in ps)
reactions = median(p["reactions_total"] or 0 for p in ps)
reach = f"{views / subs:.0%}" if subs else "-"
print(f"{name:16} {subs:>10,} subscribers median {views:>8,.0f} views ({reach}) {reactions:,.0f} reactions")
The same rows show what spreads: sort by views, or count forwarded_from_channel to see which sources a channel reposts most.
A date window
run = client.actor("aurelix/telegram-channel-scraper").call(
run_input={
"channels": ["cointelegraph"],
"postedAfter": "2026-10-01",
"postedBefore": "2026-11-01",
"maxPostsPerChannel": 0,
}
)
postedAfter and postedBefore take a date (read as UTC midnight), an ISO date-time, or an age such as 7 days or 12 hours. With postedBefore, the run jumps straight to that part of the history instead of reading every newer post first. Posts outside the window are not charged.
New posts every morning
run_input = {
"channels": ["bitcoin", "cointelegraph"],
"maxPostsPerChannel": 100,
"incremental": True,
"stateKey": "crypto-news-daily",
"slackWebhookUrl": "https://hooks.slack.com/services/...",
}
With incremental on, each run returns only posts newer than the newest one an earlier run returned for the same channel, so a scheduled run never repeats a post. The memory is kept per channel and per stateKey for 60 days. Save the input as a task in the Apify Console and schedule it daily or hourly: the Slack URL gets the count and the top 10 new posts with links, and nothing is sent when there is nothing new. webhookUrl posts the same digest as JSON to Zapier, Make or n8n.
Why this tool
- No account, phone number,
api_idor bot token: it reads the public web preview. - Every post field: views, reactions, media, polls, link previews, forwards and replies, plus the exact subscriber count.
- Many channels per run, a date window, and an only-new mode with a Slack or webhook digest.
- What it doesn't cover: groups, comments, member lists, user accounts and private channels.
Limits
- Public channels only. A channel whose owner turned off the web preview, a group, a bot or an unknown name is skipped and named in the run's status, and not charged.
- Rounded figures. Views, reactions and poll voters are Telegram's own rounded numbers (74.4K becomes 74400); the subscriber count is exact.
- Media links expire. Video and voice links stop working after a while, and large videos have a thumbnail only; download soon after the run.
- Telegram's terms. They restrict scraping and prohibit using its data to train or develop AI models, so don't use the output for that.
Cost
$0.20 per 1,000 posts ($0.15 on paid Apify plans), plus a $0.002 start fee per run. Skipped channels, posts outside your dates, posts already returned in only-new mode and repeated channels are never charged. The current prices are on the Store page; Apify's free plan includes $5 of credit a month, about 24,000 posts.
Top comments (0)