Получить список видеозаписей YouTube о человеке
YouTube API - это способ получить список YouTube видеозаписей человека, но он может стоить дорого.
Несколько дней назад мне задали вопрос: Можно ли скачать все публичные видео YouTube человека, которые были загружены в период с 2020 года по сегодняшний день. Общее количество видеороликов было около двухсот. И нет, я не смог получить доступ к учетной записи YouTube этого человека.
В этом посте я использую YouTube API для загрузки необходимых метаданных из видеороликов, по одному элементу на видео. Я поискал в PyPI, но не смог найти подходящего пакета для решения этой тривиальной задачи, поэтому решил написать код самостоятельно. Код можно найти ниже.
Все, что он делает, это получает данные из YouTube API и сохраняет их в "файле элементов". Вот и все. Вы можете использовать этот файл, например, для создания файла со строками, где каждая строка содержит команду yt-dlp , которая загружает видео. Но это уже на ваше усмотрение.
Как всегда, я делаю это на Ubuntu 22.04.
yt-dlp и yt-dlp-gui
yt-dlp - это программа командной строки, которая может использоваться для загрузки файлов из многих источников, включая YouTube. Мы можем установить ее, а затем также установить yt-dlp-gui, что даст нам GUI.
Вот как я загрузил ряд файлов. Переходим в YouTube, копируем ссылки и вставляем их в yt-dlp-gui. Но мы не хотим копировать-вставлять 200 урлов видео, что является противоположностью DRY (Don't Repeat Yourself)!
YouTube API
Для автоматизации загрузки нам необходимо получить метаданные всех видеофайлов пользователя. Мы можем получить их с помощью YouTube API. При рассмотрении этого API оказалось, что лучше всего использовать метод "YouTube - Data API - Search", см. ссылки ниже.
Получение ключа YouTube API
Для использования YouTube API необходим ключ YouTube API . Я не буду утомлять вас этим. В Интернете есть много инструкций, как получить этот ключ API .
У человека нет канала YouTube ?
Для использования метода поиска YouTube API нам нужен channelId, который является идентификатором канала YouTube данного человека. Но что делать, если человек не создал канал? Тогда все еще существует channelId. Одним из способов найти channelId для учетной записи является поиск в Интернете:
youtube channel <name>
В результате будет получена ссылка, содержащая channelId.
Метод поиска YouTube API
Я должен получить метаданные для примерно двухсот видеороликов за три года. Однако количество элементов, возвращаемых методом поиска YouTube API , ограничено 50, о чем в документации ничего не сказано. К счастью, метод поиска YouTube API позволяет осуществлять поиск между датами:
- published_after
- published_before
Я решил разбить поиск на месячные, а затем надеяться, что человек загружает не более - какой-то предел - видео в месяц.
YouTube API не возвращает все элементы сразу, а использует пагинацию. В ответе содержится параметр 'nextPageToken', если элементов больше. В этом случае мы добавляем его в следующий запрос, получаем ответ и т.д. до тех пор, пока этот параметр не станет NULL.
Вот пример ответа:
{
"kind": "youtube#searchListResponse",
"etag": "Hlc-6V55ICoxEujG5nA274peA0o",
"nextPageToken": "CAUQAA",
"regionCode": "NL",
"pageInfo": {
"totalResults": 29,
"resultsPerPage": 5
},
"items": [
...
]
}
А элементы выглядят следующим образом:
[
{
'kind': 'youtube#searchResult',
'etag': <etag>,
'id': {
'kind': 'youtube#video',
'videoId': <video id>
},
'snippet': {
'publishedAt': '2023-07-12T01:55:21Z',
'channelId': <channel id>,
'title': <title>,
'description': <description>,
'thumbnails': {
...
},
'channelTitle': <channel title>,
...
}
},
...
]
Filename complications
Это никак не связано с данными, полученными из YouTube API. Я столкнулся с этим, когда использовал эти данные для загрузки видеофайлов с помощью yt-dlp , и хотел поделиться этим с вами.
Типичная команда yt-dlp для загрузки видео YouTube в mp4 такова:
yt-dlp -f "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best" https://www.youtube.com/watch?v=lRlbcxXnMpQ -o "%(title)s.%(ext)s"
Здесь мы позволяем yt-dlp создать имя файла на основе названия видео. Но в зависимости от используемой операционной системы не все символы допустимы в имени файла.
Чтобы сделать его максимально похожим на название видео, yt-dlp преобразует некоторые символы в юникод, в результате чего имя файла выглядит почти как название видео, но часто это НЕ то же самое! Это хорошая функция, но совершенно непригодная для использования, если вы хотите:
- сравнить имена файлов или
- передавать файлы между различными системами.
В итоге я решил создавать имена файлов самостоятельно, заменяя ненужные символы на символ подчеркивания. Кроме того, я создал текстовый файл, содержащий строки:
<filename> <video title>
Таким образом, можно восстановить название файла, начиная с имени файла. Обратите внимание, что во избежание путаницы в названиях лучше включать в имя файла уникальное значение, например, дату и время публикации.
YouTube API credits: finished ... :-(
Когда вы начинаете использовать эти APIs, вы получаете бесплатные кредиты от Google , чтобы вы могли начать работу. Многие в Интернете уже предупреждали, что метод поиска YouTube API потребляет много кредитов.
Я провел несколько коротких экспериментов и три полных прогона. Во время третьего полного прогона у меня закончились кредиты. Это быстро! Или это много денег или мало простых прогонов!
В любом случае, во время второго прогона я уже собрал все данные, которые хотел, так что для этого проекта этого было достаточно. И не паникуйте, бесплатные кредиты возобновляются каждый день.
Код
Если вы хотите попробовать сами, вот код: введите channelId человека и свой ключ YouTube API . Мы начинаем с последнего месяца, извлекаем элементы, сохраняем их и переходим к предыдущему месяцу, пока не дойдем до последнего месяца. Начало и последнее число мы указываем как Tuples.
В "файл элементов" загружаются элементы JSON , извлеченные из YouTube API. Элементы добавляются только для новых videoIds. Таким образом, нет необходимости удалять этот файл между запусками.
Установите их в первую очередь:
pip install python-dateutil
pip install requests
Код:
# get_video_list.py
import calendar
import datetime
import json
import logging
import os
import sys
import time
import urllib.parse
from dateutil import parser
from dateutil.relativedelta import relativedelta
import requests
def get_logger(
console_log_level=logging.DEBUG,
file_log_level=logging.DEBUG,
log_file=os.path.splitext(__file__)[0] + '.log',
):
logger_format = '%(asctime)s %(levelname)s [%(filename)-30s%(funcName)30s():%(lineno)03s] %(message)s'
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
if console_log_level:
# console
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(console_log_level)
console_handler.setFormatter(logging.Formatter(logger_format))
logger.addHandler(console_handler)
if file_log_level:
# file
file_handler = logging.FileHandler(log_file)
file_handler.setLevel(file_log_level)
file_handler.setFormatter(logging.Formatter(logger_format))
logger.addHandler(file_handler)
return logger
logger = get_logger()
class YouTubeUtils:
def __init__(
self,
logger=None,
channel_id=None,
api_key=None,
yyyy_mm_start=None,
yyyy_mm_last=None,
items_file=None,
):
self.logger = logger
self.channel_id = channel_id
self.api_key = api_key
self.items_file = items_file
# create empty items file if not exists
if not os.path.exists(self.items_file):
items = []
json_data = json.dumps(items)
with open(self.items_file, 'w') as fo:
fo.write(json_data)
self.year_month_dt_start = datetime.datetime(yyyy_mm_start[0], yyyy_mm_start[1], 1, 0, 0, 0)
self.year_month_dt_current = self.year_month_dt_start
self.year_month_dt_last = datetime.datetime(yyyy_mm_last[0], yyyy_mm_last[1], 1, 0, 0, 0)
# api request
self.request_delay = 3
self.request_timeout = 6
def get_previous_year_month(self):
self.logger.debug(f'()')
self.year_month_dt_current -= relativedelta(months=1)
self.logger.debug(f'year_month_dt_current = {self.year_month_dt_current}')
if self.year_month_dt_current < self.year_month_dt_last:
return None, None
yyyy = self.year_month_dt_current.year
m = self.year_month_dt_current.month
return yyyy, m
def get_published_between(self, yyyy, m):
last_day = calendar.monthrange(yyyy, m)[1]
published_after = f'{yyyy}-{m:02}-01T00:00:00Z'
published_before = f'{yyyy}-{m:02}-{last_day:02}T23:59:59Z'
self.logger.debug(f'published_after = {published_after}, published_before = {published_before}')
return published_after, published_before
def get_data_from_youtube_api(self, url):
self.logger.debug(f'(url = {url})')
r = None
try:
r = requests.get(url, timeout=self.request_timeout)
except Exception as e:
self.logger.exception(f'url = {url}')
raise
self.logger.debug(f'status_code = {r.status_code}')
if r.status_code != 200:
raise Exception(f'url = {url}, status_code = {r.status_code}, r = {r.__dict__}')
try:
data = r.json()
self.logger.debug(f'data = {data}')
except Exception as e:
raise Exception(f'url = {url}, converting json, status_code = {r.status_code}, r = {r.__dict__}')
raise
return data
def add_items_to_items_file(self, items_to_add):
self.logger.debug(f'(items_to_add = {items_to_add})')
# read file + json to dict
with open(self.items_file, 'r') as fo:
json_data = fo.read()
items = json.loads(json_data)
self.logger.debug(f'items = {items}')
# add only unique video_ids
video_ids = []
for item in items:
id = item.get('id')
if id is None:
continue
video_id = id.get('videoId')
if video_id is None:
continue
video_ids.append(video_id)
self.logger.debug(f'video_ids = {video_ids}')
items_added_count = 0
for item_to_add in items_to_add:
self.logger.debug(f'item_to_add = {item_to_add})')
kind_to_add = item_to_add['id']['kind']
if kind_to_add != 'youtube#video':
self.logger.debug(f'skipping kind_to_add = {kind_to_add})')
continue
video_id_to_add = item_to_add['id']['videoId']
if video_id_to_add not in video_ids:
self.logger.debug(f'adding video_id_to_add = {video_id_to_add})')
items.append(item_to_add)
items_added_count += 1
video_ids.append(video_id_to_add)
self.logger.debug(f'items_added_count = {items_added_count})')
if items_added_count > 0:
# dict to json + write file
json_data = json.dumps(items)
with open(self.items_file, 'w') as fo:
fo.write(json_data)
return items_added_count
def fetch_year_month_videos(self, yyyy, m):
self.logger.debug(f'(yyyy = {yyyy}, m = {m})')
published_after, published_before = self.get_published_between(yyyy, m)
url_base = 'https://youtube.googleapis.com/youtube/v3/search?'
url_params = {
'part': 'snippet,id',
'channelId': self.channel_id,
'publishedAfter': published_after,
'publishedBefore': published_before,
'sort': 'date',
'key': self.api_key,
}
url = url_base + urllib.parse.urlencode(url_params)
total_items_added_count = 0
while True:
time.sleep(self.request_delay)
data = self.get_data_from_youtube_api(url)
page_info = data.get('pageInfo')
self.logger.debug(f'page_info = {page_info})')
items = data.get('items')
if items is None:
break
if not isinstance(items, list) or len(items) == 0:
break
# add items
total_items_added_count += self.add_items_to_items_file(items)
next_page_token = data.get('nextPageToken')
self.logger.debug(f'next_page_token = {next_page_token})')
if next_page_token is None:
break
# add next page token
url_params['pageToken'] = next_page_token
url = url_base + urllib.parse.urlencode(url_params)
self.logger.debug(f'total_items_added_count = {total_items_added_count})')
return total_items_added_count
def main():
# replace CHANNEL_ID and API_KEY with your values
yt_utils = YouTubeUtils(
logger=logger,
channel_id='CHANNEL_ID',
api_key='API_KEY',
# current month + 1
yyyy_mm_start=(2023, 10),
yyyy_mm_last=(2020, 1),
items_file='./items.json',
)
while True:
yyyy, m = yt_utils.get_previous_year_month()
if yyyy is None or m is None:
break
logger.debug(f'fetching for {yyyy}-{m:02}')
yt_utils.fetch_year_month_videos(yyyy, m)
if __name__ == '__main__':
main()
Сводка
Получение данных о видеороликах YouTube с помощью YouTube API не представляет особой сложности. Мы создали отдельный "файл элементов", который использовали для дальнейшей обработки.
Это был интересный проект, который стал большим сюрпризом, когда я начал загружать видеоролики YouTube , используя информацию из "файла элементов". yt-dlp может генерировать имена файлов, которые очень близки к названию видео. Для этого он вставляет символы юникода, и это выглядит замечательно, но мне это показалось очень запутанным.
И еще один сюрприз. Использование YouTube API может быть очень дорогим. Не так уж много потребовалось, чтобы израсходовать ежедневные бесплатные кредиты.
Ссылки / кредиты
YouTube - Data API - Search
https://developers.google.com/youtube/v3/docs/search
yt-dlp
https://github.com/yt-dlp/yt-dlp
yt-dlp-gui and others
https://www.reddit.com/r/youtubedl/wiki/info-guis
Подробнее
Web automation YouTube
Недавний
- Скрытие первичных ключей базы данных UUID вашего веб-приложения
- Don't Repeat Yourself (DRY) с Jinja2
- SQLAlchemy, PostgreSQL, максимальное количество строк для user
- Показать значения в динамических фильтрах SQLAlchemy
- Безопасная передача данных с помощью шифрования Public Key и pyNaCl
- rqlite: альтернатива dist с высокой степенью готовности и SQLite
Большинство просмотренных
- Используя Python pyOpenSSL для проверки SSL-сертификатов, загруженных с хоста
- Использование UUID вместо Integer Autoincrement Primary Keys с SQLAlchemy и MariaDb
- Подключение к службе на хосте Docker из контейнера Docker
- Использование PyInstaller и Cython для создания исполняемого файла Python
- SQLAlchemy: Использование Cascade Deletes для удаления связанных объектов
- Flask Удовлетворительный запрос API проверка параметров запроса с помощью схем Маршмэллоу