2026, Jan 05 09:02

Как получить весь список видео канала YouTube через плейлист загрузок

Надежный способ получить все видео канала в YouTube Data API: найдите плейлист загрузок в channels?part=contentDetails и обходите его playlistItems.list.

Когда вам нужен надежный список видео с канала YouTube, вызов метода search.list с channelId и увеличенным maxResults кажется прямолинейным решением. На практике он может вернуть лишь несколько элементов, «скакать» от запроса к запросу и вовсе не давать токена пагинации. Если цель — получить все загрузки или хотя бы последние 30–40, есть более стабильный и в квотах дешевый путь.

Постановка проблемы

Базовый подход — использовать эндпоинт поиска YouTube Data API с параметрами вроде API key, channelId, part=id и maxResults=30. Тем не менее ответ нередко содержит всего пять элементов, количество плавает между повторными запросами, а токен пагинации отсутствует.

import requests

def demo_channel_search(api_key, channel_id):
    url = (
        f"https://www.googleapis.com/youtube/v3/search?"
        f"part=id&channelId={channel_id}&maxResults=30&key={api_key}"
    )
    print(f"Calling search endpoint: {url}")
    resp = requests.get(url)
    data = resp.json()
    items = data.get("items", [])
    print(f"Items returned: {len(items)}")
    print(f"nextPageToken: {data.get('nextPageToken')}")

Что происходит на самом деле

Эндпоинт search не предназначен для перечисления всей истории загрузок канала. В этом сценарии он может возвращать меньше элементов, чем запрошено, и вести себя непоследовательно. У каждого канала YouTube есть отдельный плейлист загрузок, в котором собраны все опубликованные на канале видео. Обращение к этому плейлисту — каноничный способ получить полный список роликов и пройти по нему постранично. Плюс очевидная выгода по квоте: playlistItems.list стоит 1 единицу квоты за вызов, тогда как search.list — 100 единиц за вызов.

Решение: запрос плейлиста загрузок

Решение состоит из двух шагов. Сначала запросите contentDetails канала, чтобы получить идентификатор плейлиста загрузок. Затем итерируйте этот плейлист через playlistItems.list, следуя по nextPageToken до конца.

import requests

def resolve_uploads_feed_id(chan_id, key_token):
    endpoint = (
        f"https://www.googleapis.com/youtube/v3/channels?"
        f"part=contentDetails&id={chan_id}&key={key_token}"
    )
    print(f"Requesting uploads playlist via: {endpoint}")

    try:
        resp = requests.get(endpoint)
        payload = resp.json()

        if 'error' in payload:
            print(f"Error: {payload['error']['message']}")
            return None

        if 'items' in payload and len(payload['items']) > 0:
            upl_id = payload['items'][0]['contentDetails']['relatedPlaylists']['uploads']
            print(f"Uploads playlist resolved: {upl_id}")
            return upl_id
        else:
            print(f"No channel found for: {chan_id}")
            return None

    except Exception as exc:
        print(f"Unexpected failure: {exc}")
        return None


def collect_videos_from_uploads(list_id, key_token):
    if not list_id:
        print("Uploads playlist ID is missing")
        return []

    collected = []
    token = None

    print(f"Fetching videos from uploads playlist: {list_id}")

    while True:
        base = (
            f"https://www.googleapis.com/youtube/v3/playlistItems?"
            f"part=snippet&maxResults=50&playlistId={list_id}&key={key_token}"
        )
        url = f"{base}&pageToken={token}" if token else base
        print(f"Issuing playlistItems request: {url}")

        try:
            resp = requests.get(url)
            data = resp.json()

            if 'error' in data:
                print(f"Error: {data['error']['message']}")
                break

            items = data.get('items', [])
            print(f"Page size: {len(items)}")

            for it in items:
                entry = {
                    'video_id': it['snippet']['resourceId']['videoId'],
                    'title': it['snippet']['title'],
                    'published_at': it['snippet']['publishedAt'],
                }
                collected.append(entry)

            token = data.get('nextPageToken')
            if not token:
                print("Reached end of playlist")
                break
            else:
                print(f"Advancing with page token: {token}")

        except Exception as exc:
            print(f"Unexpected failure: {exc}")
            break

    return collected


if __name__ == "__main__":
    API_TOKEN = "###"
    CHANNEL_ID = "UCupvZG-5ko_eiXAupbDfxWw"

    uploads_id = resolve_uploads_feed_id(CHANNEL_ID, API_TOKEN)
    print(f"Uploads playlist ID: {uploads_id}")

    videos = collect_videos_from_uploads(uploads_id, API_TOKEN)
    print(f"Total videos fetched: {len(videos)}")

    for idx, v in enumerate(videos[:5]):
        print(f"Video {idx+1}:")
        print(f"  ID: {v['video_id']}")
        print(f"  Title: {v['title']}")
        print(f"  Published: {v['published_at']}")

Почему это важно

Выбор плейлиста загрузок устраняет непредсказуемость search при перечислении видео канала и дает стабильную пагинацию через nextPageToken. К тому же это дешевле: playlistItems.list тратит 1 единицу квоты на вызов против 100 у search.list — критично при сканировании каналов или выгрузке длинной истории.

Выводы

Если нужен список видео канала, не полагайтесь на search.list для перечисления. Получите плейлист загрузок через channels?part=contentDetails, затем пройдитесь по нему с помощью playlistItems.list. Так вы получите полный набор загрузок, стабильную постраничную навигацию и гораздо более экономный расход квоты. Для playlistItems держите maxResults равным 50 и повторяйте запросы, пока есть nextPageToken, если нужна вся история; либо остановитесь раньше, если достаточно последних N записей.