<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Signal Wharf</title>
    <description>The latest articles on DEV Community by Signal Wharf (@signalwharf).</description>
    <link>https://dev.to/signalwharf</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4239989%2F286d1c94-4604-4460-a9e6-499849e47203.png</url>
      <title>DEV Community: Signal Wharf</title>
      <link>https://dev.to/signalwharf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/signalwharf"/>
    <language>en</language>
    <item>
      <title>How to Get Substack Newsletter Data: Posts, Comments and Subscriber Counts</title>
      <dc:creator>Signal Wharf</dc:creator>
      <pubDate>Wed, 11 Nov 2026 23:52:09 +0000</pubDate>
      <link>https://dev.to/signalwharf/how-to-get-substack-newsletter-data-posts-comments-and-subscriber-counts-70f</link>
      <guid>https://dev.to/signalwharf/how-to-get-substack-newsletter-data-posts-comments-and-subscriber-counts-70f</guid>
      <description>&lt;p&gt;Substack hosts many of the most-read independent newsletters, and the data around them is useful. Sponsors want to know how big a newsletter really is. Researchers want the full text of posts. Media teams track what competitors publish and how readers react.&lt;/p&gt;

&lt;p&gt;Substack has no official public API for this. The data is visible in the browser, though: post pages, comment threads, the archive, and the "Over 466,000 subscribers" line on a publication's homepage. This guide shows how to collect it in a structured way without logging in, using the &lt;a href="https://apify.com/signalwharf/substack-scraper" rel="noopener noreferrer"&gt;Substack Scraper&lt;/a&gt; on Apify. The output works as JSON, CSV or Excel, or from Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  What data you can get from Substack
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Posts&lt;/strong&gt; (one item per post):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;title, subtitle, URL, publish date, authors, section and tags&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isPaid&lt;/code&gt; and audience (free or subscriber-only)&lt;/li&gt;
&lt;li&gt;word count, likes (reactions), comment count and restacks&lt;/li&gt;
&lt;li&gt;full body text, optionally the HTML, and the links in the post&lt;/li&gt;
&lt;li&gt;podcast URL and duration for audio posts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Publication profiles&lt;/strong&gt; (one per newsletter):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the subscriber estimate as shown publicly ("Over 466,000 subscribers" → &lt;code&gt;466000&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;the paid-subscriber tier text, and the Substack bestseller tier&lt;/li&gt;
&lt;li&gt;monthly, yearly and founding-member prices&lt;/li&gt;
&lt;li&gt;posting cadence (posts per week), the share of posts that are paid, and average likes, comments and restacks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Comments&lt;/strong&gt; (optional): author, date, body, likes, reply count and thread depth.&lt;/p&gt;

&lt;p&gt;What it does &lt;strong&gt;not&lt;/strong&gt; do: it doesn't log in, bypass paywalls or collect subscriber emails. For subscriber-only posts you get the metadata plus the free preview that Substack shows every visitor, flagged with &lt;code&gt;isFullTextComplete: false&lt;/code&gt;. Substack Notes aren't covered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: List the newsletters
&lt;/h2&gt;

&lt;p&gt;Use whatever identifier you have. The scraper accepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a subdomain: &lt;code&gt;noahpinion&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a Substack URL: &lt;code&gt;https://noahpinion.substack.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a custom domain: &lt;code&gt;https://www.astralcodexten.com&lt;/code&gt; or &lt;code&gt;www.noahpinion.blog&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a profile link: &lt;code&gt;https://substack.com/@noahpinion&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 2: Run the scraper
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://apify.com/signalwharf/substack-scraper" rel="noopener noreferrer"&gt;Substack Scraper&lt;/a&gt; and click &lt;strong&gt;Try for free&lt;/strong&gt;. You need a free Apify account.&lt;/li&gt;
&lt;li&gt;Add your newsletters under &lt;strong&gt;Publications&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Max posts per publication&lt;/strong&gt;. Use 20–50 for a quick look, or more for a full archive.&lt;/li&gt;
&lt;li&gt;Keep &lt;strong&gt;Include full text&lt;/strong&gt; on if you want the article bodies.&lt;/li&gt;
&lt;li&gt;Turn on &lt;strong&gt;Include comments&lt;/strong&gt; if you need them. You can also set &lt;strong&gt;Max comments per post&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Optional: add a date range or keywords.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Start&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Our test input (Nov 11, 2026):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"publications"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"noahpinion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"https://www.astralcodexten.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"maxPostsPerPublication"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"includeFullText"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"includePublicationProfile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It finished with &lt;strong&gt;40 posts from 2 of 2 publications&lt;/strong&gt;, plus a profile for each.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Read the output
&lt;/h2&gt;

&lt;p&gt;Every item has an &lt;code&gt;itemType&lt;/code&gt;: &lt;code&gt;post&lt;/code&gt;, &lt;code&gt;publication&lt;/code&gt; or &lt;code&gt;comment&lt;/code&gt;. Here are real items from our test runs, trimmed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A publication profile:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"itemType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"publication"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"publicationName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Noahpinion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"publicationUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"https://www.noahpinion.blog"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"authorName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Noah Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"createdAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"2020-03-28T03:32:51.086Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"subscriberCountText"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Over 466,000 subscribers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"subscriberCountApprox"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;466000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"paidSubscriberTierText"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Tens of thousands of paid subscribers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"bestsellerTier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"monthlyPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"yearlyPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"foundingMemberPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;420.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"postsPerWeek"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;7.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"avgReactions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;190.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"avgComments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;12.7&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;A post:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"itemType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"post"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"publicationName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Noahpinion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"India is quietly doing great"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"subtitle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"The world’s biggest country continues to defy the skeptics."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"https://www.noahpinion.blog/p/india-is-quietly-doing-great"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"postDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"2026-11-11T08:51:56.921Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"audience"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"everyone"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"isPaid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"wordcount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1596&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"reactionCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"commentCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"restacks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;47&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"bodyText"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"My last post was about how for the first time, I’m becoming bearish on China’s ..."&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;A comment&lt;/strong&gt; (with comments enabled):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"itemType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"comment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"postTitle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"India is quietly doing great"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"authorName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Matthew"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"2026-11-11T09:29:04.810Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"reactionCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"replyCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"depth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Great piece. I wish you had gone into some of the politics of this, ..."&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How accurate are the subscriber counts?
&lt;/h3&gt;

&lt;p&gt;They're as accurate as what Substack shows publicly, and that's a rounded figure. "Over 466,000 subscribers" becomes &lt;code&gt;subscriberCountApprox: 466000&lt;/code&gt;. The real number is at least that high, and it includes free subscribers. Paid subscribers only appear as a tier ("Tens of thousands of paid subscribers"), never as an exact count. Some newsletters hide the count entirely, and then the field is &lt;code&gt;null&lt;/code&gt;. The averages (&lt;code&gt;avgReactions&lt;/code&gt;, &lt;code&gt;postsPerWeek&lt;/code&gt;) are computed from the posts scraped in that run. The field &lt;code&gt;statsBasedOnPosts&lt;/code&gt; tells you how many posts that was, so scrape more posts if you want steadier averages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Export to CSV, Excel or Google Sheets
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Output&lt;/strong&gt; tab has views for each item type:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;View&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Overview&lt;/td&gt;
&lt;td&gt;one row per post: date, title, paid flag, word count, likes, comments, restacks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content&lt;/td&gt;
&lt;td&gt;full text, authors, tags and links, for reading or analysis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Publications&lt;/td&gt;
&lt;td&gt;subscriber estimates, prices, bestseller tier, cadence and engagement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Comments&lt;/td&gt;
&lt;td&gt;comment text, author, likes and depth&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Choose a view, then &lt;strong&gt;Export&lt;/strong&gt; as CSV, Excel or JSON. To send results to Google Sheets automatically, use Apify's Google Sheets integration on the Actor or on a scheduled task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Use it from Python
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"APIFY_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"signalwharf/substack-scraper"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"publications"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"noahpinion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"https://www.astralcodexten.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"maxPostsPerPublication"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"defaultDatasetId"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pubs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"itemType"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"publication"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"itemType"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"post"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pubs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"publicationName"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"subscriberCountApprox"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"monthlyPrice"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Most-liked posts first&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"reactionCount"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"reactionCount"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With pandas, &lt;code&gt;pd.DataFrame(posts)&lt;/code&gt; gives you a table ready to filter or plot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Track new posts automatically
&lt;/h2&gt;

&lt;p&gt;Set &lt;code&gt;onlyNewPosts: true&lt;/code&gt; and create a schedule, for example hourly or daily. The scraper remembers the newest post it saw for each publication and returns only newer ones on the next run. A run that finds nothing new costs nothing. This is a simple way to build a newsletter monitor that feeds Slack, email or a spreadsheet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical use cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sponsorship research:&lt;/strong&gt; compare subscriber estimates, posting cadence and engagement across newsletters before you buy an ad slot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitive content tracking:&lt;/strong&gt; see what competitors publish, how long their posts are, and which topics get the most likes and restacks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Research and NLP datasets:&lt;/strong&gt; collect full text from free posts for topic modeling, summarization or sentiment analysis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pricing benchmarks:&lt;/strong&gt; see the monthly and yearly prices that newsletters in your niche charge.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How much does it cost?
&lt;/h2&gt;

&lt;p&gt;Pay per event, no subscription:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Post (full text included)&lt;/td&gt;
&lt;td&gt;$0.0015 ($1.50 per 1,000)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Publication profile&lt;/td&gt;
&lt;td&gt;$0.003&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Comment (only if enabled)&lt;/td&gt;
&lt;td&gt;$0.0003&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Our 40-post test with 2 profiles cost 40 × $0.0015 + 2 × $0.003 = &lt;strong&gt;$0.066&lt;/strong&gt;. A single newsletter with 100 posts and a profile costs about $0.15. You can cap the cost per run in the run options. Apify's free plan includes a monthly platform credit, which is enough to try it out. Check Apify's pricing page for the current amount.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A custom domain isn't recognized:&lt;/strong&gt; paste the full homepage URL, e.g. &lt;code&gt;https://www.astralcodexten.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paid post with short text:&lt;/strong&gt; expected. Only the public preview is available without a subscription.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subscriber fields are &lt;code&gt;null&lt;/code&gt;:&lt;/strong&gt; that publication doesn't show a public count.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comment counts differ from the comments returned:&lt;/strong&gt; check &lt;code&gt;maxCommentsPerPost&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Is it legal?
&lt;/h2&gt;

&lt;p&gt;The scraper reads only public pages and public JSON that any visitor's browser loads. It doesn't log in, bypass paywalls, or collect emails or private subscriber data. Post text is copyrighted by its authors, so use it for analysis and research, and don't republish it. Comments are personal data under the GDPR. Ask a lawyer if you're unsure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related tools
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://apify.com/signalwharf/telegram-channel-scraper" rel="noopener noreferrer"&gt;Telegram Channel Scraper&lt;/a&gt;: public Telegram channel posts, views and reactions, no login.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://apify.com/signalwharf/ai-brand-visibility-tracker" rel="noopener noreferrer"&gt;AI Brand Visibility Tracker&lt;/a&gt;: check whether ChatGPT, Claude and Gemini recommend your brand.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Built and maintained by Signal Wharf. Substack is a trademark of its owner; this tool is not affiliated with or endorsed by Substack. Example newsletters are shown only to illustrate public data.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>substack</category>
      <category>scraping</category>
      <category>python</category>
      <category>data</category>
    </item>
    <item>
      <title>How to Scrape a Telegram Channel to CSV (No Login)</title>
      <dc:creator>Signal Wharf</dc:creator>
      <pubDate>Wed, 11 Nov 2026 23:49:52 +0000</pubDate>
      <link>https://dev.to/signalwharf/how-to-scrape-a-telegram-channel-to-csv-no-login-126p</link>
      <guid>https://dev.to/signalwharf/how-to-scrape-a-telegram-channel-to-csv-no-login-126p</guid>
      <description>&lt;p&gt;Public Telegram channels are a good source of news, crypto signals, product announcements and community chatter. Getting that data into a spreadsheet is harder than it should be. The official Telegram API (MTProto) needs an account, a phone number and an &lt;code&gt;api_id&lt;/code&gt;. Libraries like Telethon need a logged-in session, and copying posts by hand doesn't scale.&lt;/p&gt;

&lt;p&gt;There is a simpler route for &lt;strong&gt;public&lt;/strong&gt; channels. Telegram publishes a web preview of every public channel at &lt;code&gt;https://t.me/s/&amp;lt;channel&amp;gt;&lt;/code&gt;. Anyone can see it without logging in. This tutorial shows how to turn that preview into a clean CSV: posts, view counts, reactions, links and media. It uses the &lt;a href="https://apify.com/signalwharf/telegram-channel-scraper" rel="noopener noreferrer"&gt;Telegram Channel Scraper&lt;/a&gt; on Apify. You can run it from the browser, or call it from Python or &lt;code&gt;curl&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can and can't scrape
&lt;/h2&gt;

&lt;p&gt;What you &lt;strong&gt;can&lt;/strong&gt; get from a public channel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;post text, date (UTC), post URL and ID&lt;/li&gt;
&lt;li&gt;view count and the reaction breakdown (including paid ⭐ Stars reactions)&lt;/li&gt;
&lt;li&gt;links, hashtags, mentions and link previews&lt;/li&gt;
&lt;li&gt;photos, videos and documents (CDN URLs), forwards and replies&lt;/li&gt;
&lt;li&gt;channel title and subscriber count&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What you &lt;strong&gt;can't&lt;/strong&gt; get, by design: private channels, invite links (&lt;code&gt;t.me/+...&lt;/code&gt;), groups, chats, comment threads and member lists. If you need those, you need the official API and an account. Some channels also switch off the web preview. The scraper reports these as &lt;code&gt;preview_disabled&lt;/code&gt; and doesn't charge for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Check that the channel has a web preview
&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;https://t.me/s/durov&lt;/code&gt; in a browser. If you see a scrollable list of posts, the channel can be scraped this way. If Telegram only shows a "View in Telegram" button, the owner has disabled the preview.&lt;/p&gt;

&lt;p&gt;The input accepts several formats, so use whichever you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;durov&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@durov&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://t.me/durov&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://t.me/s/durov&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 2: Run the scraper
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://apify.com/signalwharf/telegram-channel-scraper" rel="noopener noreferrer"&gt;Telegram Channel Scraper&lt;/a&gt; and click &lt;strong&gt;Try for free&lt;/strong&gt;. You need a free Apify account. No credit card is needed for the free plan.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;Channels&lt;/strong&gt;, add one or more channel names.&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Max posts per channel&lt;/strong&gt;. Start with 100. Telegram serves about 20 posts per page, so 100 posts is about five page loads.&lt;/li&gt;
&lt;li&gt;Optional: set &lt;strong&gt;Date from / Date to&lt;/strong&gt; for a time window, or add &lt;strong&gt;Keywords&lt;/strong&gt; to keep only matching posts.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Start&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's the input we used for a test run on Nov 11, 2026:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"channels"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"durov"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"telegram"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"maxPostsPerChannel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"includeMedia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That run returned &lt;strong&gt;200 posts from 2 of 2 channels&lt;/strong&gt; in about a minute.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Look at the output
&lt;/h2&gt;

&lt;p&gt;Each post is one dataset item. Here's a real item from that run, trimmed (media CDN URLs shortened):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"channel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"durov"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"channelTitle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Pavel Durov"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"channelSubscribers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"postId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;554&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"https://t.me/durov/554"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"datetime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"2026-11-10T12:59:06+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Any Telegram message can now become an app. ..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"views"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;993000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"isForwarded"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"isReply"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"links"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"https://t.me/BotNews/122"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"hashtags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"media"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"video"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"https://cdn4.telesco.pe/file/...mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;"thumbnailUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"https://cdn4.telesco.pe/file/..."&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"reactions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;"emoji"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"⭐"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;"isPaidStars"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4870&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes on the data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;views&lt;/code&gt; are abbreviated on Telegram's side ("993K" becomes &lt;code&gt;993000&lt;/code&gt;), so treat them as rounded. Very new posts keep gaining views.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;channelSubscribers&lt;/code&gt; is also Telegram's rounded public figure.&lt;/li&gt;
&lt;li&gt;Media URLs are &lt;strong&gt;temporary&lt;/strong&gt; Telegram CDN links. If you need the files, download them soon after the run.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 4: Export to CSV or Excel
&lt;/h2&gt;

&lt;p&gt;In the run's &lt;strong&gt;Output&lt;/strong&gt; tab, pick a view and click &lt;strong&gt;Export&lt;/strong&gt;. You can choose CSV, Excel (XLSX), JSON, XML or HTML. The views are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;View&lt;/th&gt;
&lt;th&gt;Columns&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Overview&lt;/td&gt;
&lt;td&gt;channel, postId, datetime, text, views, mediaTypes, isForwarded, url&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engagement&lt;/td&gt;
&lt;td&gt;channel, channelSubscribers, postId, datetime, views, reactions, url&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Links &amp;amp; media&lt;/td&gt;
&lt;td&gt;channel, postId, links, linkPreview, media, forwardedFrom, replyTo, url&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Overview is the best fit for a spreadsheet. Nested fields like &lt;code&gt;reactions&lt;/code&gt; and &lt;code&gt;media&lt;/code&gt; are flattened into columns in CSV. Use JSON if you want to keep them as lists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Automate it from Python or the command line
&lt;/h2&gt;

&lt;p&gt;To skip the browser, call the Actor through the Apify API. Get your API token from &lt;strong&gt;Apify Console → Settings → API &amp;amp; Integrations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;curl:&lt;/strong&gt; this endpoint runs the scraper and returns the results as CSV in one call (for runs up to 5 minutes):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl -X POST \
  &lt;span class="s2"&gt;"https://api.apify.com/v2/acts/signalwharf~telegram-channel-scraper/run-sync-get-dataset-items?format=csv&amp;amp;view=overview"&lt;/span&gt; \
  -H &lt;span class="s2"&gt;"Authorization: Bearer $APIFY_TOKEN"&lt;/span&gt; \
  -H &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; \
  -d &lt;span class="s1"&gt;'{"channels": ["durov"], "maxPostsPerChannel": 50}'&lt;/span&gt; \
  -o durov.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt; (&lt;code&gt;pip install apify-client&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"APIFY_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"signalwharf/telegram-channel-scraper"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"channels"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"durov"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"telegram"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"maxPostsPerChannel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Save as CSV&lt;/span&gt;
&lt;span class="n"&gt;csv_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"defaultDatasetId"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_items_as_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item_format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"csv"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"telegram_posts.csv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"wb"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csv_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Or work with the items directly&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"defaultDatasetId"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"datetime"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"views"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6: Monitor a channel for new posts
&lt;/h2&gt;

&lt;p&gt;If you want only the posts published since the last run, turn on &lt;strong&gt;Only new posts&lt;/strong&gt; (&lt;code&gt;onlyNewPosts: true&lt;/code&gt;). The scraper remembers the last post it saw for each channel. The state lives in a named key-value store in your own Apify account. Then create a &lt;strong&gt;Schedule&lt;/strong&gt; in Apify Console, for example every 15 minutes or every morning, and connect it to Google Sheets, Slack, a webhook, Make or Zapier using Apify's integrations.&lt;/p&gt;

&lt;p&gt;Example monitoring input with a keyword filter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"channels"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"durov"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"telegram"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"onlyNewPosts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"update"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"launch"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"maxPostsPerChannel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A monitoring run that finds nothing new charges nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  How much does it cost?
&lt;/h2&gt;

&lt;p&gt;The Actor uses pay-per-event pricing, with no monthly fee:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Channel with results (once per channel per run, only if it yields at least one post)&lt;/td&gt;
&lt;td&gt;$0.002&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Post saved&lt;/td&gt;
&lt;td&gt;$0.0015 ($1.50 per 1,000 posts)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Our 200-post test run cost 2 × $0.002 + 200 × $0.0015 = &lt;strong&gt;$0.304&lt;/strong&gt;. One channel with 100 posts costs about $0.15. Filtered-out posts, empty channels and failed channels aren't charged. You can set a &lt;strong&gt;maximum cost per run&lt;/strong&gt; in the run options, and the scraper stops cleanly when it reaches it. Apify's free plan includes a monthly platform credit, which covers small tests like these. Check Apify's pricing page for the current amount.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Channel returns &lt;code&gt;preview_disabled&lt;/code&gt;:&lt;/strong&gt; the owner turned off the web preview. There's no way around this without the official API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fewer posts than requested:&lt;/strong&gt; the channel has fewer posts, or your date or keyword filters removed them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;views&lt;/code&gt; is null:&lt;/strong&gt; some service messages have no view count.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Very deep history is slow:&lt;/strong&gt; each page is about 20 posts, fetched with a polite delay (1.5 s by default). Thousands of posts take a few minutes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Is it legal to scrape Telegram?
&lt;/h2&gt;

&lt;p&gt;This approach reads only what Telegram publishes openly at &lt;code&gt;t.me/s/&lt;/code&gt;. It doesn't log in, bypass access controls, or touch private messages or member lists. Posts can still contain personal data, so make sure your use complies with the GDPR and other privacy laws, Telegram's Terms of Service, and copyright. When in doubt, ask a lawyer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related tools
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://apify.com/signalwharf/substack-scraper" rel="noopener noreferrer"&gt;Substack Scraper&lt;/a&gt;: newsletter posts, comments and subscriber estimates.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://apify.com/signalwharf/ai-brand-visibility-tracker" rel="noopener noreferrer"&gt;AI Brand Visibility Tracker&lt;/a&gt;: see how ChatGPT, Claude and Gemini talk about your brand.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Built and maintained by Signal Wharf. Telegram is a trademark of its owner; this tool is not affiliated with or endorsed by Telegram.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>telegram</category>
      <category>scraping</category>
      <category>python</category>
      <category>data</category>
    </item>
  </channel>
</rss>
