API плейлистов YouTube не возвращает все видео на канале

Мы пытаемся собрать все видео на канале, например это. В этом списке 291 тысяча видео, мы выяснили идентификатор этого канала (и заменили в идентификаторе второй алфавит «C» на «U») и попробовали этот код, перебирая более 50 видео одновременно. Мы получаем только около 20 тысяч видео, не более того. Есть идеи, как это исправить и получить все 291 тысячу видео на этом канале? Проверил на разных каналах с большим количеством видео, у всех одна и та же проблема.

api_key = "my Google YouTube API V3 key"
from googleapiclient.discovery import build
youtube = build('youtube', 'v3', developerKey=api_key)
def get_channel_videos():    
    videos = []
    next_page_token = None
    while 1:
        res = youtube.playlistItems().list(playlistId = "UU...", 
                                           part='snippet', 
                                           maxResults=50,
                                           pageToken=next_page_token).execute()
        videos += res['items']
        next_page_token = res.get('nextPageToken')
        if next_page_token is None:
            break
    return videos
videos = get_channel_videos()
with open("video.txt", "a") as myfile:
    for video in videos:
        myfile.write(f"{video['snippet']['resourceId']['videoId']} => {video['snippet']['title']}\n")

print(f"Total video count => {len(videos)}")

Кажется, дублируется этот вопрос о переполнении стека.

Benjamin Loison 16.03.2024 16:18

Откуда именно 291k videos статистика? @NBA упоминает 56K videos и @NBA загружает плейлист упоминает 14,451 видео.

Benjamin Loison 18.03.2024 18:53

@BenjaminLoison, извини, я хотел вставить ссылку на MLB. Обновил свой пост, добавив правильную ссылку.

NedStarkOfWinterfell 18.03.2024 21:12
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
3
323
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Если сам API не работает, вы можете рассмотреть возможность перемещения по XML, аналогично этому:

internal async Task<VideoInfo[]> GetDataFromWebsite(string url)
{
    //strings used for traversing the website
    string startSearch = "else {window.addEventListener('script-load-dpj',";
    string endSearch = "</div>";
    string beforeEachEntry = "\"ADD_TO_QUEUE_TAIL\"";
    string beforeurl = "{\"webCommandMetadata\":{\"url\":\"/watch";
    string afterUrl = "\",\"webPageType\"";
    string filterTitleBetter = ",\"width\":336,\"height\":188}]}";
    string beforeTitle = ",\"title\":{\"runs\":[{\"text\":\"";
    string aftertitle = "\"";

    VideoInfo[] videoInfos;

    using (HttpClient client = new HttpClient())
    {
        //Get a response from Youtube
        HttpResponseMessage response = await client.GetAsync(url);

        //check if the Request was successful
        if (response.IsSuccessStatusCode)
        {
            //Get the XML from the response
            string xml = await response.Content.ReadAsStringAsync();

            //narrow down the section where we search for useful data
            string[] allData = xml.Split(startSearch)[1].Split(endSearch)[0].Split(beforeEachEntry);

            //initialize the return object 
            //the size is one less than allData.Length because the first entry contains no data
            videoInfos = new VideoInfo[allData.Length - 1];

            //iterate through the data
            for (int i = 1; i < allData.Length; i++)
            {
                //narrow down the entry section for the title
                string[] splitForVideoName = allData[i].Split(filterTitleBetter);
                //split the entry string exactly to contain the video title
                string videoName = splitForVideoName[splitForVideoName.Length - 1].Split(beforeTitle)[1].Split(aftertitle)[0];

                //split the entry string exactly to contain the video url
                string videoURL = "https://www.youtube.com/watch" + allData[i].Split(beforeurl)[1].Split(afterUrl)[0];

                //add those information to our return object
                videoInfos[i - 1] = new VideoInfo() { videoURL = videoURL, videoName = videoName };
            }
            client.Dispose();
            return videoInfos;
        }
        else
        {
            client.Dispose();
            throw new Exception();
        }
    }

internal class VideoInfo
{
    internal string videoURL;
    internal string videoName;
}

Чтобы стать полностью функциональным для каждого плейлиста, потребуется еще немного поработать, но с https://www.youtube.com/playlist?list=PLlVlyGVtvuVlklX5bYqk9RHnOSmUP6j0h до сих пор это работало.

Пожалуйста, поделитесь полным рабочим кодом, я не могу понять логику здесь. Спасибо!

NedStarkOfWinterfell 18.03.2024 14:29

С чем url ты звонишь GetDataFromWebsite?

Benjamin Loison 18.03.2024 15:56
Ответ принят как подходящий

Я исследовал много разных подходов, и единственный, который, кажется, работает идеально, — это следующий, основанный на веб-скрапинге вкладки Videos указанного канала:

import requests
from lxml import html
import json

CHANNEL_HANDLE = '@MLB'
text = requests.get(f'https://www.youtube.com/{CHANNEL_HANDLE}/videos').text
tree = html.fromstring(text)

ytVariableName = 'ytInitialData'
ytVariableDeclaration = ytVariableName + ' = '
for script in tree.xpath('//script'):
    scriptContent = script.text_content()
    if ytVariableDeclaration in scriptContent:
        ytVariableData = json.loads(scriptContent.split(ytVariableDeclaration)[1][:-1])
        break

contents = ytVariableData['contents']['twoColumnBrowseResultsRenderer']['tabs'][1]['tabRenderer']['content']['richGridRenderer']['contents']

videoIds = set()

def treatContents(contents):
    for content in contents:
        if not 'richItemRenderer' in content:
            break
        videoId = content['richItemRenderer']['content']['videoRenderer']['videoId']
        videoIds.add(videoId)
    print(len(videoIds))
    return getContinuationToken(contents)

def getContinuationToken(contents):
    # Sometimes have 29 actual results instead of 30.
    lastContent = contents[-1]
    if not 'continuationItemRenderer' in lastContent:
        return None
    return lastContent['continuationItemRenderer']['continuationEndpoint']['continuationCommand']['token']

continuationToken = treatContents(contents)
if continuationToken is not None:
    url = 'https://www.youtube.com/youtubei/v1/browse'
    headers = {
        'Content-Type': 'application/json'
    }
    requestData = {
        'context': {
            'client': {
                'clientName': 'WEB',
                'clientVersion': '2.20240313.05.00'
            }
        }
    }
    while True:
        requestData['continuation'] = continuationToken
        data = requests.post(url, headers = headers, json = requestData).json()
        # Happens not deterministically sometimes.
        if not 'onResponseReceivedActions' in data:
            print('Retrying')
            continue
        continuationItems = data['onResponseReceivedActions'][0]['appendContinuationItemsAction']['continuationItems']
        continuationToken = treatContents(continuationItems)
        if continuationToken is None:
            break

Хотя @MLBAbout утверждает 291,597 videos, мой метод находит 289,814 уникальные видео. Неизвестно, откуда взялась разница в количестве, возможно, из-за Live и видео, не включенных в список.

Выглядит многообещающе, позвольте мне попробовать. Можете ли вы попробовать использовать другие каналы с большим количеством видео и посмотреть, работает ли он одинаково?

NedStarkOfWinterfell 19.03.2024 17:03

В настоящее время я пытаюсь сравнить Список каналов YouTube с наибольшим количеством загрузок видео.

Benjamin Loison 20.03.2024 11:28

звучит здорово, получаете ли вы полные результаты по таким каналам?

NedStarkOfWinterfell 20.03.2024 18:57

Подробности о текущем процессе сравнительного анализа я рассказал здесь.

Benjamin Loison 20.03.2024 19:06

Кажется, работает. Какова, по вашему мнению, вероятность того, что YouTube/Google заметит вас за повторные звонки? Я пробовал использовать прокси (метод requests.post), работа работала очень медленно и часто просто полностью зависала.

NedStarkOfWinterfell 21.03.2024 15:53

Если вы продолжите работу с приведенным выше алгоритмом, не являющимся многопоточным, у Google нет особых причин для обнаружения, см. YouTube-operational-API/issues/11 и комментарий 1317163330.

Benjamin Loison 21.03.2024 15:56

Хм, но я хочу парсить сотни и тысячи каналов и все их видео. :( Как это сделать без многопоточности или запуска нескольких экземпляров этого кода для разных полезных данных каналов?

NedStarkOfWinterfell 21.03.2024 16:11

Для каналов с количеством видео менее 20 000 вы можете просто продолжить в многопоточном режиме с помощью YouTube Data API v3. Каналы с числом более 20 000 являются скорее исключением, чем обычные каналы, поэтому однопоточная обработка их кажется достаточно хорошей.

Benjamin Loison 21.03.2024 16:13

Привет, последний цикл while выглядит бесконечным, каково условие выхода из цикла?

NedStarkOfWinterfell 11.04.2024 13:11

Здравствуйте, хорошая мысль. Как обсуждалось ранее, целью была работа с каналами с наибольшим количеством видео, чтобы в этом случае алгоритм никогда не заканчивался, поскольку ответы YouTube отправляются все медленнее и медленнее. Для небольших каналов должно возникнуть исключение выхода за пределы, но дайте мне несколько минут, чтобы изучить и очистить его.

Benjamin Loison 11.04.2024 17:10

Ну, я только что протестировал небольшой канал (@Lusk-AI), и благодаря exit(0) все прекратилось. Согласен, это довольно грязно, я постараюсь это улучшить.

Benjamin Loison 11.04.2024 17:12
Я обновил свой ответ , заменив exit на break. Я также сделал алгоритм совместимым с отдельными страницами видеоканалов, таких как @jawed.
Benjamin Loison 11.04.2024 17:19

Спасибо. Другое дело - на каналах с огромным количеством видео скрипт часто глючит на полпути из-за какой-то неполной загрузки метаданных или чего-то еще. После исправления, если мы начнем снова, все придется начинать заново, получая видео, которые мы уже получили в прошлый раз. Разве мы не можем заставить его «забрать» оттуда, где он остался, или что-то в этом роде? Будут ли видео всегда идти в определенном порядке с каким-то порядковым номером или чем-то еще (при условии, что между ними на канал не добавляется видео)? Если да, то не можем ли мы сохранить состояние, откуда его можно будет забрать при следующем запуске?

NedStarkOfWinterfell 11.04.2024 18:58

Порядок сохраняется, если между первым и последним видео не добавлено видео. Вместо того, чтобы искать возобновление выполнения алгоритма на вашем месте, я бы сначала сосредоточился на понимании и исправлении временной ошибки, с которой вы столкнулись, если она есть (я не помню, чтобы у вас были какие-либо проблемы с этим кодом).

Benjamin Loison 11.04.2024 19:40

Для канала с идентификатором bwftv он просто возвращает 4 КБ из 17 000 видео, а затем повторяет попытки бесконечно.

NedStarkOfWinterfell 12.04.2024 14:46

Также эта ошибка выскакивает случайным образом: UnboundLocalError: local variable 'ytVariableData' referenced before assignment has context menu на строке contents = ytVariableData['contents']....

NedStarkOfWinterfell 12.04.2024 16:13

Что касается UnboundLocalError, поделитесь со мной text, когда это произойдет, иначе я не смогу многое отладить.

Benjamin Loison 12.04.2024 16:26

Текст? Вы имеете в виду идентификатор канала, на котором это происходит? Не уверен, что это соответствует действительности, но я проверю и сообщу вам.

NedStarkOfWinterfell 12.04.2024 17:43

Я имею в виду строку 22 кода, который я упомянул.

Benjamin Loison 12.04.2024 17:48

Плохо, что меня интересует строка значения data 57, вы можете использовать json.dumps(data, indent = 4) для этой цели.

Benjamin Loison 12.04.2024 17:55

Я точно проверил, что код, который я упомянул , правильный и это.

Benjamin Loison 12.04.2024 18:25

На этом канале это происходит постоянно - tv5newschannel.

NedStarkOfWinterfell 14.04.2024 19:22

Вы имеете в виду @tv5news?

Benjamin Loison 14.04.2024 20:28

Извините, это была моя ошибка, ваш код работает нормально. «Повторная попытка» иногда продолжается бесконечно, поэтому, если мы установим ограничение примерно 20 раз, а затем выйдем, это сработает как шарм, обычно это происходит, когда видео больше не осталось, а он пытается найти его.

NedStarkOfWinterfell 21.04.2024 12:46

Другие вопросы по теме