Disclosure: I'm the maker of the YouTube Comments Scraper on the Apify Store and the ScrapeMole x402 API used below. Other comment tools exist; this guide shows how to use ours, with real output.
You want to know how viewers feel about a video: a launch, a product review, a competitor's channel. The YouTube Data API needs a Google Cloud project, quota and an API key, and scraping the page yourself means dealing with continuation tokens and blocks. This guide does it in three small steps in Python:
- fetch the top comments of a video in one HTTP call,
- score each comment with VADER (a small rule-based sentiment model, one
pip install, no API key, no GPU), - weight by like count, because a comment with 9,600 likes speaks for more viewers than one with 2.
Everything below was run on 27 October 2026 against 3Blue1Brown's "But what is a neural network?" and the numbers are the real output.
1. Fetch comments (one HTTP call)
The Actor's run-sync-get-dataset-items endpoint runs it and returns the rows in the same response. Keep maxComments small while you test:
# pip install requests vaderSentiment
import os, json, requests
ACTOR = "scrapemole~youtube-comments-scraper"
URL = f"https://api.apify.com/v2/acts/{ACTOR}/run-sync-get-dataset-items"
def fetch_comments(video, max_comments=50, sort_by="top"):
r = requests.post(
URL,
headers={"Authorization": f"Bearer {os.environ['APIFY_TOKEN']}"},
json={"videos": [video], "maxComments": max_comments, "sortBy": sort_by},
timeout=300,
)
r.raise_for_status()
return r.json() # one row per comment
comments = fetch_comments("https://www.youtube.com/watch?v=aircAruvnKk")
json.dump(comments, open("comments.json", "w"), indent=1)
print(len(comments), "rows")
It printed 50 rows after about 15 seconds. One real row, trimmed:
{
"status": "ok",
"type": "comment",
"videoId": "aircAruvnKk",
"videoTitle": "But what is a neural network? | Deep learning chapter 1",
"videoChannelName": "3Blue1Brown",
"totalCommentsOnVideo": 9441,
"position": 1,
"commentUrl": "https://www.youtube.com/watch?v=aircAruvnKk&lc=Ugwjrf9gsokjLeQ7JW14AaABAg",
"text": "In class : printf(\"Hello world\");\nThe exam :",
"publishedAtApprox": "2020-10-26",
"likeCount": 3500,
"replyCount": 50,
"isHeartedByCreator": false,
"isPinned": false
}
Rows carry no commenter names or channel IDs, which keeps the dataset simple to store. Useful options: sortBy: "newest" for recent reactions (good for tracking sentiment after a launch), includeReplies: true to add replies as extra rows (parentCommentId set), and several URLs in videos to compare videos in one run. Videos with comments turned off come back with a non-ok status and aren't charged, so filter on it.
2. Score each comment with VADER
VADER was built for short social-media text: it handles caps, "!!!", emoji and slang, and runs in microseconds per comment. Its compound score goes from -1 (very negative) to +1 (very positive); the usual thresholds are ±0.05.
import json
from collections import Counter
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
comments = [c for c in json.load(open("comments.json")) if c["status"] == "ok"]
sia = SentimentIntensityAnalyzer()
def label(compound):
# VADER's standard thresholds
if compound >= 0.05:
return "positive"
if compound <= -0.05:
return "negative"
return "neutral"
for c in comments:
c["compound"] = sia.polarity_scores(c["text"])["compound"]
c["sentiment"] = label(c["compound"])
3. Aggregate by like count
A plain count treats every comment as one vote. Weighting by likeCount tells you what the audience agreed with. Show both:
# 1) plain count: every comment is one vote
counts = Counter(c["sentiment"] for c in comments)
# 2) like-weighted: a comment with 3,500 likes speaks for more viewers
likes = Counter()
for c in comments:
likes[c["sentiment"]] += c["likeCount"] + 1 # +1 so 0-like comments still count
total_c, total_l = sum(counts.values()), sum(likes.values())
print(f'{comments[0]["videoTitle"]} ({comments[0]["totalCommentsOnVideo"]:,} comments on video, {total_c} analysed)')
print(f'{"":10}{"comments":>10}{"share":>8}{"likes":>10}{"share":>8}')
for s in ("positive", "neutral", "negative"):
print(f'{s:10}{counts[s]:>10}{counts[s]/total_c:>8.0%}{likes[s]:>10,}{likes[s]/total_l:>8.0%}')
mean = sum(c["compound"] for c in comments) / total_c
wmean = sum(c["compound"] * (c["likeCount"] + 1) for c in comments) / total_l
print(f"mean compound {mean:+.3f}, like-weighted {wmean:+.3f}")
print("\nMost-liked negative comments (check these by hand):")
for c in sorted((c for c in comments if c["sentiment"] == "negative"), key=lambda c: -c["likeCount"])[:3]:
print(f' {c["likeCount"]:>6,} likes {c["compound"]:+.2f} {c["text"][:70]!r}')
Real output for the 50 top comments:
But what is a neural network? | Deep learning chapter 1 (9,441 comments on video, 50 analysed)
comments share likes share
positive 29 58% 58,820 62%
neutral 15 30% 17,425 18%
negative 6 12% 18,925 20%
mean compound +0.289, like-weighted +0.282
Most-liked negative comments (check these by hand):
9,600 likes -0.65 '📂Documents\n └📁Videos\n └📁 3Blue1Brown\n └📁 Bad Videos\n '
6,500 likes -0.44 'Quote: “Any fool can make something complicated. It takes a genius to '
1,800 likes -0.57 'Brain: "I\'m a stupid human beeing.."\nNeuron: "Hold my 0 to 1!"'
4. Read the negatives before you trust the number
That last block is the most useful part of the script. The top "negative" comment is a folder tree ending in 📁 Bad Videos └⚠️ This folder is empty: a joke that means this channel has no bad videos. The second is the "Any fool can make something complicated. It takes a genius to make it simple" quote, also praise. VADER sees "bad", "fool" and "stupid" and scores them negative.
Those two comments hold 16,100 of the 18,925 "negative" likes. Read as praise, the like-weighted negative share drops from 20% to about 3%. YouTube comment sections are full of jokes and sarcasm, and the most-liked comments are often the jokes, so:
- Always print the most-liked comments per label and read them. It takes a minute and catches the errors that move the weighted number most.
- Re-check only the high-impact ones with an LLM. Sending 10 to 20 top-liked comments to any chat model with a prompt like "Label each YouTube comment as positive, negative or neutral toward the video. Jokes that praise the video are positive. Reply as JSON: [{\"i\": 1, \"label\": \"...\"}]" costs a fraction of a cent and fixes most sarcasm. Keep VADER for the long tail, where one wrong label barely moves the result.
- Report both numbers. "58% of comments are positive, 62% of likes" is honest; a single score hides how much a few viral comments drive it.
5. Scale it up
- More comments: raise
maxComments(top comments are YouTube's own ranking, so the first 100 to 500 cover most of the likes on a typical video). - Track a launch over time: run with
sortBy: "newest"every day and append to a CSV; the daily like-weighted score is your trend line. On Apify you can schedule the Actor and read the dataset instead. - Compare videos or channels: pass several URLs in
videosand group byvideoId. To get a channel's video list first, use the YouTube Channel Scraper. - Other languages: VADER is English-only. For other languages, use a multilingual model (for example a Hugging Face sentiment pipeline) or the LLM prompt above for everything.
Alternative for AI agents: pay per call with x402 (no account)
If an AI agent does the fetching, there's also a pay-per-call HTTP API with no signup and no API key: up to 100 top or newest comments per call for $0.002 in USDC on Base or Solana via x402, charged only on a 200 response:
curl -i "https://scrapemole-api-mainnet.scrapemole.workers.dev/v1/youtube/comments?video=aircAruvnKk&sort=top&limit=100"
# -> HTTP 402 with the payment requirements; an x402 client pays and retries automatically
The paid response is one JSON object with title, totalComments and a comments list whose items have text, likeCount, replyCount, isPinned and isByVideoCreator. Map it to the rows above and steps 2 to 4 run unchanged:
d = paid_response_json
comments = [
{**c, "status": "ok", "videoTitle": d["title"], "totalCommentsOnVideo": d["totalComments"]}
for c in d["comments"]
]
Agents that speak MCP can use the remote MCP server at https://scrapemole-api-mainnet.scrapemole.workers.dev/mcp (Streamable HTTP); its youtube_comments tool returns the same data and is paid through the x402 MCP transport. Docs: openapi.json, llms.txt and the tool page.
What it costs
On Apify you pay per comment delivered: $0.001 on the free plan, down to $0.0005 on higher plans, so the 50 comments here are 5 cents or less, and 1,000 comments are $0.50 to $1. Videos with comments off, private or deleted videos aren't charged. Through x402 it's a flat $0.002 per call of up to 100 comments. VADER is free and runs locally.
Related
- YouTube Comments Scraper on Apify
- YouTube transcripts for RAG with timestamp citations, to analyse what the video says next to what viewers say
- YouTube Scraper, for likes, views and comment counts across many videos
- All our Actors: apify.com/scrapemole
Questions or a video that doesn't work? Open an issue on the Actor page or email us and I'll look at it.