Обвиваем YouTube змеем, или как смотреть и скачивать видео с YouTube без VPN на чистом Python-е. Часть 2

от автора

Приветствую! Эта статья является продолжением (2 частью) статьи Обвиваем YouTube змеем, или как смотреть и скачивать видео с YouTube без VPN на чистом Python-е. Часть 1 Если честно, я был приятно удивлен популярностью первой части: 115К просмотров за неделю и 137 голосов, которые принесли мне 21 место в рейтинге Хабра. Учитывая, что эта статья была из песочницы (отдельное спасибо @Ilha за приглашение), для меня это большой результат. Поэтому всем, кто поставил стрелочку вверх – авторское спасибо!)

В этой статье я покажу, как можно скачивать с YouTube каналы и плейлисты. Если кто-то не читал первую часть, настоятельно рекомендую это сделать. По крайней мере, если по мере чтения у вас возникнут какие-то вопросы, скорее всего там есть на них ответы. Напомню, что у нас уже есть средство, которое решает «проблему с устаревшим и изношенным оборудованием Google Global Cache» (к сожалению, оно не у всех работает, учтите), а также мы разобрались с тем, как скачивать с YouTube видео и аудио в любом качестве. Итак, начнём!


Как там наши кэширующие сервера?

По многочисленным заявкам телезрителей читателей, даю ссылку на мой репозиторий с утилитой для обхода DPI. Там вы найдёте не только работающий код, но и скомпилированный exe для Windows, который не требует дополнительных программ/библиотек (главное — не забудьте положить рядом с exe файл blacklist.txt). Разбор исходного кода утилиты приведён в первой части статьи.

Подготовка инструментария

Для тех, кто не читал первую часть, повторю, что нам понадобится библиотека pytubefix, которая устанавливается командой:

pip install pytubefix

Также нам понадобится ffmpeg, для объединения аудио и видео (зачем и почему – см. часть 1). Если у вас Windows, то скачать его можно с моего Google Диска (ну или найдите в интернете)

Также приведу код функции, которая объединяет аудио с видео, из прошлой статьи. Я не буду дублировать его каждый раз, а просто буду ссылаться на него:

def combine(audio: str, video: str, output: str) -> None:      if os.path.exists(output):         os.remove(output)      code = os.system(         f'.\\ffmpeg.exe -i "{video}" -i "{audio}" -c copy "{output}"')      if code != 0:         raise SystemError(code)

Простой пример загрузки плейлиста

Для работы с плейлистами в pytubefix есть объект Playlist. В принципе, кроме него нам почти больше ничего и не нужно.

Давайте посмотрим, как можно скачать плейлист:

from pytubefix import Playlist from pytubefix.cli import on_progress  url = "https://www.youtube.com/playlist?list=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"  playlist = Playlist(url,                     proxies={"http": "http://127.0.0.1:8881",                              "https": "http://127.0.0.1:8881"})  for video in playlist.videos:     video.register_on_progress_callback(on_progress)     stream = video.streams.get_highest_resolution()     stream.download() 

Давайте разберём, что делает код. Вначале мы импортируем класс Playlist и функцию on_progress, которая нужна для отображения прогрессбара во время загрузки видео. url —  это ссылка на плейлист. Далее, мы создаём экземпляр класса Playlist и передаём ему url и адрес нашего прокси, то бишь nodpi.

Затем циклом мы проходимся по списку playlist.videos (хотя вообще-то это не список, а генератор). Каждый его элемент – это экземпляр класса YouTube, непосредственно с которым можно работать и скачивать потоки (про потоки см. в 1 части). Для каждого видео мы регистрируем обработчик для on_progress_callback, который обеспечивает отображение прогрессбара во время загрузки. После этого мы получаем поток с самым высоким разрешением, который содержит и аудио, и видео, и скачиваем его. Как я уже говорил в прошлой статье, таким разрешением окажется 360p (из-за ограничений YouTube-а), но пока для нас это неважно, в более высоком разрешение мы будем скачивать позднее.

Сохраните этот код в файл, запустите nodpi, а затем запустите этот скрипт (вставив свою ссылку на плейлист). Должно работать! (но это не точно)

Что можно узнать о плейлисте

У Playlist есть несколько свойств, которые помогают получить о нём информацию. Ниже представлены самые основные из них:

  • last_updated – возвращает дату последнего обновления плейлиста (добавления в него последнего видео).

  • title —  название плейлиста.

  • description —  описание.

  • owner —  владелец плейлиста. Обычно это название канала.

  • length —  количество видео в плейлисте.

Загрузка плейлиста в хорошем качестве

Давайте объединим все наши знания и напишем скрипт, который будет скачивать все видео из плейлиста в одном из разрешений:

  1. Самое высокое разрешение (будет определяться для каждого видео автоматически)

  2. Дефолтное разрешение (360p есть у всех видео)

  3. Самое низкое разрешение (будет определяться для каждого видео автоматически)

Пишем код

Для начала импортируем библиотеки:

import os import sys  from pytubefix import YouTube, Playlist from pytubefix.cli import on_progress from pytubefix.helpers import safe_filename from pytubefix.file_system import file_system_verify 

Далее вставьте код функции combine, приведенный выше.

Затем идёт функция скачивания видео:

def download(url: str, res: str, progressive: bool = False) -> None:      yt = YouTube(         proxies={"http": "http://127.0.0.1:8881",                  "https": "http://127.0.0.1:8881"},         url=url,         on_progress_callback=on_progress,     )      video_stream = yt.streams.\         filter(type='video', resolution=res, progressive=progressive).\         desc().first()      audio_stream = yt.streams.\         filter(mime_type='audio/mp4').\         order_by('filesize').\         desc().first()      kernel = sys.platform      if kernel == "linux":         file_system = "ext4"     elif kernel == "darwin":         file_system = "APFS"     else:         file_system = "NTFS"      translation_table = file_system_verify(file_system)      audio_filename = audio_stream.default_filename.translate(translation_table)     video_filename = video_stream.default_filename.translate(translation_table)     output_filename = f'{safe_filename(yt.title)}.mp4'      if output_filename == video_filename:         output_filename = f'{safe_filename(yt.title)}_1.mp4'      print('\nInformation:')     print("\tTitle:", yt.title)     print("\tAuthor:", yt.author)     print("\tDate:", yt.publish_date)     print("\tResolution:", video_stream.resolution)     print("\tViews:", yt.views)     print("\tLength:", round(yt.length/60), "minutes")     print("\tFilename of the video:", output_filename)     print("\tFilesize of the video:", round(         video_stream.filesize / 1000000), "MB")      print('Download video...')     video_stream.download()      if not video_stream.is_progressive:         print('\nDownload audio...')         audio_stream.download()          combine(audio_filename, video_filename, output_filename)  

Она похожа на функцию из первой части, но есть и отличия. Я добавил аргументы res (разрешение) и progressive (указывает на то, есть ли в потоке аудио и видео). После того, как мы получаем потоки, идёт блок, который преобразует имена файлов в «безопасные» для текущей файловой системе. Также в прошлой статье у меня был косяк – если имя файла с видео и имя конвертированного файла совпадали, то происходила ошибка. Здесь я поправил это, добавив соответствующую проверку. Скачивание аудио и конвертирование мы производим, только если видеопоток не содержит его. В целом код понятный, и я думаю подробные объяснения излишни.

Далее напишем функцию, которая будет выбирать разрешение для конкретного видео, исходя из выбранного режима:

def resolution(video: YouTube, type: str):      if type == 'default':         stream = video.streams.get_highest_resolution()         return stream.resolution      if type == 'worst':         stream = video.streams.get_lowest_resolution()         return stream.resolution      if type == 'best':         stream = video.streams.\             filter(type='video').\             order_by('resolution').\             desc().first()          return stream.resolution  

Надеюсь в объяснениях не нуждается.

Ну и основная часть:

url = "https://www.youtube.com/playlist?list=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"  playlist = Playlist(url,                     proxies={"http": "http://127.0.0.1:8881",                              "https": "http://127.0.0.1:8881"})  while True:     res = str(input(         'In what resolution would you like to download videos from the playlist (0 - 360p, 1 - minimum resolution, 2 - maximum resolution)? '))     if res not in ('0', '1', '2'):         print('Incorrect value entered')         continue     break  print('\nInformation:') print("\tTitle:", playlist.title) print("\tAuthor:", playlist.owner) print("\tVideos:", playlist.length)  input('\nPress Enter to start downloading...\n') 

Здесь мы спрашиваем пользователя, как скачивать плейлист и выводим информацию о нём. Затем качаем:

for video in playlist.videos:     try:         video.check_availability()     except Exception as e:         print(f"Error: {e}")         continue     else:         try:             download(video.watch_url, resolution(                 video, 'default' if res == '0' else 'worst' if res == '1' else 'best'),                 progressive=True if res == '0' else False)         except Exception as e:             print(f"Error: {e}")             continue 

video.check_availability() проверяет доступно ли видео для скачивания и вызывает ошибку, если нет. Мы перехватываем её и уведомляем пользователя, иначе скачивание может пойти насмарку. В остальном код, я надеюсь, понятный.

Примечание

Отдельно хочу заметить, что я не претендую на красоту и продуманность кода – моя цель показать how it works, а не дать готовый идеальный инструмент.

Ну и наконец проверяем. Запускаем nodpi и наш скрипт. Не забудьте вставить свой url (просто копипастите из строки браузера).

Загрузка канала

Как это ни странно, но скачивание канала почти ничем не отличается от скачивания плейлиста. Для этого в pytubefix предназначен класс Channel (который, кстати, наследуется от Playlist).

Как известно, видео на каналах YouTube делятся на несколько категорий: просто видео, трансляции (стримы) и shorts. pytubefix позволяет скачивать все эти категории, за исключением трансляций, которые закончились совсем недавно или ещё идут.

Итак, какие изменения мы внесём.

Для начала импортируем Channel

from pytubefix import YouTube, Channel

Также мы напишем функцию, которая глобально устанавливает прокси, т. е. для всего скрипта, потому что, по какой-то причине, Channel не хочет работать с прокси. Импортируем urllib и напишем функцию:

from urllib import request   def install_proxy(proxy_handler: dict):     proxy_support = request.ProxyHandler(proxy_handler)     opener = request.build_opener(proxy_support)     request.install_opener(opener) 

proxy_handler — это словарь примерно в таком формате:

{"http": "http://127.0.0.1:8881",  "https": "http://127.0.0.1:8881"}

Далее, изменим часть кода, где мы создавали экземпляр класса Playlist. Теперь здесь мы устанавливаем прокси и используем Channel:

url = "https://www.youtube.com/@xxxx"  install_proxy({"http": "http://127.0.0.1:8881",                "https": "http://127.0.0.1:8881"}) channel = Channel(url) 

Затем уберём информацию об авторе и названии, т. к. Channel не поддерживает их:

print('\nInformation:') print("\tVideos:", channel.length)  input('\nPress Enter to start downloading...\n')

Ну а всё остальное оставляем без изменений, кроме цикла. Для итерации по видео используйте for video in channel.videos:; по трансляциям for video in channel.live:; по shorts‑ам for video in channel.shorts:

Всё! Скрипт готов к работе. Как обычно, запускаем nodpi и проверяем.

Загрузка субтитров

Здесь вообще всё просто. Простейший код, который сохраняет субтитры в файл:

from pytubefix import YouTube  yt = YouTube('http://youtube.com/watch?v=xxxxxxxxxxx')  caption = yt.captions['a.ru'] caption.save_captions("captions.txt")

yt.captions — это словарь, в котором хранятся все доступные для этого видео субтитры (caption будет объектом Caption)

caption.save_captions сохраняет субтитры в текстовом формате в файл. xml-формат можно получить с помощью caption.xml_captions

Плюсом pytubefix, является то, что он позволяет. скачивать даже те субтитры, которые были созданы YouTube-ом автоматически (в pytube такой возможности не было).

Добавить субтитры к видео можно вручную. Например в VLC это делается так: Субтиры -> Добавить файл субтитров.

Можно и с помощью ffmpeg. В данном случае команда будет выглядеть так:

ffmpeg.exe -i filename.mp4 -vf subtitles=captions.txt filename_new.mp4

В данном случае субтитры будут зашиты текстом непосредственно в видео (а не отдельным потоком).


На этом всё! В двух частях статьи я рассказал, как можно скачивать с YouTube видео, аудио, плейлисты, каналы и субтитры, а также поделился способом использования YouTube без VPN. Надеюсь статьи были полезными и интересными!

Что может из этого получится

Может кто-то скажет, что я забыл добавить хаб «Я пиарюсь», но нет — я делаю это не ради рекламы, а просто, чтобы показать, на что способен pytubefix. А дело в том, что уже около полугода я пишу пет-проект — утилита для скачивания с YouTube всего того, о чём я рассказывал. Она полностью написана на python, но исходный код я пока не выложил, несмотря на то, что он работает (работал до августа 2024), из-за «замедления» YouTube. Большинство попыток скачивания заканчиваются ошибкой Remote end closed connection Ниже я покажу несколько скриншотов программы. Возможно, кто-то заинтересуется ей и будет готов по разбираться — я не против, велком в диалоги.

Главный экран (графика - собственная либа, написанная с нуля)

Главный экран (графика — собственная либа, написанная с нуля)
Экран загрузок

Экран загрузок
Подготовка к загрузке видео

Подготовка к загрузке видео
Загрузка видео

Загрузка видео
Загрузка окончена

Загрузка окончена
Список настроек для понимания функционала

Список настроек для понимания функционала

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Вы скаичваете видео с YouTube?

65.82% Да52
34.18% Нет27

Проголосовали 79 пользователей. Воздержались 6 пользователей.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Если да, то как?

66.67% yt-dlp/youtube-dl38
26.32% Сайты для скачивания15
17.54% Приложения для скачивания10
3.51% pytube/pytubefix и т. п.2
1.75% Другие библиотеки/языки1
3.51% Напишу в комментариях2

Проголосовали 57 пользователей. Воздержались 11 пользователей.

ссылка на оригинал статьи https://habr.com/ru/articles/871202/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *