DEV Community

Hay Equipos
Hay Equipos

Posted on

How to export posts from a public Telegram channel to CSV or JSON

You want the posts from a public Telegram channel in a spreadsheet: what was posted, when, and how many people saw it. Telegram's official API needs a phone number, an app registration and a client library, which is a lot of setup for "give me the last 500 posts as CSV". Copying posts by hand stops working after the first dozen.

The Telegram Channel Scraper actor, published by Hay Equipos on the Apify Store, reads the public web preview that Telegram itself serves at t.me/s/<channel> (the same page anyone can open in a browser) and walks back through the channel history page by page until it reaches your date or post limit. No Telegram account, no phone number, no API key.

What you get back

One row per post. A trimmed example (values are illustrative):

{
  "channel": "examplechannel",
  "channelTitle": "Example Channel",
  "channelSubscribers": 125000,
  "channelVerified": false,
  "postId": 4821,
  "postUrl": "https://t.me/examplechannel/4821",
  "date": "2026-09-14T08:15:00.000Z",
  "edited": false,
  "text": "Weekly market recap is out. #markets #recap",
  "views": 48300,
  "reactions": [{ "emoji": "👍", "count": 310 }, { "emoji": "🔥", "count": 95 }],
  "reactionsTotal": 405,
  "mediaTypes": ["photo"],
  "photoUrls": ["https://cdn4.telesco.pe/file/..."],
  "videoCount": 0,
  "documentNames": [],
  "poll": null,
  "links": ["https://example.com/recap"],
  "hashtags": ["#markets", "#recap"],
  "linkPreview": null,
  "forwardedFrom": null,
  "isServiceMessage": false,
  "scrapedAt": "2026-09-27T06:09:41.563Z"
}
Enter fullscreen mode Exit fullscreen mode

Each row also carries videoThumbnailUrl and forwardedFromUrl. Polls come with their question and option percentages, and link previews come with site name, title and description. A RUN_SUMMARY record in the run's key value store lists every channel with its status, title, subscriber count, description, and how many posts were read and saved.

In table form, the columns most people keep:

postUrl date views reactionsTotal text
https://t.me/examplechannel/4821 2026-09-14 48300 405 Weekly market recap is out...
https://t.me/examplechannel/4820 2026-09-13 51200 388 New listing announced...

Step by step in the Apify Console

  1. Open the actor on the Apify Store (link at the end) and click Try for free. You need a free Apify account.
  2. In Public channels, add one channel per line. A username (telegram), @username, or a link (https://t.me/durov or https://t.me/s/durov) all work.
  3. Set Maximum posts per channel (default 100, newest posts first) and, if you like, Maximum posts in total (default 10,000).
  4. To limit the date range, fill Posted on or after and Posted before, for example 2026-09-01. Reading stops once the actor reaches posts older than your start date.
  5. To keep only posts about a topic, add words to Text contains any of. Matching ignores case.
  6. Leave Include service messages off unless you want notices like a changed channel photo or a pinned message.
  7. Click Start. When the run finishes, open the Output tab and export as CSV, Excel, JSON or HTML.

Example input:

{
  "channels": ["telegram", "https://t.me/durov"],
  "maxPostsPerChannel": 200,
  "postedAfter": "2026-06-01",
  "keywords": []
}
Enter fullscreen mode Exit fullscreen mode

Calling it from code

The actor id is pistachio_implementation/telegram-channel-posts. Keep your token in the APIFY_TOKEN environment variable.

With curl, this runs the actor and returns the dataset items in one call:

curl -X POST \
  "https://api.apify.com/v2/acts/pistachio_implementation~telegram-channel-posts/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"channels": ["telegram"], "maxPostsPerChannel": 50}'
Enter fullscreen mode Exit fullscreen mode

With Python and the apify-client package (pip install apify-client):

import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])

run = client.actor("pistachio_implementation/telegram-channel-posts").call(
    run_input={
        "channels": ["telegram", "durov"],
        "maxPostsPerChannel": 100,
        "postedAfter": "2026-09-01",
        "keywords": ["update"],
    }
)

for post in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(post["postUrl"], post["date"], post["views"])
Enter fullscreen mode Exit fullscreen mode

To monitor a channel, schedule the actor daily with postedAfter set to yesterday, or store the highest postId from the last run and compare.

Pricing

Pay per event, with no subscription and no platform usage charges on top:

  • $0.30 per 1,000 posts saved ($0.0003 per post).
  • Posts removed by your date or keyword filters, and channels that cannot be read, cost nothing.

Limits and what it does not do

  • Public channels only. Private channels, groups, chats and user accounts have no web preview. They are reported as "no public preview" in RUN_SUMMARY instead of failing the whole run. Some public channels also turn their preview off.
  • Views and reactions are the rounded numbers Telegram shows. "1.48M" becomes 1480000, so treat large counts as approximate.
  • Custom emoji reactions come back as custom:<id>, because Telegram's page shows them as images.
  • Media links point to Telegram's CDN and expire after a while. Download the files soon if you need them.
  • The actor waits 1.5 seconds between pages to stay polite, which works out to roughly 800 posts a minute.
  • It does not collect post signatures (the admin who posted), member lists, phone numbers or user profiles. It reads only what a public channel shows to any visitor.
  • It depends on the markup of Telegram's preview page. If Telegram changes that page, the parser needs an update.

Please use the posts you collect in line with Telegram's terms and the rights of the people who wrote them.

Try it on the Apify Store: https://apify.com/pistachio_implementation/telegram-channel-posts

Top comments (0)