Introduction
The YouTube Data API v3 provides everything you need to track how videos perform over time. In this tutorial, I'll show how to collect and store virality metrics — the approach used at ViralVidVault to monitor trending videos across 7 European regions.
Setting Up the API Client
First, get an API key from the Google Cloud Console. Then create a lightweight client:
<?php
class YouTubeClient
{
private const BASE_URL = 'https://www.googleapis.com/youtube/v3';
public function __construct(
private readonly string $apiKey,
) {}
public function getVideoStats(array $videoIds): array
{
$ids = implode(',', array_slice($videoIds, 0, 50)); // API max 50
$url = self::BASE_URL . '/videos?' . http_build_query([
'part' => 'statistics,snippet,contentDetails',
'id' => $ids,
'key' => $this->apiKey,
]);
$response = file_get_contents($url);
if ($response === false) {
throw new \RuntimeException('YouTube API request failed');
}
return json_decode($response, true)['items'] ?? [];
}
public function getTrending(string $regionCode = 'US', int $maxResults = 25): array
{
$url = self::BASE_URL . '/videos?' . http_build_query([
'part' => 'snippet,statistics,contentDetails',
'chart' => 'mostPopular',
'regionCode' => $regionCode,
'maxResults' => min($maxResults, 50),
'key' => $this->apiKey,
]);
$response = file_get_contents($url);
return json_decode($response, true)['items'] ?? [];
}
}
Storing Metrics Over Time
To detect virality, you need to compare metrics at different points in time. SQLite works great for this:
<?php
class MetricsStore
{
private \PDO $db;
public function __construct(string $dbPath)
{
$this->db = new \PDO("sqlite:{$dbPath}");
$this->db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$this->db->exec('PRAGMA journal_mode=WAL');
$this->migrate();
}
private function migrate(): void
{
$this->db->exec('
CREATE TABLE IF NOT EXISTS video_metrics (
id INTEGER PRIMARY KEY AUTOINCREMENT,
video_id TEXT NOT NULL,
region TEXT NOT NULL DEFAULT "US",
views INTEGER NOT NULL,
likes INTEGER NOT NULL DEFAULT 0,
comments INTEGER NOT NULL DEFAULT 0,
checked_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_metrics_video_region
ON video_metrics(video_id, region, checked_at);
');
}
public function record(string $videoId, string $region, array $stats): void
{
$stmt = $this->db->prepare('
INSERT INTO video_metrics (video_id, region, views, likes, comments)
VALUES (:vid, :region, :views, :likes, :comments)
');
$stmt->execute([
':vid' => $videoId,
':region' => $region,
':views' => (int)($stats['viewCount'] ?? 0),
':likes' => (int)($stats['likeCount'] ?? 0),
':comments' => (int)($stats['commentCount'] ?? 0),
]);
}
public function getVelocity(string $videoId, string $region, int $windowSeconds = 3600): ?float
{
$stmt = $this->db->prepare('
SELECT views, checked_at FROM video_metrics
WHERE video_id = :vid AND region = :region
ORDER BY checked_at DESC LIMIT 2
');
$stmt->execute([':vid' => $videoId, ':region' => $region]);
$rows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
if (count($rows) < 2) return null;
$viewDelta = $rows[0]['views'] - $rows[1]['views'];
$timeDelta = strtotime($rows[0]['checked_at']) - strtotime($rows[1]['checked_at']);
if ($timeDelta <= 0) return null;
return ($viewDelta / $timeDelta) * 3600; // views per hour
}
}
Fetching Multi-Region Trending Data
At ViralVidVault, we fetch trending videos from multiple regions in each cron run:
<?php
$regions = ['US', 'GB', 'PL', 'NL', 'SE', 'NO', 'AT'];
$client = new YouTubeClient($apiKey);
$store = new MetricsStore(__DIR__ . '/data/metrics.db');
foreach ($regions as $region) {
$trending = $client->getTrending($region, 25);
foreach ($trending as $video) {
$store->record(
videoId: $video['id'],
region: $region,
stats: $video['statistics']
);
$velocity = $store->getVelocity($video['id'], $region);
if ($velocity !== null && $velocity > 5000) {
echo "[{$region}] FAST MOVER: {$video['snippet']['title']} ({$velocity} views/hr)\n";
}
}
// Respect rate limits
usleep(200000); // 200ms between regions
}
API Quota Management
YouTube API v3 gives you 10,000 quota units per day. A videos.list call costs 1 unit per request (not per video). Fetching 50 videos in one call is much cheaper than 50 individual calls.
// Efficient: 1 quota unit for 50 videos
$stats = $client->getVideoStats($fiftyVideoIds);
// Wasteful: 50 quota units
foreach ($fiftyVideoIds as $id) {
$stats = $client->getVideoStats([$id]); // Don't do this
}
With 7 regions and 25 videos per region, each cron run uses roughly 14 quota units (7 trending calls + 7 batch stat calls). Running every 7 hours gives us about 3 runs per day, totaling ~42 units. Plenty of headroom.
Key Takeaways
- Batch your API calls (up to 50 video IDs per request)
- Store metrics over time to calculate velocity
- Use SQLite WAL mode for safe concurrent reads during cron
- Normalize velocity per region for fair comparison
- Rate limit your requests to stay within quota
This metrics tracking system is what powers the trending feeds at viralvidvault.com. The combination of multi-region data collection and velocity calculation lets us surface genuinely viral European content.
Part of the "Building ViralVidVault" series. Visit ViralVidVault to see these techniques in action.
Top comments (0)