Доброго времени суток всем.


Захотелось мне скачать всю мою музыку со ВКонтакте на флешку, как в старые добрые времена. Немного погуглив и не найдя практически ничего более менее приемлемого, я решил действовать своими силами. Спустя пол часа получился вполне себе рабочий скрипт. Итак, начнём.


Для работы нужно скачать модули vk_api и request!


Для начала подключим необходимые модули и объявим некоторые переменные:


import os
import pickle
import vk_api
import requests

from vk_api import audio

from time import time

vk_file = "vk_config.v2.json"
REQUEST_STATUS_CODE = 200 
path = 'vk_music/'

Теперь напишем метод авторизации в аккаунт ВКонтакте:


def Auth(new=False):
    try:
        USERDATA_FILE = r"AppData/UserData.datab" #файл хранит логин, пароль и id
        global my_id # объявляем переменную глобально, дабы иметь к ней доступ из других методов
                # проверяем, нет ли сохранённых данных авторизации? Если есть, то загружаем
        if (os.path.exists(USERDATA_FILE) and new == False):
            with open(USERDATA_FILE, 'rb') as DataFile:
                LoadedData = pickle.load(DataFile)

            login = LoadedData[0]
            password = LoadedData[1]
            my_id = LoadedData[2]
        else: # если есть, но пользователь выбрал новую авторизацию, то удаляем данных и просим ввести новые
            if (os.path.exists(USERDATA_FILE) and new == True):
                os.remove(USERDATA_FILE)

            login = str(input("Введите логин\n> ")) 
            password = str(input("Введите пароль\n> ")) 
            my_id = str(input("Введите id профиля\n> "))
            SaveUserData(login, password, my_id)

        SaveData = [login, password, my_id]
        with open(USERDATA_FILE, 'wb') as dataFile:
            pickle.dump(SaveData, dataFile) # сохраняем введённые данные

        vk_session = vk_api.VkApi(login=login, password=password)
        try:
            vk_session.auth() # пробуем авторизоваться, если возникнет исключение, значит у пользователя включена двухфакторная аутентификация. Просим ввести код.
        except:
            vk_session = vk_api.VkApi(login=login, password=password, 
                auth_handler=auth_handler) # auth_handler=auth_handler - вызываем метод, см. далее
            vk_session.auth()
        print('Вы успешно авторизовались.')
        vk = vk_session.get_api()
        global vk_audio # объявляем глобально, дабы иметь доступ из других методов
        vk_audio = audio.VkAudio(vk_session)
    except KeyboardInterrupt:
        print('Вы завершили выполнение программы.')

Метод будет проверять, не авторизовывались ли мы уже раньше? Если такое было, то можно будет продолжить в этом аккаунте, или авторизоваться по-новой. В этом случае старые данные будут стёрты.


Далее напишем метод auth_handler, который нужен для авторизации в аккаунтах, в которых включена двухфакторная аутентификация:


def auth_handler():
    code = input("Введите код подтверждения\n> ")
    remember_device = True # True -  запоминаем и не просим каждый раз вводить код
    return code, remember_device

И так, теперь мы можем авторизоваться во ВКонтакте. В методе Auth упомянался метод SaveUserData(), он нужен для сохранения данных. Напишем его:


def SaveUserData(login, password, profile_id):
    USERDATA_FILE = r"AppData/UserData.datab"
    if (not os.path.exists("AppData")): # если нет папки AppData - создадим ее
        os.mkdir("AppData")

    SaveData = [login, password, profile_id] # список данных для сохранения

    with open(USERDATA_FILE, 'wb') as dataFile: # собственно записываем данные в файл
        pickle.dump(SaveData, dataFile)

Данные будут записаны в бинарном виде, дабы не хранить логин и пароль пользователя в открытом виде.


Осталось написать метод загрузки аудио со ВКонтакте, давайте это и сделаем:


def main():
    try:
        if (not os.path.exists("AppData")):
            os.mkdir("AppData")
        if not os.path.exists(path):
            os.makedirs(path)
                # спросим пользователя, нужно ли авторизоваться по-новой или продолжить старую сессию
        auth_dialog = str(input("Авторизоваться заново? yes/no\n> "))
        if (auth_dialog == "yes"):
            Auth(new=True)
        elif (auth_dialog == "no"):
            Auth(new=False)
        else:
            print('Ошибка, неверный ответ.')
            main()
        print('Подготовка к скачиванию...')
        os.chdir(path) #меняем текущую директорию

        audio = vk_audio.get(owner_id=my_id)[0]
        print('Будет скачано:', len(vk_audio.get(owner_id=my_id)), 'аудиозаписей.')
        count = 0
        time_start = time()
        print("Скачивание началось...\n")
                # этим циклом, собственно, и скачиваем нашу музыку.
        for i in vk_audio.get(owner_id=my_id):
            try:
                print('Скачивается: ' + i["artist"] + " - " + i["title"])
                count += 1
                r = requests.get(audio["url"])
                if r.status_code == REQUEST_STATUS_CODE:
                    print('Скачивание завершено: ' + i["artist"] + " - " + i["title"])
                    with open(i["artist"] + ' - ' + i["title"] + '.mp3', 'wb') as output_file:
                        output_file.write(r.content)
            except OSError:
                print("!!! Не удалось скачать аудиозапись №", count)
        time_finish = time()
        print("" + vk_audio.get(owner_id=my_id) + " аудиозаписей скачано за: ", 
                                            time_finish - time_start + " сек.")
    except KeyboardInterrupt:
        print('Вы завершили выполнение программы.')

Ну вот и всё. Теперь у нас есть рабочий скрипт для загрузки аудиозаписей из ВКонтакте.
Вот так выглядит весь исходный код:


Показать исходный код
import os
import pickle
import vk_api
import requests

from vk_api import audio

from time import time

__version__ = 'VK Music Downloader v1.0'

APP_MESSAGE = '''
        _   .   ___
    /\\   |   | | \\  |  |   |  \\    /  | /
   /__\\  |   | |  \\ |  |   |   \\  /   |/
  /    \\ |___| |__/ |  |___|    \\/    |\  '''

vk_file = "vk_config.v2.json"
REQUEST_STATUS_CODE = 200 
path = 'vk_music/'

def auth_handler(remember_device=None):
    code = input("Введите код подтверждения\n> ")
    if (remember_device == None):
        remember_device = True
    return code, remember_device

def SaveUserData(login, password, profile_id):
    USERDATA_FILE = r"AppData/UserData.datab"
    SaveData = [login, password, profile_id]

    with open(USERDATA_FILE, 'wb') as dataFile:
        pickle.dump(SaveData, dataFile)

def Auth(new=False):
    try:
        USERDATA_FILE = r"AppData/UserData.datab" #файл хранит логин, пароль и id
        global my_id
        if (os.path.exists(USERDATA_FILE) and new == False):
            with open(USERDATA_FILE, 'rb') as DataFile:
                LoadedData = pickle.load(DataFile)

            login = LoadedData[0]
            password = LoadedData[1]
            my_id = LoadedData[2]
        else:
            if (os.path.exists(USERDATA_FILE) and new == True):
                os.remove(USERDATA_FILE)

            login = str(input("Введите логин\n> ")) 
            password = str(input("Введите пароль\n> ")) 
            my_id = str(input("Введите id профиля\n> "))
            SaveUserData(login, password, my_id)

        SaveData = [login, password, my_id]
        with open(USERDATA_FILE, 'wb') as dataFile:
            pickle.dump(SaveData, dataFile)

        vk_session = vk_api.VkApi(login=login, password=password)
        try:
            vk_session.auth()
        except:
            vk_session = vk_api.VkApi(login=login, password=password, 
                auth_handler=auth_handler)
            vk_session.auth()
        print('Вы успешно авторизовались.')
        vk = vk_session.get_api()
        global vk_audio 
        vk_audio = audio.VkAudio(vk_session)
    except KeyboardInterrupt:
        print('Вы завершили выполнение программы.')

def main():
    try:
        if (not os.path.exists("AppData")):
            os.mkdir("AppData")
        if not os.path.exists(path):
            os.makedirs(path)

        auth_dialog = str(input("Авторизоваться заново? yes/no\n> "))
        if (auth_dialog == "yes"):
            Auth(new=True)
        elif (auth_dialog == "no"):
            Auth(new=False)
        else:
            print('Ошибка, неверный ответ.')
            main()
        print('Подготовка к скачиванию...')
        os.chdir(path) #меняем текущую директорию

        audio = vk_audio.get(owner_id=my_id)[0]
        print('Будет скачано:', len(vk_audio.get(owner_id=my_id)), 'аудиозаписей.')
        count = 0
        time_start = time() # сохраняем время начала скачивания
        print("Скачивание началось...\n")
                # собственно циклом загружаем нашу музыку 
        for i in vk_audio.get(owner_id=my_id):
            try:
                print('Скачивается: ' + i["artist"] + " - " + i["title"]) # выводим информацию о скачиваемой в данный момент аудиозаписи
                count += 1
                r = requests.get(audio["url"])
                if r.status_code == REQUEST_STATUS_CODE:
                    print('Скачивание завершено: ' + i["artist"] + " - " + i["title"])
                    with open(i["artist"] + ' - ' + i["title"] + '.mp3', 'wb') as output_file:
                        output_file.write(r.content)
            except OSError:
                print("!!! Не удалось скачать аудиозапись №", count)
        time_finish = time()
        print("" + vk_audio.get(owner_id=my_id) + " аудиозаписей скачано за: ", 
                                            time_finish - time_start + " сек.")
    except KeyboardInterrupt:
        print('Вы завершили выполнение программы.')

if __name__ == '__main__':
    print(APP_MESSAGE)
    print(__version__ + "\n")
    main()

Я только учусь, поэтому буду рад всем замечаниям в коде. Спасибо за внимание.

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


  1. vasiaplaton
    24.09.2019 18:20

    Есть savefromnet для chorma'а. Открываете его в своей музыке и нажимаете скачать все. Ждете. Profit.
    А потом еще можно пронумеровать имя файла что б слушать в том же порядке. Для Linux когда давно писал скрипт.

    Заголовок спойлера
    a = 1
    ls -1t | while read line
    do
    let a=a+1
    echo "***"
    b=${a}${line}
    
    mv "$line" "$b"
    echo "***"
    done
    


    1. rpiontik
      24.09.2019 18:32
      +1

      Ну… готовая тулза не решает всего спектра вопросов, который можно решить скриптом. Например автоматическая синхронизация медиа вконтакте на диск дома или в свое, родное облачко.


      1. vasiaplaton
        24.09.2019 19:39

        Вы конечно правы. На то и готовая тулза, что бы решать только типовые задачи.


    1. Free_ze
      24.09.2019 19:02
      +1

      Не этот ли плагин попалился недавно на воровстве данных юзеров?


      1. Jessy_James
        25.09.2019 13:30

        Если своё vk_api подпихнуть, то вполне можно так сделать…


    1. y_durov
      24.09.2019 21:43
      +3

      Скажу честно — всегда безопаснее написать свой скрипт и быть уверенным, что твои данные останутся при тебе. А savefromnet не опенсорсный, и понять что у него под капотом происходит — нужно немного запариться.


    1. DiffeRT
      25.09.2019 13:54

      А вас не смущает savefromnet-у отдавать свои креды от аккаунта? Вы же не знаете что он может еще делать помимо скачивания. Лично я для этого плагина отдельный аккаунт завел и отдельный браузер


    1. ChieF_Of_ReD
      25.09.2019 17:03

      Рекомендую к ознакомлению
      habr.com/ru/company/globalsign/blog/460987


  1. kAIST
    24.09.2019 22:40

    А зачем на каждый чих вызывать vk_audio.get()?
    Можно же вызвать один раз и записать в переменную. Или я чего то не понимаю.


    1. Jessy_James
      25.09.2019 13:25

      Можно, но в статье написано что человек учится. Все с опытом приходит.


  1. valera5505
    24.09.2019 23:46

    Youtube-dl справляется с этой задачей и еще с миллионами подобных. Опен-сорс, данные не крадет, скрипты в браузер ставить не нужно.


    1. Gutt
      25.09.2019 11:12

      Справляется ли? Последняя версия получает отлуп с «bad browser», хотя и представляется Хромом.


    1. ChieF_Of_ReD
      25.09.2019 17:17
      -1

      Вы сейчас абсолютно серьёзно?

      youtube-dl.exe


      1. Ti_Fix
        26.09.2019 09:08

        По ссылке официальный сайт, а не исполняемый файл. Где вы увидели youtube-dl.exe?


        1. ChieF_Of_ReD
          26.09.2019 13:06
          -1

          По ссылке внутри сайта, что скинули, в разделе скачать, лежит такой миленький экзешник.

          yt-dl.org/downloads/2019.09.12.1/youtube-dl.exe

          примерно тут
          image


          1. Ti_Fix
            26.09.2019 14:21
            +1

            На этой же странице ссылка на исходный код и документацию. Почему вас так расстраивает наличие возможности скачать исполняемый файл (один из вариантов)?


            1. ChieF_Of_ReD
              26.09.2019 14:29
              -1

              Т.е. вы по умолчанию верите, что именно этот исходик использовался при сборке экзешника?


              1. faiwer
                26.09.2019 15:15

                Кто-то мешает вам собрать его самостоятельно? Сам факт наличия уже собранной версии по-вашему как-то очерняет программу?


                P.S. gentoo?


                1. ChieF_Of_ReD
                  26.09.2019 15:30
                  -1

                  Посмотрите на этот вопрос немного под другим углом. Имеется три варианты сборки, две из них чисты, один, допустим экзешник с лишними функциями, вы пройдёте мимо него, собрав нужную версию из исходников, обыватель видит, «о, отлично, можно просто установить и использовать, там же ещё код приложен, что может пойти не так это же опенсорс.» и качает файлик затем устанавливает. Таких вот пользователей миллионы, по сути я один из них, чтобы собрать последнюю версию и заставить её работать мне всё же потребуется затратить время, а его жаль. Но тут лежит такой простой *.exe файл, бери, ставь, пользуй.

                  Надеюсь, у меня получилось донести мысль.

                  По сути не вы целевая аудитория таких вот схем, а простые обыватели.

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

                  Паранойя спросите вы? Здравый смысл отвечу я.


                  1. faiwer
                    26.09.2019 16:11

                    Повторю вопрос — Gentoo? :)


                    1. ChieF_Of_ReD
                      26.09.2019 16:42

                      Поясните, что вы хотите услышать?


                      1. faiwer
                        26.09.2019 17:10

                        Обычно паранойа такого уровня это признак гентушника. Если вы до сих пор не знакомы с этой операционной системой, то с вашими взглядами на open source у вас просто нет выбора, — вы созданы друг для друга.


                        1. ChieF_Of_ReD
                          26.09.2019 17:19

                          А, вы это имели в виду, нет, не гентушник.

                          Считаете, что ситуация которую я описал выше нереальна?


                          1. Jessy_James
                            26.09.2019 17:29

                            Крайне мала.


                          1. faiwer
                            26.09.2019 20:44

                            Более, чем реальна. Но уровень паранойи — это выбор каждого. Я вот скорее скачаю готовый deb пакет из источника к которому питаю доверие, нежели буду качать исходные коды, изучать их под лупой и пересобирать. Свои риски я осознаю, и сам же за них несу ответственность. 99%+ пользователей будет качать уже собранный бинарник, и не по глупости, а целенаправлено. Желающим же большего контроля доступны исходные коды. Вы из вторых — велкам, для вас там всё подготовлено.


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


                            Хехе, представил себе как каждая вторая рабочая станция качает обновления в виде сырсов и ночами пересобирает сама себя. Дивный мир.


                            1. Jessy_James
                              26.09.2019 21:18

                              Хехе, представил себе как каждая вторая рабочая станция качает обновления в виде сырсов и ночами пересобирает сама себя. Дивный мир.


                              Для таких можно компилятор правильный подсунуть, который будет вкомпиливать правильные дополнения )


                            1. ChieF_Of_ReD
                              27.09.2019 02:14

                              Да, с вами я полностью согласен и рад, что мы поняли друг друга.


                  1. valera5505
                    27.09.2019 00:15

                    А какое решение вы предлагаете? Всех заставлять ставить из исходников? Ну так мало просто скачать и скомпилировать, надо же еще тогда глазами проверить на то, что код чист. То есть всем надо внезапно стать программистами, способными пропускать через себя десятки и сотни тысяч строк кода, чтобы не стать жертвой какой-нибудь схемы.


                    1. ChieF_Of_ReD
                      27.09.2019 02:24

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

                      Ну так мало просто скачать и скомпилировать, надо же еще тогда глазами проверить на то, что код чист.

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

                      чтобы не стать жертвой какой-нибудь схемы.
                      Увы, мы максимум, что можем — это сделать эту задачу нерентабельной.


  1. Tihon_V
    25.09.2019 00:01

    А можно код хотя бы на gist.github.com выложить?


  1. Max_JK
    25.09.2019 04:37

    Я давненько писал бота для скачивания музыки, для себя, vk.com/music_bott некоторые функции уже не работают, но музыку скачать все ещё можно, удобно что не нужно уходить с сайта чтобы скачать музыку, может кому пригодится.


  1. bvn13
    25.09.2019 10:00
    +1

    Учебный пример — это хорошо. Но в свете последних новшевств музыка ВК действительно того стоит, чтобы ей пользоваться?


    1. Ti_Fix
      25.09.2019 10:40

      О каких новшествах идет речь?


    1. Gutt
      25.09.2019 11:13

      Да, там можно найти (и утянуть себе на плеер) музыку, которую в других местах найти трудно.


    1. nebularia
      25.09.2019 11:29

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

      Есть и минусы, конечно — некоторая хаотичность и полная чушь в рекомендациях, да.


      1. w3ga
        25.09.2019 12:59

        некоторая хаотичность и полная чушь в рекомендациях


        мне, яндекс.музЫка, недавно (2 дня назад как) порекомендовал сборку в стиле «регги», среди прочего там — песня — «течёт река волга» (не знаю кто поёт, какое-то ВИА или ещё кто), игра на балалайке, парочку песен из бродвейских мюзиклов и прочая муть со всех концов планеты… всё что их объединяет с «регги», так это года — почти все композиции родом из 60х +\-


  1. polar11beer
    25.09.2019 10:17
    +1

    Оффтоп:

    Данные будут записаны в бинарном виде, дабы не хранить логин и пароль пользователя в открытом виде.
    Так всё равно данные можно прочитать hex-редактором, так как они не зашифорованы. Не по теме статьи, но всё же.

    А за скрипт респект, просто и практично.


    1. Jessy_James
      25.09.2019 13:18

      Они блокнотом читаются, проверено ))


  1. Gutt
    25.09.2019 11:10

    Вы успешно авторизовались.
    Подготовка к скачиванию...
    Traceback (most recent call last):
      File "vkdl.py", line 119, in <module>
        main()
      File "vkdl.py", line 93, in main
        audio = vk_audio.get(owner_id=my_id)[0]
      File "/home/onboard/.local/lib/python3.6/site-packages/vk_api/audio.py", line 96, in get
        return list(self.get_iter(owner_id, album_id, access_hash))
      File "/home/onboard/.local/lib/python3.6/site-packages/vk_api/audio.py", line 77, in get_iter
        filter_root_el={'class_': 'audioPlaylist__list'} if album_id else None
      File "/home/onboard/.local/lib/python3.6/site-packages/vk_api/audio.py", line 236, in scrap_data
        for audio in root_el.find_all('div', {'class': 'audio_item'}):
    AttributeError: 'NoneType' object has no attribute 'find_all'
    


    И, действительно, ноды с id == au_search_items в коде страницы с аудио нет.


  1. x88
    25.09.2019 11:35

    Теперь на некоторых пользователях тестируется отдача музыки через HLS (M2TS поток), который поддерживает шифрование, так что придётся использовать инструменты для дальнейшей сборки чанков.


  1. Jessy_James
    25.09.2019 13:11

    Недели 2-ве назад видел эту статью в песочнице, код был в ошибках. Скачивалась всегда 1-ая песня под разными именами. Код себе я тогда взял и поправил ошибки… )

    UP ошибка то осталась: audio = vk_audio.get(owner_id=my_id)[0]


    1. Gutt
      25.09.2019 15:27

      А можно нам исправленную версию?


      1. Jessy_James
        25.09.2019 15:30

        1. Gutt
          26.09.2019 11:29

          Спасибо, работает! Единственное, для юниксов пришлось сделать следующее, чтобы убрать слеши из имени файла:

          diff --git a/src/main.py b/src/main.py
          index bfadfa5..b09d4c7 100755
          --- a/src/main.py
          +++ b/src/main.py
          @@ -7,6 +7,7 @@ import requests
           from time import time
           import vk_api
           from vk_api import audio
          +import re
          
           class vkMusicDownloader():
          
          @@ -94,6 +95,7 @@ class vkMusicDownloader():
                       # собственно циклом загружаем нашу музыку
                       for i in audio:
                           fileM = "{} - {}.mp3".format(i["artist"], i["title"])
          +                fileM = re.sub('/', '_', fileM)
                           try:
                               if os.path.isfile(fileM) :
                                   print("{} Уже скачен: {}.".format(index, fileM))
          


          Нулевой байт убирать смысла не вижу (у меня ни одной записи с ним в имени не нашлось :-). Для других ОС будут инвалидными другие символы, но, думаю, убранный слеш покроет 99 % случаев ошибок записи файлов из-за неверных символов в имени.


          1. Jessy_James
            26.09.2019 18:24

            Поправил.