angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Получить список видеозаписей YouTube о человеке

YouTube API - это способ получить список YouTube видеозаписей человека, но он может стоить дорого.

7 сентября 2023
post main image
https://pixabay.com/users/27707-27707

Несколько дней назад мне задали вопрос: Можно ли скачать все публичные видео 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

Оставить комментарий

Комментируйте анонимно или войдите в систему, чтобы прокомментировать.

Комментарии

Оставьте ответ

Ответьте анонимно или войдите в систему, чтобы ответить.