Collect YouTube video comments and replies with ytscrape¶
Iterate every comment and reply on a YouTube video using ytscrape's lazy CommentThread, control sort order, and handle disabled comments.
YouTube comment sections can run into the thousands. yt.comments() returns a lazy CommentThread iterator that pages through all of them automatically — you never need to handle a continuation token yourself.
Basic iteration¶
Pass a video id or any YouTube URL. Use max_results to cap how many comments you consume:
from ytscrape import YouTube
with YouTube() as yt:
for comment in yt.comments("https://youtu.be/dQw4w9WgXcQ", max_results=50):
print(comment.author, "-", comment.text)
yt.comments() accepts the same video input formats as yt.video(): a bare 11-character id, a watch?v= URL, a youtu.be/ short link, a /shorts/ URL, or an /embed/ URL.
Collecting replies¶
By default only top-level comments are collected. Pass include_replies=True to also expand each thread's replies. Replies are yielded immediately after the comment they belong to, and they carry is_reply=True:
with YouTube() as yt:
for comment in yt.comments("https://youtu.be/dQw4w9WgXcQ", include_replies=True):
marker = " ↳" if comment.is_reply else "-"
print(f"{marker} {comment.author}: {comment.text}")
When include_replies=True, max_results counts replies alongside top-level comments.
Sort order¶
The sort order matters for completeness.
| Sort | Constant | What it returns |
|---|---|---|
| Top comments | CommentSort.TOP (default) |
Mirrors YouTube's ranked view — hides some comments flagged as low-relevance or potential spam |
| Newest first | CommentSort.NEWEST |
Reverse-chronological — returns every comment |
Use CommentSort.NEWEST whenever you need to collect all comments, not just the highlighted ones:
from ytscrape import YouTube, CommentSort
with YouTube() as yt:
for comment in yt.comments(
"https://www.youtube.com/watch?v=dQw4w9WgXcQ",
sort=CommentSort.NEWEST,
):
print(comment.author, "-", comment.text)
sort also accepts the string shorthand: sort="newest" and sort="top" are both valid.
Counting comments¶
yt.comments() is a lazy iterator, so the total is only known once every page has been consumed. Count as you go, or materialise all comments into a list first:
# Count while iterating
total = 0
for comment in yt.comments(video_url, sort="newest"):
total += 1
print(f"Collected {total} comments")
# Or materialise everything at once
comments = list(yt.comments(video_url, sort="newest"))
print(len(comments))
Full example¶
The following snippet is drawn from the bundled examples/07_video_comments.py:
from ytscrape import CommentSort, YouTube
def main() -> None:
with YouTube() as yt:
total = 0
for comment in yt.comments(
"https://www.youtube.com/watch?v=75IuMfHdTfc",
max_results=1000,
include_replies=True,
sort=CommentSort.NEWEST,
):
total += 1
prefix = " ↳ " if comment.is_reply else ""
# `like_count_text` keeps YouTube's raw count (e.g. "1.2K") even
# when the integer `like_count` is None because it was abbreviated.
count = comment.like_count_text
likes = f" ({count} likes)" if count else ""
heart = " ❤️" if comment.heart else ""
print(f"{prefix}{comment.author}{likes}{heart}: {comment.text}")
print(f"\nComments collected: {total}")
if __name__ == "__main__":
main()
Comment fields¶
Every comment is a frozen dataclass with the following fields:
| Field | Type | Description |
|---|---|---|
comment_id |
str |
Unique comment id. |
text |
str \| None |
The comment body. |
author |
str \| None |
Display name of the author. |
author_channel_id |
str \| None |
Channel id of the author (when available). |
author_thumbnail |
str \| None |
URL of the author's avatar image. |
published |
str \| None |
Human-readable published time (e.g. "2 days ago"). |
like_count |
int \| None |
Like count as an integer, or None when abbreviated. |
like_count_text |
str \| None |
Like count as YouTube renders it, preserving abbreviations (e.g. "1.2K", "894"). |
reply_count |
int \| None |
Number of replies (top-level comments only). |
reply_count_text |
str \| None |
Reply count as a raw display string. |
heart |
bool |
True if the video creator hearted the comment. |
is_reply |
bool |
True for replies, False for top-level comments. |
Note
like_count is None when YouTube returns an abbreviated string like "1.2K" instead of an exact integer. Use like_count_text whenever you need to display the count exactly as YouTube shows it.
Async comments¶
Install ytscrape[async], then use AsyncYouTube with await / async for. Arguments (max_results, include_replies, sort) are the same:
import asyncio
from ytscrape import AsyncYouTube, CommentSort
async def main() -> None:
async with AsyncYouTube(max_concurrency=8) as yt:
thread = await yt.comments(
"dQw4w9WgXcQ",
max_results=500,
include_replies=True,
sort=CommentSort.NEWEST,
)
async for comment in thread:
print(comment.author, comment.text)
asyncio.run(main())
To collect comments from many videos faster, gather independent tasks on one shared client (see Async API and examples/10_async_concurrency.py).
Disabled comments¶
If a video has comments turned off, yt.comments() raises ParseError as soon as the call is made — before you start iterating: