Telegram Channel Scraper - Posts, Views, Forwards & Watchlists avatar

Telegram Channel Scraper - Posts, Views, Forwards & Watchlists

Pricing

from $0.00005 / actor start

Go to Apify Store
Telegram Channel Scraper - Posts, Views, Forwards & Watchlists

Telegram Channel Scraper - Posts, Views, Forwards & Watchlists

Extract public Telegram channel posts, view counts, forwards, reactions, media and outbound links — no bot token, no MTProto, no phone number. Adds a cross-channel mention graph, keyword watchlists across scheduled runs, and subscriber-delta change events.

Pricing

from $0.00005 / actor start

Rating

0.0

(0)

Developer

Eonix Pvt Ltd

Eonix Pvt Ltd

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a month ago

Last modified

Share

Telegram Public Channel Intelligence — Posts, Views, Forwards & Watchlists

Extract everything a public Telegram channel exposes to the open web, and turn it into a monitored feed.

No bot token. No MTProto. No phone number. No session string. No login of any kind.

This actor reads https://t.me/s/{channel} — the fully server-rendered public preview that Telegram serves to anyone — and parses it with Cheerio. There is no browser, no API key, and nothing to authorize.


Why this one

A raw post dump is table stakes; several well-rated actors already do it. Three layers that none of them ship are the reason this exists:

LayerWhat it gives you
Cross-channel mention graphWeighted, directed {source, target, weight, kinds} edges built from forwards and @mentions. Written to GRAPH.json. Shows who amplifies whom.
Incremental watchlistsKeywords and regexes matched on every scheduled run. New matches fire typed change records — a real alerting primitive, not a one-off search.
Subscriber-delta eventsGrowth and collapse detected across runs and emitted as typed change records when the move exceeds your threshold.

Plus the thing this category gets wrong most often: when a channel has no public preview, this actor says so. See Reliability.


Free plan limits

Free-plan runs are capped to 1 channel, 25 messages, no AI, and no channel expansion, and the run logs:

Free-plan run: capped to 25 records. Paid plans run uncapped.

Paid plans run uncapped, up to your configured limits.


What a run costs you

Before any work begins, the run computes an estimate from your resolved input, logs it, and writes it to the key-value store as COST-ESTIMATE.json.

EventWhen it firesPrice
channel-resolvedOnce per channel successfully fetched, after its record is pushed$0.02
message-returnedOnce per message pushed$0.0004
watchlist-hitOnce per watchlist match$0.005
ai-briefOnce per successful AI brief$0.02
request-servedStandby HTTP requests only$0.001

You are never charged for:

  • a channel with no public preview (status: "unreachable")
  • a channel whose markup drifted (kind: "health")
  • a failed or malformed AI call
  • an incremental run that finds nothing new (beyond the per-channel event)

Example — the shipped default input (2 channels × 200 messages, 2 keywords):

channel-resolved 2 x $0.020000 = $0.040000
message-returned 400 x $0.000400 = $0.160000
watchlist-hit 20 x $0.005000 = $0.100000
ESTIMATED MAXIMUM TOTAL: $0.300000

That is an upper bound. With incremental: true (the default), later scheduled runs only fetch new posts and typically cost a small fraction of it.


Input

FieldTypeDefaultNotes
channelsstring[]["telegram","telegramtips"]Accepts @handle, t.me/x, t.me/s/x, a full URL, or a bare name
maxMessagesPerChannelinteger200Max 5000
sinceDatestringISO date; stop walking once posts are older
incrementalbooleantrueOnly fetch posts newer than the last run (see "Runs are never empty")
keywordsstring[]Case-insensitive plain-text terms
regexesstring[]JavaScript regexes; unsafe patterns are rejected
expandMentionsbooleanfalseQueue newly discovered channels
maxDiscoveredChannelsinteger0Ceiling for expansion
subscriberDeltaPctnumber1Threshold for a subscriber change event
openaiApiKeystring (secret)Enables one AI brief per channel
modelstringgpt-4o-miniModel for the brief
proxyConfigurationobjectApify Proxy

Output

Every record carries the same envelope, shared across this portfolio:

{ kind, target, status, collectedAt, source, ...payload }

kind is record | change | summary | health. status is ok | partial | unreachable | endpoint_unavailable | skipped.

Real sample output

Copied verbatim from a local run on 16 Aug 2026 — not hand-written.

Channel record

{
"kind": "record",
"target": "telegram",
"status": "ok",
"collectedAt": "2026-08-16T16:56:58.762Z",
"source": "https://t.me/s/telegram",
"recordType": "channel",
"channel": "telegram",
"title": "Telegram News",
"description": "The official Telegram on Telegram. Much recursion. Very Telegram. Wow.",
"subscribers": 9810000,
"photosCount": 14,
"videosCount": 225,
"linksCount": 372,
"avatarUrl": "https://cdn1.telesco.pe/file/LvdKdKHGD-oGHuw...jpg",
"isVerified": true,
"contentAdvisory": false
}

Message record

{
"kind": "record",
"target": "telegram",
"status": "ok",
"collectedAt": "2026-08-16T16:56:58.762Z",
"source": "https://t.me/s/telegram",
"recordType": "message",
"channel": "telegram",
"messageId": 454,
"url": "https://t.me/telegram/454",
"datetime": "2026-07-19T17:58:20+00:00",
"text": "For all the details on these new features, check out our blog:\nhttps://telegram.org/blog/communities-editor-invisible-messages\n\nJuly Features\n1 • 2 • 3 • 4 • More",
"views": 1500000,
"forwardedFromChannel": null,
"forwardedFromName": null,
"isForwarded": false,
"replyToId": null,
"hasMedia": false,
"mediaTypes": [],
"mediaUrls": [],
"outboundLinks": ["https://telegram.org/blog/communities-editor-invisible-messages"],
"mentionedChannels": [],
"hashtags": [],
"reactions": [],
"totalReactions": 0,
"edited": false,
"editedAt": null,
"authorSignature": null
}

Watchlist hit

{
"kind": "change",
"target": "telegram",
"status": "ok",
"collectedAt": "2026-08-16T16:56:58.762Z",
"source": "https://t.me/s/telegram",
"type": "watchlist_hit",
"channel": "telegram",
"messageId": 450,
"url": "https://t.me/telegram/450",
"matched": "privacy",
"matchKind": "keyword",
"snippet": "…in a visual editor that supports dozens of formatting options and allows you to generate text with privacy-conscious AI tools. July Features 1 • 2 • 3 • 4 • More",
"views": 944000,
"datetime": "2026-07-19T17:57:15+00:00"
}

Runs are never empty

In incremental mode a run over a quiet channel legitimately finds nothing new — many channels post weekly or monthly, so a daily run would otherwise return an empty dataset and look broken.

When a run finds no new posts on any channel, it re-delivers the 25 most recent posts from the first reachable channel and sets fallbackUsed: true on the run summary. Watchlists are not re-evaluated for those posts: they were already matched on an earlier run, so firing the same alerts again would spam you and charge you twice for one event.

Turn this off by setting incremental: false, which always walks history up to your message limit.

Artifacts written to the key-value store

KeyContents
COST-ESTIMATE.jsonWhat this run is expected to charge you
SUMMARY.jsonRun totals, per-channel outcomes, charge counts
GRAPH.jsonMention/forward graph: nodes and weighted edges
UNIT-ECONOMICS.jsonDeveloper-facing cost and margin measurement
{channel}-brief.mdAI brief, when an OpenAI key is supplied

Field notes

  • editedAt is always null. The public preview exposes only that a post was edited, never when. Rather than invent a timestamp, edited is a boolean and editedAt is kept null for schema stability.
  • htmlText is sanitized. Telegram's own markup ships inline onclick handlers on every outbound link. Script/iframe elements, on* attributes and javascript:/data: URLs are stripped so the field is safe to render.
  • views may be null. Absent is not zero, and the two are never conflated.
  • mentionedChannels excludes self-mentions, which would otherwise dominate the graph.

Reliability

Measured during the build, 16 August 2026:

  • 17 distinct public channel names tested.
  • 9 exposed a public preview — all 9 parsed successfully (100%).
  • 8 did not expose a preview — all 8 were correctly reported as unreachable, with no charge, and the run still succeeded (100%).
  • No run failed. Full-history walks, incremental re-runs, standby requests and all-unreachable runs all completed successfully.
  • Default input completes in ~24 seconds (400 messages, 2 channels).
  • A repeat incremental run completes in ~3 seconds and logs 0 new messages.

The volume leader in this niche is rated 2.11/5 across 16 reviews precisely because it reports SUCCESS while returning nothing. That failure mode is designed out here:

  • A page with no preview is detected explicitly (Telegram serves these with HTTP 200) and reported as unreachable.
  • A 2xx response whose shape no longer matches emits SOURCE DRIFT: <source> returned unexpected shape and a kind: "health" record, so breakage is visible in your dataset rather than as a silently empty result.
  • If every source fails, the run still pushes health records and a summary. It never exits empty.

What this actor does NOT do

Honest coverage limits, so you can judge fit before you spend anything:

  • It cannot read channels without a public preview. Many well-known names — bbcnews, reuters, nasa, techcrunch among those tested — do not serve one. They return unreachable and cost you nothing, but no amount of retrying will produce their posts.
  • No private channels, no groups, no DMs, no member lists. There is no login, so anything behind one is out of reach by construction.
  • No comment threads or discussion-group replies. Only in-channel reply references.
  • No reaction-per-user data, only aggregate counts per emoji.
  • No edit timestamps — see Field notes.
  • No media file downloads. Media URLs are extracted; the bytes are not fetched.
  • No historical view-count series. Views are whatever the page shows at collection time.
  • Deleted posts are invisible, and gaps in message ids are not reconstructed.
  • Not a real-time firehose. It is a polling actor; latency is your schedule interval.

Ethics and compliance

  • Public channels only. This reads the same page any logged-out browser can load.
  • No member lists, no private groups, no direct messages, no user profiling.
  • No authentication is performed or required, so no account is impersonated.
  • When a channel self-labels as restricted, the channel record carries contentAdvisory: true rather than the content being silently filtered — you decide.
  • You remain responsible for how you use collected data, including GDPR obligations where posts contain personal data.

Use from an AI agent

Apify MCP. This actor is callable as a tool through the Apify MCP server. Point your MCP client at https://mcp.apify.com and include this actor; the input schema above is the tool schema.

Standby mode. When ACTOR_STANDBY_PORT is set, the actor stays warm and answers HTTP requests, running the identical pipeline and charging the identical events plus request-served:

GET https://<your-actor>.apify.actor/?channels=telegram,telegramtips&maxMessagesPerChannel=50&keywords=privacy
GET https://<your-actor>.apify.actor/health

Query parameters mirror the input fields; list fields accept repeats or comma-separated values. The response is the same JSON envelope, plus charged, economics and warnings.

openaiApiKey is deliberately ignored in standby mode — secrets must not travel in a query string, where they land in access logs and browser history. Use a normal run for AI briefs.


Maintenance

  • Broken-source issues are triaged within 2 business days, with a fix or a written workaround within 5 business days.
  • Endpoint drift is detected automatically and surfaced in the dataset, so breakage is visible immediately rather than after a silent empty run.
  • Report issues on the actor's Apify page.

Running it locally

npm install
npm run verify # lint + typecheck + 73 unit tests
npm run acceptance # live run against the shipped INPUT.json

Useful scripts:

ScriptPurpose
npm run verifyLint, type-check and the full unit suite
npm run acceptanceLive run using INPUT.json, snapshots economics
npm run acceptance:scaled10x-scaled run for cost-per-item comparison
npm run acceptance:freeExercises the free-plan caps
npm run calibratePrints the pricing calibration report

For the developer: unit-economics calibration

profit = (0.8 × revenue) − platform usage cost, so prices must be validated, not guessed. Every run writes UNIT-ECONOMICS.json and logs a verdict.

Set your own rates. Read them off your Apify billing page and put them in .env:

COST_CU_USD=0.25
COST_PROXY_GB_USD=8.0

The values in .env.example are Apify's commonly published list rates, used as a documented assumption so calibration produces an arguable number. They vary by plan and change over time — verify them. If they are unset, platform cost resolves to $0 and the report correctly prints CALIBRATION NOT PERFORMED — rates unset instead of a fake healthy margin.

Measured result (16 Aug 2026, local estimate at the rates above)

EventHypothesisMeasured costFloor (3× cost)Recommended
channel-resolved$0.020000$0.000825$0.003096$0.020000
message-returned$0.000400$0.000054$0.000201$0.000400

Both spec prices survive calibration. message-returned sits at 2.0× its cost-covering floor and channel-resolved at 6.5×. Run margin was 94.9%, verdict HEALTHY.

Scale comparison (default vs 10×):

ScaleDriver eventsCost/eventMargin
default400$0.00005494.9%
scaled869$0.00005395.4%

Cost per message fell as volume rose, so fixed cost amortizes correctly and there is no per-item fixed-cost problem the pricing needs to absorb.

On residential proxy. Residential is the default proxy tier and it is affordable here because a page is ~134 KB and yields ~20 messages (~6.7 KB/message). Telegram serves this surface uncompressed (no content-encoding), so the byte accounting equals real transfer rather than under-counting it. The pay-per-event + usage toggle is deliberately not enabled: Apify warns it reduces pricing transparency and hurts the quality score, and this actor does not need it to be profitable.


License

MIT