Статья Загружаем видео из ВК с помощью Python

Загрузка видео на локальный диск, это дело хорошее. Потому, что у любого контента в интернете есть не очень хорошее свойство — рано или поздно он попросту теряется, либо его удаляют. Я немного покопался в коде страничек ВК и сделал небольшой скрипт, который загружает видео. А использовал я для этого Python.

kak-skatshat-video-vk_1588839602-1280x640.jpg



Что потребуется?

Для корректной работы скрипта нужно установить библиотеку requests, чтобы можно было выполнять запросы к странице и загружать видео. Также, для того, чтобы парсить содержимое полученных страниц нужно установить библиотеки BeautifulSoup и lxml. А для того, чтобы немного раскрасить вывод в терминале, установим библиотеку colorama. Для установки данных библиотек пишем в терминале команду:

pip install requests bs4 lxml colorama

После того, как необходимые библиотеки установятся, нужно импортировать их в скрипт. А также выполнить импорт библиотек, которые уже предустановлены вместе с python и также нужны для работы. Инициализируем colorama.

Python:
import os
import re
import shutil
import sys

import requests
from bs4 import BeautifulSoup
from colorama import Fore
from colorama import init

init()

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

Python:
headers = {
    'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 '
                  'Safari/537.36',
    'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,'
              'application/signed-exchange;v=b3;q=0.9 '
}


Вспомогательный скрипт замены символов

Для того, чтобы сохранить видео на диск, нужно получить его название. А вот с названием не всегда все бывает хорошо. Иногда в нем встречаются символы в виде «/», «?» и прочих, которые не дадут сохранить видео и вызовут исключение при сохранении файла. Вот для этого нужно сделать функцию, чтобы все эти символы по возможности заменить.

Создадим функцию rep_symbol(text: str). На вход она получает текст, в котором нужно произвести замену символов. После чего, с помощью функции replace выполним замену и возвратим уже очищенный текст из функции.

Python:
def rep_symbol(text: str):
    """
    Функция выполняет замену символов в названии, которые
    могут помешать сохранению файла на диск.
    :param text: текст для замены символов.
    :return: модифицированный текст
    """
    tex = text.replace("'", "").replace('"', '').replace('|', '_').replace(' | ', '_').replace('/', '_'). \
        replace('\\', '_').replace('*', '_').replace('?', '').replace('<', '').replace('>', '_').replace(':', ''). \
        replace(';', '').replace('.', '').replace(' ', '_').replace(')', '').replace('(', '').strip()
    return tex

Давайте двигаться дальше. Вспомогательная функция создана и теперь начнем создавать функции в порядке их использования.


Получение ссылки на плейлист со ссылками на фрагменты

Создадим функцию get_m3u8(url). На вход она получает ссылку на страницу с видео. Создадим словарь, в который будем складывать все найденные разрешения и ссылки на них. Выполним запрос к странице с видео, после чего передадим полученный код в BeautifulSoup, для парсинга. И сразу же найдем название видео, которое отправим в функцию замены символов. Забыл упомянуть ранее, что функция замены символов также заменяет пробелы на «_». Это необходимо для того, чтобы ffmpeg мог корректно обработать видео. Так как он не понимает русский язык да еще и с пробелами.

Python:
    res_m3u8 = dict()
    req_m3u8 = requests.get(url=url, headers=headers)
    soup = BeautifulSoup(req_m3u8.text, 'lxml')
    title = rep_symbol(soup.find('h1', class_='VideoPageInfoRow__title').text)

Теперь обернем весь остальной код в блок try-except. Это необходимо для обработки ошибки, когда не будет найден тег со ссылками на плейлисты. Такое случается, если попытаться скачать стрим. К сожалению, ссылок на фрагменты видео и плейлисты я не нашел, чтобы его скачивать.

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

Python:
        m3u8_link = soup.find('video', class_='vv_inline_video').find('source').get('src')
        m3u8_text = requests.get(url=m3u8_link, headers=headers).text.splitlines()

Запускаем цикл от 0 значения длины получившегося списка строк. Обрабатываем каждую строку. Проверяем, есть ли в начале строки текст «#EXT-X-STREAM», так как в этой строке содержится разрешение видео. С помощью регулярного выражения «r'RESOLUTION=*\w*'» находим строку с разрешением, разбиваем ее по «=», забираем 1 элемент, разбиваем его по «х» и забираем 0 элемент. После чего обновляем словарь, добавив в него разрешение и ссылку на плейлист с этим разрешением.

Python:
        m3u8_link = soup.find('video', class_='vv_inline_video').find('source').get('src')
        m3u8_text = requests.get(url=m3u8_link, headers=headers).text.splitlines()
        for num in range(0, len(m3u8_text)):
            if m3u8_text[num].startswith("#EXT-X-STREAM"):
                res_video = re.findall(r'RESOLUTION=*\w*', m3u8_text[num])[0].split("=")[1].split("x")[0]
                res_m3u8.update({res_video: m3u8_text[num+1]})

Создадим список, в который сложим разрешения из словаря, предварительно переведя их в int. Это будет необходимо для их сортировки, чтобы получить максимальное разрешение, которое доступно для данного видео. Для этого пробегаемся в цикле по ключам словаря, и добавляем их в список обернув в int. Затем печатаем сообщения в терминал. Возвращаем из функции ссылку на максимальное значение отсортированного списка и название видео.

Python:
        list_res = []
        for res in res_m3u8.keys():
            list_res.append(int(res))
        print(Fore.GREEN + f'[+] Найдено видео: "{Fore.RESET}{title}"')
        print(Fore.GREEN + f'[+] Доступно для загрузки качество: {Fore.RESET}{sorted(list_res)[-1]}')
        return res_m3u8[str(sorted(list_res)[-1])], title

Python:
def get_m3u8(url):
    """
    Выполняем запрос к странице с видео. Получаем код со страницы.
    Ищем название видео, заменяем все знаки, которые могут помешать
    сохранить видео с этим названием и сохраняем значение в переменной title.
    Получаем ссылку на плейлист - m3u8_link, в котором содержаться ссылки на плейлисты
    с разным разрешением. Забираем все разрешения и ссылки из плейлиста. Добавляем
    в словарь - res_m3u8. В цикле забираем из словаря все ключи, которыми являются
    разрешения видео, добавляем в список list_res переведя в int.
    Возвращаем из функции ссылку на плейлист видео с самым большим разрешением,
    которую получаем путем сортировки списка с разрешениями, откуда забираем
    последний элемент и получаем ссылку из словаря. Также возвращаем название видео.
    :param url: ссылка на страницу с видео.
    :return: Возвращает ссылку на видео с самым большим разрешением и название.
    """
    res_m3u8 = dict()
    req_m3u8 = requests.get(url=url, headers=headers)
    soup = BeautifulSoup(req_m3u8.text, 'lxml')
    title = rep_symbol(soup.find('h1', class_='VideoPageInfoRow__title').text)
    try:
        m3u8_link = soup.find('video', class_='vv_inline_video').find('source').get('src')
        m3u8_text = requests.get(url=m3u8_link, headers=headers).text.splitlines()
        for num in range(0, len(m3u8_text)):
            if m3u8_text[num].startswith("#EXT-X-STREAM"):
                res_video = re.findall(r'RESOLUTION=*\w*', m3u8_text[num])[0].split("=")[1].split("x")[0]
                res_m3u8.update({res_video: m3u8_text[num+1]})

        list_res = []
        for res in res_m3u8.keys():
            list_res.append(int(res))
        print(Fore.GREEN + f'[+] Найдено видео: "{Fore.RESET}{title}"')
        print(Fore.GREEN + f'[+] Доступно для загрузки качество: {Fore.RESET}{sorted(list_res)[-1]}')
        return res_m3u8[str(sorted(list_res)[-1])], title
    except AttributeError:
        return


Получение ссылок на фрагменты видео

Теперь необходимо получить ссылки на фрагменты видео, которые мы впоследствии будем загружать. Для этого создадим функцию get_chunk_link(res_max_link), которая на входе принимает ссылку на плейлист с фрагментами максимального разрешения.
Создадим список, в который будем складывать полученные ссылки на фрагменты. Этот список мы будем возвращать из функции. Скачиваем плейлист с названиями фрагментов и так как это текст, делим его на строки.

Python:
    links_chunk = []
    m3u8_chunk = requests.get(url=res_max_link, headers=headers).text.splitlines()

Пробегаемся по списку строк в цикле. Проверяем, не является ли начало строки «#». Если нет, делаем следующую проверку, нет ли в строке знака вопроса. Если знак вопроса есть, значит ссылка на фрагмент содержится в самом плейлисте. Если знака вопроса нет, значит ссылкой на фрагмент будет ссылка на плейлист. Но, так как нам нужна не вся ссылка, а только часть до «index», разбиваем строку по «/». И проверяем получившийся список в цикле. Если искомого слова нет в элементе списка, добавляем его к формируемой ссылке. Если есть, не добавляем. А так как это и есть последний элемент списка, после этого выходим из цикла. После этого формируем ссылку и добавляем в список. Если вопроса нет, тогда просто формируем ссылку из ссылки на плейлист и названия фрагмента. После этого, возвращаем получившийся словарь из функции.

Python:
    for line in m3u8_chunk:
        if not line.startswith("#"):
            if "?" in line:
                res_max = ''
                for n in res_max_link.split("/"):
                    if not n.startswith("index"):
                        res_max = res_max + n + "/"
                line = line.split("?")[0]
                links_chunk.append(f'{res_max}{line}')
            else:
                links_chunk.append(f'{res_max_link}{line}')
    return links_chunk

Python:
def get_chunk_link(res_max_link):
    """
    Загружаем плейлист со списком фрагментов и разбиваем его построчно - m3u8_chunk.
    В цикле пробегаемся по каждой строке. Забираем названия фрагментов и формируем
    ссылки на видео, которые добавляем в список.
    :param res_max_link: ссылка на плейлист с видео.
    :return: возвращает сформированный список ссылок на фрагменты видео.
    """
    links_chunk = []
    m3u8_chunk = requests.get(url=res_max_link, headers=headers).text.splitlines()
    for line in m3u8_chunk:
        if not line.startswith("#"):
            if "?" in line:
                res_max = ''
                for n in res_max_link.split("/"):
                    if not n.startswith("index"):
                        res_max = res_max + n + "/"
                line = line.split("?")[0]
                links_chunk.append(f'{res_max}{line}')
            else:
                links_chunk.append(f'{res_max_link}{line}')
    return links_chunk


Скачивание фрагментов видео

Для скачивания фрагментов видео я создал отдельную функцию и назвал ее chunk_download(links_chunk, title). На вход она получает список со ссылками на фрагменты и название видео, которое нужно будет для создания папки, в которую будут загружаться фрагменты видео.
Формируем путь к директории, в которую будут загружаться фрагменты видео. Создаем папки, нужные для загрузки.

Python:
    dir_vid = os.path.join(os.getcwd(), 'video', title)
    if not os.path.exists(os.path.join(os.getcwd(), 'video')):
        os.mkdir(os.path.join(os.getcwd(), 'video'))
    if not os.path.exists(dir_vid):
        os.mkdir(dir_vid)

Создадим список, в который будем складывать полный путь к загруженным фрагмента видео. Это необходимо для того, чтобы впоследствии удалить загруженные фрагменты. Выводим сообщение о загрузке в терминал.

Python:
    path_list = []
    print(Fore.GREEN + f'\n[+] Загрузка фрагментов {Fore.RESET}({len(links_chunk)}):')

Устанавливаем длину прогресс-бара, которая будет равна количеству ссылок на фрагменты. Выводим в консоль сообщение с «[» + пробелы, количество которых равно установленной длине прогресс-бара. Очищаем буфер. Возвращаем каретку в начало строки + 1 символ. Для того, чтобы выводить прогресс загрузки.

Python:
    toolbar_width = len(links_chunk)
    sys.stdout.write("[%s]" % (" " * toolbar_width))
    sys.stdout.flush()
    sys.stdout.write("\b" * (toolbar_width + 1))

Создаем цикл, в котором пробегаемся по каждой ссылке из словаря ссылок к фрагментам видео. Выводим в консоль текст, очищаем буфер. Текст будет равен индексу ссылки на фрагмент + 1, который был получен с помощью функции enumerate. Формируем название для загружаемого фрагмента. Название я решил сделать одинаковое для всех, так как в каждом плейлисте разные названия и не всегда получается легко достать это название оттуда. А так, одно, универсальное название для всех.

Python:
    for num, chunk in enumerate(links_chunk):
        sys.stdout.write(f"{num+1}.")
        sys.stdout.flush()
        chunk_name = f'seg{num}.ts'

Скачиваем фрагмент с помощью requests, после чего сохраняем его на диск присвоив ему сформированное имя. В список путей к загруженным фрагментам видео добавляем путь.

Python:
        chunk_d = requests.get(url=chunk, headers=headers).content
        with open(os.path.join(dir_vid, chunk_name), 'wb') as chunk_f:
            path_list.append(os.path.join(dir_vid, chunk_name))
            chunk_f.write(chunk_d)

После того, как последний фрагмент будет загружен, печатаем завершающее сообщение в консоли, выводим сообщение, что загрузка завершена и возвращаем из функции список с путями к загруженным фрагментам и путь к директории, в которую загружали видео.

Python:
    sys.stdout.write("end]")
    print(Fore.CYAN + '\n[+] Загрузка фрагментов завершена')
    return path_list, dir_vid


Объединение фрагментов в один и конвертация объединенного фрагмента в mp4

После того, как все фрагменты видео будут загружены, все их нужно объединить в один фрагмент для конвертации. Создадим функцию chunk_merge(path_list, dir_vid, title), которая на входе получает список с путями к загруженным фрагментам, путь к папке в которую загружены фрагменты, и название видео.

Выводим в терминал сообщение для пользователя о конвертации. Создаем в той же папке, куда загружали фрагменты файл с расширением «.ts», в который будем записывать объединенные фрагменты и откроем его для записи в побайтовом режиме. Затем в цикле пробежимся по списку ссылок на фрагменты, откроем каждый из фрагментов в побайтовом режиме и добавим его содержимое с помощью функции shutil.copyfileobj к записываемому объединенному файлу.

После того, как цикл завершит работу и объединенный фрагменты будут сохранены с названием видео, которое мы получили на входе функции, запускаем команду для конвертации видео из формата «.ts» в формат mp4 с помощью ffmpeg. В команду, которую нужно выполнить передаем путь к объединенным фрагментам и путь с названием видео, по которому нужно будет сохранить конвертированное видео.

Python:
    print(Fore.YELLOW + "\n[+] Конвертация")
    with open(os.path.join(dir_vid, f'{title}.ts'), 'wb') as merged:
        for cnk in path_list:
            with open(cnk, 'rb') as mergefile:
                shutil.copyfileobj(mergefile, merged)
    os.system(f"ffmpeg -i {os.path.join(dir_vid, title)}.ts {os.path.join(dir_vid, title)}.mp4 2> /dev/null")

После того, как закончиться конвертация видео, нужно удалить как загруженные фрагменты, так и объединенный файл. Поэтому в цикле пробегаемся по списку с путями к фрагментам и удаляем их с помощью функции os.remove. После того, как цикл выполниться, удаляем объединенный файл.

Python:
    for it_ch in path_list:
        os.remove(it_ch)
    os.remove(f'{os.path.join(dir_vid, title)}.ts')
    print(Fore.YELLOW + "[+] Конвертация завершена")

Python:
def chunk_merge(path_list, dir_vid, title):
    """
    Открываем каждый файл побайтово, и с помощью shutil.copyfileobj
    копируем открываемые файлы в файл для записи. После чего, записываем
    объединенные фрагменты с названием видео.
    Выполняем команду конвертации видео с помощью ffmpeg, вывод
    убираем в null.
    После того как конвертация видео будет завершена, удаляем фрагменты,
    список путей к которым мы передали в функцию, а также удаляем объединенный
    файл с расширением .ts
    :param path_list: список ссылок на загруженные фрагменты видео.
    :param dir_vid: директория загрузки видео.
    :param title: название видео.
    """
    print(Fore.YELLOW + "\n[+] Конвертация")
    with open(os.path.join(dir_vid, f'{title}.ts'), 'wb') as merged:
        for cnk in path_list:
            with open(cnk, 'rb') as mergefile:
                shutil.copyfileobj(mergefile, merged)
    os.system(f"ffmpeg -i {os.path.join(dir_vid, title)}.ts {os.path.join(dir_vid, title)}.mp4 2> /dev/null")

    for it_ch in path_list:
        os.remove(it_ch)
    os.remove(f'{os.path.join(dir_vid, title)}.ts')
    print(Fore.YELLOW + "[+] Конвертация завершена")


Функция для пользовательского ввода и запуска функций

Создадим еще одну функцию, которую назовем main(). В данной функции мы будем запрашивать у пользователя ссылку на страницу с видео. В данном случае, так как наш скрипт работает со ссылкой на мобильную версию, нужно будет сделать ряд проверок. Но вводить ссылки можно как к обычной версии ВК, так и к мобильной.

Запросим у пользователя ссылку. Проверим, является ли ссылка ссылкой на ВК. Затем выполним проверку, нет ли в ссылке, которую ввел пользователь слова «pleylist», так как скрипт не умеет работать с плейлистами. А работает только с отдельными видео. Затем проверяем, не является ли введенная ссылка, ссылкой на мобильную версию ВК. Если да, присваиваем переменной url значение, которое ввел пользователь. Если же пользователем была введена обычная ссылка, тогда разберем ее на части и соберем снова, добавив к адресу «m», что и будет ссылкой на мобильную версию.

Python:
    user_input = input('Введите ссылку на страницу с видео: ')
    if 'vk.com/video' not in user_input:
        print(Fore.RED + '[-] Ссылка неверна.')
        return
    if 'playlist' in user_input:
        print(Fore.RED + '[-] Вы ввели ссылку на плейлист.')
        return
    if 'https://m.vk.com/' in user_input:
        url = user_input
    else:
        url = f'{user_input.split("/")[0]}//m.{user_input.split("/")[2]}/{user_input.split("/")[3]}'

И теперь осталось запускать функции по порядку. Для начала, функцию для получения ссылки на плейлист со ссылками на плейлисты. Затем функцию для получения ссылок на фрагменты, после запускаем функцию загрузки фрагментов видео, и в самом конце функцию объединения и конвертации видео.

Обернем код запуска функций в блок try-except, так как иногда отлавливается исключение, когда не получается найти ссылок на видео. Такое у меня было, пока, только со ссылками на стрим.

Python:
    try:
        res_max_link, title = get_m3u8(url)
        links_chunk = get_chunk_link(res_max_link)
        path_list, dir_vid = chunk_download(links_chunk, title)
        chunk_merge(path_list, dir_vid, title)
    except TypeError:
        print(Fore.RED + '[-] Не удалось получить ссылки на фрагменты.')
        return

Python:
def main():
    """
    Запрашиваем у пользователя ссылку на видео.
    Проверяем, является ли ссылка, ссылкой на ВК.
    Проверяем, не является ли ссылка ссылкой на плейлист.
    Проверяем, не ввел ли пользователь ссылку на страницу
    для мобильных телефонов.
    Запускаем функцию получения ссылки на плейлист видео и
    заголовок.
    Запускаем функцию получения ссылок на фрагменты видео.
    Запускаем функцию скачивания видео.
    Запускаем функцию конвертации из ts в mp4.
    :return: выход из функции.
    """
    user_input = input('Введите ссылку на страницу с видео: ')
    if 'vk.com/video' not in user_input:
        print(Fore.RED + '[-] Ссылка неверна.')
        return
    if 'playlist' in user_input:
        print(Fore.RED + '[-] Вы ввели ссылку на плейлист.')
        return
    if 'https://m.vk.com/' in user_input:
        url = user_input
    else:
        url = f'{user_input.split("/")[0]}//m.{user_input.split("/")[2]}/{user_input.split("/")[3]}'

    try:
        res_max_link, title = get_m3u8(url)
        links_chunk = get_chunk_link(res_max_link)
        path_list, dir_vid = chunk_download(links_chunk, title)
        chunk_merge(path_list, dir_vid, title)
    except TypeError:
        print(Fore.RED + '[-] Не удалось получить ссылки на фрагменты.')
        return

Таким вот способом, с помощью обычного парсинга мы смогли загрузить видео. На самом деле, я и раньше загружал видео из ВК при большой необходимости. И пользовался для этого сторонними сервисами, такими, как SaveFromNet. Но, почему-то, загрузка видео из ВК для этого сервиса прямо испытание. Очень медленно грузит. С другой стороны, если видео большое, то и скрипт на питоне будет так же долго подгружать фрагменты, а потом еще время уйдет на конвертацию. Тем не менее, я хотя бы понимаю, что происходит в данный момент.

В процессе написания данного скрипта я понял, как осуществляется доставка видео до конечного пользователя из ВК. А теперь, вот полный код скрипта загрузки видео:

Python:
"""
Скрипт для загрузки видео из ВК.
Загружает почти любое видео, проблемы при загрузке стримов.
На них он не находит ссылок. Для того чтобы данный скрипт
работал, нужно установить следующие библиотеки:
pip install requests bs4 lxml colorama
"""

import os
import re
import shutil
import sys

import requests
from bs4 import BeautifulSoup
from colorama import Fore
from colorama import init

init()

headers = {
    'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 '
                  'Safari/537.36',
    'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,'
              'application/signed-exchange;v=b3;q=0.9 '
}


def rep_symbol(text: str):
    """
    Функция выполняет замену символов в названии, которые
    могут помешать сохранению файла на диск.
    :param text: текст для замены символов.
    :return: модифицированный текст
    """
    tex = text.replace("'", "").replace('"', '').replace('|', '_').replace(' | ', '_').replace('/', '_'). \
        replace('\\', '_').replace('*', '_').replace('?', '').replace('<', '').replace('>', '_').replace(':', ''). \
        replace(';', '').replace('.', '').replace(' ', '_').replace(')', '').replace('(', '').strip()
    return tex


def get_m3u8(url):
    """
    Выполняем запрос к странице с видео. Получаем код со страницы.
    Ищем название видео, заменяем все знаки, которые могут помешать
    сохранить видео с этим названием и сохраняем значение в переменной title.
    Получаем ссылку на плейлист - m3u8_link, в котором содержаться ссылки на плейлисты
    с разным разрешением. Забираем все разрешения и ссылки из плейлиста. Добавляем
    в словарь - res_m3u8. В цикле забираем из словаря все ключи, которыми являются
    разрешения видео, добавляем в список list_res переведя в int.
    Возвращаем из функции ссылку на плейлист видео с самым большим разрешением,
    которую получаем путем сортировки списка с разрешениями, откуда забираем
    последний элемент и получаем ссылку из словаря. Также возвращаем название видео.
    :param url: ссылка на страницу с видео.
    :return: Возвращает ссылку на видео с самым большим разрешением и название.
    """
    res_m3u8 = dict()
    req_m3u8 = requests.get(url=url, headers=headers)
    soup = BeautifulSoup(req_m3u8.text, 'lxml')
    title = rep_symbol(soup.find('h1', class_='VideoPageInfoRow__title').text)
    try:
        m3u8_link = soup.find('video', class_='vv_inline_video').find('source').get('src')
        m3u8_text = requests.get(url=m3u8_link, headers=headers).text.splitlines()
        for num in range(0, len(m3u8_text)):
            if m3u8_text[num].startswith("#EXT-X-STREAM"):
                res_video = re.findall(r'RESOLUTION=*\w*', m3u8_text[num])[0].split("=")[1].split("x")[0]
                res_m3u8.update({res_video: m3u8_text[num+1]})

        list_res = []
        for res in res_m3u8.keys():
            list_res.append(int(res))
        print(Fore.GREEN + f'[+] Найдено видео: "{Fore.RESET}{title}"')
        print(Fore.GREEN + f'[+] Доступно для загрузки качество: {Fore.RESET}{sorted(list_res)[-1]}')
        return res_m3u8[str(sorted(list_res)[-1])], title
    except AttributeError:
        return


def get_chunk_link(res_max_link):
    """
    Загружаем плейлист со списком фрагментов и разбиваем его построчно - m3u8_chunk.
    В цикле пробегаемся по каждой строке. Забираем названия фрагментов и формируем
    ссылки на видео, которые добавляем в список.
    :param res_max_link: ссылка на плейлист с видео.
    :return: возвращает сформированный список ссылок на фрагменты видео.
    """
    links_chunk = []
    m3u8_chunk = requests.get(url=res_max_link, headers=headers).text.splitlines()
    for line in m3u8_chunk:
        if not line.startswith("#"):
            if "?" in line:
                res_max = ''
                for n in res_max_link.split("/"):
                    if not n.startswith("index"):
                        res_max = res_max + n + "/"
                line = line.split("?")[0]
                links_chunk.append(f'{res_max}{line}')
            else:
                links_chunk.append(f'{res_max_link}{line}')
    return links_chunk


def chunk_download(links_chunk, title):
    """
    Создаем папки, если они не существуют, в которые будем загружать
    фрагменты видео. Для загрузки фрагментов создаем папку с названием
    видео. Устанавливаем значение для отображения прогресс-бара - toolbar_width.
    Печатаем в консоли "[" и очищаем буфер. После чего возвращаем каретку
    к началу строки, плюс один символ.
    Перебираем все ссылки в списке и выполняем загрузку каждого фрагмента.
    В консоль выводим цифры соответствующие загрузке определенного фрагмента.
    Сохраняем фрагменты в папку с названием видео.
    :param links_chunk: список ссылок на фрагменты видео.
    :param title: название видео.
    :return: список путей к загруженным фрагментам, путь к папке с фрагментами.
    """
    dir_vid = os.path.join(os.getcwd(), 'video', title)
    if not os.path.exists(os.path.join(os.getcwd(), 'video')):
        os.mkdir(os.path.join(os.getcwd(), 'video'))
    if not os.path.exists(dir_vid):
        os.mkdir(dir_vid)

    path_list = []
    print(Fore.GREEN + f'\n[+] Загрузка фрагментов {Fore.RESET}({len(links_chunk)}):')

    toolbar_width = len(links_chunk)
    sys.stdout.write("[%s]" % (" " * toolbar_width))
    sys.stdout.flush()
    sys.stdout.write("\b" * (toolbar_width + 1))

    for num, chunk in enumerate(links_chunk):
        sys.stdout.write(f"{num+1}.")
        sys.stdout.flush()
        chunk_name = f'seg{num}.ts'
        chunk_d = requests.get(url=chunk, headers=headers).content
        with open(os.path.join(dir_vid, chunk_name), 'wb') as chunk_f:
            path_list.append(os.path.join(dir_vid, chunk_name))
            chunk_f.write(chunk_d)
    sys.stdout.write("end]")
    print(Fore.CYAN + '\n[+] Загрузка фрагментов завершена')
    return path_list, dir_vid


def chunk_merge(path_list, dir_vid, title):
    """
    Открываем каждый файл побайтово, и с помощью shutil.copyfileobj
    копируем открываемые файлы в файл для записи. После чего, записываем
    объединенные фрагменты с названием видео.
    Выполняем команду конвертации видео с помощью ffmpeg, вывод
    убираем в null.
    После того как конвертация видео будет завершена, удаляем фрагменты,
    список путей к которым мы передали в функцию, а также удаляем объединенный
    файл с расширением .ts
    :param path_list: список ссылок на загруженные фрагменты видео.
    :param dir_vid: директория загрузки видео.
    :param title: название видео.
    """
    print(Fore.YELLOW + "\n[+] Конвертация")
    with open(os.path.join(dir_vid, f'{title}.ts'), 'wb') as merged:
        for cnk in path_list:
            with open(cnk, 'rb') as mergefile:
                shutil.copyfileobj(mergefile, merged)
    os.system(f"ffmpeg -i {os.path.join(dir_vid, title)}.ts {os.path.join(dir_vid, title)}.mp4 2> /dev/null")

    for it_ch in path_list:
        os.remove(it_ch)
    os.remove(f'{os.path.join(dir_vid, title)}.ts')
    print(Fore.YELLOW + "[+] Конвертация завершена")


def main():
    """
    Запрашиваем у пользователя ссылку на видео.
    Проверяем, является ли ссылка, ссылкой на ВК.
    Проверяем, не является ли ссылка ссылкой на плейлист.
    Проверяем, не ввел ли пользователь ссылку на страницу
    для мобильных телефонов.
    Запускаем функцию получения ссылки на плейлист видео и
    заголовок.
    Запускаем функцию получения ссылок на фрагменты видео.
    Запускаем функцию скачивания видео.
    Запускаем функцию конвертации из ts в mp4.
    :return: выход из функции.
    """
    user_input = input('Введите ссылку на страницу с видео: ')
    if 'vk.com/video' not in user_input:
        print(Fore.RED + '[-] Ссылка неверна.')
        return
    if 'playlist' in user_input:
        print(Fore.RED + '[-] Вы ввели ссылку на плейлист.')
        return
    if 'https://m.vk.com/' in user_input:
        url = user_input
    else:
        url = f'{user_input.split("/")[0]}//m.{user_input.split("/")[2]}/{user_input.split("/")[3]}'

    try:
        res_max_link, title = get_m3u8(url)
        links_chunk = get_chunk_link(res_max_link)
        path_list, dir_vid = chunk_download(links_chunk, title)
        chunk_merge(path_list, dir_vid, title)
    except TypeError:
        print(Fore.RED + '[-] Не удалось получить ссылки на фрагменты.')
        return


if __name__ == "__main__":
    main()

А на этом, пожалуй, все.

Спасибо за внимание. Надеюсь, данная информация будет вам полезна
 

Вложения

  • vk_video_download.zip
    3,9 КБ · Просмотры: 340
  • Нравится
Реакции: batu5ai и Faust_1st

satfan

Green Team
26.06.2022
78
1
BIT
80
Уважаемый
Выдало ошибку:

Введите ссылку на страницу с видео: https://vk.com/video-59391110_456240230
Traceback (most recent call last):
File "C:\Users\Пк\Desktop\Загружаем видео из ВК с помощью Python\vk_video_download\vk_video_download.py", line 208, in <module>
main()
File "C:\Users\Пк\Desktop\Загружаем видео из ВК с помощью Python\vk_video_download\vk_video_download.py", line 198, in main
res_max_link, title = get_m3u8(url)
File "C:\Users\Пк\Desktop\Загружаем видео из ВК с помощью Python\vk_video_download\vk_video_download.py", line 60, in get_m3u8
title = rep_symbol(soup.find('h1', class_='VideoPageInfoRow__title').text)
AttributeError: 'NoneType' object has no attribute 'text'
------------------------
Что у меня не так ?
 

Johan Van

Green Team
13.06.2020
363
691
BIT
390
Уважаемый
Выдало ошибку:

Введите ссылку на страницу с видео: https://vk.com/video-59391110_456240230
Traceback (most recent call last):
File "C:\Users\Пк\Desktop\Загружаем видео из ВК с помощью Python\vk_video_download\vk_video_download.py", line 208, in <module>
main()
File "C:\Users\Пк\Desktop\Загружаем видео из ВК с помощью Python\vk_video_download\vk_video_download.py", line 198, in main
res_max_link, title = get_m3u8(url)
File "C:\Users\Пк\Desktop\Загружаем видео из ВК с помощью Python\vk_video_download\vk_video_download.py", line 60, in get_m3u8
title = rep_symbol(soup.find('h1', class_='VideoPageInfoRow__title').text)
AttributeError: 'NoneType' object has no attribute 'text'
------------------------
Что у меня не так ?

Скорее всего у вас все так. То есть, вы делаете все правильно. Однако, если вы заметили, то с момента публикации скрипта прошло уже довольно много времени. И судя по наблюдаемым изменениям в VK в общем, скорее всего, способ парсинга плейлистов изменился. Поэтому, нужно пересмотреть запросы в инструментах разработчиков и возвращаемые значения. Вполне возможно, что проблема именно там. Так как явно указана ошибка, что атрибут текст не найден. Так что, скорее всего, проблема именно в этом.
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!