Предварительные замечания
На тему скачивания музыки из социальной сети "В контакте" я уже писал ранее, но с тех пор кое-что изменилось, так что скрипты, актуальные для того времени - больше не работают. Мало того, "вконтактик" стал "шифроваться": если раньше страница содержала прямые ссылки на аудиофайлы, то теперь их там нет и процесс существенно усложнился.
Далее следует упомянуть, что для скачивания музыки из "вконтактика" есть расширения для браузеров, так что, если нужно просто что-то скачать, то лучше использовать именно их, а то, что я опишу здесь, будет интересно скорее, если надо сделать что-то нестандартное. Например, ранее я приводил скрипт для скачивания видео оттуда же, так там в браузере просто формировался bash-скрипт для скачивания, после чего его можно разместить на какой-то другой машине, которая будет все это дело качать. Для музыки это, возможно, и не так актуально, поскольку музыкальные файлы меньше по размеру и качаются быстро, но, тем не менее сценарии, в которых может потребоваться получение списка ссылок на аудиофайлы - вполне могут быть востребованными. Хотя, лично для меня весь этот процесс представляет сугубо "научный" интерес.
Получение данных о треке
Как уже было сказано выше, страница с музыкой "вконтактика" не содержит прямых ссылок на аудиофайлы ни в каком виде. Мало того, там и другая информация о треках появляется только по мере прокрутки страницы и появления их описания в поле видимости. В то же время браузеру, для того, чтобы что-то воспроизвести, надо знать откуда это "что-то" надо качать. Это означает, что в какой-то момент нужная ссылка все-таки появляется и нам надо понять когда и как это происходит. Наиболее логичным будет предположение, что информация о файле запрашивается с сервера в тот самый момент, когда пользователь нажимает на "кнопку" воспроизведения. Исходя из этого предположения, нам надо открыть страницу с музыкой и инструменты разработчика браузера(вместо инструментов это может быть и внешняя программа, позволяющая мониторить траффик), запустить какой-нибудь трек и посмотреть, что после этого подгрузилось. Возможно нам повезет и мы найдем что-то похожее на ссылку на аудиофайл.
Вот я взял для примера один трек, запустил его и среди прочих ответов нашел один следующего содержания
Код:
3555327223824<!><!>0<!>6864<!>0<!><!json>[[456239027,236521131,"https:\/\/vk.com\/mp3\/audio_api_unavailable.mp3?extra=yvHQuL0Tn1DYsZiUu3boDgvJsuLLCgzHq2vUnMXkCvv1D105uOzyuOu5otvSvxjuvO9tBxqWmhDYqKq5AZDRlMm5DeTSChbnqs8WDujVCMvtrw12CdDwELzIncOXwu1zyMvZq3GWAwjgnODZmez6qZfdsermoc9sse8ZnfbFBvHAlJjdCdboogK5AsO9AdHqCMvsnwrZm24VC3q5zZHJBNy4AuHXEhaVsxq5zun3nJLSyZm4p286Eerpsu9TmfvdA1DrmLvOCW#CWS2mde","Toxicity","SOAD",221,0,0,"",0,66,"","[]","87ef4ee798155bb9c7\/5af6233c4ebfce2bc7\/dbcca94dfef4740bcb\/1b09969c356c6cd7b7\/","https:\/\/pp.userapi.com\/c637525\/v637525450\/43363\/fq34v93oQ-E.jpg,https:\/\/pp.userapi.com\/c637525\/v637525450\/43362\/Empa6PNfdd4.jpg",13],[456239026,236521131,"https:\/\/vk.com\/mp3\/audio_api_unavailable.mp3?extra=Ady1DZHZyxa3mOTcAY5tlKTem3iYrtqZEdi3CtDLvvvFyNfHvLj2BtfSmgKWqNnWzxmOB1DHBI5KodmZELKWuxPdr2mVytrdqN0Wus9bBMH1ANfrs2DRohnZAZuWrxbHyvjwB3POwxu1AMTWCMLPxOPiy2e2uhHTnJiXqtPzBdqVuxbZtKTrmN09qwTOsJKWrK9kttHzDOjVBM1yDxvNvMOYA20YpZHYufu3A3GTmg9Vy3qTC3LcBwvJss9tDLzJvMmVBZu1x3rertnMsfH4vtzsls9zuffY#CWS5nJG","Bohemian Raphsody","Queen",367,0,0,"",0,2,"","[]","de85fcf68b66fdfc72\/20835a3ec770f404a1\/143a1ba9f6cf77e927\/98f642833f224eebc4\/","",[]],[456239021,236521131,"https:\/\/vk.com\/mp3\/audio_api_unavailable.mp3?extra=qL05v1btyxb4lZzWAwnMzfi5CvrLutrrmhrpz3qXnhjLt3zkrM54tNmOwMvslNPMufy9B1D4EwnoCOH1BxfiEdbixZi2CNrOs1bwAfLNqxr4BOjkzK84CvvHmdD1ttq2Cxe\/Bhe3Cfn0CY4TlZn2Es8WAtzJEwe6mfbqzeTjoejZuKrdovvcmLu5DdfkvLPyoc53m2HIlwXjCePZuuPTAM44z3bUAxHTyvDLmfrinKrVCMnjvxLSwezJrM5Yx2vXrOy1lZu1vq#CWS4mJC","Прелюдия op.23 N5 g-moll (C. Рихтер)","C. Рахманинов",224,0,0,"",0,66,"","[]","72dd9492b75b10c6d2\/cb7082fdf361019a23\/aedd4d728dcd893ab6\/ccb81055a794889803\/","https:\/\/pp.userapi.com\/c604720\/v604720447\/4d59e\/3FEenUEGjcc.jpg,https:\/\/pp.userapi.com\/c604720\/v604720447\/4d59d\/mK7PAW1l6_8.jpg",32]]<!><!json>{"236521131":"1"}<!>970abaeddddd19fa50
Несмотря на то, что везде в этих примерах мы получаем один и тот же файл, все-таки параметр extra у каждого адреса свой, поэтому, как показали дальнейшие исследования, вожделенную прямую ссылку мы, в конце концов, будем получать именно из него. Стало быть для начала было бы совсем не лишним разобраться как получить от сервера такой же ответ.
Для получения такого ответа был отправлен POST-запрос на адрес https://vk.com/al_audio.php, со следующими параметрами
act: reload_audio
al: 1
ids: 236521131_456239027,236521131_456239026,236521131_456239025,236521131_456239024,236521131_456239021,236521131_456239027
Первые два параметра всегда одинаковые, что касается третьего, то нетрудно догадаться, что здесь записаны через запятую уникальные идентификаторы треков. Здесь указано пять идентификаторов, хотя мы получили информацию только о трех треках, но на странице, с которой я это взял в списке среди первых пяти треков третий и четвертый стали доступны только по подписке, поэтому информация о них и не пришла.
Кроме изложенного следует упомянуть о cookies. Если запрос отправляется из браузера с учетом контекста страницы, как это происходит, если выполнять все в скрипте GreaseMonkey или ему подобных, то можно об этом не думать. В то же время, если надо отправить запрос из стороннего приложения, или даже из браузера, но когда не пройдена авторизация в vk, то вразумительного ответа от сервера не будет. Пока мы на этом сосредотачиваться не будем, а займемся идентификаторами.
Открываем страничку с аудиозаписями берем любой трек и просматриваем блок с записью с помощью инструментов разработчика. Я смотрю в файрфоксе, там в контекстном меню есть пункт "Исследовать элемент". В одном из контейнеров, открытого таким образом элемента можно обнаружить атрибут data-full-id в котором как раз и содержится нужный нам id. Этой информации вполне достаточно для того, чтобы собрать ids со страницы.
Код javascript | Выделить |
function GetVisibleIds()
{
var audiorows = document.querySelectorAll("div[data-full-id]");
return Array.prototype.map.call(audiorows, function(e){return e.getAttribute("data-full-id")});
}
Кликните здесь для просмотра всего текста
Код javascript | Выделить |
function GetTrackDataAsync(ids, callback)
{
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://vk.com/al_audio.php?act=reload_audio&al=1&ids=" + ids, true);
xhr.onreadystatechange = function()
{
if (xhr.readyState != 4) return;
if (xhr.status != 200)
{
console.log(xhr.status + ': ' + xhr.statusText);
}
else
{
//console.log("response");
var re = /\<\!json\>([^<]+)\<!>/;
var resp = re.exec( xhr.responseText)[1];
callback(JSON.parse(resp));
}
}
xhr.send();
}
function GetTrackData(ids)
{
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://vk.com/al_audio.php?act=reload_audio&al=1&ids=" + ids, false);
xhr.send();
var re = /\<\!json\>([^<]+)\<!>/;
var resp = re.exec( xhr.responseText)[1];
return JSON.parse(resp);
}
Получение прямой ссылки
Теперь рассмотрим как из полученного негодного адреса файла получить ссылку на файл, который мы ищем. Получить информацию о том, как на странице обрабатываются данные - задача непростая, поскольку придется перелопатить очень много всего. Я пошел по более простому пути и воспользовался результатом работы проделанной другими людьми. А именно: установил расширение для браузера Firefox, предназначенное скачивания аудиофайлов оттуда же, откуда качаем и мы и поискал решение там. Ссылка на это расширение здесь.VK Universal Downloader.
Исследование кода этого расширения привело к тому, что нам понадобится следующий фрагмент.
Кликните здесь для просмотра всего текста
Код javascript | Выделить |
function fix_direct_url(t) {
if (~t.indexOf("audio_api_unavailable")) {
var e = t.split("?extra=")[1].split("#"), o = "" === e[1] ? "" : a(e[1]);
if (e = a(e[0]), "string" != typeof o || !e)return t;
o = o ? o.split(String.fromCharCode(9)) : [];
for (var s, r, n = o.length; n--;) {
if (r = o[n].split(String.fromCharCode(11)), s = r.splice(0, 1, e)[0], !l[s])return t;
e = l[s].apply(null, r)
}
if (e && "http" === e.substr(0, 4))return e
}
return t
}
function s(t, e) {
var i = t.length, o = [];
if (i) {
var a = i;
for (e = Math.abs(e); a--;)o[a] = (e += e * (a + i) / e) % i | 0
}
return o
}
var r = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN0PQRSTUVWXYZO123456789+/=", l = {
v: function (t) {
return t.split("").reverse().join("")
}, r: function (t, e) {
t = t.split("");
for (var i, o = r + r, a = t.length; a--;)i = o.indexOf(t[a]), ~i && (t[a] = o.substr(i - e, 1));
return t.join("")
}, s: function (t, e) {
var i = t.length;
if (i) {
var o = s(t, e), a = 0;
for (t = t.split(""); ++a < i;)t[a] = t.splice(o[i - 1 - a], 1, t[a])[0];
t = t.join("")
}
return t
}, x: function (t, e) {
var i = [];
return e = e.charCodeAt(0), each(t.split(""), function (t, o) {
i.push(String.fromCharCode(o.charCodeAt(0) ^ e))
}), i.join("")
}
}
function a(t) {
if (!t || t.length % 4 == 1)return !1;
for (var e, i, o = 0, a = 0, s = ""; i = t.charAt(a++);)i = r.indexOf(i), ~i && (e = o % 4 ? 64 * e + i : i, o++ % 4) && (s += String.fromCharCode(255 & e >> (-2 * o & 6)));
return s
}
Код javascript | Выделить |
function each(arr, f)
{
for (var i = 0; i < arr.length; i++)
{
f(arr, arr[i]);
}
}
Скрипт для Tampermonkey
Теперь можно все вышесказанное объединить во что-то более-менее работающее и способное проиллюстрировать все вышесказанное. Скрипт запускался в браузере Google Chrome с расширением Tampermonkey. В Firefox, к сожалению на vk.com это расширение по непонятной причине не хочет реагировать на GM_registerMenuCommand и не создает пункт меню. Я написал об этом в комментариях на странице расширения, разработчик в ответ предложил скачать бета-версию "from here", но где это "here" указать, видимо, забыл. Кроме того попросил, напсать работет или нет, но куда писать - тоже неизвестно. )) Поэтому пока тестировалось только на хроме.
Кликните здесь для просмотра всего текста
Код javascript | Выделить |
// ==UserScript==
// @name DownloadVkMusic
// @namespace https://vk.com/
// @match https://vk.com/*
// @version 1
// @description Получает данные о музыкальных треках на vk.com
// @author diadiavova
// @grant GM_registerMenuCommand
// ==/UserScript==
(function(){
GM_registerMenuCommand("Показать данные о треках", function(){
TextInNewPage(JSON.stringify(GetDataFromPage().map(GetObjFromArray)));
});
function GetVisibleIds()
{
var audiorows = document.querySelectorAll("div[data-full-id]");
return Array.prototype.map.call(audiorows, function(e){return e.getAttribute("data-full-id");});
}
function GetTrackDataAsync(ids, callback)
{
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://vk.com/al_audio.php?act=reload_audio&al=1&ids=" + ids, true);
xhr.onreadystatechange = function()
{
if (xhr.readyState != 4) return;
if (xhr.status != 200)
{
console.log(xhr.status + ': ' + xhr.statusText);
}
else
{
var re = /<\!json\>([^<]+)<!>/;
var resp = re.exec( xhr.responseText)[1];
callback(JSON.parse(resp));
}
};
xhr.send();
}
function GetTrackData(ids)
{
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://vk.com/al_audio.php?act=reload_audio&al=1&ids=" + ids, false);
xhr.send();
var re = /<\!json\>([^<]+)<!>/;
var resp = re.exec( xhr.responseText)[1];
return JSON.parse(resp);
}
function GetIdRanges()
{
var idarray = GetVisibleIds();
var result = [];
for(var i = 0; i < idarray.length; i += 5)
{
result.push(idarray.slice(i, i + 5).join(","));
}
return result;
}
function GetDataFromPage()
{
var result = [];
for(var ids of GetIdRanges())
{
result = result.concat(GetTrackData(ids));
}
return result;
}
function GetObjFromArray(a)
{
return {author: a[4], track: a[3], url: fix_direct_url(a[2]), duration: a[5]};
}
function TextInNewPage(txt)
{
var newwin = unsafeWindow.open("about:blank");
setTimeout(function(){newwin.document.body.appendChild(document.createTextNode(txt));}, 1000);
}
// Честно стыренный код расшифровки имен файлов
function fix_direct_url(t) {
if (~t.indexOf("audio_api_unavailable")) {
var e = t.split("?extra=")[1].split("#"), o = "" === e[1] ? "" : a(e[1]);
if (e = a(e[0]), "string" != typeof o || !e)return t;
o = o ? o.split(String.fromCharCode(9)) : [];
for (var s, r, n = o.length; n--;) {
if (r = o[n].split(String.fromCharCode(11)), s = r.splice(0, 1, e)[0], !l[s])return t;
e = l[s].apply(null, r);
}
if (e && "http" === e.substr(0, 4))return e;
}
return t;
}
function s(t, e) {
var i = t.length, o = [];
if (i) {
var a = i;
for (e = Math.abs(e); a--;)o[a] = (e += e * (a + i) / e) % i | 0;
}
return o;
}
var r = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN0PQRSTUVWXYZO123456789+/=", l = {
v: function (t) {
return t.split("").reverse().join("");
}, r: function (t, e) {
t = t.split("");
for (var i, o = r + r, a = t.length; a--;)i = o.indexOf(t[a]), ~i && (t[a] = o.substr(i - e, 1));
return t.join("");
}, s: function (t, e) {
var i = t.length;
if (i) {
var o = s(t, e), a = 0;
for (t = t.split(""); ++a < i;)t[a] = t.splice(o[i - 1 - a], 1, t[a])[0];
t = t.join("");
}
return t;
}, x: function (t, e) {
var i = [];
return e = e.charCodeAt(0), each(t.split(""), function (t, o) {
i.push(String.fromCharCode(o.charCodeAt(0) ^ e));
}), i.join("");
}
};
function a(t) {
if (!t || t.length % 4 == 1)return !1;
for (var e, i, o = 0, a = 0, s = ""; i = t.charAt(a++);)i = r.indexOf(i), ~i && (e = o % 4 ? 64 * e + i : i, o++ % 4) && (s += String.fromCharCode(255 & e >> (-2 * o & 6)));
return s;
}
})();
Данный скрипт создает пункт меню "Показать данные о треках" в меню расширения на странице вконтктика. Если на странице отображены треки, то в результате работы скрипта откроется новая вкладка и через секунду в ней отобразится JSON с данными о треках, включая прямые ссылки. Для отображения данных пришлось использовать setTimeout при открытии нового окна, поскольку обработка DOMContentLoaded в хроме почему-то не работает.
Полученный документ надо использовать сразу, поскольку прямые ссылки на файлы со временем устаревают.
Комментариев нет :
Отправить комментарий