The YouTube Data API lets you fetch trending videos from specific countries using the regionCode parameter. In this article, I'll show how to build a multi-region fetching system, handle API quotas intelligently, and normalize the results. This approach powers DailyWatch, which aggregates trending content from 8 countries.
Understanding the API
The key endpoint is videos.list with the chart=mostPopular parameter:
GET https://www.googleapis.com/youtube/v3/videos
?part=snippet,statistics,contentDetails
&chart=mostPopular
®ionCode=US
&maxResults=50
&key=YOUR_API_KEY
This costs 1 quota unit and returns up to 50 videos.
The Multi-Region Fetcher
class RegionalVideoFetcher {
private const API_BASE = 'https://www.googleapis.com/youtube/v3/videos';
private const PARTS = 'snippet,statistics,contentDetails';
private int $quotaUsed = 0;
public function __construct(
private readonly string $apiKey,
private readonly int $dailyQuotaLimit = 10000,
) {}
/**
* Fetch trending videos from multiple regions
* @param string[] $regions ISO 3166-1 alpha-2 country codes
* @return array<string, Video[]> Region => Videos mapping
*/
public function fetchMultiRegion(array $regions, int $maxPerRegion = 50): array {
$results = [];
foreach ($regions as $region) {
if (!$this->hasQuota(cost: 1)) {
echo "Quota limit approaching, stopping at region {$region}\n";
break;
}
try {
$results[$region] = $this->fetchRegion($region, $maxPerRegion);
echo "Fetched " . count($results[$region]) . " videos for {$region}\n";
} catch (\RuntimeException $e) {
echo "Error fetching {$region}: {$e->getMessage()}\n";
$results[$region] = [];
}
usleep(200000); // 200ms delay between regions
}
return $results;
}
private function fetchRegion(string $regionCode, int $maxResults): array {
$url = self::API_BASE . '?' . http_build_query([
'part' => self::PARTS,
'chart' => 'mostPopular',
'regionCode' => strtoupper($regionCode),
'maxResults' => min($maxResults, 50),
'key' => $this->apiKey,
]);
$response = file_get_contents($url);
$this->quotaUsed += 1;
if ($response === false) {
throw new \RuntimeException("API request failed for region {$regionCode}");
}
$data = json_decode($response, true);
if (isset($data['error'])) {
throw new \RuntimeException($data['error']['message'] ?? 'Unknown API error');
}
return array_map(
fn(array $item) => $this->normalizeVideo($item, $regionCode),
$data['items'] ?? []
);
}
private function normalizeVideo(array $item, string $region): array {
$snippet = $item['snippet'] ?? [];
$stats = $item['statistics'] ?? [];
$content = $item['contentDetails'] ?? [];
return [
'video_id' => $item['id'],
'title' => $snippet['title'] ?? '',
'description' => mb_substr($snippet['description'] ?? '', 0, 500),
'channel_title' => $snippet['channelTitle'] ?? '',
'channel_id' => $snippet['channelId'] ?? '',
'category_id' => (int)($snippet['categoryId'] ?? 0),
'thumbnail_url' => $snippet['thumbnails']['medium']['url']
?? $snippet['thumbnails']['default']['url'] ?? '',
'published_at' => $snippet['publishedAt'] ?? '',
'duration' => $content['duration'] ?? '',
'view_count' => (int)($stats['viewCount'] ?? 0),
'like_count' => (int)($stats['likeCount'] ?? 0),
'region' => $region,
];
}
public function hasQuota(int $cost = 1): bool {
return ($this->quotaUsed + $cost) <= $this->dailyQuotaLimit;
}
public function getQuotaUsed(): int {
return $this->quotaUsed;
}
}
Storing with Deduplication
Videos trending in multiple regions need deduplication:
function storeVideos(PDO $db, array $regionResults): int {
$stored = 0;
$insertVideo = $db->prepare("
INSERT OR IGNORE INTO videos
(video_id, title, description, channel_title, channel_id,
category_id, thumbnail_url, published_at, duration,
view_count, like_count)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$insertRegion = $db->prepare("
INSERT OR IGNORE INTO video_regions (video_id, region, fetched_at)
VALUES (?, ?, datetime('now'))
");
$updateViews = $db->prepare("
UPDATE videos SET view_count = MAX(view_count, ?)
WHERE video_id = ?
");
$db->beginTransaction();
foreach ($regionResults as $region => $videos) {
foreach ($videos as $v) {
$insertVideo->execute([
$v['video_id'], $v['title'], $v['description'],
$v['channel_title'], $v['channel_id'], $v['category_id'],
$v['thumbnail_url'], $v['published_at'], $v['duration'],
$v['view_count'], $v['like_count'],
]);
$insertRegion->execute([$v['video_id'], $region]);
$updateViews->execute([$v['view_count'], $v['video_id']]);
$stored++;
}
}
$db->commit();
return $stored;
}
Usage
$fetcher = new RegionalVideoFetcher(apiKey: $apiKey);
$regions = ['US', 'GB', 'DE', 'FR', 'IN', 'BR', 'AU', 'CA'];
$results = $fetcher->fetchMultiRegion($regions, maxPerRegion: 50);
$stored = storeVideos($db, $results);
echo "Stored {$stored} video records, quota used: {$fetcher->getQuotaUsed()}\n";
This system powers the content pipeline at dailywatch.video, fetching fresh trending data every 2 hours across 8 regions while staying well within API quota limits.
Top comments (0)