Пользователь
0,0
рейтинг
15 января 2015 в 15:53

Разработка → Бекап аудиозаписей с плейлиста ВКонтакте (до 6000) средствами Python и Vk API из песочницы

Здравствуйте.

Раньше часто слушал музыку средствами «Вконтакте» (далее ВК). После перехода на Ubuntu 14.10 возникли проблемы в виде полного зависания компьютера во время прослушивания аудиозаписей через браузер Google Chrome для linux систем. В связи с этим возникла необходимость забекапить свой плейлист для прослушивания музыки в оффлайн режиме. Для этих целей решил написать маленький скрипт на языке Python, которым можно будет не только скачивать музыку с нуля, но и обновлять существующую библиотеку.

Я использовал такие модули:
  • Selenium webdriver
  • requests
  • json
  • os

Собственно, начнем.

Сначала подключаем модули:

import os
import requests
from selenium import webdriver
import json

Далее нужно получить access_token для выполнения запросов к API и получения необходимых нам прав доступа.
Перед этим нам нужно создать и активировать свое Standalone/Desktop приложение, id которого мы будем указывать в запросе.

Схема довольно простая: Мы открываем окно браузера, переходим по ссылке, вводим данные от аккаунта, разрешаем доступ, на выходе копируем необходимые данные с url(это access_token и expires_in — срок истекания токена).

Более детально о создании приложения и авторизации можно почитать здесь.

Я использовал selenium из-за собственной лени. У этого модуля достаточно функционала для автоматизации всех вышеуказанных действий. Вы можете использовать любой другой виртуальный браузер, который вам нравится.

Собственно код с комментариями:

# Создаем объект драйвера

driver = webdriver.Firefox()

# Переходим по ссылке.
# client_id - идентификатор созданного нами приложения
# scope - права доступа
driver.get("http://api.vkontakte.ru/oauth/authorize?"
           "client_id=4591034&scope=audio"
           "&redirect_uri=http://api.vk.com/blank.html"
           "&display=page&response_type=token")

user = "email/phone"
password = "password"

# Находим элементы формы и вводим данные для авторизации
user_input = driver.find_element_by_name("email")
user_input.send_keys(user)
password_input = driver.find_element_by_name("pass")
password_input.send_keys(password)

# Нажимаем на кнопку
submit = driver.find_element_by_id("install_allow")
submit.click()

# Получаем необходимые данные для выполнения запросов к api
current = driver.current_url
access_list = (current.split("#"))[1].split("&")
access_token = (access_list[0].split("="))[1] # acces_token
expires_in = (access_list[1].split("="))[1] # срок времени действия токена
user_id = (access_list[2].split("="))[1] # id нашей учетной записи в ВК
# Закрываем окно браузера

driver.close()

До недавнего времени «ВК API» возвращало все данные в формате xml, что было немного неудобным. Сейчас же ответы сериализируются в json словарь с которым можно без проблем работать. Здесь нам пригодится библиотека json, а конкретнее метод loads(), который преобразовывает строку в словарь.

Дальше план действий довольно простой:
  1. Выполняем запрос и получаем в ответе список всех аудиозаписей с плейлиста учетной записи (ограничение до 6000);
  2. Преобразовываем ответ в словарь;
  3. Берем необходимую информацию со словаря (исполнитель, название трека, ссылка на трек);
  4. Скачиваем аудиозаписи в нужную директорию, называя нужными названиями, а не стандартным набором букв, в котором хранится название трека.


Первый шаг:

# Принт для дебага
print "Connecting"
# Адрес запроса
url = "https://api.vkontakte.ru/method/" \
      "audio.get?uid=" + user_id +\
      "&access_token=" + access_token
# Создаем листы для хранения данных
artists_list = []
titles_list = []
links_list = []
# счетчик для дебага и перехода по элементам листов
number = 0
# Читаем ответ сервера и сохраняем в переменную
page = requests.get(url)
html = page.text

Второй шаг:

my_dict = json.loads(html) # используем loads()


Третий шаг, думаю все понятно без комментирования:

for i in my_dict['response']:
    artists_list.append(i['artist'])
    titles_list.append(i['title'])
    links_list.append(i['url'])
    number += 1

Четвертый и заключительный шаг:

# Создаем директорию, если она не была создана ранее

path = "downloads"

if not os.path.exists(path):
    os.makedirs(path)
# Принт для дебага
print "Need to download: ", number

# Процесс скачивания файлов

for i in range(0, number):

# Путь по которому будет храниться/скачиваться конкретная аудиозапись
    new_filename = path+"/"+artists_list[i] + " - " + titles_list[i] + ".mp3"
    print "Downloading: ", new_filename, i
# Проверка на существующий файл
    if not os.path.exists(new_filename):
# Сама загрузка файла, отсекаем из ссылки все аргументы и указываем путь куда скачивать
        with open(new_filename, "wb") as out:
            response = requests.get(links_list[i].split("?")[0])
            out.write(response.content)

print "Download complete."

Сейчас веду работу над самопальным плеером для прослушивания аудиозаписей с ВК (да, именно отдельный плеер, а не плагин для уже существующих популярных проигрывателей). В дальнейшем планирую написать статью, в которой будет описан процесс.

На этом все, спасибо за внимание.

UPD:
Статья немного отредактирована в соответствии с пожеланиями из комментариев.
Валерий Гайдарь @boo_v2
карма
8,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (60)

  • +1
    А мне всегда было интересно сделать флешку VK. Кстати тут проскакивала статья про псевдо фс на stm, надо будет попробовать.
    • 0
      Давно когда-то была интересная статья на эту тему: VKFS — Файловая система для VKontakte на основе Fuse
      Вы о ней? И что за идея с «флешкой»?
      • 0
        Не совсем, идея скорее всего маразматична. Но мысль в том что бы на лету через 3G грузить файлы. и подсовывать файловой системе магнитолы. Но это больше не реализуемо, чем реализуемо
        • 0
          Я посмотрел тот ресурс, что вам кинул. Похоже, что автор упёрся в капчу, которую на магнитоле не ввести.
          Кстати, тогда, получается, я помню была ещё одна статья, где файловую систему симулировали на каком-то контроллере. Как раз можно было бы обмануть магнитолу.
          • 0
            В принципе капча появиться не должна, во всяком случае когда я баловался с плагином для DLNA сервера, не появлялось
  • +2
    Написал похожее же под айфон, потому что там ни одно бесплатное приложение из эппстора не соглашается кешировать музыку из вк(платные не смотрел). Это сохраняет их на диск и требует инета только для получения списка песен(потом и это будет кешироваться, пока некогда заняться)
    github.com/MaxPovver/VKPlayer
    В эппсторе нет, но если вы эпплоразраб, можете под себя исходники перекомпилить, только с id приложения не уверен будет ли работать. Ну и вк-сдк в репозитории нет — сессия, не до того, но, думаю, сами найдете.
    • +1
      в YoPlayer на iOS можно качать свою музыку из ВК:
      • 0
        ну ему пришлось сильно функционал порезать, чтобы пропустили. и еще пишут, что оно не может в бэкграунд, а в моем смысл именно в этом — запустил, и играет оно себе в фоне, пока ты в том же приложении хабра сидишь
        • 0
          у меня играет как надо: слушаю всё время в нём аудиокниги — очень удобно
      • 0
        На последней iOS даже не показывает интерефейса и сразу закрывается. Был бы рад посмотреть, но увы.
        • 0
          да, у моего друга тоже не запускается, но апп только в декабре вышел: надеюсь в следующей версии будет стабильнее
    • –1
      потому что там ни одно бесплатное приложение из эппстора не соглашается кешировать музыку из вк(платные не смотрел)
      Ну не знаю как там эппстор, но гугл плей подобные вещи банит относительно активно. Даже платные.
      • 0
        офф приложение вк кешировало, по крайней мере последний раз когда я андроид использовал…
        • 0
          офф приложение вроде не выдаёт оригинальные файлы (т.е. даёт слушать только внутри), чем сторонние приложения вроде не хворают
          • 0
            ну я впилю какую-нибудь простую шифровочку чтобы расшифровывалось быстро, но вне проги не воспроизводилось. но это потом. и как я понял в иос это не помогло, там даже офф приложение не кеширует
  • +5
    Я почти вручную качал:
    var list = document.querySelectorAll('div.audio.fl_l');
    var files = [];
    for (var i = 0; i < list.length; i++) {
        var tel = list[i];
        var f_name = tel.querySelector('.title_wrap').innerText;
        var f_url = tel.querySelector('input').value;
        files.push(f_url+'\t'+f_name+'.mp3');
    }
    var a = document.createElement('a');
    a.download = 'vk_music.txt';
    var oUrl = URL.createObjectURL(new Blob([files.join("\n")], {type: 'text/plain'}));
    a.href = oUrl;
    a.click();
    delete a;
    URL.revokeObjectURL(oUrl);
    

    Если вставить этот код в developer console, то скачается файл vk_music.txt, в нем будет:
    URL<tab>track title.mp3
    
    Все песни, которые видны на странице (без прокрутки)

    Затем
    cat vk_music.txt |while read url name; do wget "$url" -O "$name" --no-check-certificate; done;
    
    • +2
      Спасибо!
      Уж сколько раз собирался скрипт накидать, да всё руки не доходили. А тут так просто и легко для поддержки…
      Добавил для авто-прокрутки в начало:

      var scrollTop = -1; 
      var iv = setInterval( function() {
          if (document.body.scrollTop > scrollTop) {
              scrollTop = document.body.scrollTop; 
          	document.body.scrollTop = scrollTop + 10000;
          } else {
            clearInterval(iv);
            /* код загрузки */
          }
      }, 1000);
      

      полностью:
      function getUrlList() {
      	var list = document.querySelectorAll('div.audio.fl_l');
      	var files = [];
      	for (var i = 0; i < list.length; i++) {
      		var tel = list[i];
      		var f_name = tel.querySelector('.title_wrap').innerText;
      		var f_url = tel.querySelector('input').value;
      		files.push(f_url+'\t'+f_name+'.mp3');
      	}
      	var a = document.createElement('a');
      	a.download = 'vk_music.txt';
      	var oUrl = URL.createObjectURL(new Blob([files.join("\n")], {type: 'text/plain'}));
      	a.href = oUrl;
      	a.click();
      	delete a;
      	URL.revokeObjectURL(oUrl);
      }
      
      var scrollTop = -1; 
      var iv = setInterval( function() {
          if (document.body.scrollTop > scrollTop) {
              scrollTop = document.body.scrollTop; 
          	document.body.scrollTop = scrollTop + 10000;
          } else {
            clearInterval(iv);
      	  getUrlList();
          }
      }, 1000);
      
      • +2
        Ну и для тех, кто под виндой — Powershell скрипт

        $dest = "C:\temp"
        $lines = Get-Content -Encoding UTF8 vk_music.txt | Where {$_ -notmatch '^\s+$'}  
        
        foreach ($line in $lines){
            $url = $line.Split('?')[0];		
            $s = $line.Split(',')[1];
            $title = $s.substring(4);
            $path = $dest, $title -join "\";	
            Write-Output "Downloading ($title) .....";
            Invoke-WebRequest $url -OutFile $path;
        #    (new-object System.Net.WebClient).DownloadFile($url,$path);
        }
        


        PS. у кого винда семерка надо раскомментить последнюю строчку и закомментить предпоследнюю, на восьмерке ничего менять не надо
  • 0
    Подобная качалка для node-webkit. Только приложение вроде уже забанили, так что надо заменить его ID. Писалось для себя, сильно не пинать.
  • +6
    Немного критики, если Вы не против.

    1. Используйте urllib.urlencode. В итоге создание url-ов выглядело бы вот так:
    payload = {
        'client_id': 4591034,
        ...
        'response_type': 'token',
    }
    driver.get("http://api.vkontakte.ru/oauth/authorize?%s" % urllib.urlencode(payload))
    


    2. Эти переменные лишние
    # Создаем листы для хранения данных
    artists_list = []
    titles_list = []
    links_list = []
    number = 0
    


    и этот код тоже:
    for i in my_dict['response']:
        artists_list.append(i['artist'])
        titles_list.append(i['title'])
        links_list.append(i['url'])
        number += 1
    


    После json.loads Вы можете приступить к процессу сохранения файлов на жесткий диск. Будет выглядеть примерно так (код не проверял):
    my_dict = json.loads(html)
    for data in my_dict.get('response', {}).values():
        filename = os.path.join(path, "%s-%s.mp3" % (data['artist'], data['title']))
        if not os.path.exists(filename):
            urllib.urlretrieve(data['url'].split("?")[0], filename)
    


    3. Ну и используйте больше os.path:
    root = os.path.realpath(os.path.dirname(__file__))
    path = os.path.join(root, "downloads")
    


    4. Посмотрите на либу requests.
    • 0
      Спасибо, действительно упрощает код.
  • 0
    А что за ограничение в 6000 аудиозаписей? Это ограничение Вконтакте на максимальное количество аудиозаписей у пользовател? Или что-то другое?
    • +1
      vk.com/dev/audio.get выделено на сером фоне это ограничение
      • 0
        можно же просто count=0 указать, или 0 автоматически ограничивается шестью тысячами?
      • 0
        Тут было предложение указать offset, но
        Обратите внимание, что даже с использованием параметра offset получить информацию об аудиозаписях, находящихся после первых 6 тысяч в списке пользователя или сообщества, невозможно.
        • 0
          я процитирую
          Обратите внимание, что даже с использованием параметра offset получить информацию об аудиозаписях, находящихся после первых 6 тысяч в списке пользователя или сообщества, невозможно.
  • –5
    > После перехода на Ubuntu 14.10 возникли проблемы в виде полного зависания компьютера во время прослушивания аудиозаписей через браузер Google Chrome для linux систем.

    Убунта такая убунта… спасибо за информацию, планировал переходить с 14.04, — теперь не буду.
    • 0
      Конечно же, виновата убунта.
      • +1
        Я не уверен, что виновата именно Ubuntu, но источник проблемы локализировать не смог. Зависания возникали только при прослушивании музыки.
        • 0
          Однажды, это было в далеком 2009 году, мы смотрели в свежей Ubuntu 9.10 мультик, и плеер после 10 минут показа упал с ошибкой. Я перезапустил, но минут через 15 плеер снова упал. Когда плеер упал в третий раз, моя дочь, которой тогда было 7 лет, спросила меня: «Папа, а знаешь почему мы не можем посмотреть мультфильм?» — «Почему же?» — и тут дочь мне говорит: «Потому что это Линукс!»

          Проблема была в библиотеке gstreamer. Я отправил баг на Launchpad. Баг был подтвержден, передан в работу, провисел в статусе «в работе» несколько лет. Его закрыли впоследствии. В связи с прекращением поддержки релиза. А сам баг проявлялся еще года полтора, пока разработчики gstreamer наконец его не пофиксили.

          Грустно, что подобные неприятные баги вылезают не только в осенних релизах, которые на моей памяти все проблемные (хотя я иногда ставлю их в качестве основной ОС на домашем ПК), но и в LTS. Впрочем, LTS ведь означает лишь long time support а не «очень стабильный» релиз.
      • 0
        Конечно, убунта никогда ни в чем не виновата. Но читаем в отраслевых новостях:

        «Разработчики Ubuntu признали наличие ошибки в установщике дистрибутива, которая приводит к полному удалению всех данных с жесткого диска. Первое сообщение об ошибке появилось в декабре 2013 г., но только сейчас — после появления достаточного количества жалоб — на нее обратили внимание. В общей сложности о наличии ошибки заявил 91 пользователь.

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

        Подробнее: www.cnews.ru/news/top/index.shtml?2014/08/29/584343

        После этого убунту язык не поворачивается назвать дружелюбной и уж тем более — продуктом.

        И, судя по прежнему личному опыту общения с техподдержкой Canonical, — вполне возможно, что этот критикал до сих пор не закрыт :)
  • 0
    Я делал похоже (github), но я не понимаю, зачем у тебя webdriver?
    • 0
      Как уже написал в публикации, из-за собственной лени. У меня есть заготовки для выполнения подобных действий сделанные с помощью pycurl, mechanize, grab, но я решил не раздувать скрипт особо.
  • 0
    Уж слишком надуманная причина, чтобы «забэкапить свой плэйлист» и писать никому не нужный плеер. Нет, чтобы источник проблемы устранить — обязательно нужен еще один костыль-велосипед.
    • 0
      Возможно он никому не нужный, но сам процесс познавательный, по крайней мере мне.
      • +1
        geekgirl.io/concurrent-http-requests-with-python3-and-asyncio/
        на python3 достаточно просто сделать асинхронную загрузку в 100 потоков, либо на tornado
        попробуйте, познавательно)
        • 0
          Честно сказать, я ни разу не имел дела с python 3 :)
  • 0
    А я только недавно изучаю Python. У меня версия 3. При вводе вашего кода посыпались ошибки. Про print() я не говорю… А вот urllib2 пишет, что уже нет такого модуля. Теперь urllib3. А в нём нет функции page.read(). Подскажите как же мне на urllib3 создать page= и html=, чтобы их смог увидеть json? А ещё возникает ошибка, что нужен сертификат…
    Заранее спасибо!
    Я новичок в питоне, не ругайте.
    • 0
      Здравствуйте. Я использую python 2.7. Вы можете использовать библиотеку requests.Например:
      page = requests.get(url)
      html = page.text
      • 0
        Спасибо! Сейчас попробую…
  • 0
    PS: а ещё и urllib.urlretrieve не может найти… беда совсем.
    • 0
      Для того, чтобы скачать файл иным способом можно опять использовать requests :)

          with open(new_filename, "wb") as out:
              response = requests.get(links_list[i].split("?")[0])
              out.write(response.content)
      


      • 0
        Ого! Всё работает! Спасибо, мил человек!
        Питон это вещь конечно…
        • 0
          Всегда пожалуйста :)
  • 0
    Ещё сейчас вылез момент: если в названии файла есть символы типа "/" (а Контакт их допускает), то скрипт не может записать такой файл и сваливается. Мелочь, но может кому пригодится…
    • 0
      Да, этого я не предусмотрел т.к. в моих аудиозаписях нету подобных названий.
      Как вариант решения: в цикле заполнения словаря можно отредактировать 2 строчки:

      artists_list.append(i['artist'].replace("/", ""))
      titles_list.append(i['title'].replace("/", ""))
      
      • 0
        Он там ещё плохие символы находит, типа кавычек. Обошёл пока с помощью «try»… Потом допишу как надо)
        • 0
          Я в подобном скрипте подчищал регулярками /http:\/\/[-._\w]+/i и /[^-_.() \w]/
  • –2
    Для этого использую плагин для браузера VKOpt
  • 0
    Выложите ссылку на скачивание готового скрипта плиз
  • 0
    Касательно авторизации — можно было запросить токен, срок действия которого никогда не истекает, тогда можно было бы его просто в коде прописать и не эмулировать ввод логина и пароля в браузере.

    Ограничение в 6000 треков можно обойти, если разместить треки по альбомам. Правда, один трек может быть лишь в одном альбоме почему-то. Но в вашем случае — это лишь упрощает скачивание.

    Хорошо, что вы лишь список треков получаете. Мне потребовалось загрузить ВКонтакте около 4000 треков (для теста караоке), так ВК запрашивало капчу примерно каждый 30 трек.

    И, кстати, никакого «обновления существующей библиотеки» в вашем коде я не нашел. Или речь о том, что уже скачанные файлы второй раз не скачиваются, если имя совпадает?)
    • 0
      «И, кстати, никакого «обновления существующей библиотеки» в вашем коде я не нашел. Или речь о том, что уже скачанные файлы второй раз не скачиваются, если имя совпадает?)»
      Да, об этом.
  • 0
    После перехода на Ubuntu 14.10 возникли проблемы в виде полного зависания компьютера во время прослушивания аудиозаписей через браузер Google Chrome для linux систем.

    Такое наблюдалось только в Хроме и только под Убунтой?

    Сталкивался с проблемой, когда при прослушивании музыки в ВК вкладка отъедала всю оперативную память. Смог наблюдать такое и на Linux-е и на Windows, причём в разных браузерах. Техподдержка ответила, что это из-за моего провайдера (Ростелеком):
    Мы посмотрели отчёт и поняли, в чём дело. Ваш провайдер передаёт данные через несколько шлюзов одновременно. Из-за этого запросы к сайту идут параллельно с разных IP-адресов, что и приводит к проблемам. Имеет смысл обратиться в поддержку Вашего провайдера и пожаловаться на это. Либо просто подключиться к другому провайдеру. Если будете обращаться в поддержку, обязательно заострите их внимание на том, что проблема не в динамическом IP-адресе, а в параллельно идущих запросах с разных IP-адресов. Это следствие плохо реализованного распределения нагрузки между разными внешними каналами провайдера.
    • 0
      Да, такое наблюдалось только в Chrome и только под Ubuntu.
  • +1
    Кстати, если кому интересно, можете посмотреть в моём репозитории такую же утилиту, написанную на C++ github.com/junc/vk-music-sync. Комментарии и замечания приветствуются.
  • 0
    Есть замечательная штука: github.com/kidig/vk-audio-sync
    В отличие от топикстартера, не использует никаких тяжеловесных селениумов, работает и под Win, и под Ubuntu.
    Плюс ко всему — ставит правильные ID3 теги, чтобы в плеере/мобилке правильно отображались названия.

    Автор: habrahabr.ru/users/kidig/

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