Скачивание музыкальной коллекции vk.com

от автора

Привет, хабрахабр!

Решил я как-то скачать свою музыкальную коллекцию из vkontakte(а это без малого 1000 песен). Связываться с vk.api не хотелось, поэтому решил

использовать python + библиотека request. Что из этого получилось — под катом!

Сначала посмотрим, что делает наш браузер, когда мы обращаемся к странице своих аудиозаписей вконтакте. Открываем инструменты разработчика (я использовал Chrome, F12) и заходим на vk.com/audio. Мы можем видеть все запросы, которые совершает браузер:

image

Смысл действий браузера таков:

Первая строчка — GET запрос, который мы посылаем к серверу при первом заходе на страницу. В ответе сервер отдает нам html код страницы.
Затем браузер начинает подгружать все необходимые ресурсы: css, js и изображения.
Ближе к концу списка видим нестандартную строчку: это запрос типа POST с именем audio. Скорее всего, этот запрос посылает javascript для получения списка аудиозаписей.
В ответе сервер нам возвращает строчку типа:

11055<!>audio.css,audio.js<!>0<!>6362<!>0<!>{"all":[   ['17738938','173762121',     'http://cs1276.userapi.com/u1040081/audio/c0e97293c5e2.mp3','300','5:00',     'Louis Prima','Sing, Sing, Sing (With A Swing)','369754','0','0','','0','1'],   ['17738938','173368012',     'http://cs4372.userapi.com/u9237008/audio/5f51ceac6ca1.mp3','326','5:26',     'Look at my horse','My horse is amazing','10324035','0','0','','0','1'], ...

Бинго! Это именно то что нам и надо. В ответе сервер возвращает нам JSON-список всех наших композиций и для каждой передает следующие параметры:

  • 0 — мой id
  • 1 — id композиции
  • 2 — ссылку на композицию
  • 3 — битрейт?
  • 4 — длительность
  • 5 — автор
  • 6 — название композиции
  • 7 — размер в байтах?
  • Остальные параметры непонятны.

Получаем список аудиозаписей

Как же нам получить желанный список? Посмотрим, какие headers отправляет браузер в нашем запросе:

Request Headers:   Accept:*/*   Accept-Charset:windows-1251,utf-8;q=0.7,*;q=0.3   Accept-Encoding:gzip,deflate,sdch   Accept-Language:ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4   Connection:keep-alive   Content-Length:45   Content-Type:application/x-www-form-urlencoded   Cookie:remixlang=0; remixseenads=2; audio_vol=100; remixdt=0;remixsid=************; remixflash=11.4.31   Host:vk.com   Origin:http://vk.com   Referer:http://vk.com/audio   User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4   X-Requested-With:XMLHttpRequest  Form:   Dataview URL encoded   act:load_audios_silent   al:1   gid:0   id:17738938  

Попробуем сымитировать наш запрос:

import requests as r  def getAudio():     response = r.post(url = "http://vk.com/audio",            data = {                "act":"load_audios_silent",                 "al":"1",                 "gid":"0",                 "id":"17738938"                 }            )     print response.content  getAudio()  

Функция request.post создает POST запрос к url. Ей можно передать несколько параметров. Вот главные из них:

  • headers — словарь хидеров, которые мы хотим отправиь серверу
  • data — словарь данных, которые надо передать в запросе

Функция нам выведет

<!--11055<!>audio.css,audio.js<!>0<!>6362<!>3<!>230b860567731c4875

Результат предсказуем — ведь мы никак не указали что мы авторизованный пользователь. Для этого надо передать серверу cookies. Исправим немного наш запрос:

 import requests as r  def getAudio():     response = r.post(                     "http://vk.com/audio",                     data = {                         'act':"load_audios_silent",                         "al":"1",                         "gid":"0",                         "id":"17738938"                             },                     headers = {                         "Cookie":"remixlang=0; remixseenads=2; remixdt=0; remixsid=**************; audio_vol=96; remixflash=11.4.31"                                }                       )     print response.content[0:1000]  getAudio()  

Теперь получаем то что нужно.

Хорошо. Список мы получили. Теперь надо его отпарсить и скачать каждую песню по отдельности. Я решил не заморачиваться, и просто использовать регулярные выражения:

#-*-coding:cp1251-*- import requests as r import re import random as ran import os import urllib as ur  #Разрешенные символы в названиях песен: ALLOW_SYMBOLS = " qwertyuiopasdfghjklzxcvbnmйцукенгшщзхъэждлорпавыфячсмитьбюЙЦУКЕНГШЩЗХЪЭЖДЛОРПАВЫФЯЧСМИТЬБЮ.,-()" COOKIE = ""   #Здесь надо указать cookies, которые вы передаете контакту.  def getAllowName(string):     """Возвращает для каждой строки подстроку, состоящую только из разрешенных символов     ALLOW_SYMBOLS"""     s=''     for x in string.lower():         if x in ALLOW_SYMBOLS:             s += x     return s  def getRandomElement(arr, delete = False):     """Возвращает рандомный элемент массива arr. Если delete = True, то этот элемент удаляется из массива."""     index = ran.randrange(0, len(arr), 1)     value = arr[index]     if delete:         arr.remove(value)     return value  def getAudio():     """С этой функцией мы уже сталкивались. Только тут она полученную строку     разбивает на массив элементов"""     response = r.post(                     "http://vk.com/audio",                     data = {                         'act':"load_audios_silent",                         "al":"1",                         "gid":"0",                         "id":"17738938"                             },                     headers = {                         "Cookie":COOKIE                                }                       )     i=0     pat = re.compile(r"\[.+?\]")  #соответствует всем подстрокам типа [.*]     return pat.findall(response.content) #тут и происходит разбиение  already_added = []  #тут будем хранить id композиций, которые уже скачаны. pat = re.compile(r"\'(.+?)\'")  #паттерн соответствует всем подстрокам вида '.*'  def OneDownload(x):     """Делает ОДНУ закачку песни, описание которой (типа ['...', '...', '...', ...]) передается аргументом     """     global already_added     try:         elements = pat.findall(x) #получаем свойства композиции         id, url, author, name = (elements[1], elements[2], elements[5], elements[6])          #нужные свойства - id, url, author, name     except:         return     if id not in already_added: #если мы не скачивали композицию         already_added.append(id)    #добавляем ее в скачанные     file_path = "audio/"+getAllowName(author+" - "+name)+".mp3" #создаем путь, по которому будет храниться эта композиция      with open(file_path, "w"):  #создаем пустой файл с указанным путем         pass     ur.urlretrieve(url, file_path)  #и производим закачку     print name, "downloaded"      def getFirstNSongs(first=0, last = None):     """Функция, получает номер первой песни, которую надо скачать и номер последней     и производит закачку"""     if not os.path.exists(os.path.join(os.getcwd(), 'audio')):         #если нет папки audio создаем ее         os.mkdir('audio')     songs = getAudio()  #получаем описания песен          #обрезаем массив песен, в соответствии с указанными first и last:     if last!=None:           songs = songs[first:last+1]     else:         songs = songs[first:]              for x in songs: #для каждой нужной песни         OneDownload(x)  #скачиваем ее  getFirstNSongs(last = 10)  

Основная функя здесь — OneDownload(). По сути, именно она скачивает песни. Делается это с помощью стандартной функции urllib.urlretrieve(url, file_path, …). Эта функция скачивает данные, которые возвращает сервер при обращении к url и пишет в файл, который находится на пути file_path.

Все хорошо, все скачивается, но медленно!

Можем попробовать распараллелить наш алгоритм. Функции которые хотелось бы выполнять параллельно — это OneDownload. Создаем декоратор распараллеливания:

 def Thread(f):     def _inside(*a, **k):         thr = threading.Thread(target = f, args = a, kwargs = k)         thr.start()     return _inside  

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

Добавляем глобальню переменную — число потоков. Напрямую из Thread-ов изменять эту переменную будет нельзя, поэтому добавляем функции

инкремента, и получения:

 alive_threads = 0 def inc(x):     #изменяет переменую     global alive_threads     alive_threads+=x     return alive_threads def get():     #возвращает значение     global alive_threads     return alive_threads  

Теперь вносим изменения в код. Вот конечная версия программы:

 #-*-coding:cp1251-*- import requests as r import re import threading import time import random as ran import os import urllib as ur  THREADS_COUNT = 10 ALLOW_SYMBOLS = " qwertyuiopasdfghjklzxcvbnmйцукенгшщзхъэждлорпавыфячсмитьбюЙЦУКЕНГШЩЗХЪЭЖДЛОРПАВЫФЯЧСМИТЬБЮ.,-()" COOKIE = "" #Здесь ваш cookies  def getAllowName(string):     s=''     print string.lower()     for x in string.lower():         if x in ALLOW_SYMBOLS:             s += x     return s          def getRandomElement(arr, delete = False):     index = ran.randrange(0, len(arr), 1)     value = arr[index]     if delete:         arr.remove(value)     return value   alive_threads = 0 def inc(x):     global alive_threads     alive_threads+=x     return alive_threads def get():     global alive_threads     return alive_threads  def Thread(f):     def _inside(*a, **k):         thr = threading.Thread(target = f, args = a, kwargs = k)         thr.start()     return _inside   def getAudio():     response = r.post(                     "http://vk.com/audio",                     data = {                         'act':"load_audios_silent",                         "al":"1",                         "gid":"0",                         "id":"17738938"                             },                     headers = {                         "Cookie":COOKIE                                }                       )     i=0     pat = re.compile(r"\[.+?\]")     return pat.findall(response.content)   already_added = []  #тут будем хранить id композиций, которые уже скачаны. pat = re.compile(r"\'(.+?)\'")  #паттерн соответствует всем подстрокам вида '.*' count = 0  @Thread def OneDownload(x):     global already_added     inc(1)    #когда запустился новый тред - инкрементируем число тредов     try:         elements = pat.findall(x)         id, url, author, name = (elements[1], elements[2], elements[5], elements[6])      except:         return     if id not in already_added:         already_added.append(id)     file_path = "audio/"+getAllowName(author+" - "+name)+".mp3"     with open(file_path, "w"):         pass     ur.urlretrieve(url, file_path)     inc(-1)     #тред закончился - декрементируем            def getFirstNSongs(a=0, N = None):     if not os.path.exists(os.path.join(os.getcwd(), 'audio')):         os.mkdir('audio')     songs = getAudio()     if N!=None:         songs = songs[a:N]     else:         songs = songs[a:]     previous = 0    #тут будем хранить число еще не скачанных песен     cc=10     while (len(songs)>0 and len(songs)!=previous) or (len(songs) == previous and cc>0):         #пока число песен непусто, или количество оставшихся песен не изменялось в менее чем 10 циклах         if previous != len(songs):             previous = len(songs)   #смотрим, изменилось ли число песен. Если да - присваиваем             cc=10   #число шагов - 10         else:             cc-=1   #если не изменилось, уменьшаем число шагов на 1. Если кол-во песен не изменится за 10 шагов мы выйдем из цикла         print "Осталось скачать", len(songs), "Число нитей", alive_threads         while alive_threads<THREADS_COUNT:  #пока можем создавать новые треды             x = getRandomElement(songs, delete = True)  #получаем рандомную песню, которую надо скачать             try:                 OneDownload(x)  #пытаемся скачать             except:                 songs.append(x) #если не получилось - возвращаем назад.         while alive_threads>=THREADS_COUNT:             time.sleep(10)  #если не можем добавлять новые треды - спим 10 секунд.                getFirstNSongs(N=3) #скачиваем, например, первые 3 песни  

Теперь все работает.

Исходники и компилированную версию можно скачать по этой ссылке:

VKmusic

ссылка на оригинал статьи http://habrahabr.ru/post/157925/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *