<?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: mitonton310</title>
    <description>The latest articles on DEV Community by mitonton310 (@sk_mt_6d73aa71f2d46d8539c).</description>
    <link>https://dev.to/sk_mt_6d73aa71f2d46d8539c</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3985416%2Fc2959fde-55c4-446f-888f-4e3d3d4bacfb.jpg</url>
      <title>DEV Community: mitonton310</title>
      <link>https://dev.to/sk_mt_6d73aa71f2d46d8539c</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sk_mt_6d73aa71f2d46d8539c"/>
    <language>en</language>
    <item>
      <title>I built an anime ranking site that measures real buzz, not marketing hype</title>
      <dc:creator>mitonton310</dc:creator>
      <pubDate>Mon, 15 Jun 2026 11:32:25 +0000</pubDate>
      <link>https://dev.to/sk_mt_6d73aa71f2d46d8539c/i-built-an-anime-ranking-site-that-measures-real-buzz-not-marketing-hype-2ji6</link>
      <guid>https://dev.to/sk_mt_6d73aa71f2d46d8539c/i-built-an-anime-ranking-site-that-measures-real-buzz-not-marketing-hype-2ji6</guid>
      <description>&lt;p&gt;Most anime ranking sites show you what's being &lt;em&gt;promoted&lt;/em&gt;. The big seasonal titles, the ones with the biggest ad budgets, the ones every site agrees on. I wanted to see something different: what are fans &lt;strong&gt;actually&lt;/strong&gt; watching and talking about right now?&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://anime.douga-summary.jp" rel="noopener noreferrer"&gt;AnimeBuzz&lt;/a&gt; — an anime discovery site that ranks shows by a single number I call the &lt;strong&gt;Buzz Score&lt;/strong&gt;, computed from real public engagement data instead of marketing.&lt;/p&gt;

&lt;p&gt;Here's how it works under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea: a "Buzz Score" from real signals
&lt;/h2&gt;

&lt;p&gt;The core problem is turning messy, multi-source engagement data into one comparable 0–100 number. I blend four public signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;YouTube view counts&lt;/strong&gt; (60%) — trailers, clips, reaction videos. The strongest "people are actually watching this" signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;YouTube likes&lt;/strong&gt; (15%) — positive reactions, not just views.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AniList popularity&lt;/strong&gt; (20%) — how many community members track the title.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AniList favourites&lt;/strong&gt; (5%) — the most dedicated fans.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;YouTube buzz (75%) blended with AniList fan support (25%).&lt;/p&gt;

&lt;h2&gt;
  
  
  The scoring math (and why naive normalization fails)
&lt;/h2&gt;

&lt;p&gt;You can't just add raw numbers — millions of views versus a few thousand favourites would make views completely dominate. Each metric has to be normalized to 0–1 first.&lt;/p&gt;

&lt;p&gt;The naive approach is to divide by the maximum value. But that breaks the moment one mega-hit (say, a &lt;em&gt;Demon Slayer&lt;/em&gt;) shows up: it becomes the 1.0 reference and flattens everything else to near-zero.&lt;/p&gt;

&lt;p&gt;The fix: normalize against the &lt;strong&gt;95th percentile&lt;/strong&gt; instead of the max.&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;def&lt;/span&gt; &lt;span class="nf"&gt;percentile_95&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Use the 95th-percentile value as the &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;full marks&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; line,
    so a single viral outlier can&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t flatten the whole distribution.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&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;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&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;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# avoid div-by-zero
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the score is just a weighted sum of the normalized metrics:&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="n"&gt;score&lt;/span&gt; &lt;span class="o"&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;views&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;max_views&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;likes&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;max_likes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;   &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;max_popular&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;favs&lt;/span&gt;  &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;max_favs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="o"&gt;*&lt;/span&gt;  &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One more detail: the normalization basis is calculated on the &lt;strong&gt;current season only&lt;/strong&gt;, so the scale reflects "what's hot right now" and doesn't get dragged around by all-time classics in the back catalog.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data pipeline
&lt;/h2&gt;

&lt;p&gt;No database — everything is plain JSON files in git. A set of Python scripts run on a schedule:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fetch_anilist.py&lt;/code&gt; — seasonal anime, studios, genres, streaming links (AniList GraphQL API, free)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fetch_youtube.py&lt;/code&gt; — view/like counts (YouTube Data API, quota-limited so it fills in a batch per day)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fetch_themes.py&lt;/code&gt; — opening/ending songs from the wonderful &lt;a href="https://animethemes.moe" rel="noopener noreferrer"&gt;AnimeThemes.moe&lt;/a&gt; API, keyed by AniList ID&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;calc_buzz_score.py&lt;/code&gt; — computes scores and emits all the ranking JSON files&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;generate_sitemap.py&lt;/code&gt; — rebuilds the sitemap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A gotcha worth sharing: when re-fetching from AniList, I have to &lt;strong&gt;preserve&lt;/strong&gt; keys that other scripts wrote (YouTube data, themes, etc.), or a daily refresh would wipe them:&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="n"&gt;PRESERVE_KEYS&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;youtube&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;buzz_score&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;voice_actors&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;streaming&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;themes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# ← learned this one the hard way
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Frontend: static all the way
&lt;/h2&gt;

&lt;p&gt;The site is &lt;a href="https://vike.dev" rel="noopener noreferrer"&gt;Vike&lt;/a&gt; (vike-react) + React + TypeScript, fully prerendered to static HTML at build time, then served from &lt;strong&gt;Cloudflare Workers&lt;/strong&gt; as static assets. No server, no runtime — every page is a file.&lt;/p&gt;

&lt;p&gt;For ~3,000 pages this is fantastic: instant loads, trivial scaling, basically free hosting. The trade-off is build time and the discipline of treating data as a build input.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automation
&lt;/h2&gt;

&lt;p&gt;A GitHub Actions cron runs daily: fetch → score → regenerate → commit → build → deploy. The site refreshes itself every day with zero manual work, including recording each title's score into a history file so I can chart how buzz rises and falls over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What got built along the way
&lt;/h2&gt;

&lt;p&gt;Once the core ranking worked, the dataset opened up a bunch of features almost for free:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://anime.douga-summary.jp/music/openings" rel="noopener noreferrer"&gt;Best anime openings &amp;amp; endings&lt;/a&gt; — ranked, and you can watch the actual OP/ED&lt;/li&gt;
&lt;li&gt;Voice actor (seiyuu) filmographies, aggregated from the per-anime cast data&lt;/li&gt;
&lt;li&gt;Hidden gems (high community score, low buzz), all-time rankings, airing calendar&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lessons learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Percentile normalization &amp;gt;&amp;gt;&amp;gt; max normalization&lt;/strong&gt; for any "score from mixed signals" problem. Outliers are the enemy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON-in-git is underrated&lt;/strong&gt; for read-heavy sites. No DB to run, full version history, trivial to deploy statically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A brand-new domain is slow to get indexed&lt;/strong&gt; — no amount of clever engineering substitutes for time and real links. (Still very much in that phase!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're into anime, take a look and tell me what your Buzz Score gut-check gets wrong: &lt;strong&gt;&lt;a href="https://anime.douga-summary.jp" rel="noopener noreferrer"&gt;anime.douga-summary.jp&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer any questions about the stack in the comments 👇&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>python</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
