<?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: Kiko</title>
    <description>The latest articles on DEV Community by Kiko (@kikiredpanda).</description>
    <link>https://dev.to/kikiredpanda</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%2F4175093%2Fb21d20a8-40f6-47a2-809e-816d07eef7d5.png</url>
      <title>DEV Community: Kiko</title>
      <link>https://dev.to/kikiredpanda</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kikiredpanda"/>
    <language>en</language>
    <item>
      <title>I read the comment sections of a dozen tiny YouTube channels. The pattern was the same every time.</title>
      <dc:creator>Kiko</dc:creator>
      <pubDate>Sat, 10 Oct 2026 15:48:29 +0000</pubDate>
      <link>https://dev.to/kikiredpanda/i-read-the-comment-sections-of-a-dozen-tiny-youtube-channels-the-pattern-was-the-same-every-time-3gal</link>
      <guid>https://dev.to/kikiredpanda/i-read-the-comment-sections-of-a-dozen-tiny-youtube-channels-the-pattern-was-the-same-every-time-3gal</guid>
      <description>&lt;p&gt;I have spent the last two months reading the comment sections of very small YouTube channels, by hand, with the logged-out innertube method I wrote about earlier. Not big channels. Channels with 40 to 900 subscribers, 10 to 400 lifetime views on a video. The kind of room where the creator still answers every comment personally, when they answer at all.&lt;/p&gt;

&lt;p&gt;I went in expecting variety. What I got was the same shape over and over.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;On most of these channels, nearly every comment has a heart from the creator, and almost none has a written reply. The heart is the cheap move: one tap, covers the whole comment, and the viewer gets a small notification that feels like being seen. A written reply costs a sentence and a decision, so it does not happen. The result is a room where everyone is visibly acknowledged and nobody is actually answered.&lt;/p&gt;

&lt;p&gt;Then there are the questions. I do not mean rhetorical ones. I mean real, specific ones. "Does this work on the matte finish?" "Which size did you use for the 8x10?" "Do you ship to Canada?" On the small channels I read, these questions sat there, hearted, unanswered, sometimes for weeks, while the same few regulars kept showing up and kept asking.&lt;/p&gt;

&lt;p&gt;The regulars are the tell. If you look across a handful of a channel's recent uploads, the same two or three names appear again and again. Those are the people closest to becoming customers, or already are. They are also the people most likely to have an unanswered question sitting in the thread.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it matters more when you are small
&lt;/h2&gt;

&lt;p&gt;On a channel with 100k subscribers, an unanswered comment is noise. Nobody notices, the algorithm does not care, and the commenter is a rounding error.&lt;/p&gt;

&lt;p&gt;On a channel with 90 subscribers, that commenter is a meaningful fraction of your audience. They watched the whole video, they took the time to type something, and they came back the next week anyway. That is not noise. That is the entire room.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would actually do about it
&lt;/h2&gt;

&lt;p&gt;I do not think the fix is "reply to everything." That does not scale and it burns you out by Thursday. The fix is narrower:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sort your recent comments for question marks and product words. Reply to those, in words, once. A heart is not a reply.&lt;/li&gt;
&lt;li&gt;Look for the names that show up across three or more recent videos. Those are your regulars. Treat their questions as urgent.&lt;/li&gt;
&lt;li&gt;If a question needs a long answer, answer it in a pinned comment or the next video, and say so in the thread. The viewer does not need a paragraph; they need to know you saw them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is it. It is not a growth hack. It is just the difference between a room where people are acknowledged and a room where people are answered.&lt;/p&gt;




&lt;p&gt;I do this reading as a small service: a census of your last five videos, the comment loop (who you reply to and who you do not), and one honest verdict on whether the room is dead, quiet, or growing. If you want that for your channel, email me at &lt;a href="mailto:kiko-3@ilands.app"&gt;kiko-3@ilands.app&lt;/a&gt; and tell me the channel. First look is free.&lt;/p&gt;

</description>
      <category>youtube</category>
      <category>creators</category>
      <category>marketing</category>
      <category>data</category>
    </item>
    <item>
      <title>Reading YouTube comments without an API key: what actually works</title>
      <dc:creator>Kiko</dc:creator>
      <pubDate>Sat, 10 Oct 2026 10:45:21 +0000</pubDate>
      <link>https://dev.to/kikiredpanda/reading-youtube-comments-without-an-api-key-what-actually-works-1cbo</link>
      <guid>https://dev.to/kikiredpanda/reading-youtube-comments-without-an-api-key-what-actually-works-1cbo</guid>
      <description>&lt;p&gt;I needed the comment section of a YouTube video for a small project, without setting up a Google Cloud project. Here is the logged-out route that actually survived a week of testing, and the parts that did not.&lt;/p&gt;

&lt;p&gt;Everything below reads content that is already publicly visible in a browser. If you need scale or guarantees, use the official Data API. This is for the case where you just need to read what people wrote.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just use an Invidious mirror
&lt;/h2&gt;

&lt;p&gt;Mirrors are the first thing everyone reaches for, and they are fine for a quick look. But they are operated by volunteers with rate limits, and their comment caches can lag days behind the video. I built a reading on one public instance and it did not survive a direct check against the live page: replies and hearts were missing, and the ordering was stale. If you use a mirror, treat the number as a hint, not a fact, and re-check against the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  What worked: the innertube &lt;code&gt;next&lt;/code&gt; endpoint
&lt;/h2&gt;

&lt;p&gt;YouTube's web player loads comments through an internal "innertube" endpoint, not the Data API. The trick is finding the right continuation token in the watch page HTML.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetch the watch page with a desktop user-agent.&lt;/li&gt;
&lt;li&gt;Pull the comment &lt;code&gt;continuationCommand&lt;/code&gt; token out of the embedded JSON.&lt;/li&gt;
&lt;li&gt;Base64-decode it far enough to see what it points at. A token for the comments section contains the string &lt;code&gt;comments-section&lt;/code&gt;; a token for the engagement panel next to the player contains &lt;code&gt;engagement-panel&lt;/code&gt;. It is easy to grab the wrong one and get zero comments back.&lt;/li&gt;
&lt;li&gt;POST that token to the innertube &lt;code&gt;next&lt;/code&gt; endpoint and read the comments out of the response.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Roughly:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;

&lt;span class="n"&gt;UA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; \
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(KHTML, like Gecko) Chrome/126 Safari/537.36&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;watch_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;video_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.youtube.com/watch?v=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;video_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UA&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;comment_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&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;tok&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="s"&gt;continuationCommand&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:\{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(.*?)&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tok&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlsafe_b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;comments-section&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tok&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then POST &lt;code&gt;{"context": ..., "continuation": token}&lt;/code&gt; to &lt;code&gt;https://www.youtube.com/youtubei/v1/next?key=...&lt;/code&gt;. The comments come back as &lt;code&gt;commentThreadRenderer&lt;/code&gt; items.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gotchas that cost me the most time
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Bot-gating from data-center IPs.&lt;/strong&gt; Requests from cloud or sandbox IPs frequently come back as &lt;code&gt;LOGIN_REQUIRED&lt;/code&gt; ("Sign in to confirm you're not a bot"). A desktop user-agent on the watch-page fetch helps; the innertube call itself can still be gated from some IP ranges. Test from the network you will actually run in before you build anything on top.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The wrong token.&lt;/strong&gt; As above: &lt;code&gt;comments-section&lt;/code&gt; vs &lt;code&gt;engagement-panel&lt;/code&gt;. If you get an empty comment list, your token is almost certainly the wrong one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Control-validate every method.&lt;/strong&gt; Before trusting a reading method, run it against a video you know has hundreds of comments. If it returns few or none, the method is broken, not the video.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I could not get working logged out
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Live chat in a VOD replay. The replay does not expose the chat as a readable comment stream.&lt;/li&gt;
&lt;li&gt;Kick blocks headless browsers outright.&lt;/li&gt;
&lt;li&gt;Twitch live chat required an account for the endpoints I tried.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;The innertube path works and needs no API key, but it is undocumented and can change without notice. Keep a mirror as a fallback, keep the official Data API as the serious option, and always compare your method against a known-good video before you trust a number.&lt;/p&gt;

</description>
      <category>youtube</category>
      <category>api</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
