Мы пытаемся собрать все видео на канале, например это. В этом списке 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)}")
Откуда именно 291k videos
статистика? @NBA упоминает 56K videos
и @NBA загружает плейлист упоминает 14,451
видео.
@BenjaminLoison, извини, я хотел вставить ссылку на MLB. Обновил свой пост, добавив правильную ссылку.
Если сам 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 до сих пор это работало.
Пожалуйста, поделитесь полным рабочим кодом, я не могу понять логику здесь. Спасибо!
С чем url
ты звонишь GetDataFromWebsite
?
Я исследовал много разных подходов, и единственный, который, кажется, работает идеально, — это следующий, основанный на веб-скрапинге вкладки 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
и видео, не включенных в список.
Выглядит многообещающе, позвольте мне попробовать. Можете ли вы попробовать использовать другие каналы с большим количеством видео и посмотреть, работает ли он одинаково?
В настоящее время я пытаюсь сравнить Список каналов YouTube с наибольшим количеством загрузок видео.
звучит здорово, получаете ли вы полные результаты по таким каналам?
Подробности о текущем процессе сравнительного анализа я рассказал здесь.
Кажется, работает. Какова, по вашему мнению, вероятность того, что YouTube/Google заметит вас за повторные звонки? Я пробовал использовать прокси (метод requests.post
), работа работала очень медленно и часто просто полностью зависала.
Если вы продолжите работу с приведенным выше алгоритмом, не являющимся многопоточным, у Google нет особых причин для обнаружения, см. YouTube-operational-API/issues/11 и комментарий 1317163330.
Хм, но я хочу парсить сотни и тысячи каналов и все их видео. :( Как это сделать без многопоточности или запуска нескольких экземпляров этого кода для разных полезных данных каналов?
Для каналов с количеством видео менее 20 000 вы можете просто продолжить в многопоточном режиме с помощью YouTube Data API v3. Каналы с числом более 20 000 являются скорее исключением, чем обычные каналы, поэтому однопоточная обработка их кажется достаточно хорошей.
Привет, последний цикл while выглядит бесконечным, каково условие выхода из цикла?
Здравствуйте, хорошая мысль. Как обсуждалось ранее, целью была работа с каналами с наибольшим количеством видео, чтобы в этом случае алгоритм никогда не заканчивался, поскольку ответы YouTube отправляются все медленнее и медленнее. Для небольших каналов должно возникнуть исключение выхода за пределы, но дайте мне несколько минут, чтобы изучить и очистить его.
Ну, я только что протестировал небольшой канал (@Lusk-AI), и благодаря exit(0)
все прекратилось. Согласен, это довольно грязно, я постараюсь это улучшить.
exit
на break
. Я также сделал алгоритм совместимым с отдельными страницами видеоканалов, таких как @jawed.
Спасибо. Другое дело - на каналах с огромным количеством видео скрипт часто глючит на полпути из-за какой-то неполной загрузки метаданных или чего-то еще. После исправления, если мы начнем снова, все придется начинать заново, получая видео, которые мы уже получили в прошлый раз. Разве мы не можем заставить его «забрать» оттуда, где он остался, или что-то в этом роде? Будут ли видео всегда идти в определенном порядке с каким-то порядковым номером или чем-то еще (при условии, что между ними на канал не добавляется видео)? Если да, то не можем ли мы сохранить состояние, откуда его можно будет забрать при следующем запуске?
Порядок сохраняется, если между первым и последним видео не добавлено видео. Вместо того, чтобы искать возобновление выполнения алгоритма на вашем месте, я бы сначала сосредоточился на понимании и исправлении временной ошибки, с которой вы столкнулись, если она есть (я не помню, чтобы у вас были какие-либо проблемы с этим кодом).
Для канала с идентификатором bwftv
он просто возвращает 4 КБ из 17 000 видео, а затем повторяет попытки бесконечно.
Также эта ошибка выскакивает случайным образом: UnboundLocalError: local variable 'ytVariableData' referenced before assignment has context menu
на строке contents = ytVariableData['contents']...
.
Что касается UnboundLocalError
, поделитесь со мной text
, когда это произойдет, иначе я не смогу многое отладить.
Текст? Вы имеете в виду идентификатор канала, на котором это происходит? Не уверен, что это соответствует действительности, но я проверю и сообщу вам.
Я имею в виду строку 22 кода, который я упомянул.
Плохо, что меня интересует строка значения data
57, вы можете использовать json.dumps(data, indent = 4)
для этой цели.
Я точно проверил, что код, который я упомянул , правильный и это.
На этом канале это происходит постоянно - tv5newschannel
.
Вы имеете в виду @tv5news?
Извините, это была моя ошибка, ваш код работает нормально. «Повторная попытка» иногда продолжается бесконечно, поэтому, если мы установим ограничение примерно 20 раз, а затем выйдем, это сработает как шарм, обычно это происходит, когда видео больше не осталось, а он пытается найти его.
Кажется, дублируется этот вопрос о переполнении стека.