Every creator and every brand channel has a few videos that did far better than the rest. If you're studying a competitor, planning content, or just curious why one channel grows and another doesn't, the useful question isn't "which video has the most views". It is "which videos beat this channel's own normal, and what do they have in common?"
To answer that you need the whole upload list in a table. Scrolling the Videos tab and copying titles works for 30 videos, not for 600. The YouTube Data API can do it (playlistItems.list on the channel's uploads playlist, then videos.list for statistics), but you need a Google Cloud project, an API key and some code to page through it all before you start the actual analysis.
This tutorial shows a shorter route: an Apify Actor that exports a channel's uploads, newest first, as JSON, CSV or Excel. Then a few lines of pandas turn that list into an answer.
Disclosure: I built and maintain this Actor. It is a paid tool ($1.00 per 1,000 results). The limitations section matters here, especially the part about dates.
What you get per video
One row per upload with these main fields:
-
title,url,videoId -
viewCount(a number) andviewCountText(the original text, e.g. "1.6M views") -
durationSecondsanddurationText -
publishedTimeText("2 weeks ago") andpublishedAtEstimated(a date calculated from that text) -
isShort,isLive, and the channel'schannelName,channelHandleandchannelId
There are no likes, comments or exact publish dates. YouTube doesn't show those on the channel's video list. More on that below.
Step 1: Set up a run in Apify Console
Open YouTube Channel Videos Scraper and click Try for free. The form is short:
| Field | What it does |
|---|---|
Channels (channelUrls) |
Channel URLs (youtube.com/@handle, /channel/UC..., /c/name, /user/name), handles like @Fireship, or channel ids. |
Max results (maxItems) |
Upper limit for the whole run (default 100). You're only charged for saved results. |
Max videos per channel (maxItemsPerQuery) |
Stops one big channel from using up the run. Videos come newest first. |
Include Shorts (includeShorts) |
Off by default. Adds the channel's Shorts (title and view count only). |
Include live streams (includeLive) |
Off by default. Adds past and current live streams. |
No API key, login or proxy is needed. For a full history, set Max results higher than the channel's video count; the Actor stops when the channel runs out.
Step 2: Run it and look at the data
For this article I ran the newest 20 regular videos of Fireship:
{
"channelUrls": ["@Fireship"],
"maxItems": 20
}
It finished in about 4 seconds and saved 20 videos. Here are two, unchanged except for the removed thumbnailUrl. This is a public channel, so nothing was redacted:
[
{
"type": "video",
"videoId": "wajoP_5Cpw8",
"url": "https://www.youtube.com/watch?v=wajoP_5Cpw8",
"title": "Every app on Earth is now open source... whether it likes it or not",
"descriptionSnippet": null,
"viewCount": 6600000,
"viewCountText": "6.6M views",
"liveViewers": null,
"durationSeconds": 356,
"durationText": "5:56",
"publishedTimeText": "2 weeks ago",
"publishedAtEstimated": "2026-10-13T21:24:58Z",
"isLive": false,
"isShort": false,
"channelName": "Fireship",
"channelId": "UCsBjURrPoezykLs9EqgamOA",
"channelHandle": "@Fireship",
"channelUrl": "https://www.youtube.com/channel/UCsBjURrPoezykLs9EqgamOA",
"channelIsVerified": null,
"scrapedAt": "2026-10-27T21:24:58Z",
"channelTab": "videos"
},
{
"type": "video",
"videoId": "Y5rSSvXfL4g",
"url": "https://www.youtube.com/watch?v=Y5rSSvXfL4g",
"title": "5 open source tools that replaced my $320/mo AI stack...",
"descriptionSnippet": null,
"viewCount": 1200000,
"viewCountText": "1.2M views",
"liveViewers": null,
"durationSeconds": 326,
"durationText": "5:26",
"publishedTimeText": "1 month ago",
"publishedAtEstimated": "2026-09-27T21:24:58Z",
"isLive": false,
"isShort": false,
"channelName": "Fireship",
"channelId": "UCsBjURrPoezykLs9EqgamOA",
"channelHandle": "@Fireship",
"channelUrl": "https://www.youtube.com/channel/UCsBjURrPoezykLs9EqgamOA",
"channelIsVerified": null,
"scrapedAt": "2026-10-27T21:24:58Z",
"channelTab": "videos"
}
]
Even 20 rows say something. Every video was between 4:59 and 6:44 long. The median was about 1.75M views, and the first video above, at 6.6M, did almost four times that. Twelve of the 20 were uploaded in the last four weeks, so roughly three a week. Six titles mention OpenAI.
They also show the main catch. The other eight videos all say "1 month ago", so all eight got the same publishedAtEstimated, 2026-09-27. The estimate is only as precise as YouTube's text. That's why the analysis below uses upload order instead of dates.
Step 3: Analyse the full history in Python
Install the official client and pandas:
pip install apify-client pandas
Export the whole channel (here up to 2,000 videos):
from apify_client import ApifyClient
import pandas as pd
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("pipiagent/youtube-channel-videos-scraper").call(run_input={
"channelUrls": ["@Fireship"],
"maxItems": 2000,
})
items = list(client.dataset(run.default_dataset_id).iterate_items())
df = pd.DataFrame(items)
df.to_csv("fireship_uploads.csv", index=False)
Older videos have had more time to collect views, and a channel's audience grows or shrinks over the years, so raw views are a poor comparison. Instead, compare each video with the uploads around it. The data comes newest first, so reversing it gives upload order:
df = df[df["isLive"].ne(True)].iloc[::-1].reset_index(drop=True)
df["upload_no"] = df.index + 1
# Median views of the 21 uploads around each video (fewer at the edges)
df["neighbour_median"] = df["viewCount"].rolling(21, center=True, min_periods=5).median()
df["score"] = df["viewCount"] / df["neighbour_median"] # 2.0 = twice the channel's normal at that time
print(df.nlargest(15, "score")[["upload_no", "title", "viewCount", "score"]])
Now group by topic. These keywords fit this channel; write your own after reading the top 15:
topics = {
"OpenAI": r"openai|chatgpt",
"Google": r"google|gemini",
"open source": r"open[- ]source|open[- ]weight",
"drama": r"furious|quitting|off the rails|getting ugly",
}
title = df["title"].fillna("").str.lower()
rows = []
for name, pattern in topics.items():
mask = title.str.contains(pattern)
rows.append({"topic": name, "videos": int(mask.sum()), "median_score": df.loc[mask, "score"].median()})
print(pd.DataFrame(rows).sort_values("median_score", ascending=False))
# Does length matter?
bins = [0, 300, 600, 1200, 3600, float("inf")]
print(df.groupby(pd.cut(df["durationSeconds"], bins), observed=True)["score"].agg(["count", "median"]))
For upload cadence, count by YouTube's own age text instead of calendar dates. "2 years ago" means anywhere from two to three years:
print(df.iloc[::-1].groupby("publishedTimeText", sort=False).size().head(25))
What it costs
You pay per saved result: $1.00 per 1,000 videos, with no start fee and no monthly rental.
- The 20-video test above: $0.02
- A channel with 600 uploads: $0.60
- The latest 200 videos of 10 competitor channels (2,000 rows): $2.00
You can set a maximum charge per run in the run options, and the Actor stops when it reaches that amount.
Limitations
-
Dates are estimates.
publishedAtEstimatedis calculated from text like "1 month ago" or "3 years ago", so older videos can be off by months, or by up to a year for "N years ago". For exact dates, feed theurlvalues into the YouTube Scraper in video details mode. - No likes or comment counts. The channel's video list doesn't show them. The same YouTube Scraper can add them per video.
- View counts are rounded the way YouTube displays them ("1.6M" becomes 1,600,000). Fine for ratios, not for small differences.
- Shorts have only a title and view count when you turn on Include Shorts. They are left out by default, which is usually what you want for this kind of analysis because Shorts views aren't comparable with long-form views.
- Members-only and private videos are not listed. Neither are videos the channel deleted, so the history is what's public today.
-
channelIsVerifiedwas empty for every video in my test.
If you try this on a channel and something doesn't match what this article says, open an issue on the store page and I'll look into it.
Top comments (0)