Перейти к главному содержимому

Укрощаем YouTube с помощью Selenium

В этот раз - снова про программирование и автоматизацию. Если вы ещё не знали про существование Selenium WebDriver и ему подобных инструментов, то этот небольшой обзор поможет сэкономить некоторое время на рутине.

Задача: получить новые видео с подписок в виде RSS

Я очень люблю некоторых людей, которые ведут YouTube-каналы, но очень не люблю тратить кучу времени на просмотр рекомендаций, на рассылки с "интересными" видео и на отсмотр подписок вручную. У меня имеется некоторый список любимых каналов в подписках, каждый из которых со своей периодичностью выпускает новые видео.

Обновления для новых видео с ютуб-канала можно получать не только с помощью рассылки и пуш-уведомлений, но и через весьма надёжные RSS-каналы. Проблема лишь в том, что для генерации RSS-ленты нужно знать идентификаторы каналов и правильно составлять ссылки, которые нужно проталкивать в клиенты-читалки.

А что если ты отпишешься или наоборот подпишешься на канал? Тогда придётся брать и копировать ссылку вручную в RSS-клиент или агрегатор, что крайне неудобно и грозит путаницей. В идеале можно обновлять вручную файлик subscription_manager, который содержит список всех твоих подписок, но это всё равно надо делать вручную.

YouTube API и сложности с ним

Казалось бы, можно воспользоваться YouTube API и получать всю информацию оттуда. Хорошо сказано, да трудно сделано. Для скачиванияв личной информации пользователя требуется получать специальный Oauth-токен, который просто так не достанешь (нужно создавать Web Endpoint с редиректом в нужное место, поднимать отдельный сервис на подтверждённом домене и.т.п.). К тому же, токены имеют свойство протухать, и требуется как-то задумываться об обновлениях. Зачем вся эта возня для какого-то простейшего скрипта, который должен будет тихо-мирно запускаться в Cron, отработать секунду и заглохнуть?

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

Selenium: управляем браузером через скрипты

Цитата из Википедии:

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

У Selenium имеется куча биндингов для разных языков программирования, в том числе для Python. Перед началом использования требуется установить некоторые пакеты из репозиториев, в Debian и Ubuntu их доставить проще простого: apt-cache search selenium, и всё сразу вылезет. Доступны движки на основе Firefox и Chromium (разумеется, сам браузер нужно перед этим тоже установить).

Ну что, поехали пушкой по воробьям

Решил использовать свой любимый браузер - Firefox. Благо, здесь можно воспользоваться уже существующим браузерным профилем, хотя по умолчанию Selenium обычно создаёт свежий. Приготовления:

  1. Устанавливаем все пакеты вида python3-selenium, firefoxdriver, geckodriver и.т.п.
  2. Из текущего профиля фаерфокса логинимся в Гугле, пробуем скачать файл вручную
  3. Выбираем при скачивании (или напрямую в настройках), что файл автоматически будет сохраняться в папку загрузок без подтверждения
  4. Копируем путь в системе к профилю браузера
#!/usr/bin/env python3

import os, sys
import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.firefox.options import Options

yturl = "https://www.youtube.com/subscription_manager?action_takeout=1"
file_path = "/home/user/Downloads/subscription_manager"

if os.path.exists(file_path):
    os.remove(file_path)

options = Options()
options.headless = True
fp = webdriver.FirefoxProfile("/home/user/.mozilla/firefox/blablabla.default")
driver = webdriver.Firefox(fp, options=options)
driver.set_page_load_timeout(15)

try:
    driver.get(yturl)
except:
    pass
time.sleep(5)
driver.quit()

Важное замечание насчёт Headless-режима. Headless представляет собой запуск вне графического окружения, без всякой отрисовки. Это требуется как раз для работы браузера вообще без участия человека. При отладке Selenium-скриптов лучше пользоваться браузером в обычном режиме, но когда вся автоматизация будет доведена до совершенства, то можно включить Headless и закидывать скрипты в Cron. При отсутствии запущенного X-сервера скрипты в Cron будут работать только в Headless.

Кроме простого посещения сайтов Selenium умеет и многое другое, например:

  • нажимать на ссылки/кнопки, прокручивать страницы, вводить текст и "сёрфить веб" как человек
  • запускать произвольный Javascript на сайте
  • доставать полезную информацию из любого куска страницы
  • делать скриншоты сайтов

Итак, список каналов вытащили. Теперь дело остаётся за малым - распарсить его, вытащить айдишники и закинуть в программу, которая слепит из них красивую RSS-ленту.

Обрабатываем подписки

#!/usr/bin/env python3

from xml.dom import minidom
xmldoc = minidom.parse("subscription_manager")

itemlist = xmldoc.getElementsByTagName("outline")

del(itemlist[0]) # there is no xmlUrl in 1-st element

for i in itemlist:
    print(i.attributes['xmlUrl'].value.split("=")[1])

Для того чтобы полученный список ID преобразовать в RSS-ленту, рекомендую программу ytsubs: https://github.com/ali1234/ytsubs. Она консольная и отлично работает.

В качестве бонуса

Одной из самых популярных статей здесь в блоге является статья про построение графиков в matplotlib. Из новых добавлений к лайфхакам в той статье:

  • Упомянул параметр width_ratios для более удобного управления размерами графиков
  • Написал вариант итерации по subplots с помощью объекта axes.flat: теперь ещё меньше строк кода и никакой возни с двухмерными массивами!
  • Масштабы сетки можно устанавливать отдельно для разных осей, для красивых графиков это очень полезно