# Укрощаем YouTube с помощью Selenium
vit01(mira, 1) — All
2020-07-07 11:35:52


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


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

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

Обновления для новых видео с ютуб-канала можно получать не только с помощью рассылки и пуш-уведомлений, но и через весьма надёжные [RSS-каналы]( https://amateurblogger.ru/rss-eto-dolzhen-znat-kazhdyj/ ). Проблема лишь в том, что для генерации RSS-ленты нужно знать идентификаторы каналов и правильно составлять ссылки, которые нужно проталкивать в клиенты-читалки.

А что если ты отпишешься или наоборот подпишешься на канал? Тогда придётся брать и копировать ссылку вручную в RSS-клиент или агрегатор, что крайне неудобно и грозит путаницей. В идеале можно обновлять вручную файлик [subscription_manager]( https://www.youtube.com/subscription_manager?action_takeout=1 ), который содержит список всех твоих подписок, но это всё равно надо делать вручную.

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

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

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

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

Цитата [из Википедии]( https://ru.wikipedia.org/wiki/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]( https://blog.alicorn.tk/posts/best-plot-mpl.html ). Из новых добавлений к лайфхакам в той статье:

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

Этот пост в блоге: https://blog.alicorn.tk/posts/selenium.html