DEV Community

Hải Hà Phan Thanh
Hải Hà Phan Thanh

Posted on

Export posts from public Telegram channels to JSON or CSV with Python (no API keys, no phone number)

Telegram channels are where a lot of news, crypto, product and community announcements land first. If you want to archive a channel, track keywords across several channels, or feed posts into an LLM, the official route (MTProto via Telethon) needs an API ID, a phone number and a logged-in session. For public channels there is a simpler way.

The trick: the public web preview

Every public channel has a read-only web preview at https://t.me/s/<channel>. It is plain HTML, works without login, and shows the latest ~20 posts with dates, view counts and text.

# pip install requests beautifulsoup4
import requests
from bs4 import BeautifulSoup

def latest_posts(channel):
    html = requests.get(f"https://t.me/s/{channel}", timeout=20).text
    soup = BeautifulSoup(html, "html.parser")
    for msg in soup.select(".tgme_widget_message"):
        text = msg.select_one(".tgme_widget_message_text")
        views = msg.select_one(".tgme_widget_message_views")
        yield {
            "url": "https://t.me/" + msg["data-post"],
            "date": msg.select_one(".tgme_widget_message_date time")["datetime"],
            "views": views.text if views else None,
            "text": text.get_text(" ", strip=True) if text else "",
        }

for p in latest_posts("telegram"):
    print(p["date"], p["views"], p["url"], p["text"][:60])
Enter fullscreen mode Exit fullscreen mode

Output:

2026-08-26T19:12:24+00:00 1.87M https://t.me/telegram/459 Signed Gifts. You can now add your signature and a comment t
2026-08-26T19:12:34+00:00 2.12M https://t.me/telegram/460 For all the info from today's update and a look back at 13 y
Enter fullscreen mode Exit fullscreen mode

Gotchas if you extend this yourself:

  • Only ~20 posts per page. Older posts need ?before=<postId> pagination, looping until you pass your start date.
  • Views come as 1.87M / 12.3K strings; convert them before sorting.
  • Use the time inside .tgme_widget_message_date — videos also contain <time> elements (durations) without a datetime attribute, which crashes a naive select_one("time").
  • Forwards, replies, reactions, media URLs and edited flags each live in different elements.
  • Channels with the preview disabled return no messages at all.

If you don't want to maintain that

Disclosure: I built a small Apify Actor that does the pagination, date range, keyword filter and field parsing above: Telegram Channel Posts Scraper. It costs $1.50 per 1,000 posts (Apify's free monthly credit covers a few thousand) and you can call it with plain requests:

# pip install requests
import os, requests

URL = ("https://api.apify.com/v2/acts/bachiembmt~telegram-channel-posts-scraper"
       "/run-sync-get-dataset-items")
run_input = {
    "channels": ["durov", "https://t.me/telegram"],
    "maxPostsPerChannel": 60,
    "fromDate": "2026-01-01",
    "keywords": ["gift", "update"],
    "includeChannelInfo": False,
}
items = requests.post(URL, json=run_input, timeout=300,
                      headers={"Authorization": f"Bearer {os.environ['APIFY_TOKEN']}"}).json()
for post in items:
    print(post.get("date"), post.get("views"), post.get("url"), (post.get("text") or "")[:60])
Enter fullscreen mode Exit fullscreen mode
2026-02-10T17:44:05+00:00 1940000 https://t.me/telegram/426 Gift Crafting. Collectible gifts can now be combined to crea
2026-01-07T18:17:53+00:00 2730000 https://t.me/telegram/423 Happy New Year! Telegram released 13 major updates in 2025 –
Enter fullscreen mode Exit fullscreen mode

Each post has channel, postId, url, date, text, views (as an integer), forwardedFrom, replyTo, hasMedia, mediaType, mediaUrls, links, reactions, edited. The dataset exports to CSV/Excel/JSON from the Apify console, and you can schedule the run daily to build a keyword alert.

Limits, honestly

  • Public channels with the web preview enabled only. No private channels, groups or comments.
  • It reads the same public HTML as the snippet above, so if Telegram changes the markup both break until fixed.
  • Respect each channel's content rights when you republish anything.

If the DIY snippet is enough for you, use it — it is genuinely 20 lines. The Actor is for when you need many channels, history and a schedule without babysitting a parser.

Top comments (0)