Используя Python и Selenium, получим все плейлисты и альбомы из Яндекс.Музыки, а с помощью библиотеки spotipy перенесем фонотеку в Спотифай. Возможности скрипта: перенос альбомов; перенос своих плейлистов и треков из плейлиста «Мне нравится»; перенос лайкнутых плейлистов. Единственное условие: фонотека Яндекс.Музыки должна быть открытой. *** Первую часть пути мы пройдем окольным путем: чтобы чему-то научиться откажемся от использования неофициальной библиотеки yandex-music-api и получим данные своими руками. Во второй части пути к нам на помощь придет библиотека spotipy, которая перенесет в Спотифай все личные плейлисты, лайкнутые треки и альбомы. Поехали! Переносим альбомы У Яндекс.Музыки (ЯМ) нет API. Совсем. Поэтому данные нам придется брать со страниц ЯМ. Перейдем на свою страницу ЯМ в раздел альбомы и начнем поиск информации, открыв инструменты разработчика (Ctrl + Shift + i в Chrome и FireFox). В теле страницы с альбомами есть JSON-массив Mu, который в двух ключах содержит все, что нам нужно знать об альбоме: его ID, название и имя исполнителя: C массивом Mu будем работать постоянно, так как в нем содержится самое ценное – ID альбомов, плейлистов и треков, по которому мы получаем всю необходимую информацию. Поэтому ID будет нашей целью на всем пути следования. Первый ключ – albumIds – содержит ID всех альбомов, но нам нужно будет дополнительно переходить на страницу каждого альбома и получать его название и имя исполнителя и тегов. Второй ключ – albums – сразу выдает название альбома и имя исполнителя, но только первые 150 пар (это ограничение распространяется на плейлисты тоже). JSON Яндекс.Музыки Мы воспользуемся первым ключом, чтобы перенести все альбомы. Как вывести json в более читаемом виде, как на картинке выше? Через функцию pprint: from pprint import pprint pprint(json) Как получить название альбома и имя исполнителя? Получив ID, перейдем на страницу альбома https://music.yandex.ru/album/ID_альбома. Из тегов узнаем название альбома и имя исполнителя. Допустим, в нашей фонотеке есть альбом «Greatest Hits In Japan» – Queen. Откроем страницу альбома. В браузере перейдем в инструменты разработчика и выберем инструмент Селектор (Ctrl + Shift + C): И просто кликнем по названию альбома на странице: Тег <h1> с названием альбома найдется автоматически: Тег <a> с именем исполнителя: Автоматизация получения данных, Selenium Библиотека Selenium умеет управлять браузером и часто используется для автоматизации тестирований. С помощью Selenium мы автоматизируем действия браузера: открытие страниц плейлистов, альбомов и треков. Установим Selenium: pip install selenium Установим драйвер Chrome: sudo apt-get install chromium-chromedriver Chromedriver установится в /usr/lib/chromium-browser/chromedriver. Алгоритм получения данных об альбоме: Зайти на страницу со своими/чужими альбомами. Получить ID альбомов из массива Mu. Перейти на страницу альбома и найти в тегах название альбома и имя исполнителя. Создадим в корневой директории папку proglib, а в ней файл get_music_data.py. Импортируем Selenium и напишем функцию run_driver(), запускающую браузер: get_music_data.py from selenium import webdriver from selenium.webdriver.chrome.options import Options import os import re # функция, запускающая браузер def run_driver(): # включает возможность управления опциями браузера chrome_options = Options() # аргумент '--headless' запускает браузер без всплывающего окна chrome_options.add_argument('--headless') # аргумент '--log-level=3' скрывает логирование chrome_options.add_argument('--log-level=3') # запуск браузера driver = webdriver.Chrome('chromedriver', options=chrome_options) return driver Здесь: 'chromedriver' – путь к драйверу. Либо можем записать полный путь: '/usr/lib/chromium-browser/chromedriver'. Дальше идет небольшой велосипед. Дело в том, что Selenium при извлечении массива Mu со страницы ЯМ выдает ошибку: selenium.common.exceptions.StaleElementReferenceException: Message: stale element reference: stale element not found Пауза после загрузки, чтобы страница загрузилась до конца и листание в конец страницы не помогли, другие подходы – тоже. Оказалось, если сохранить страницу на локальной машине, то можно вытащить всю информацию из массива Mu без каких-либо проблем. Сделаем это. Алгоритм следующий: Обратиться к странице ЯМ. Создать локальную html-страницу и скопировать в нее содержимое страницы ЯМ. Извлечь массив Mu. Удалить локальную страницу. Напишем функцию create_local_html_page(), которая создает локальный html-файл и заполняет ее содержимым страницы из ЯМ: get_music_data.py # функция, создающая локальный html-файл def create_local_html_page(page_filename, page_source): with open(page_filename + '.html', 'w', encoding='UTF-8') as file: # запишем в локальный html-файл содержимое интернет-страницы ЯМ (page_source) file.write(page_source) Здесь: page_filename – название html-файла. page_source – код страницы ЯМ. Теперь напишем функцию get_local_html_page(), которая с помощью Selenium открывает локальную страницу: get_music_data.py # функция, открывающая локальный html-файл def get_local_html_page(yandex_username, page_filename, url): # запускаем браузер driver = run_driver() # браузер переходит на страницу Яндекс.Музыки driver.get(url) # создаем локальную html-страницу с содержимым из страницы Яндекс.Музыки create_local_html_page(page_filename, driver.page_source) # путь к локальной html-странице path_to_local_html_page = str('file://' + os.getcwd() + '/' + page_filename + '.html') # браузер переходит на локальную html-страницу driver.get(path_to_local_html_page) return driver Здесь: yandex_username – имя пользователя ЯМ. page_filename – название html-файла. url – ссылка на ЯМ. И, наконец, функция delete_local_html_page() удаляет локальную страницу: get_music_data.py # функция, удаляющая локальный html-файл def delete_local_html_page(page_path): os.remove(page_path) Здесь: page_path – путь к html-файлу. Получим название альбома и имя исполнителя. Создадим функцию get_albums(): get_music_data.py # функция, создающая словарь с названиями альбомов и именами исполнителей def get_albums(yandex_username): # создаем ссылку на альбом url = ''.join(['https://music.yandex.ru/users/', yandex_username, '/albums/']) # создадим и перейдем на локальную страницу с альбомом из раздела «Также вам понравились эти плейлисты» driver = get_local_html_page(yandex_username, 'album_page', url) # прочтем массив с данными плейлистов json = driver.execute_script('return Mu') # создадим словарь, в который запишем название альбома и имя исполнителя albums_for_spotify = {} # получим данные альбомов all_albums_ids = json['pageData']['albumIds'] В этом блоке кода мы: Создаем ссылку на страницу альбомов ЯМ пользователя. Создаем локальную страницу всех альбомов. Получаем всю информацию об альбомах из массива Mu. Получаем ID всех альбомов. Создаем словарь albums_for_spotify, в который запишем название альбома и имя исполнителя. get_music_data.py # заполним словарь albums_for_spotify for i in range(len(all_albums_ids)): try: # перейдем на страницу альбома driver.get(''.join(['https://music.yandex.ru/album/', str(all_albums_ids[i])])) # создадим для каждого альбома вложенный словарь albums_for_spotify[i] = {} # запишем во вложенный словарь название альбома albums_for_spotify[i]['album_title'] = driver.find_element_by_xpath( "//h1[@class='deco-typo']").text # запишем во вложенный словарь имя исполнителя albums_for_spotify[i]['artist_name'] = driver.find_element_by_xpath( "//span[@class='d-artists']//a[@class='d-link deco-link']").text except: pass # удалим локальный html-файл delete_local_html_page('album_page.html') # закроем браузер driver.quit() return albums_for_spotify Здесь происходит следующее: Переходим на страницу альбома ЯМ. Создаем вложенный словарь для каждого альбома, в который записываем название альбома и имя исполнителя. Метод xpath позволяет искать вложенные друг в друга теги и извлекать из них текст. С его помощью найдем в коде страницы тег <h1> с классом deco-typo. И получим текст, содержащийся в теге с помощью .text. Записываем в словарь название альбома и имя исполнителя. Удаляем локальную страницу со всеми альбомами ЯМ. Закрываем браузер В итоге словарь выглядит так: {0: {'album_title': 'The Essential Aerosmith', 'artist_name': 'Aerosmith'}, 1: {'album_title': 'Hyperion', 'artist_name': 'Gesaffelstein'}, ... driver.find_elements_by_xpath("//span[@class='d-artists']//a[@class='d-link deco-link']")[0].text – возвращает список, где первый элемент – имя первого исполнителя, если их несколько или единственного, если он записал трек соло. Данные получили, как их перенести? За перенос треков отвечает библиотека spotipy. Установим ее: pip install spotipy Для начала нужно создать приложение в Спотифай, получить client_id и client_secret. Зайдем на страницу developer.spotify.com/dashboard И кликнем на Create an app: Создание приложения в Spotify заполним поля и нажмем Create: После создания приложения кликнем по нему и найдем Client ID и Client Secret: Получение Client ID и Client Secret в приложении Spotify Зайдем в настройки приложения: и впишем в поле Redirect URIs адрес http://localhost:8888/callback/: Редактирование поля Redirect URIs в приложении Spotify Сохраним и закроем. Дальше нам понадобится второй файл transfer.py для авторизации в Spotify через библиотеку spotify и для переноса альбомов (а также плейлистов, но об этом позже). Запишем в transfer.py следующие строчки: transfer.py import spotipy from spotipy.oauth2 import SpotifyOAuth from spotipy import oauth2 from get_music_data import get_my_playlists, get_liked_playlists, get_albums # функция для авторизации в Spotify def autorisation(): client_id = '' client_secret = '' # разрешения нашего приложения scope = ('user-library-read, playlist-read-private, playlist-modify-private, playlist-modify-public, user-read-private, user-library-modify, user-library-read') # URL, на который переадресуется браузер пользователя после получения прав доступа при получении ключа доступа redirect_uri = 'http://localhost:8888/callback/' sp_oauth = oauth2.SpotifyOAuth(client_id, client_secret, redirect_uri, scope=scope) code = sp_oauth.get_auth_response(open_browser=True) # получаем токен token = sp_oauth.get_access_token(code, as_dict=False) sp = spotipy.Spotify(auth=token) # id пользователя Spotify username = sp.current_user()['id'] return sp, username Здесь: scope – права приложения. redirect_uri – ссылка, которая откроется (и сразу закроется) в браузере при получении прав доступа. username – имя пользователя в Спотифай. Напишем функцию get_album_id(), которая ищет в спотифай ID альбома: transfer.py # функция, получающая id альбома def get_album_id(query, sp): # получим данные по альбому из поискового запроса в Spotify album_id = sp.search(q=query, limit=1, type='album') # получим id альбома # split() – делает список, состоящий из одной строчки для метода current_user_saved_albums_add() return album_id['albums']['items'][0]['id'].split() Здесь: sp.search(q=query, limit=1, type='album') – поисковый запрос query выдает первый результат из раздела альбомы. В итоге функция возвращает ID альбома. А метод split делает из строки список, потому что метод current_user_saved_albums_add(), добавляющий в Спотифай альбом по ID, принимает в качестве аргумента именно список. Перенесем альбомы через функцию transfer_albums(): transfer.py # функция для переноса альбомов def transfer_albums(yandex_username, albums_for_spotify): # авторизуемся в Spotify sp, username = autorisation() # albums_for_spotify – альбомы для переноса из Яндекс.Музыки # содержит название трека и имя исполнитель for i in range(len(albums_for_spotify)): try: # получим название альбома album_title = albums_for_spotify[i]['album_title'] # получим имя исполнителя artist_name = albums_for_spotify[i]['artist_name'] # сформируем поисковый запрос в Spotify query = ' '.join([artist_name, album_title]) # получим id альбома в Spotify album_id = get_album_id(query, sp) # добавим альбом в свою фонотеку sp.current_user_saved_albums_add(album_id) except: pass Здесь: sp, username = autorisation() – авторизация в Спотифай. query = ' '.join([artist_name, album_title]) – формируем поисковый запрос из названия альбома и имени исполнителя. get_album_id(query, sp) – ищем в Спотифай альбом по ID. sp.current_user_saved_albums_add(album_id) – добавляем альбом в свою медиатеку. except: pass – если альбом не найден (функция get_album_id вернет нам IndexError или KeyError), то пропустить и начать сначала. Переносим плейлисты Плейлисты делятся на два вида: Плейлист «Мне понравилось» и созданные пользователем плейлисты. Лайкнутые плейлисты. Мои плейлисты Алгоритм: Перейти на странцу всех плелистов пользователя https://music.yandex.ru/users/yandex_username/playlists/. Получить ID плейлистов из массива Mu. Перейти на страницу плейлиста по его ID. Получить со страницы плейлиста ID треков из массива Mu. Перейти на страницу каждого трека и получить их названия и имена исполнителей из тегов. Вернемся к файлу get_music_data.py и добавим в него функцию get_my_playlists_id для получения ID альбомов на ЯМ: get_music_data.py # функция, получающая id плейлистов def get_my_playlists_id(yandex_username): # создадим ссылку на плейлист url = ''.join(['https://music.yandex.ru/users/', yandex_username, '/playlists/']) # создадим и перейдем на локальную страницу плейлиста driver = get_local_html_page(yandex_username, 'playlist_page', url) # получим массив с данными плейлистов json = driver.execute_script('return Mu') # получим id плейлистов my_playlists_id = json['pageData']['playlistIds'] return my_playlists_id Также как с альбомами: переходим на страницу всех плейлистов, извлекаем данные из массива Mu, находим ключ playlistIds с ID всех плейлистов. Напишем функцию get_my_playlists для переноса своих плейлистов: get_music_data.py # функция, создающая словарь с названиями личных плейлистов, треков и именами исполнителей def get_my_playlists(yandex_username): # получим id плейлистов my_playlists_id = get_my_playlists_id(yandex_username) # создадим словарь, в который запишем название плейлистов, треков и имена исполнителей my_playlists_for_spotify = {} # заполним словарь my_playlists_for_spotify for i in range(len(my_playlists_id)): # создадим ссылку на плейлист my_playlist_url = ''.join(['https://music.yandex.ru/users/', yandex_username, '/playlists/', str(my_playlists_id[i])]) # создадим и перейдем на локальную страницу плейлиста driver = get_local_html_page(yandex_username, 'my_playlist', my_playlist_url) # получим массив с данными плейлистов json = driver.execute_script('return Mu') # создадим словарь, в который запишем название трека и имя исполнителя my_playlists = {} # получим из json значение id трека и id плейлиста в формате track_id:playlist_id all_track_ids = json['pageData']['playlist']['trackIds'] # получим название плейлиста playlist_name = json['pageData']['playlist']['title'] В этой части кода мы получаем ID всех наших плейлистов и записываем в словарь my_playlists, их ID и названия. Теперь получим названия трека и имя исполнителя: get_music_data.py # заполним словарь my_playlists for j in range(len(all_track_ids)): # получим id трека track_id = re.findall(r'd+(?=:)', all_track_ids[j])[0] # перейдем на страницу трека driver.get(''.join(['https://music.yandex.ru/track/', track_id])) # создадим вложенный словарь для каждого плейлиста my_playlists[j] = {} # запишем в словарь название трека try: my_playlists[j]['track_name'] = driver.find_elements_by_xpath("//span[@class='']//a[@class='d-link deco-link']")[0].text except: my_playlists[j]['track_name'] = [''] # запишем в словарь имя исполнителя try: my_playlists[j]['artist_name'] = driver.find_elements_by_xpath( "//span[@class='d-artists']//a[@class='d-link deco-link']")[0].text except: my_playlists[j]['track_name'] = [''] # запишем в словарь название плейлиста, его треки и имена исполнителей my_playlists_for_spotify[playlist_name] = my_playlists # удалим локальный html-файл delete_local_html_page('my_playlist.html') # закроем браузер driver.quit() return my_playlists_for_spotify Здесь: track_id = re.findall(r'd+(?=:)', all_track_ids[j])[0] – all_track_ids имеет вид ID_трека:ID_плейлиста. Используем регулярное выражение, чтобы получить все числа до двоеточия – ID трека. В итоге словарь my_playlists_for_spotify выглядит так: {'Мне нравится': {0: {'track_name': 'Memory (From "Cats")', 'artist_name': 'London Theatre Orchestra'}, 1: {'track_name': 'All By Myself', 'artist_name': 'Céline Dion'}, ... Теперь перенесем плейлисты в Спотифай. Откроем файл transfer.py и напишем функцию get_track_id() для поиска ID трека в Спотифай: transfer.py # функция, получающая id трека def get_track_id(query, sp): # получаем данные по первому треку из поисковой выдачи Spotify track_id = sp.search(q=query, limit=1, type='track') # Теперь найдем id первого трека из поисковой выдачи. # метод split() сделает список из одной строчки.eя # Это нужно для метода playlist_add_items(), принимающего в качестве второго аргумента список. return track_id['tracks']['items'][0]['id'].split() sp.search(q=query, limit=1, type='track') – получаем первый результат поискового запроса query. Напишем функцию transfer_playlists, которая будет переносить как плейлисты пользователя, так и лайкнутые: transfer.py # функция, переносящая плелисты def transfer_playlists(yandex_username, playlists_for_spotify): # авторизуемся в Spotify sp, username = autorisation() # playlists_for_spotify – плейлисты из Яндекс Музыки (название и исполнитель) # плейлисты берем из функций get_liked_playlists() и get_my_playlists() # создадим плейлисты for i in range(len(playlists_for_spotify)): # сделаем список из ключей/названий плейлистов playlist_name = list(playlists_for_spotify.keys())[i] # создадим в Spotify плейлист с именем (playlist_name) create_spotify_playlist = sp.user_playlist_create(username, playlist_name) # получим id созданного плейлиста new_spotify_playlist_id = create_spotify_playlist['id'] # number_of_tracks – количество треков в плейлисте playlist_name number_of_tracks = range(len(playlists_for_spotify[playlist_name])) new_spotify_playlist = {} create_spotify_playlist – создает пустой плейлист в Спотифай с названием из ЯМ. number_of_tracks – количество треков в плейлисте. transfer.py # добавим песни в плейлист Spotify for j in number_of_tracks: try: # получим имя исполнителя artist_name = playlists_for_spotify[playlist_name][j]['artist_name'] # получим название трека track_name = playlists_for_spotify[playlist_name][j]['track_name'] # query – поисковый запрос в Spotify, состоящий из имени исполнителя (artist_name), пробела (' ') и названия трека (track_name) query = ' '.join([artist_name, track_name]) # получим id найденного трека в Spotify spotify_track_id = get_track_id(query, sp) # добавим в словарь id трека (ключ) и id плейлиста (значение) new_spotify_playlist[spotify_track_id[0]] = new_spotify_playlist_id except: pass # если плейлист пустой (треки отсутствуют в каталоге Spotify), то удалить его из Spotify if all(query == '' for query in new_spotify_playlist.values()): sp.user_playlist_unfollow(username, new_spotify_playlist_id) continue # если в каталоге есть хотя бы один трек, то добавить трек(и) в плейлист Spotify else: for new_spotify_playlist_id, track_id in new_spotify_playlist.items(): sp.playlist_add_items(track_id, new_spotify_playlist_id.split()) Здесь мы ищем в Спотифай треки и записываем их ID и название плейлиста в словарь new_spotify_playlist. if all(query == '' for query in new_spotify_playlist.values()) – если все значения словаря пустые, значит треки отсутствуют в каталоге Спотифай, например, подкасты или локальные исполнители. sp.user_playlist_unfollow(username, new_spotify_playlist_id) – удаляет пустой плейлист из Спотифай. Лайкнутые плейлисты Лайкнутые плейлисты создали другие пользователи, но мы можем их перенести также как свои плейлсты. Откроем файл get_music_data и напишем функцию get_liked_playlists_data(), которая создает словарь с ID лайкнутых плейлистов, их названиями и именами пользователей, создавших плейлист. get_music_data.py # функция, создающая словарь с id лайкнутых плейлистов, их названиями # и именами пользователя, создавших плейлист def get_liked_playlists_data(yandex_username): # создадим ссылку на плейлист url = ''.join(['https://music.yandex.ru/users/', yandex_username, '/playlists/']) # создадим и перейдем на локальную страницу с плейлистами из раздела «Также вам понравились эти плейлисты» driver = get_local_html_page(yandex_username, 'playlist_page', url) # получим массив с данными плейлистов json = driver.execute_script('return Mu') # создадим словарь, в который запишем имя пользователя, создавшего плейлист, id и название плейлиста liked_playlists_data = {} # получим все данные об избранных плейлистах bookmarked_playlists_data = json['pageData']['bookmarks'] # заполним словарь liked_playlists_data for i in range(len(bookmarked_playlists_data)): try: # создадим вложенный словарь для каждого плейлиста liked_playlists_data[i] = {} # запишем во вложенный словарь id плейлиста liked_playlists_data[i]['id'] = bookmarked_playlists_data[i]['kind'] # запишем во вложенный словарь имя пользователя liked_playlists_data[i]['yandex_username'] = bookmarked_playlists_data[i]['owner']['login'] # запишем во вложенный словарь название плейлиста liked_playlists_data[i]['playlist_title'] = bookmarked_playlists_data[i]['title'] except: pass # удалим локальный html-файл delete_local_html_page('playlist_page.html') return liked_playlists_data Здесь мы переходим на страницу своих плейлистов, извлекаем содержимое из массива Mu и создаем словарь с ID плейлиста, его названием и именем пользователя создавшего плейлист. Теперь напишем функцию get_liked_playlists(), которая переходит на страничку лайкнутых плейлистов и получает названия треков и исполнителей get_music_data.py # функция, которая переходит на страничку лайкнутых плейлистов и получает названия треков и исполнителей def get_liked_playlists(yandex_username): # получим id плейлистов, их названия и имя пользователя, создавшего плейлист liked_playlists_data = get_liked_playlists_data(yandex_username) # создадим словарь, в который запишем название плейлиста, треков и имя исполнителя liked_playlists_for_spotify = {} for key, value in liked_playlists_data.items(): # ключ – порядковый номер плейлиста в словаре liked_playlists_data, а значения – имя пользователя и id плейлиста # создадим ссылку на плейлист пользователя url = ''.join(['https://music.yandex.ru/users/', str(value['yandex_username']), '/playlists/', str(value['id'])]) # создадим и перейдем на локальную html-страницу плейлиста driver = get_local_html_page(yandex_username, str(key), url) # получим массив с данными плейлистов json = driver.execute_script('return Mu') # создадим словарь, в который запишем треки и имя исполнителей liked_playlists = {} # получим из json значение id трека и id альбома в формате track_id:album_id all_track_ids = json['pageData']['playlist']['trackIds'] # получим название плейлиста playlist_name = json['pageData']['playlist']['title'] В этом блоке кода мы: Переходим на страницу плейлиста. Получаем ID треков в формате ID_трека:ID_альбома. get_music_data.py # запишем в словарь треки и имена исполнителей for i in range(len(all_track_ids)): try: # отфильтруем только id трека track_id = re.findall(r'd+(?=:)', all_track_ids[i])[0] # перейдем на страницу трека driver.get(''.join(['https://music.yandex.ru/track/', track_id])) # создадим вложенный словарь для каждого плейлиста liked_playlists[i] = {} # получим название трека из боковой панели try: liked_playlists[i]['track_name'] = driver.find_elements_by_xpath("//span[@class='']//a[@class='d-link deco-link']")[0].text except: liked_playlists[i]['track_name'] = [''] # получим имя исполнителя из боковой панели try: liked_playlists[i]['artist_name'] = driver.find_elements_by_xpath( "//span[@class='d-artists']//a[@class='d-link deco-link']")[0].text except: liked_playlists[i]['artist_name'] = [''] except: pass # запишем в словарь название плейлиста, его треки и имена исполнителей liked_playlists_for_spotify[playlist_name] = liked_playlists # удалим локальный html-файл delete_local_html_page(str(key) + '.html') # закроем браузер driver.quit() return liked_playlists_for_spotify Здесь: С помощью регулярного выражения получаем ID трека. Переходим на страницу трека. Получаем название трека и имя исполнителя. Записываем в словарь название плейлиста, его треки и имена исполнителей. Лайкнутые плейлисты переносим через функцию transfer_playlsits(). Запускаем миграцию музыки В конце файла transfer.py напишем функцию main(), которая перенесет всю фонотеку в Спотифай. transfer.py def main(yandex_username): # перенос плейлиста «Мне нравится» и личных плейлистов #transfer_playlists(yandex_username, get_my_playlists(yandex_username)) # перенос лайкнутых плейлистов #transfer_playlists(yandex_username, get_liked_playlists(yandex_username)) # перенос альбомов transfer_albums(yandex_username, get_albums(yandex_username)) if __name__ == "__main__": yandex_username = 'имя пользователя Яндекс.Музыки' main(yandex_username) GitHub Весь код с выводом на экран процесса переноса доступен в репозитории yandex-music-to-spotify на GitHub. Плейлисты переносятся долго, поэтому попробуйте сначала перенести альбомы, чтобы посмотреть, как работает скрипт. Альбомы переносятся так: *** Мы написали скрипт для переноса всей фонотеки из Яндекс.Музыки в Спотифай и научились: запускать браузер с помощью библиотеки Selenium; извлекать информацию из переменных JavaScript и из html-тегов; автоматизировать процесс получения данных; прокачали навык работы со словарями Python. Материалы по теме: — Осваиваем парсинг сайта: короткий туториал на Python — Инструменты дата-журналиста #2: веб-скрапинг, парсинг и визуализация данных — Веб-скрапинг по расписанию с Django и Heroku
Возможности скрипта:
Единственное условие: фонотека Яндекс.Музыки должна быть открытой. ***
Первую часть пути мы пройдем окольным путем: чтобы чему-то научиться откажемся от использования неофициальной библиотеки yandex-music-api и получим данные своими руками. Во второй части пути к нам на помощь придет библиотека spotipy, которая перенесет в Спотифай все личные плейлисты, лайкнутые треки и альбомы. Поехали!
yandex-music-api
У Яндекс.Музыки (ЯМ) нет API. Совсем. Поэтому данные нам придется брать со страниц ЯМ. Перейдем на свою страницу ЯМ в раздел альбомы и начнем поиск информации, открыв инструменты разработчика (Ctrl + Shift + i в Chrome и FireFox).
Ctrl + Shift + i
В теле страницы с альбомами есть JSON-массив Mu, который в двух ключах содержит все, что нам нужно знать об альбоме: его ID, название и имя исполнителя:
Mu
C массивом Mu будем работать постоянно, так как в нем содержится самое ценное – ID альбомов, плейлистов и треков, по которому мы получаем всю необходимую информацию. Поэтому ID будет нашей целью на всем пути следования.
albumIds
albums
JSON Яндекс.Музыки
Мы воспользуемся первым ключом, чтобы перенести все альбомы.
Через функцию pprint:
pprint
from pprint import pprint pprint(json)
Получив ID, перейдем на страницу альбома https://music.yandex.ru/album/ID_альбома. Из тегов узнаем название альбома и имя исполнителя. Допустим, в нашей фонотеке есть альбом «Greatest Hits In Japan» – Queen. Откроем страницу альбома.
https://music.yandex.ru/album/ID_альбома
В браузере перейдем в инструменты разработчика и выберем инструмент Селектор (Ctrl + Shift + C):
Ctrl + Shift + C
И просто кликнем по названию альбома на странице:
Тег <h1> с названием альбома найдется автоматически:
<h1>
Тег <a> с именем исполнителя:
<a>
Библиотека Selenium умеет управлять браузером и часто используется для автоматизации тестирований. С помощью Selenium мы автоматизируем действия браузера: открытие страниц плейлистов, альбомов и треков.
Selenium
Установим Selenium:
pip install selenium
Установим драйвер Chrome:
sudo apt-get install chromium-chromedriver
Chromedriver установится в /usr/lib/chromium-browser/chromedriver.
/usr/lib/chromium-browser/chromedriver.
Алгоритм получения данных об альбоме:
Создадим в корневой директории папку proglib, а в ней файл get_music_data.py.
proglib
get_music_data.py
Импортируем Selenium и напишем функцию run_driver(), запускающую браузер:
run_driver()
from selenium import webdriver from selenium.webdriver.chrome.options import Options import os import re # функция, запускающая браузер def run_driver(): # включает возможность управления опциями браузера chrome_options = Options() # аргумент '--headless' запускает браузер без всплывающего окна chrome_options.add_argument('--headless') # аргумент '--log-level=3' скрывает логирование chrome_options.add_argument('--log-level=3') # запуск браузера driver = webdriver.Chrome('chromedriver', options=chrome_options) return driver
Здесь:
'chromedriver'
'/usr/lib/chromium-browser/chromedriver'
Дальше идет небольшой велосипед. Дело в том, что Selenium при извлечении массива Mu со страницы ЯМ выдает ошибку:
selenium.common.exceptions.StaleElementReferenceException: Message: stale element reference: stale element not found
Пауза после загрузки, чтобы страница загрузилась до конца и листание в конец страницы не помогли, другие подходы – тоже. Оказалось, если сохранить страницу на локальной машине, то можно вытащить всю информацию из массива Mu без каких-либо проблем. Сделаем это.
Алгоритм следующий:
Напишем функцию create_local_html_page(), которая создает локальный html-файл и заполняет ее содержимым страницы из ЯМ:
create_local_html_page()
# функция, создающая локальный html-файл def create_local_html_page(page_filename, page_source): with open(page_filename + '.html', 'w', encoding='UTF-8') as file: # запишем в локальный html-файл содержимое интернет-страницы ЯМ (page_source) file.write(page_source)
page_filename
page_source
Теперь напишем функцию get_local_html_page(), которая с помощью Selenium открывает локальную страницу:
get_local_html_page()
# функция, открывающая локальный html-файл def get_local_html_page(yandex_username, page_filename, url): # запускаем браузер driver = run_driver() # браузер переходит на страницу Яндекс.Музыки driver.get(url) # создаем локальную html-страницу с содержимым из страницы Яндекс.Музыки create_local_html_page(page_filename, driver.page_source) # путь к локальной html-странице path_to_local_html_page = str('file://' + os.getcwd() + '/' + page_filename + '.html') # браузер переходит на локальную html-страницу driver.get(path_to_local_html_page) return driver
yandex_username
url
И, наконец, функция delete_local_html_page() удаляет локальную страницу:
delete_local_html_page()
# функция, удаляющая локальный html-файл def delete_local_html_page(page_path): os.remove(page_path)
page_path
Получим название альбома и имя исполнителя. Создадим функцию get_albums():
get_albums()
# функция, создающая словарь с названиями альбомов и именами исполнителей def get_albums(yandex_username): # создаем ссылку на альбом url = ''.join(['https://music.yandex.ru/users/', yandex_username, '/albums/']) # создадим и перейдем на локальную страницу с альбомом из раздела «Также вам понравились эти плейлисты» driver = get_local_html_page(yandex_username, 'album_page', url) # прочтем массив с данными плейлистов json = driver.execute_script('return Mu') # создадим словарь, в который запишем название альбома и имя исполнителя albums_for_spotify = {} # получим данные альбомов all_albums_ids = json['pageData']['albumIds']
В этом блоке кода мы:
albums_for_spotify
# заполним словарь albums_for_spotify for i in range(len(all_albums_ids)): try: # перейдем на страницу альбома driver.get(''.join(['https://music.yandex.ru/album/', str(all_albums_ids[i])])) # создадим для каждого альбома вложенный словарь albums_for_spotify[i] = {} # запишем во вложенный словарь название альбома albums_for_spotify[i]['album_title'] = driver.find_element_by_xpath( "//h1[@class='deco-typo']").text # запишем во вложенный словарь имя исполнителя albums_for_spotify[i]['artist_name'] = driver.find_element_by_xpath( "//span[@class='d-artists']//a[@class='d-link deco-link']").text except: pass # удалим локальный html-файл delete_local_html_page('album_page.html') # закроем браузер driver.quit() return albums_for_spotify
Здесь происходит следующее:
xpath
deco-typo
.text
В итоге словарь выглядит так:
{0: {'album_title': 'The Essential Aerosmith', 'artist_name': 'Aerosmith'}, 1: {'album_title': 'Hyperion', 'artist_name': 'Gesaffelstein'}, ...
driver.find_elements_by_xpath("//span[@class='d-artists']//a[@class='d-link deco-link']")[0].text – возвращает список, где первый элемент – имя первого исполнителя, если их несколько или единственного, если он записал трек соло.
driver.find_elements_by_xpath("//span[@class='d-artists']//a[@class='d-link deco-link']")[0].text
За перенос треков отвечает библиотека spotipy. Установим ее:
pip install spotipy
Для начала нужно создать приложение в Спотифай, получить client_id и client_secret. Зайдем на страницу developer.spotify.com/dashboard
client_id
client_secret
И кликнем на Create an app:
Create an app
Создание приложения в Spotify
заполним поля и нажмем Create:
Create
После создания приложения кликнем по нему и найдем Client ID и Client Secret:
Client ID
Client Secret
Получение Client ID и Client Secret в приложении Spotify
Зайдем в настройки приложения:
и впишем в поле Redirect URIs адрес http://localhost:8888/callback/:
Redirect URIs
http://localhost:8888/callback/
Редактирование поля Redirect URIs в приложении Spotify
Сохраним и закроем.
Дальше нам понадобится второй файл transfer.py для авторизации в Spotify через библиотеку spotify и для переноса альбомов (а также плейлистов, но об этом позже).
transfer.py
Spotify
spotify
Запишем в transfer.py следующие строчки:
import spotipy from spotipy.oauth2 import SpotifyOAuth from spotipy import oauth2 from get_music_data import get_my_playlists, get_liked_playlists, get_albums # функция для авторизации в Spotify def autorisation(): client_id = '' client_secret = '' # разрешения нашего приложения scope = ('user-library-read, playlist-read-private, playlist-modify-private, playlist-modify-public, user-read-private, user-library-modify, user-library-read') # URL, на который переадресуется браузер пользователя после получения прав доступа при получении ключа доступа redirect_uri = 'http://localhost:8888/callback/' sp_oauth = oauth2.SpotifyOAuth(client_id, client_secret, redirect_uri, scope=scope) code = sp_oauth.get_auth_response(open_browser=True) # получаем токен token = sp_oauth.get_access_token(code, as_dict=False) sp = spotipy.Spotify(auth=token) # id пользователя Spotify username = sp.current_user()['id'] return sp, username
scope
redirect_uri
username
Напишем функцию get_album_id(), которая ищет в спотифай ID альбома:
get_album_id()
# функция, получающая id альбома def get_album_id(query, sp): # получим данные по альбому из поискового запроса в Spotify album_id = sp.search(q=query, limit=1, type='album') # получим id альбома # split() – делает список, состоящий из одной строчки для метода current_user_saved_albums_add() return album_id['albums']['items'][0]['id'].split()
sp.search(q=query, limit=1, type='album')
split
current_user_saved_albums_add()
Перенесем альбомы через функцию transfer_albums():
transfer_albums()
# функция для переноса альбомов def transfer_albums(yandex_username, albums_for_spotify): # авторизуемся в Spotify sp, username = autorisation() # albums_for_spotify – альбомы для переноса из Яндекс.Музыки # содержит название трека и имя исполнитель for i in range(len(albums_for_spotify)): try: # получим название альбома album_title = albums_for_spotify[i]['album_title'] # получим имя исполнителя artist_name = albums_for_spotify[i]['artist_name'] # сформируем поисковый запрос в Spotify query = ' '.join([artist_name, album_title]) # получим id альбома в Spotify album_id = get_album_id(query, sp) # добавим альбом в свою фонотеку sp.current_user_saved_albums_add(album_id) except: pass
sp, username = autorisation()
query = ' '.join([artist_name, album_title])
get_album_id(query, sp)
sp.current_user_saved_albums_add(album_id)
except: pass
get_album_id
IndexError
KeyError
Плейлисты делятся на два вида:
Алгоритм:
https://music.yandex.ru/users/yandex_username/playlists/
Вернемся к файлу get_music_data.py и добавим в него функцию get_my_playlists_id для получения ID альбомов на ЯМ:
get_my_playlists_id
# функция, получающая id плейлистов def get_my_playlists_id(yandex_username): # создадим ссылку на плейлист url = ''.join(['https://music.yandex.ru/users/', yandex_username, '/playlists/']) # создадим и перейдем на локальную страницу плейлиста driver = get_local_html_page(yandex_username, 'playlist_page', url) # получим массив с данными плейлистов json = driver.execute_script('return Mu') # получим id плейлистов my_playlists_id = json['pageData']['playlistIds'] return my_playlists_id
Также как с альбомами: переходим на страницу всех плейлистов, извлекаем данные из массива Mu, находим ключ playlistIds с ID всех плейлистов.
playlistIds
Напишем функцию get_my_playlists для переноса своих плейлистов:
get_my_playlists
# функция, создающая словарь с названиями личных плейлистов, треков и именами исполнителей def get_my_playlists(yandex_username): # получим id плейлистов my_playlists_id = get_my_playlists_id(yandex_username) # создадим словарь, в который запишем название плейлистов, треков и имена исполнителей my_playlists_for_spotify = {} # заполним словарь my_playlists_for_spotify for i in range(len(my_playlists_id)): # создадим ссылку на плейлист my_playlist_url = ''.join(['https://music.yandex.ru/users/', yandex_username, '/playlists/', str(my_playlists_id[i])]) # создадим и перейдем на локальную страницу плейлиста driver = get_local_html_page(yandex_username, 'my_playlist', my_playlist_url) # получим массив с данными плейлистов json = driver.execute_script('return Mu') # создадим словарь, в который запишем название трека и имя исполнителя my_playlists = {} # получим из json значение id трека и id плейлиста в формате track_id:playlist_id all_track_ids = json['pageData']['playlist']['trackIds'] # получим название плейлиста playlist_name = json['pageData']['playlist']['title']
В этой части кода мы получаем ID всех наших плейлистов и записываем в словарь my_playlists, их ID и названия.
my_playlists
Теперь получим названия трека и имя исполнителя:
# заполним словарь my_playlists for j in range(len(all_track_ids)): # получим id трека track_id = re.findall(r'd+(?=:)', all_track_ids[j])[0] # перейдем на страницу трека driver.get(''.join(['https://music.yandex.ru/track/', track_id])) # создадим вложенный словарь для каждого плейлиста my_playlists[j] = {} # запишем в словарь название трека try: my_playlists[j]['track_name'] = driver.find_elements_by_xpath("//span[@class='']//a[@class='d-link deco-link']")[0].text except: my_playlists[j]['track_name'] = [''] # запишем в словарь имя исполнителя try: my_playlists[j]['artist_name'] = driver.find_elements_by_xpath( "//span[@class='d-artists']//a[@class='d-link deco-link']")[0].text except: my_playlists[j]['track_name'] = [''] # запишем в словарь название плейлиста, его треки и имена исполнителей my_playlists_for_spotify[playlist_name] = my_playlists # удалим локальный html-файл delete_local_html_page('my_playlist.html') # закроем браузер driver.quit() return my_playlists_for_spotify
track_id = re.findall(r'd+(?=:)', all_track_ids[j])[0]
all_track_ids
ID_трека:ID_плейлиста
В итоге словарь my_playlists_for_spotify выглядит так:
my_playlists_for_spotify
{'Мне нравится': {0: {'track_name': 'Memory (From "Cats")', 'artist_name': 'London Theatre Orchestra'}, 1: {'track_name': 'All By Myself', 'artist_name': 'Céline Dion'}, ...
Теперь перенесем плейлисты в Спотифай. Откроем файл transfer.py и напишем функцию get_track_id() для поиска ID трека в Спотифай:
get_track_id()
# функция, получающая id трека def get_track_id(query, sp): # получаем данные по первому треку из поисковой выдачи Spotify track_id = sp.search(q=query, limit=1, type='track') # Теперь найдем id первого трека из поисковой выдачи. # метод split() сделает список из одной строчки.eя # Это нужно для метода playlist_add_items(), принимающего в качестве второго аргумента список. return track_id['tracks']['items'][0]['id'].split()
sp.search(q=query, limit=1, type='track') – получаем первый результат поискового запроса query.
sp.search(q=query, limit=1, type='track')
query
Напишем функцию transfer_playlists, которая будет переносить как плейлисты пользователя, так и лайкнутые:
transfer_playlists
# функция, переносящая плелисты def transfer_playlists(yandex_username, playlists_for_spotify): # авторизуемся в Spotify sp, username = autorisation() # playlists_for_spotify – плейлисты из Яндекс Музыки (название и исполнитель) # плейлисты берем из функций get_liked_playlists() и get_my_playlists() # создадим плейлисты for i in range(len(playlists_for_spotify)): # сделаем список из ключей/названий плейлистов playlist_name = list(playlists_for_spotify.keys())[i] # создадим в Spotify плейлист с именем (playlist_name) create_spotify_playlist = sp.user_playlist_create(username, playlist_name) # получим id созданного плейлиста new_spotify_playlist_id = create_spotify_playlist['id'] # number_of_tracks – количество треков в плейлисте playlist_name number_of_tracks = range(len(playlists_for_spotify[playlist_name])) new_spotify_playlist = {}
create_spotify_playlist – создает пустой плейлист в Спотифай с названием из ЯМ.
create_spotify_playlist
number_of_tracks – количество треков в плейлисте.
number_of_tracks
# добавим песни в плейлист Spotify for j in number_of_tracks: try: # получим имя исполнителя artist_name = playlists_for_spotify[playlist_name][j]['artist_name'] # получим название трека track_name = playlists_for_spotify[playlist_name][j]['track_name'] # query – поисковый запрос в Spotify, состоящий из имени исполнителя (artist_name), пробела (' ') и названия трека (track_name) query = ' '.join([artist_name, track_name]) # получим id найденного трека в Spotify spotify_track_id = get_track_id(query, sp) # добавим в словарь id трека (ключ) и id плейлиста (значение) new_spotify_playlist[spotify_track_id[0]] = new_spotify_playlist_id except: pass # если плейлист пустой (треки отсутствуют в каталоге Spotify), то удалить его из Spotify if all(query == '' for query in new_spotify_playlist.values()): sp.user_playlist_unfollow(username, new_spotify_playlist_id) continue # если в каталоге есть хотя бы один трек, то добавить трек(и) в плейлист Spotify else: for new_spotify_playlist_id, track_id in new_spotify_playlist.items(): sp.playlist_add_items(track_id, new_spotify_playlist_id.split())
new_spotify_playlist
if all(query == '' for query in new_spotify_playlist.values())
sp.user_playlist_unfollow(username, new_spotify_playlist_id)
Лайкнутые плейлисты создали другие пользователи, но мы можем их перенести также как свои плейлсты. Откроем файл get_music_data и напишем функцию get_liked_playlists_data(), которая создает словарь с ID лайкнутых плейлистов, их названиями и именами пользователей, создавших плейлист.
get_music_data
get_liked_playlists_data()
# функция, создающая словарь с id лайкнутых плейлистов, их названиями # и именами пользователя, создавших плейлист def get_liked_playlists_data(yandex_username): # создадим ссылку на плейлист url = ''.join(['https://music.yandex.ru/users/', yandex_username, '/playlists/']) # создадим и перейдем на локальную страницу с плейлистами из раздела «Также вам понравились эти плейлисты» driver = get_local_html_page(yandex_username, 'playlist_page', url) # получим массив с данными плейлистов json = driver.execute_script('return Mu') # создадим словарь, в который запишем имя пользователя, создавшего плейлист, id и название плейлиста liked_playlists_data = {} # получим все данные об избранных плейлистах bookmarked_playlists_data = json['pageData']['bookmarks'] # заполним словарь liked_playlists_data for i in range(len(bookmarked_playlists_data)): try: # создадим вложенный словарь для каждого плейлиста liked_playlists_data[i] = {} # запишем во вложенный словарь id плейлиста liked_playlists_data[i]['id'] = bookmarked_playlists_data[i]['kind'] # запишем во вложенный словарь имя пользователя liked_playlists_data[i]['yandex_username'] = bookmarked_playlists_data[i]['owner']['login'] # запишем во вложенный словарь название плейлиста liked_playlists_data[i]['playlist_title'] = bookmarked_playlists_data[i]['title'] except: pass # удалим локальный html-файл delete_local_html_page('playlist_page.html') return liked_playlists_data
Здесь мы переходим на страницу своих плейлистов, извлекаем содержимое из массива Mu и создаем словарь с ID плейлиста, его названием и именем пользователя создавшего плейлист.
Теперь напишем функцию get_liked_playlists(), которая переходит на страничку лайкнутых плейлистов и получает названия треков и исполнителей
get_liked_playlists()
# функция, которая переходит на страничку лайкнутых плейлистов и получает названия треков и исполнителей def get_liked_playlists(yandex_username): # получим id плейлистов, их названия и имя пользователя, создавшего плейлист liked_playlists_data = get_liked_playlists_data(yandex_username) # создадим словарь, в который запишем название плейлиста, треков и имя исполнителя liked_playlists_for_spotify = {} for key, value in liked_playlists_data.items(): # ключ – порядковый номер плейлиста в словаре liked_playlists_data, а значения – имя пользователя и id плейлиста # создадим ссылку на плейлист пользователя url = ''.join(['https://music.yandex.ru/users/', str(value['yandex_username']), '/playlists/', str(value['id'])]) # создадим и перейдем на локальную html-страницу плейлиста driver = get_local_html_page(yandex_username, str(key), url) # получим массив с данными плейлистов json = driver.execute_script('return Mu') # создадим словарь, в который запишем треки и имя исполнителей liked_playlists = {} # получим из json значение id трека и id альбома в формате track_id:album_id all_track_ids = json['pageData']['playlist']['trackIds'] # получим название плейлиста playlist_name = json['pageData']['playlist']['title']
ID_трека:ID_альбома
# запишем в словарь треки и имена исполнителей for i in range(len(all_track_ids)): try: # отфильтруем только id трека track_id = re.findall(r'd+(?=:)', all_track_ids[i])[0] # перейдем на страницу трека driver.get(''.join(['https://music.yandex.ru/track/', track_id])) # создадим вложенный словарь для каждого плейлиста liked_playlists[i] = {} # получим название трека из боковой панели try: liked_playlists[i]['track_name'] = driver.find_elements_by_xpath("//span[@class='']//a[@class='d-link deco-link']")[0].text except: liked_playlists[i]['track_name'] = [''] # получим имя исполнителя из боковой панели try: liked_playlists[i]['artist_name'] = driver.find_elements_by_xpath( "//span[@class='d-artists']//a[@class='d-link deco-link']")[0].text except: liked_playlists[i]['artist_name'] = [''] except: pass # запишем в словарь название плейлиста, его треки и имена исполнителей liked_playlists_for_spotify[playlist_name] = liked_playlists # удалим локальный html-файл delete_local_html_page(str(key) + '.html') # закроем браузер driver.quit() return liked_playlists_for_spotify
Лайкнутые плейлисты переносим через функцию transfer_playlsits().
transfer_playlsits()
В конце файла transfer.py напишем функцию main(), которая перенесет всю фонотеку в Спотифай.
main()
def main(yandex_username): # перенос плейлиста «Мне нравится» и личных плейлистов #transfer_playlists(yandex_username, get_my_playlists(yandex_username)) # перенос лайкнутых плейлистов #transfer_playlists(yandex_username, get_liked_playlists(yandex_username)) # перенос альбомов transfer_albums(yandex_username, get_albums(yandex_username)) if __name__ == "__main__": yandex_username = 'имя пользователя Яндекс.Музыки' main(yandex_username)
GitHub Весь код с выводом на экран процесса переноса доступен в репозитории yandex-music-to-spotify на GitHub. Плейлисты переносятся долго, поэтому попробуйте сначала перенести альбомы, чтобы посмотреть, как работает скрипт.
Альбомы переносятся так:
***
Мы написали скрипт для переноса всей фонотеки из Яндекс.Музыки в Спотифай и научились:
— Осваиваем парсинг сайта: короткий туториал на Python
— Инструменты дата-журналиста #2: веб-скрапинг, парсинг и визуализация данных
— Веб-скрапинг по расписанию с Django и Heroku
ΠΠ°Ρ Π°Π΄ΡΠ΅Ρ email Π½Π΅ Π±ΡΠ΄Π΅Ρ ΠΎΠΏΡΠ±Π»ΠΈΠΊΠΎΠ²Π°Π½. ΠΠ±ΡΠ·Π°ΡΠ΅Π»ΡΠ½ΡΠ΅ ΠΏΠΎΠ»Ρ ΠΏΠΎΠΌΠ΅ΡΠ΅Π½Ρ *
Π‘ΠΎΡ ΡΠ°Π½ΠΈΡΡ ΠΌΠΎΡ ΠΈΠΌΡ, email ΠΈ Π°Π΄ΡΠ΅Ρ ΡΠ°ΠΉΡΠ° Π² ΡΡΠΎΠΌ Π±ΡΠ°ΡΠ·Π΅ΡΠ΅ Π΄Π»Ρ ΠΏΠΎΡΠ»Π΅Π΄ΡΡΡΠΈΡ ΠΌΠΎΠΈΡ ΠΊΠΎΠΌΠΌΠ΅Π½ΡΠ°ΡΠΈΠ΅Π².
Δ
ΠΡΠΎΡ ΡΠ°ΠΉΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅Ρ Akismet Π΄Π»Ρ Π±ΠΎΡΡΠ±Ρ ΡΠΎ ΡΠΏΠ°ΠΌΠΎΠΌ. Π£Π·Π½Π°ΠΉΡΠ΅, ΠΊΠ°ΠΊ ΠΎΠ±ΡΠ°Π±Π°ΡΡΠ²Π°ΡΡΡΡ Π²Π°ΡΠΈ Π΄Π°Π½Π½ΡΠ΅ ΠΊΠΎΠΌΠΌΠ΅Π½ΡΠ°ΡΠΈΠ΅Π².