пятница, 8 апреля 2016 г.

Качаем видео и музыку из "вконтактика".

  1. Постановка вопроса
  2. Инструменты
  3. Загрузка музыки
  4. Загрузка видео
Постановка вопроса

Когда описывается очередной способ сделать то, что уже давным-давно жевано-пережевано, возникает вполне резонные вопрос - зачем. Существует масса приложений для разных платформ, расширений для браузера, причем предназначенных как непосредственно для решения данной задачи, так и более универсальных, которые могут перехватывать ссылку на медиа-файл и добавлять его в список загрузки. Но если для скачивания музыки это худо-бедно может подойти, то с видео картина несколько иная. Видоеролик грузится довольно долго(если речь идет о фильме, например, с большим хронометражем и хорошим разрешением) и бывает не очень удобно держать компьютер включенным все это время и ждать пока закачается. Можно, конечно, смотреть прямо с плеера, предоставленного вконтактиком, но хотелось бы увидеть все в телевизоре или иногда на телефоне, когда не хочется на это расходовать мобильных траффик. Соответственно я задался вопросом, как бы отобрать нужный набор роликов, максимально используя возможности интерфейса вконтактика, но при этом чтобы фильмы грузил сервер (роль которого в моем случае выполняет старый компьютер с установленной на него Ubuntu Server).

Инструменты

Как я уже сказал, для решения я использую те возможности, которые предоставляет интерфейс вконтактика, стало быть нужен браузер, в моем случае это Firefox. Кроме того, для выполнения скриптов в браузере надо установить расширение GreaseMonkey. Для загрузки музыки непосредственно из браузера может понадобится расширение-загрузчик, я использую DownThemAll, но думаю, выбор расширения здесь не принципиален.

Загрузка музыки

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

Кликните здесь для просмотра всего текста
Код javascript Выделить

// ==UserScript==

// @name        VKAudio

// @namespace   VK

// @description Работа с аудио социальной сети вконтакте.

// @include     /^https?://vk\.com/audio(s\d+)?/

// @require     https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js

// @icon        http://vk.com/favicon.ico

// @grant GM_registerMenuCommand

// ==/UserScript==

function TrackData(autor, name, uri, duration)

{

    this.autor = autor;

    this.name = name;

    this.uri = uri;

    this.duration = duration;

    this.toString = function ()

    {

        return "{autor: " + autor + "; name: " + name + "; uri: " + uri + ";}";

    }

    return this;

}

 

function createData()

{

     var result = [];

    var audios = $("div[class ^= 'audio'][class $= 'fl_l']");

    for (var i = 0; i < audios.length; i++)

    {

        var uridur = $("input", audios[i]).attr("value").split(",");

        var uri = uridur[0];

        var duration = uridur[1];

        var info = $("div[class = 'info fl_l'] a", audios[i]);

        var autor = info[0].innerHTML;

        var composition = $("span.title", audios[i]).text();

        result.push(new TrackData(autor, composition, uri, duration));

    }

    return result;

}

 

function createLinkBlock(data, document)

{

    var div = document.createElement("div");

    var link = document.createElement("a");

    var txt = document.createTextNode(data.autor + " - " + data.name);

    link.appendChild(txt);

    link.setAttribute("href", data.uri);

    div.appendChild(link);

    return div;

}

 

function createMu3Block(data, document)

{

    var div = document.createElement("div");

    var text = "#EXTINF:" + data.duration + "," + data.autor + " - " + data.name + "<br/>" + data.uri + "<br/>";

    div.innerHTML = text;

    return div;

}

 

function optimizeFileName(fn)

{

    return fn.replace(/[\/\\\:\*\?\"\<\>\|\+\s]/g, "_");

}

  

GM_registerMenuCommand("Сформировать ссылки на аудио", function ()

{

    var tracks = createData();

    var newwin = unsafeWindow.open("about:blank");

    var div = document.createElement("div");

    for (var i = 0; i < tracks.length; i++)

    {

 

        div.appendChild(createLinkBlock(tracks[i], document));

    }

    newwin.document.write(div.innerHTML);

    newwin.document.close();

});

 

GM_registerMenuCommand("Создать скрипт загрузки", function ()

{

    var tracks = createData();

    var newwin = unsafeWindow.open("about:blank");

    var div = document.createElement("div");

    var script = "#!/bin/bash<br/>path=/home/downloads<br/>";

    for (var i = 0; i < tracks.length; i++)

    {

        script += "wget " + tracks[i].uri + " -o \"$path\"/" + optimizeFileName(tracks[i].autor + "-" + tracks[i].name) + "<br/>";

 

    }

    newwin.document.write("<div contenteditable='true'>" + script + "</div>");

    newwin.document.close();

});

 

/*

Устаревшие методы.

 

GM_registerMenuCommand("Сформировать ссылки на аудио", function()

{

    var tracks = createData();

    var newwin = window.open("about:blank");

   

    newwin.onload = function()

    {

        for(var i = 0; i < tracks.length; i++)

        {

            newwin.document.body.appendChild(createLinkBlock(tracks[i], newwin.document));

        }

    }

});

 

GM_registerMenuCommand("Сформировать список воспроизведения M3U", function()

{

    var tracks = createData();

    var newwin = window.open("about:blank");

   

    newwin.onload = function()

    {

        newwin.document.body.innerHTML = "#EXTM3U<br/>";

        for(var i = 0; i < tracks.length; i++)

        {

            newwin.document.body.appendChild(createMu3Block(tracks[i], newwin.document));

        }

    }

});

 

*/

 


Использовать скрипт надо следующим образом. После установки в браузере заходим на страничку с интересующим списком аудио(это может быть свой или чужой список или страница какого-то альбома). Если переход был осуществлен по ссылке из самого вконтактика, то страницу сначала надо обновить, поскольку там "хитрые" ссылки и при переходе по ним GreaseMonkey обычно не "знает" о том, что был осуществлен переход на другую страницу. Кроме того, надо иметь в виду, что вконтактик не отображает все треки сразу, поэтому, если список большой, а закачать его хочется целиком и сразу, то надо сначала прокрутить страницу до конца, чтобы все подгрузилось. Далее в меню GreaseMonkey Команды скриптов выбираем Сформировать ссылки на аудио. При этом открывается новая вкладка, на которой все файлы представлены в виде обычных ссылок, с загрузкой которых прекрасно справляются DownThemAll и ему подобные расширения.

Для загрузки файлов посредством линукс-сервера предназначена вторая команда этого же меню Создать скрипт загрузки. Он также открывает новую страницу, но уже с текстом bash-скрипта загрузки, который надо сохранить, отправить на сервер и запустить там. Здесь есть пара нюансов, которые надо иметь в виду, особенно если все это делается из системы Windows. В HTML обрыв строки осуществляется с помощью элемента BR, как это выводится на странице браузера (особенно при копировании) я точно не знаю, но возможно это зависит и от браузера и от платформы. Между тем в bash-скриптах переход на новую строку осуществляется символом \n (10), а не как в винде \r\n (13 10). Если этого не учитывать - могут возникнуть неприятности. Я использую текстовый редактор Programmer's Notepad, его можно настроить так, чтобы он автоматически конвертировал завершения строк сообразно правилам, принятым в "никсах". Кроме того надо помнить о том, что файлы надо сохранять в кодировке UTF-8 (тоже настраивается).

Первоначально скрипт загрузки аудио не включал генерацию bash-скрипта и, честно говоря, на момент написания этих строк я его еще не тестировал. Просто этот подход я использовал для загрузки видео, а в скрипт загрузки музыки добавил его буквально на днях. Когда я тестировал bash-скрипт загрузки видео, я столкнулся с неожиданной проблемой: загружался только самый первый файл, после чего работа скрипта останавливалась. Решил проблему одновременной загрузкой (в конце строк проставил амперсанды). Но это может сработать только при загрузке небольшого количества файлов, что вполне приемлемо для видео, но не очень хорошо для аудио-файлов, которых иногда нужно загрузить сразу много. DownThemAll грузит одновременно по 4 файла. Как эту проблему решить пока не знаю, еще надо попробовать разные варианты. Можно попробовать не ставить амперсанды через какое-то количество команд (например те же 4 файла грузить одновременно). Выглядеть это будет так
Код javascript Выделить

GM_registerMenuCommand("Создать скрипт загрузки", function ()

{

    var tracks = createData();

    var newwin = unsafeWindow.open("about:blank");

    var div = document.createElement("div");

    var script = "#!/bin/bash<br/>path=/home/downloads<br/>";

    for (var i = 0; i < tracks.length; i++)

    {

        script += "wget " + tracks[i].uri + " -o \"$path\"/" + optimizeFileName(tracks[i].autor + "-" + tracks[i].name) +

(i % 4 == 3 ? "" : " & ") + "<br/>";

    }

    newwin.document.write("<div contenteditable='true'>" + script + "</div>");

    newwin.document.close();

});

Не завершится ли выполнение скрипта после выполнения первого же блока - сказать не могу(надо пробовать).

Скрипт начинается с задания значения переменной path
Код bash Выделить
path=/home/downloads
Далее эта переменная используется для указания пути загрузки. Естественно, надо указать ей реальный путь, в который надо сохранить файлы. Скрипт можно редактировать прямо в окне браузера(если есть в этом необходимость). В принципе можно создать отдельные скрипты для разных альбомов, объединить их в один скрипт, задав каждому альбому свой путь сохранения. Ну это уже кому как удобно.

Загрузка видео

В отличие от аудио-файлов, которые в силу своего размера грузятся довольно быстро, с видео картина другая. Каждый фильм грузится долго, поэтому создавать списки ссылок и грузить их из браузера - идея плохая. Так что, единственное, в чем я увидел смысл - это создание в браузере скрипта загрузки. По причинам, описанным выше, лучше создавать альбом с небольшим количеством фильмов (десяток качается без проблем) и скачивать его. Тут следует упомянуть еще один момент: в отличие от аудио-файлов, адреса которых присутствуют в теле документа, с видео не все так просто. Ссылки на видео - это ссылки на страницы с плеером, из которых адреса видео-фалов еще надо вытащить. И тут опять-таки, если делать это для длинного списка фильмов сразу, то будет на сервер отправлено по одному запросу страницы на каждый файл. Для этого и время понадобится, да и, чего доброго, забанить могут.
Использовать для этих целей API - тоже нет никакого смысла, поскольку хоть в ответе api-метода video.get и предусмотрено свойство files, но доступно оно не всем
Цитата:
Если в Вашем приложении используется прямая авторизация, возвращается дополнительное поле files, содержащее ссылку на файл с видео (если ролик размещен на сервере ВКонтакте) или ссылку на внешний ресурс (если ролик встроен с какого-либо видеохостинга).
Ну, а предоставлять возможность прямой авторизации тому, кто желает написать качалку видео, администрация вконтактика тоже вряд ли станет.

Код скрипта здесь
Кликните здесь для просмотра всего текста
Код javascript Выделить

// ==UserScript==

// @name        DownloadVideo

// @namespace   Vkontakte

// @description Извлекает прямые ссылки на видео для загрузки из социальной сети Вконтакте

// @include     https://vk.com/video*

// @version     1

// @icon        http://vk.com/favicon.ico

// @grant GM_registerMenuCommand

 

// ==/UserScript==

 

 

function optimizeFileName(fn)

{

    return fn.replace(/[\/\\\:\*\?\"\<\>\|\+\s]/g, "_");

}

 

 

function extractLinks(code)

{

    var re = /\\\"url(\d\d\d\d?)\\\"\:\\\"([^\"]+)\\\"/g;

    var m;

    var result = [];

    while ((m = re.exec(code)) !== null)

    {

        result.push({ "definition": m[1], "url": m[2].replace(/\\\\\\/g, "") });

    }

    return result;

}

 

function getBlockData(url)

{

    var xhr = new XMLHttpRequest();

    xhr.open("GET", url, false);

    xhr.send();

    var code = xhr.responseText;

    var links = extractLinks(code);

    var title = /\<title\>(.*)\<\/title\>/.exec(code)[1];

    return { "title": title, "fileName": optimizeFileName(title), "links": links };

}

 

 

 

function createBlock(doc, blockData)

{

    var div = doc.createElement("div");

    var h3 = doc.createElement("h3");

    div.appendChild(h3);

    h3.textContent = blockData.title;

    var txbx = doc.createElement("div");

    txbx.contentEditable = true;

    txbx.style.width = "100%";

    div.appendChild(txbx);

    txbx.textContent = blockData.fileName;

    var table = doc.createElement("table");

    div.appendChild(table);

    var hrow = doc.createElement("tr");

    table.appendChild(hrow);

    var formatth = doc.createElement("th");

    formatth.textContent = "Ссылка";

    hrow.appendChild(formatth);

    var formatth = doc.createElement("th");

    formatth.textContent = "Формат";

    hrow.appendChild(formatth);

    var definitionth = doc.createElement("th");

    definitionth.textContent = "Разрешение";

    hrow.appendChild(definitionth);

    var dwnldth = doc.createElement("th");

    hrow.appendChild(dwnldth);

    var linksLength = blockData.links.length;

    for (var l = 0; l < linksLength; l++)

    {

        var tr = doc.createElement("tr");

        table.appendChild(tr);

        var linktd = doc.createElement("td");

        tr.appendChild(linktd);

        var formattd = doc.createElement("td");

        tr.appendChild(formattd);

        var definitiontd = doc.createElement("td");

        tr.appendChild(definitiontd);

        var dwnldtd = doc.createElement("td");

        tr.appendChild(dwnldtd);

        var link = blockData.links[l];

        var a = doc.createElement("a");

        a.textContent = "Ссылка";

        a.href = link.url;

        linktd.appendChild(a);

        formattd.textContent = /\.([^\.\?]+)\?/.exec(link.url)[1];

        definitiontd.textContent = link.definition;

        var chbx = doc.createElement("input");

        chbx.type = "checkbox";

        dwnldtd.appendChild(chbx)

    }

    return div;

}

 

function extractFileDownloadData(checkbox)

{

    var row = checkbox.parentElement.parentElement;

    var c1 = row.firstElementChild;

    var url = c1.firstElementChild.href;

    var c2 = c1.nextElementSibling;

    var extension = c2.textContent;

    var c3 = c2.nextElementSibling;

    var definition = c3.textContent;

    var div = row.parentElement.parentElement.parentElement;

    var fname = div.querySelector("div").textContent;

    return { "fileName": fname + "." + definition + "." + extension, "url": url }

}

 

function createScript()

{

    var checkboxes = document.querySelectorAll("input[type=checkbox]");

    var scriptData = Array.prototype.filter.call(checkboxes, function (c) { return c.checked; }).map(extractFileDownloadData);

    var bash = "#!/bin/bash<br/> path=/home/downloads <br/>";

    for (var i = 0; i < scriptData.length; i++)

    {

        var fname = /.+\/(.+)/.exec(scriptData[i].url)[1];

        bash += "wget " + scriptData[i].url + "  -O \"$path\"/\"" + scriptData[i].fileName + "\" & <br/>";

    }

    document.getElementById("result").innerHTML = bash;

}

 

function createNewPage(urlList)

{

    var newWin = unsafeWindow.open("about:blank");

    var div = document.createElement("div");

    var basharea = document.createElement("div");

    div.appendChild(basharea);

    basharea.style.cssText = "width:100%;white-space:pre;overflow-x:scroll";

    basharea.contentEditable = true;

    basharea.id = "result";

    var createScriptButton = document.createElement("button");

    createScriptButton.textContent = "Создать скрипт";

    div.appendChild(createScriptButton);

    createScriptButton.id = "btnCreateScript";

    for (var i = 0; i < urlList.length; i++)

    {

        var blockData = getBlockData(urlList[i]);

        div.appendChild(createBlock(document, blockData));

    }

    newWin.document.write(div.innerHTML);

    newWin.document.close();

    var script = newWin.document.createElement("script");

    scriptText = extractFileDownloadData + "\r\n\r\n" + createScript;

    scriptText += "\r\n \r\n";

    scriptText += "document.getElementById('btnCreateScript').addEventListener('click', createScript, false); \r\n"

    script.text = scriptText;

    newWin.document.body.appendChild(script);

}

 

function getUrlList()

{

    return Array.prototype.map.call(document.querySelectorAll("div.video_row_info_name a"), function (a) { return a.href; });

}

 

GM_registerMenuCommand("Создать скрипт скачивания", function ()

{

    var urlList = getUrlList();

    createNewPage(urlList);

});

 



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

Комментариев нет :

Отправить комментарий