Форум программистов, компьютерный форум, киберфорум
Наши страницы

И снова музыка VK

Войти
Регистрация
Восстановить пароль
Рейтинг: 2.50. Голосов: 2.

И снова музыка VK

Запись от diadiavova размещена 07.11.2017 в 13:09

  1. Предварительные замечания
  2. Получение данных о треке
  3. Получение прямой ссылки
  4. Скрипт для Tampermonkey
Предварительные замечания

На тему скачивания музыки из социальной сети "В контакте" я уже писал ранее, но с тех пор кое-что изменилось, так что скрипты, актуальные для того времени - больше не работают. Мало того, "вконтактик" стал "шифроваться": если раньше страница содержала прямые ссылки на аудиофайлы, то теперь их там нет и процесс существенно усложнился.

Далее следует упомянуть, что для скачивания музыки из "вконтактика" есть расширения для браузеров, так что, если нужно просто что-то скачать, то лучше использовать именно их, а то, что я опишу здесь, будет интересно скорее, если надо сделать что-то нестандартное. Например, ранее я приводил скрипт для скачивания видео оттуда же, так там в браузере просто формировался 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
Здесь мы видим JSON-код, обрамленный некими дополнительными маркерами, представляющий массив массивов, где каждый внутренний массив содержит информацию об отдельном треке, в том числе адрес некоего файла с расширением mp3, хоть и с очень подозрительным именем файла audio_api_unavailable, напоминающем нам о недоступности аудио-апи. Но поскольку у адреса есть еще параметр, то остается надежда, что это то, что нам нужно. Но, к сожалению, эта ссылка ведет к файлу, который теперь уже голосом рассказывает нам о том, что мы пытаемся прослушать файл не тем приложением или что-то в этом роде.

Несмотря на то, что везде в этих примерах мы получаем один и тот же файл, все-таки параметр 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
1
2
3
4
5
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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);
}
Далее следует сказать, что при отправке более пяти идентификаторов одновременно адекватного ответа ждать не стоит. Кроме того надо помнить, что некоторые треки доступны только по подписке или недоступны по другим причинам и информация о них не приходит. Так что надо доступные id разделить на группы по 5 элементов и смотреть, что возвратил сервер, благо ответ содержит информацию о треке, которая не исчерпывается ссылкой на файл. Идентификатор полученного трека можно получить объединив первые два элемента массива знаком подчеркивания, третий элемент - url файла с которым будем дальше работать для получения прямой ссылки, далее идут: название композиции, автор, продолжительность в секундах. В остальном не разбирался, поскольку решил, что это вряд ли понадобится.

Получение прямой ссылки

Теперь рассмотрим как из полученного негодного адреса файла получить ссылку на файл, который мы ищем. Получить информацию о том, как на странице обрабатываются данные - задача непростая, поскольку придется перелопатить очень много всего. Я пошел по более простому пути и воспользовался результатом работы проделанной другими людьми. А именно: установил расширение для браузера Firefox, предназначенное скачивания аудиофайлов оттуда же, откуда качаем и мы и поискал решение там. Ссылка на это расширение здесь.VK Universal Downloader.

Исследование кода этого расширения привело к тому, что нам понадобится следующий фрагмент.
Кликните здесь для просмотра всего текста
Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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
}
Единственная неприятность, что там используется некая функция each, которая нигде не определена, и по всей видимости импортируется в этот код, но откуда - я не разбирался. В то же время, несложно догадаться, что именно делает эта функция, поэтому я ее добавил и реализовал следующим образом.
Javascript
1
2
3
4
5
6
7
        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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// ==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 в хроме почему-то не работает.

Полученный документ надо использовать сразу, поскольку прямые ссылки на файлы со временем устаревают.
Размещено в Без категории
Просмотров 341 Комментарии 0
Всего комментариев 0

Комментарии

 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru