YouTube Studio tells you about comments one at a time, and only on your own channel. A team that wants to see what viewers said yesterday, on its own videos or a competitor's, needs a daily list in the place it already reads. This post builds that list: a scheduled run that collects only new comments, a small state file so nothing is posted twice, and a Slack message with the comments worth reading first.
What a useful digest contains
- Only new comments. Yesterday's comments on recent videos, not every comment again.
- Grouped by video, with the video title, so people know what each comment refers to.
- Ranked by likes within each video. A liked comment is one other viewers agreed with.
- A link to each comment. Replying happens on YouTube, so one click should open the comment in place.
- No comments by the channel itself. The creator's own replies are not audience feedback.
Why "newest first" plus a date keeps it cheap
Best Damn YouTube Comments Scraper is an Apify Actor that reads comments through the official YouTube Data API v3. Given a channel URL, it reads the channel's most recent uploads. Two options do most of the work here:
-
sortBy: "time"asks YouTube for comments newest first. -
publishedAfter: "1 day"skips anything older, and with newest-first order the Actor stops paging as soon as it reaches an older comment.
So a video that got no comments yesterday costs one API page and nothing on your bill. In a test run on the five latest uploads of a large tech channel, two videos had nothing new and returned zero comments, and the whole run delivered 1,177 comments using 19 quota units.
The Actor ships with a saved task for exactly this input:
{
"startUrls": ["https://www.youtube.com/@YourChannel"],
"maxVideosPerChannel": 5,
"sortBy": "time",
"publishedAfter": "1 day",
"maxComments": 0
}
maxComments: 0 means no per-video cap, which is safe here because the date filter is the real limit. Add competitor channels to startUrls to watch them in the same run.
The gap to know about: only the newest maxVideosPerChannel uploads are read. A comment on a video from last year is not in the digest. Raise the number if your back catalogue still gets comments, or run a weekly job with a larger value and publishedAfter: "7 days".
Overlap the window, then de-duplicate
A schedule never fires at exactly the same second each day, and the API can show a comment a little late. A strict 24-hour window loses comments at the edges. Use publishedAfter: "2 days" and keep a file of comment IDs you have already posted. The overlap means about one extra day of comments per run, so on a busy channel it roughly doubles the comments you pay for; on a quiet one the difference is pennies.
The script
It runs the Actor, drops comments it has already reported, groups the rest by video and posts the top five per video to a Slack incoming webhook.
import json, os, urllib.request
from collections import defaultdict
from pathlib import Path
from apify_client import ApifyClient
STATE = Path("seen_comments.json")
TOP_PER_VIDEO = 5
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("josh99smith/youtube-comments-scraper").call(run_input={
"startUrls": ["https://www.youtube.com/@YourChannel"],
"maxVideosPerChannel": 5,
"sortBy": "time",
"publishedAfter": "2 days",
"maxComments": 0,
"authorData": "omit",
})
seen = set(json.loads(STATE.read_text())) if STATE.exists() else set()
by_video, failed = defaultdict(list), []
for c in client.dataset(run["defaultDatasetId"]).iterate_items():
if not c["success"]:
failed.append(f'{c["input"]}: {c["errorType"]}')
elif c["commentId"] not in seen and not c["isByVideoOwner"]:
by_video[(c["videoId"], c["videoTitle"])].append(c)
def clip(text, n=200):
text = " ".join(text.split())
text = text if len(text) <= n else text[: n - 1] + "…"
# Slack treats &, < and > as markup, and | ends the link text
return text.replace("&", "&").replace("<", "<").replace(">", ">").replace("|", "¦")
lines = []
for (video_id, title), comments in sorted(by_video.items(), key=lambda kv: -len(kv[1])):
lines.append(f"*<https://www.youtube.com/watch?v={video_id}|{clip(title, 100)}>*: {len(comments)} new")
for c in sorted(comments, key=lambda c: -c["likeCount"])[:TOP_PER_VIDEO]:
lines.append(f'• {c["likeCount"]} likes: <{c["commentUrl"]}|{clip(c["text"])}>')
if failed:
lines.append("Not read: " + ", ".join(failed))
if lines:
body = json.dumps({"text": "\n".join(lines)}).encode()
req = urllib.request.Request(os.environ["SLACK_WEBHOOK_URL"], body, {"Content-Type": "application/json"})
urllib.request.urlopen(req)
seen |= {c["commentId"] for cs in by_video.values() for c in cs}
STATE.write_text(json.dumps(sorted(seen)))
Notes on the choices:
-
authorData: "omit"leaves out commenter names and channels. A digest is about what was said, and the link opens the comment if someone needs to reply. Use"include"only if your team really needs names in Slack. - The state file keeps growing. Trim it to the last few thousand IDs, or store
publishedAtwith each ID and drop anything older than the window. - Slack truncates very long messages. On a busy channel, post one message per video, or lower
TOP_PER_VIDEOand link the run's dataset in Apify Console instead. - Failures are listed, not hidden. A video with comments turned off comes back as a free
comments-disabledrecord.
Scheduling it
Two options:
-
Run the script from cron or a CI scheduler (GitHub Actions
schedule:works) every morning. The state file needs to persist between runs, so commit it or keep it in a cache or bucket. -
No code: in Apify Console, open the Actor's "Monitor new comments on a YouTube channel daily" task, change the channel, and add a schedule (Schedules, then a cron such as
0 7 * * *). Add the Slack integration on the task to get a message when a run finishes, and open the dataset from there. You lose the de-duplication and ranking, but it takes two minutes.
Watching mentions on videos you do not own
For a brand, the interesting comments are often on other people's reviews. Put those video URLs in startUrls and set searchTerms to your brand name. That uses YouTube's own comment search, so you pay only for matches. It searches top-level comments only, and it is YouTube's search, not exact substring matching, so check a sample before you trust it. The product-feedback post in this series covers that filtering in more detail.
Cost and limits
- Price. $0.0004 per delivered comment or reply ($0.40 per 1,000). The test run above, 1,177 comments, would cost $0.47. A quiet channel with 50 new comments a day costs about $0.04 a day with the two-day window. Failures, videos with comments off and quota errors are free, and a run never goes past the maximum cost you set.
-
Use your own API key for a scheduled job. By default the Actor uses a built-in key whose 10,000-unit daily quota is shared by every user. One unit returns a page of up to 100 comments, so a digest like this uses little, but a schedule that runs every day is exactly the kind of job that should not depend on other people's usage. A free key from the Google Cloud Console, pasted into
apiKey, gives you your own 10,000 units. If a quota does run out, the run stops cleanly, the remaining videos come back as freerate-limitedrecords, and the quota resets at midnight Pacific Time. - Comments can appear late. YouTube holds some comments for review, and those only show up once approved. The two-day window catches most of them.
- Public data only. No login, so nothing members-only and nothing from YouTube Studio's held-for-review queue.
If this saves you time, a review on the Store page helps other people find it, and a star on GitHub helps too.
Disclosure: I built this Actor. Source: github.com/josh99smith/youtube-comments-scraper. YouTube is a trademark of Google; the Actor is not affiliated with Google.
Top comments (0)