Получение ссылок на аудио без VKApi

Данная страница будет полезной для тех, кто решил взять заказ на парсер аудио-треков VK и резко понял, что ничего не понял.

В чем проблема


Знакомо?

https://m.vk.com/mp3/audio_api_unavailable.mp3?extra=AeL2rMfFyZzlD3HkyvfnvNvLx1KOqw5UDfuXCOTvttm4ts1OBJnYELvHyxvODI9fnM9YztD5A3iOyI14sxv2mNiXt3iTzdLInduXzvG9C2uVr3b5mezinfj2lJbpDhGYC25rDxbwsOPQmg1eu2Pbyxr3ntPowNLhDMrrDs8XnKu2sOuOyO8XzMf1otDmBtL6BNvllNjZx3aZuLHpq3aOBvvhzenJnZKTzKnMuwfKBI4TquffrtzKv2nymMyVDu1LzJnuwMLxwMm/BeTcserWlun3ExLVBG#AqSZntu

Если да — то вы пытались парсить мобильную версию сайта и успешно доставали ссылки. Неверные ссылки. Ссылки на 25-секундный голос, сообщающий что все идет не по плану.

Если нет — вам стоит попробовать.

Как получить верный URL


А вот это верный вопрос! Дело в том, что перед воспроизведением записи, вк натравливает на такой url заготовленные js-скрипты. В общем-то ничего сложного — несколько переворотов строк, побитовые сдвиги, даже одно побитовое отрицание. И все это сжато компрессором.

Честно говоря, раньше искать функции, отвечающие за это дело, было сложнее. Судя по всему во Вконтакте завелись кроты)) Иначе как блин объяснить то, что они подписали, буквально повесили вывеску на самом нужном месте:

image

Ладно, ладно, все мы рабы сборщиков…

Без лишних слов, актуальный код


Декодер на JavaScript
var id = 0; //Ваш userid
var n = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN0PQRSTUVWXYZO123456789+/=",
	i = {
		v: function(e) {
			return e.split("").reverse().join("")
		},
		r: function(e, t) {
			e = e.split("");
			for (var i, o = n + n, s = e.length; s--;) i = o.indexOf(e[s]), ~i && (e[s] = o.substr(i - t, 1));
			return e.join("")
		},
		s: function(e, t) {
			var n = e.length;
			if (n) {
				var i = r(e, t),
					o = 0;
				for (e = e.split(""); ++o < n;) e[o] = e.splice(i[n - 1 - o], 1, e[o])[0];
				e = e.join("")
			}
			return e
		},
		i: function(e, t) {
			return i.s(e, t ^ id)
		},
		x: function(e, t) {
			var n = [];
			return t = t.charCodeAt(0), each(e.split(""), function(e, i) {
				n.push(String.fromCharCode(i.charCodeAt(0) ^ t))
			}), n.join("")
		}
	};

function o() {
	return window.wbopen && ~(window.open + "").indexOf("wbopen")
}

function s(e) {
	if (!o() && ~e.indexOf("audio_api_unavailable")) {
		var t = e.split("?extra=")[1].split("#"),
			n = "" === t[1] ? "" : a(t[1]);
		if (t = a(t[0]), "string" != typeof n || !t) return e;
		n = n ? n.split(String.fromCharCode(9)) : [];
		for (var s, r, l = n.length; l--;) {
			if (r = n[l].split(String.fromCharCode(11)), s = r.splice(0, 1, t)[0], !i[s]) return e;
			t = i[s].apply(null, r)
		}
		if (t && "http" === t.substr(0, 4)) return t
	}
	return e
}

function a(e) {
	if (!e || e.length % 4 == 1) return !1;
	for (var t, i, o = 0, s = 0, a = ""; i = e.charAt(s++);) i = n.indexOf(i), ~i && (t = o % 4 ? 64 * t + i : i, o++ % 4) && (a += String.fromCharCode(255 & t >> (-2 * o & 6)));
	return a
}

function r(e, t) {
	var n = e.length,
		i = [];
	if (n) {
		var o = n;
		for (t = Math.abs(t); o--;) t = (n * (o + 1) ^ t + o) % n, i[o] = t
	}
	return i
}


Буква в букву декодер на PHP
global $n, $i, $id;
$n = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN0PQRSTUVWXYZO123456789+/=";
$id = 123456789; //user_id
$i = [
	'v' => function($e) {
		return strrev($e);
	},
	'r' => function($e, $t){
		global $n;
		$e = str_split($e);
		for ($o = $n . $n, $s = count($e); $s--;){
			$i = stripos($o, $e[$s]);
			if(~$i){
				$e[$s] = substr($o, $i - $t, 1);
			}
		}
		return implode("", $e);
	},
	's' => function($e, $t) {
		$n = strlen($e);
		if ($n) {
			$i = r($e, $t);
			$o = 0;
			$e = str_split($e);
			for (; ++$o < $n;){
				$p = array_splice($e, $i[$n - 1 - $o], 1, $e[$o]);
				$e[$o] = $p[0];
			}

			$e = implode("", $e);
		}

		return $e;
	},
	'i' => function($e, $t){
		global $i, $id;
		$k = $i['s'];
		return $k($e, $t ^ $id);
	},
];

function o() {
	return false;
}

function a($e){
	global $n;
	if (!$e || strlen($e) % 4 == 1) {
		return !1;
	}
	$s = 0;
	for ($o = 0, $a = "";$s < strlen($e);) {
		$i = $e[$s++];
		$i = strpos($n, $i);
		if ($i !== false) {
			$t = ($o % 4) ? 64 * $t + $i : $i;
			if ($o++ % 4) {
				$a .= chr(255 & $t >> (-2 * $o & 6));
			}
		}
	}

	return $a;
}

function r($e, $t) {
	$n = strlen($e);
	$i = [];
	if ($n) {
		$o = $n;
		$t = abs($t);
		for (; $o--;){
			$t = ($n * ($o + 1) ^ $t + $o) % $n;
			$i[$o] = $t;
		}
	}
	return $i;
}

function s($e){
	global $i;
	if (!o() && strpos($e, "audio_api_unavailable") !== false) {
		$t = explode("?extra=", $e);
		$t = $t[1];
		$t = explode("#", $t);
		$n = ("" === $t[1]) ? "" : a($t[1]);
		$t = a($t[0]);
		if (!is_string($n) || !$t){ return $e;}
		$n = $n ? explode(chr(9), $n) : [];
		for ($l = count($n); $l--;) {
			$r = explode(chr(11), $n[$l]);
			$s = array_splice($r, 0, 1, $t);
			$s = $s[0];
			if (!$i[$s]){ return $e; }
			$t = $i[$s](...$r);
		}
		if ($t && "http" === substr($t, 0, 4)){ return $t;}
	}
	return $e;
}



В обоих случаях

s("https://m.vk.com/mp3/audio_api_unavailable.mp3?extra=encodeextraurl");

Думаю, при надобности с PHP на другой язык перевести код будет уже проще.

Статья написана с целью снизить кол-во человекоминут в мире, затрачиваемых на эту задачу.

P.s: Актуальное решение всегда можно будет найти здесь: gist.github.com/in4in-dev/09f32f313f11b2c10778d9e2ffe7e60e
P.s2: Пользователь ImIeee также обновляет свое решение в репозитории github.com/vodka2/vkaudio (тут вы найдете решение на Python)
Поделиться публикацией

Комментарии 40

    +9
    Поменяют и спрячут алгоритм теперь после этой публикации.
      +1
      Зато автор публикации получит новый заказ на доработку парсера )))
        +3

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

        0
        Не думаю, что из-за статьи будут что-то менять. В конце концов, они и не прячут декодер — просто скрывают таким образом музыку от обывателя.
          0
          ровно год назад была статья на эту тему.
          интересно за это время сильно изменился механизм?
          habr.com/post/340810
            0
            Сравнивал вскользь недавно.

            Они добавили привязку к id пользователя
            i: function(e, t) {
            	return i.s(e, t ^ vk.id)
            },



              0
              На сколько понимаю, они этим самым проверяют локаль пользователя… и в плейлисте появляется то и дело — «Аудиозапись your_favorite_music недоступна для прослушивания в Вашем регионе.»
              Но можно же, в теории, позаимствовать айди товарища из «нужной» страны… Хотя вроде там более замароченно.
                0
                Думаю, за это отвечает другой механизм и на такие аудио даже ссылки не выдает. К сожалению, мне такие записи не попадались и как это обойти — ответить трудно. Попробую зайти через впн глянуть.
                  0
                  Именно ВПН и позволяет обойти «ваш регион».
                  Точнее, если у туннеля выход в Европе — DMCA(или кто там у них) блюдётся. Если в России — слушай на здоровье.
                    0
                    А у меня, к сожалению, такое встречается постоянно… если не пользуюсь русским прокси.
                    Именно поэтому перестал пользоваться вк музыкой как таковой… проще уж из других источников слушать, чем слушать трэк через три.
                      0
                      Попробуйте Тайский впн, очень многие песни с Пхукета недоступны, кстати.
              0
              А никто ничего не прячет, похожий на base64 декодер подогнанный под свои нужды
                0
                Всегда есть возможность использовать telegram бота…
                +3
                главное успеть скачать всю свою музыку) я успел) автору большой респект!!!
                  0
                  А зачем? Можно же просто взять ID официального приложения и пользоваться API для аудио как раньше.
                    0

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

                      0
                      Не эту реализацию случайно — github.com/vodka2/vk-audio-token? Если эту, то можно было создать Issue, кроме того, не так давно я добавил примеры использования полученных токенов. Если другую, можете, если не трудно, написать ссылку? Вообще, на мой взгляд, использование API намного удобнее, чем парсинг сайта.
                        +1
                        Реализация ваша, да. Спасибо за нее :)
                        Я просто ради интереса решил переписать на шарпе, поэтому issue не помогут. Токены получаю от гугла и от вк, но что-то делаю не так — vk api всегда возвращает тот самый 25-секундный семпл. Хочу разобраться и запилить экспортер всего-и-вся-из-вк, но все руки не доходят.
                          0
                          Сложно сказать, не видя кода, но, возможно, вы делаете запрос к API с другим User-Agent. В принципе, я мог бы посмотреть ваш код, если бы вы выложили его на Github, всё-таки идея хорошая.
                            0
                            Если не справлюсь, напишу вам) все-таки интересно и самому поковыряться
                          +1
                          Мы вот получаем один обычный токен через авторизацию по логину-паролю под видом одного из официальных приложений и по этому токену напрямую используем старые методы аудио-апи. И никаких вытаскиваний GMS не нужно.
                            0
                            поделитесь кусочком кода?
                              0
                              def create_playlist_file(self, user_id: Union[int, str]) -> str:
                                      # Probably it is better to separate it all to different methods
                              
                                      all_music_link = f'https://api.vk.com/method/audio.get?owner_id={user_id}&access_token={self.token}&v=5.85'
                                      all_music = requests.get(all_music_link).json()['response']['items']
                                      song_ids = []
                              
                                      # Create unique song ids
                                      for song in all_music:
                                          if not song.get('access_key'):
                                              continue
                                          access_key = song['access_key']
                                          song_id = song['id']
                                          song_ids.append(f'{user_id}_{song_id}_{access_key}')
                              
                                      splited_song_ids = self.group(song_ids, 200)  # Grouping ids because of VK API limits for songs in one request
                              
                                      result = OrderedDict()  # OrderedDict here and below is to specify order of songs like in a real playlist
                              
                                      for chunk in splited_song_ids:
                                          # Making one request to get data for each 200 songs
                                          ids_for_request = ','.join(chunk)
                                          data = {'audios': ids_for_request, 'access_token': self.token, 'v': '5.85'}
                                          songs_with_urls = requests.post('https://api.vk.com/method/audio.getById', data=data).json()['response']
                              
                                          sleep(0.5)  # Excepting VK API rate limit
                              
                                          # Getting only needed data for each song to create a beautiful playlist file
                                          for song in songs_with_urls:
                                              id_ = song['id']
                                              author = song.get('artist')
                                              title = song.get('title')
                                              src = song.get('url')
                                              if not src:
                                                  is_blocked = True
                                              else:
                                                  is_blocked = False
                                              needed_data = OrderedDict(author=author, title=title, src=src, is_blocked=is_blocked)
                                              result[id_] = needed_data
                                0
                                del
                                  0
                                  Спасибо за код, но куда интересней было бы узнать название приложения.
                          +1
                          После того как появилась платная подписка на музыку, сделали завязку на google play, и для полноценной работы нужно проходить доп. валидацию с использованием android_id (device_id) при которой api токен меняется на такой же токен с доп. правами, в частности с правами на доступ к audio api, при том что документацию по audio api с сайта вк выпилили.
                            0
                            Официальный клиент ВК для Android может работать и без валидации, правда, там другие методы API.
                          0
                          Значит ли это что сейчас опять появятся на пару недель over 9000 приложений музыки в AppStore?
                            +1
                            Как ответила мне одна девушка, «У меня целых три таких приложения и я жму там одну кнопочку, а ты тут столько строк какой-то фигни написал»)
                              –1
                              «Глаза» то у девушки хоть большие?
                              –1

                              Даже Google сейчас выпиливает плагины для скачивания музыки из VK.

                              • НЛО прилетело и опубликовало эту надпись здесь
                                0

                                Такие приложения разве не нарушают условия соглашений?

                                0
                                Красивый код на php)
                                  0
                                  Подозреваю, что это шифрование ссылок сделано только для того, что бы у правообладателей не было претензий к сервису.
                                    –2
                                    Данная страница будет полезной для тех, кто решил взять заказ на парсер аудио-треков VK и резко понял, что ничего не понял.


                                    А если для себя решил написать, то страница полезной не будет?
                                      +1
                                      Давно уже поддерживаю аналогичный код на Github и на Тостере. Даже скрипт для обратного шифрования есть.
                                        0
                                        Добавил к статье :)
                                        0
                                        Спасибо за статью. Ранее через API ходил, потом через ID приложений. Этот метод куда проще кажется.
                                        Забавно, но не все ссылки декодирует нормально. После процесса декодирования, перейдя по ссылке, кидает 404. Позже скину пример…

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

                                        Самое читаемое