• Познакомьтесь с пентестом веб-приложений на практике в нашем новом бесплатном курсе

    «Анализ защищенности веб-приложений»

    🔥 Записаться бесплатно!

  • CTF с учебными материалами Codeby Games

    Обучение кибербезопасности в игровой форме. Более 200 заданий по Active Directory, OSINT, PWN, Веб, Стеганографии, Реверс-инжинирингу, Форензике и Криптографии. Школа CTF с бесплатными курсами по всем категориям.

Статья Скачиваем видео с YouTube с помощью Python, без использования pytube

Что вы делаете, когда вам нужно сохранить определенное видео с YouTube, чтобы оно не потерялось? Ну, тут логично. Можно в самом YouTube создать плейлист и добавлять туда все, что нужно. Можно просто добавить страницу в закладки. Да много чего можно сделать. А еще скачать видео себе на жесткий диск. Мало ли чего, всякое бывает. А так под рукой и в сохранности. Вот только способов для скачивания становится все меньше и меньше. Давайте попробуем скачать видео с YouTube с помощью питона. Нет, здесь не будет очередного руководства по pytube. Все интереснее и печальнее.

000.jpg

Не так давно промелькнула новость о том, что проект YouTube Vanced закрывается из-за юридического давления со стороны Google. И вроде бы ладно. Сколько еще таких проектов. Тем более, что для скачивания видео с сервиса я пользовался скриптом на питоне, в котором с помощью библиотеки pytube все благополучно скачивалось. Но, через какое-то время после этой новости pytube вдруг перестал работать. Раз и все. Ладно. Я подумал, что это всего лишь ошибка в моей программе. Сейчас поправлю и все заработает. Но, не тут-то было. Ошибок я особо-то и не нашел. А вот pytube не работал даже на простейших примерах, которые у него описаны на странице загрузки. Я решил, что это временные трудности. На время оставил данный проект в покое. Но, вот спустя почти месяц я снова к нему вернулся. И ничего не изменилось.

Тут, скорее всего, все просто. У YouTube слегка изменился код, а следовательно, и алгоритм поиска ссылок у pytube тоже должен было обновиться. Но, этого не произошло. Покопавшись в интернете, я нашел несколько решений. Нужно заменить регулярные выражения в коде модуля и все должно было заработать. Но, у меня не получилось. Хотя, делал все так как сказано, вплоть до строчек кода. Кстати, в самом коде видно, что алгоритм поиска по регулярным выражениям менялся уже не один раз, так как много строк просто закомментировано. В общем и целом, не знаю, «восстанет ли этот проект из пепла», так сказать. Будем надеяться, что да. Хороший был проект. Полезный.

screenshot1.png

К чему это я веду. А все просто. Время проходит, библиотека не работает, а автоматизировать скачивание надо. Значит придется искать другие решения. Но, что-то с поиском библиотек у меня не задалось. youtube-dl использовать не хочется. Да и не уверен, что он сейчас работает. Вот его не проверял. Каюсь. Впрочем, я просто нашел свое, несколько «костыльное» решение, но оно работает и довольно неплохо. Именно им я и хочу с вами поделиться. Давайте напишем свой «YouTube Downloader» на Python.

И вот какой Франкенштейн у меня получился в итоге…


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

Для отправки запросов нужно установить библиотеку requests, а также я использовал библиотеку tqdm для добавления в скрипт индикатора загрузки. Все же с ней веселее. Установка стандартная. Пишем в терминале:

Код:
pip install requests
pip install tqdm

После установки импортируем библиотеки в наш скрипт. Так же понадобиться импортировать библиотеку os для проверки и объединения путей к папкам и файлам. Вот блок импорта, который должен получиться в итоге:

Python:
import os.path
import time

import requests
from tqdm import tqdm

Как видите, на этот раз кучи библиотек не потребуется. В основном все будет совершаться с помощью запросов. Ими можно довольно много сделать. Нужно только понять, где и что спрашивать )))

Для начала я, конечно же, полез на сам YouTube. И немного поковырявшись в инструментах разработчика накопал довольно интересный запрос, в котором, в виде JSON прилетала самая разнообразная информация о видео, в том числе и ссылки.

screenshot2.png

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

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

screenshot3.png

Немного подумав и поэкспериментировав, я понял, что в появлении данного интересного запроса «виновно» расширение, которое установлено у меня для кастомизации просмотра видео, а именно: Enhancer for YouTube. Именно оно отправляет этот запрос для каких-то своих «злодейских» целей. Но, тем не менее, скачать видео этот запрос не поможет, а значит надо искать дальше.

Я поступил просто. Ввел запрос в поисковике и стал смотреть, как скачивается видео в популярных загрузчиках с сайтов. Большинство ничего внятного не предоставляли. Но, набрел я на сайт Freemake, тот, что разрабатывает Freemake Video Downloader. Оказывается, на нем тоже можно скачать видео с YouTube. Залез я в запросы и понял, это оно. А походив по ссылкам убедился, что это оно еще больше.

screenshot4.png

Тогда я скопировал cURL данного запроса с помощью правой кнопки мыши. Там нужно выбрать пункт меню: Copy -> Copy as cURL (bash)». Убедился, что это GET-запрос и пошел на сайт curlconverter.com добывать код из скопированного безобразия. Там все просто. Выбираем тип запроса, get или post, вставляем скопированный запрос и получаем код питона. Ну или одного из тех языков, что представлены на этом сайте.

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

Я создал функцию с именем get_video_download(vid_id, channel_name), которая на входе получает идентификатор видео, он содержится в ссылке, после знака «=», и имя канала, которое нужно в данном случае для того, чтобы скачиваемое видео помещать в отдельную папку, а не просто создавать папку типа Video Download и кидать все в кучу. Заголовки я пока опущу. Их можно будет увидеть в полном коде функции. Начнем с того момента, где уже и происходит получение JSON и загрузка видео.

Для начала вывожу принт в терминал, чтобы не было скучно, а после отправляю запрос на получение JSON, в котором параметр vid_id получается из ссылки, которую ввел пользователь для загрузки. Далее, уже в полученном JSON нахожу секцию, где указывается качество видео. Данный сайт позволяет скачивать видео в качестве 720р и 360р, именно mp4. Ниже есть еще пара пунктов, но они относятся к форматам 3gp и mp4a. Если все в порядке, и тэг соответствует, получаю название видео. Как вы видели сами, в названии видео содержится большое количество всяческих символов, которые просто не совместимы с тем, чтобы сохранять в операционной системе. А так, как название видео нужно будет именно для того, чтобы не скачивать его в обезличенном виде, а сохранять с тем названием, что и на сервисе, требуется его очистить от всякого мусора, что я и делаю в цикле, перебирая словарь. В теории, туда можно загнать еще больше символов. Так как я загнал только те, с которыми столкнулся. А кто его знает, что будет в голове у автора, когда он будет давать название. Ну и следом получаю ссылку на загрузку.

Python:
print(f'[+] Получаю название и ссылку на видео...')
    response = requests.get(f'https://downloader.freemake.com/api/videoinfo/{vid_id}', headers=headers).json()
    if response['qualities'][0]['qualityInfo']['itag'] == 22:
        video_title = str(response['metaInfo']['title'])
        for m in ["?", '"', "'", "/", ":", "#", "|", ",", " | "]:
            video_title = video_title.replace(m, "")
        url = response['qualities'][0]['url']

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

Python:
print(f'[+] Название и ссылка получены. Начинаю загрузку: "{video_title}"...')
        if not os.path.isdir(f'{channel_name}'):
            os.mkdir(f'{channel_name}')
            print(f'[+] Создаю папку для сохранения видео...\n')
        else:
            print(f'[+] Папка для сохранения существует...\n')

А теперь, собственно, основная, самая большая часть функции по загрузке видео. На самом деле, загрузка видео тут в несколько строчек кода. Основная часть отведена на различные проверки и запросы информации, если что-то не так у пользователя, чтобы программа просто не закрывалась без объяснения причин. Отправляю запрос на загрузку. Оказывается, у request есть интересный параметр stream. С его помощью можно переписать поведение загрузки тела ответа, которое по умолчанию загружается сразу же, а при указании параметра делает отсрочку загрузки, пока не будет получен доступ к атрибуту content. Это все нужно для того, чтобы реализовать индикатор загрузки, который здесь представлен библиотекой tqdm. Для начала устанавливаем количество заголовков запроса в переменную total. А далее, по мере загрузки контента и получения заголовков, увеличиваем данный параметр на количество заголовков. И выводим в терминал в удобоваримом виде.

Python:
        req = requests.get(url=url, headers=headers, stream=True)
        total = int(req.headers.get('content-length', 0))
        with open(f'{os.path.join(channel_name, f"{video_title}.mp4")}', 'wb') as file, tqdm(
                desc=f"{video_title[0:int(len(video_title) / 2)]}...",
                total=total,
                unit='iB',
                unit_scale=True,
                unit_divisor=1024,
        ) as bar:
            for data in req.iter_content(chunk_size=1024):
                size = file.write(data)
                bar.update(size)
        print(f'\n[+] Видео сохранено в папку: "{channel_name}".\n[+] Загрузка завершена.\n')
    else:
        user_change = input('\n[+] Нет видео в качестве 720р...\n[+] Загрузить в доступном качестве?:\n'
                            '\t[1]: Да\n\t[2]: Нет\n\t>>> ')
        if user_change == "1":
            video_title = str(response['metaInfo']['title'])
            for m in ["?", '"', "'", "/", ":", "#", "|", ",", " | "]:
                video_title = video_title.replace(m, "")
            url = response['qualities'][0]['url']
            print(f'[+] Название и ссылка получены. Начинаю загрузку: "{video_title}"...')
            if not os.path.isdir(f'{channel_name}'):
                os.mkdir(f'{channel_name}')
                print(f'[+] Создаю папку для сохранения видео...\n')
            else:
                print(f'[+] Папка для сохранения существует...\n')
            req = requests.get(url=url, headers=headers, stream=True)
            total = int(req.headers.get('content-length', 0))
            with open(f'{os.path.join(channel_name, f"{video_title}.mp4")}', 'wb') as file, tqdm(
                    desc=f"{video_title[0:int(len(video_title) / 2)]}...",
                    total=total,
                    unit='iB',
                    unit_scale=True,
                    unit_divisor=1024,
            ) as bar:
                for data in req.iter_content(chunk_size=1024):
                    size = file.write(data)
                    bar.update(size)
            print(f'\n[+] Видео сохранено в папку: "{channel_name}".\n[+] Загрузка завершена.\n')
        elif user_change == "2":
            main()
        else:
            print('[-] Вы ввели чушь. Закрываю программу...')
            exit(0)

Ну, а если нет видео в качестве 720р, то сообщаем об этом пользователю, спрашиваем, желает ли он загрузить видео в том качестве, что есть. Если да, то загружаем. Если нет, прерываем выполнение функции и выводим первоначальное меню. Ну, а если пользователь ввел совсем не то, то посылаем его в незабываемое путешествие по экзотическим странам (перечеркнуть) сообщаем, что он ввел чушь и прерываем работу скрипта.

Python:
def get_video_download(vid_id, channel_name):
    headers = {
        'authority': 'downloader.freemake.com',
        'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="98", "Yandex";v="22"',
        'dnt': '1',
        'x-cf-country': 'RU',
        'sec-ch-ua-mobile': '?0',
        'x-user-platform': 'Win32',
        'accept': 'application/json, text/javascript, */*; q=0.01',
        'x-user-browser': 'YaBrowser',
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/98.0.4758.141 YaBrowser/22.3.3.852 Yowser/2.5 Safari/537.36',
        'x-analytics-header': 'UA-18256617-1',
        'x-request-attempt': '1',
        'x-user-id': '94119398-e27a-3e13-be17-bbe7fbc25874',
        'sec-ch-ua-platform': '"Windows"',
        'origin': 'https://www.freemake.com',
        'sec-fetch-site': 'same-site',
        'sec-fetch-mode': 'cors',
        'sec-fetch-dest': 'empty',
        'referer': 'https://www.freemake.com/ru/free_video_downloader/',
        'accept-language': 'ru,en;q=0.9,uk;q=0.8',
    }

    print(f'[+] Получаю название и ссылку на видео...')
    response = requests.get(f'https://downloader.freemake.com/api/videoinfo/{vid_id}', headers=headers).json()
    if response['qualities'][0]['qualityInfo']['itag'] == 22:
        video_title = str(response['metaInfo']['title'])
        for m in ["?", '"', "'", "/", ":", "#", "|", ",", " | "]:
            video_title = video_title.replace(m, "")
        url = response['qualities'][0]['url']
        print(f'[+] Название и ссылка получены. Начинаю загрузку: "{video_title}"...')
        if not os.path.isdir(f'{channel_name}'):
            os.mkdir(f'{channel_name}')
            print(f'[+] Создаю папку для сохранения видео...\n')
        else:
            print(f'[+] Папка для сохранения существует...\n')
        req = requests.get(url=url, headers=headers, stream=True)
        total = int(req.headers.get('content-length', 0))
        with open(f'{os.path.join(channel_name, f"{video_title}.mp4")}', 'wb') as file, tqdm(
                desc=f"{video_title[0:int(len(video_title) / 2)]}...",
                total=total,
                unit='iB',
                unit_scale=True,
                unit_divisor=1024,
        ) as bar:
            for data in req.iter_content(chunk_size=1024):
                size = file.write(data)
                bar.update(size)
        print(f'\n[+] Видео сохранено в папку: "{channel_name}".\n[+] Загрузка завершена.\n')
    else:
        user_change = input('\n[+] Нет видео в качестве 720р...\n[+] Загрузить в доступном качестве?:\n'
                            '\t[1]: Да\n\t[2]: Нет\n\t>>> ')
        if user_change == "1":
            video_title = str(response['metaInfo']['title'])
            for m in ["?", '"', "'", "/", ":", "#", "|", ",", " | "]:
                video_title = video_title.replace(m, "")
            url = response['qualities'][0]['url']
            print(f'[+] Название и ссылка получены. Начинаю загрузку: "{video_title}"...')
            if not os.path.isdir(f'{channel_name}'):
                os.mkdir(f'{channel_name}')
                print(f'[+] Создаю папку для сохранения видео...\n')
            else:
                print(f'[+] Папка для сохранения существует...\n')
            req = requests.get(url=url, headers=headers, stream=True)
            total = int(req.headers.get('content-length', 0))
            with open(f'{os.path.join(channel_name, f"{video_title}.mp4")}', 'wb') as file, tqdm(
                    desc=f"{video_title[0:int(len(video_title) / 2)]}...",
                    total=total,
                    unit='iB',
                    unit_scale=True,
                    unit_divisor=1024,
            ) as bar:
                for data in req.iter_content(chunk_size=1024):
                    size = file.write(data)
                    bar.update(size)
            print(f'\n[+] Видео сохранено в папку: "{channel_name}".\n[+] Загрузка завершена.\n')
        elif user_change == "2":
            main()
            return
        else:
            print('[-] Вы ввели чушь. Закрываю программу...')
            exit(0)

Однако, как вы понимаете перед тем, как приступить к загрузке видео, нужно получить название канала. А где его взять, вот в чем вопрос. И я вспомнил о том запросе, который отправляло расширение на самой странице с видео. Тогда я так же скопировал его cURL, получил код и выполнил. В ответ прилетел JSON, в котором была информация о видео, в том числе и название канала. Знаю, это выглядит, как если бы я стрелял из пушки по воробьям. Но использовать BeautifulSoup и искать название канала на странице видео не хотелось. Тем более, что вот так вот, с получением JSON оказалось проще. Поэтому, я отправляю запрос на получение. В запросе, в свою очередь, отправляется JSON с кучей параметров, которые нас в общем-то не интересуют. Здесь нужно поменять только идентификатор видео. Меняется он вот тут:

Python:
json_data = {
        'videoId': vid_id,
        'context': {
            'client': {
                'hl': 'ru',
                'gl': 'RU',
                'remoteHost': '31.173.242.98',

У меня он обозначен как vid_id и передается в функцию при ее вызове. Таким образом я создал функцию get_channel_name(vid_id), которая на входе получает идентификатор, делает запрос. Выковыривает из него название канала. Чистит от «мусора» в виде символов и возвращает очищенное название туда, откуда вызывалась функция.

Python:
def get_channel_name(vid_id):
    headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/98.0.4758.141 Safari/537.36',
        'accept': '*/*',
    }

    params = {
        'key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
        'prettyPrint': 'false',
    }

    json_data = {
        'videoId': vid_id,
        'context': {
            'client': {
                'hl': 'ru',
                'gl': 'RU',
                'remoteHost': '31.173.242.98',
                'deviceMake': '',
                'deviceModel': '',
                'visitorData': 'CgtrdUNhZ3U2VGNEOCiDndSTBg%3D%3D',
                'userAgent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                             'Chrome/98.0.4758.141 Safari/537.36,gzip(gfe)',
                'clientName': 'WEB',
                'clientVersion': '2.20220502.01.00',
                'osName': 'Windows',
                'osVersion': '10.0',
                'originalUrl': 'https://www.youtube.com/watch?v=4MPWVKFaLD8',
                'platform': 'DESKTOP',
                'clientFormFactor': 'UNKNOWN_FORM_FACTOR',
                'configInfo': {
                    'appInstallData': 'CIOd1JMGELiLrgUQmN79EhCUj64FEOqQrgUQw_KtBRCY6q0FELfLrQUQ8IKuBRC7ka4FENSDrgUQ6JCu'
                                      'BRCw7q0FEK_yrQUQgub9EhCR-PwSENi-rQU%3D',
                },
                'userInterfaceTheme': 'USER_INTERFACE_THEME_DARK',
                'timeZone': 'Europe/Moscow',
                'browserName': 'Chrome',
                'browserVersion': '98.0.4758.141',
                'screenWidthPoints': 1137,
                'screenHeightPoints': 870,
                'screenPixelDensity': 1,
                'screenDensityFloat': 1,
                'utcOffsetMinutes': 360,
                'connectionType': 'CONN_CELLULAR_4G',
                'memoryTotalKbytes': '8000000',
                'mainAppWebInfo': {
                    'graftUrl': 'https://www.youtube.com/watch?v=4MPWVKFaLD8',
                    'webDisplayMode': 'WEB_DISPLAY_MODE_BROWSER',
                    'isWebNativeShareAvailable': True,
                },
                'playerType': 'UNIPLAYER',
                'tvAppInfo': {
                    'livingRoomAppMode': 'LIVING_ROOM_APP_MODE_UNSPECIFIED',
                },
                'clientScreen': 'WATCH_FULL_SCREEN',
            },
            'user': {
                'lockedSafetyMode': False,
            },
            'request': {
                'useSsl': True,
                'internalExperimentFlags': [],
                'consistencyTokenJars': [],
            },
            'adSignalsInfo': {
                'params': [
                    {
                        'key': 'dt',
                        'value': '1651838604229',
                    },
                    {
                        'key': 'flash',
                        'value': '0',
                    },
                    {
                        'key': 'frm',
                        'value': '0',
                    },
                    {
                        'key': 'u_tz',
                        'value': '360',
                    },
                    {
                        'key': 'u_his',
                        'value': '5',
                    },
                    {
                        'key': 'u_h',
                        'value': '1080',
                    },
                    {
                        'key': 'u_w',
                        'value': '1920',
                    },
                    {
                        'key': 'u_ah',
                        'value': '1032',
                    },
                    {
                        'key': 'u_aw',
                        'value': '1920',
                    },
                    {
                        'key': 'u_cd',
                        'value': '24',
                    },
                    {
                        'key': 'bc',
                        'value': '31',
                    },
                    {
                        'key': 'bih',
                        'value': '870',
                    },
                    {
                        'key': 'biw',
                        'value': '1121',
                    },
                    {
                        'key': 'brdim',
                        'value': '43,12,43,12,1920,0,1708,991,1137,870',
                    },
                    {
                        'key': 'vis',
                        'value': '1',
                    },
                    {
                        'key': 'wgl',
                        'value': 'true',
                    },
                    {
                        'key': 'ca_type',
                        'value': 'image',
                    },
                ],
            },
        },
        'playbackContext': {
            'contentPlaybackContext': {
                'html5Preference': 'HTML5_PREF_WANTS',
                'lactMilliseconds': '2979',
                'referer': 'https://www.youtube.com/watch?v=4MPWVKFaLD8',
                'signatureTimestamp': 19117,
                'autonavState': 'STATE_OFF',
                'autoCaptionsDefaultOn': False,
                'mdxContext': {},
                'playerWidthPixels': 647,
                'playerHeightPixels': 364,
            },
        },
        'cpn': 'pwy4NMkpT8PY63hl',
        'captionParams': {
            'deviceCaptionsOn': True,
        },
        'attestationRequest': {
            'omitBotguardData': True,
        },
    }

    print('\n[+] Получаю название канала...')
    channel_name = str(requests.post('https://www.youtube.com/youtubei/v1/player', params=params, headers=headers,
                                     json=json_data).json()['videoDetails']['author'])

    for m in ["?", '"', "/", ":", "#", "|", ",", " ?", "?!", "?!", "? ", " / ", " | "]:
        channel_name = channel_name.replace(m, " ")
    print(f'[+] Название канала получено: "{channel_name}"')
    return channel_name

Не стоит пугаться ее размерами. Большую часть здесь занимает JSON с параметрами, которые передаются с запросом и заголовками. Но избавиться от них не получилось. Потому, чтобы все работало без сбоев, я оставил их без изменения. И использовал так, как они и отправляются в запросе на странице.

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

screenshot5.png

Тут все по накатанной. Копируется cURL, получается код, делается запрос и забирается JSON, из которого выкорчевываются идентификаторы, помещаются в список и возвращают его туда, откуда запрос был сделан.

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

screenshot6.png

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

Python:
params = {
        'url': url,
        'nextPageToken': '',
    }

Ну, а больше особо и пояснять нечего. Все довольно просто и понятно. Цикл и перебор.

Python:
def playlist_item(url):
    headers = {
        'authority': 'api.youtubemultidownloader.com',
        'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="98", "Yandex";v="22"',
        'accept': 'application/json, text/javascript, */*; q=0.01',
        'dnt': '1',
        'sec-ch-ua-mobile': '?0',
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/98.0.4758.141 YaBrowser/22.3.3.852 Yowser/2.5 Safari/537.36',
        'sec-ch-ua-platform': '"Windows"',
        'origin': 'https://youtubemultidownloader.net',
        'sec-fetch-site': 'cross-site',
        'sec-fetch-mode': 'cors',
        'sec-fetch-dest': 'empty',
        'referer': 'https://youtubemultidownloader.net/',
        'accept-language': 'ru,en;q=0.9,uk;q=0.8',
    }

    params = {
        'url': url,
        'nextPageToken': '',
    }

    response = requests.get('https://api.youtubemultidownloader.com/playlist', params=params, headers=headers).json()
    list_items = []
    for item in range(0, len(response['items'])):
        list_items.append(response['items'][item]['id'])
    return list_items

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

Создаю функцию get_target_path(user_input), которая на входе принимает первоначальный пользовательский ввод и обрабатывает его в зависимости от того, что ввел пользователь.

Думаю, что подробно на этом останавливаться не стоит, так как тут не используется чего-то сверхъестественного. Всего лишь проверки if, elif, else. Ну и, если пользователь ввел что-то не так, в бесконечном цикле запрос правильного параметра. Тут, конечно, я предусмотрел возможность выхода в основное меню, так как пользователь может не понять, что не так и попросту запутаться. А цикл будет долбить его снова и снова. А потому, лучше предоставить ему, то есть пользователю, небольшую лазейку, возможность сбежать из бесконечного цикла. А обрабатываются здесь запросы, которые представлены на скриншоте ниже.


screenshot7.png


Python:
def get_target_path(user_input):
    if user_input == "1":
        vid_id = input('\t[+] Введите ссылку на видео\n\t[+] Для выхода в меню введите: ex\n\t>>> ')
        if vid_id == 'ex':
            main()
            return
        while not "https://www.youtube.com" in vid_id:
            vid_id = input('\t[+] Введите ссылку на видео\n\t[+] Для выхода в меню введите: ex\n\t>>> ')
            if vid_id == 'ex':
                main()
                return
        if '&list' in vid_id:
            vid_id = vid_id.split("&")[0].split("=")[-1]
        else:
            vid_id = vid_id.split("=")[-1]
        channel_name = get_channel_name(vid_id)
        get_video_download(vid_id, channel_name)
        main()
    elif user_input == "2":
        while not os.path.isfile(user_path := input("\t[+] Введите путь к списку\n\t[+] Для выхода в меню введите: ex\n"
                                                    "\t>>> ").replace('"', '')):
            if user_path == 'ex':
                main()
                return
            print(f"\n\t[+] Список {user_path} не найден\n")
        with open(f'{user_path}', 'r', encoding='utf-8') as file:
            video_list = file.readlines()
        for video in video_list:
            if '&list' in video:
                vid_id = video.split("&")[0].split("=")[-1]
            else:
                vid_id = video.split("=")[-1].strip()
            if video.strip() == "":
                continue
            else:
                channel_name = get_channel_name(vid_id)
                get_video_download(vid_id, channel_name)
        main()
    elif user_input == "3":
        vid_id = input('\t[+] Введите ссылку на плейлист\n\t[+] Для выхода в меню введите: ex\n\t>>> ')
        if vid_id == 'ex':
            main()
            return
        while not "https://www.youtube.com/playlist" in vid_id:
            vid_id = input('\t[+] Введите ссылку на плейлист\n\t[+] Для выхода в меню введите: ex\n\t>>> ')
            if vid_id == 'ex':
                main()
                return
        list_items = playlist_item(vid_id)
        print(f'[+] Видео в плейлисте: {len(list_items)}\n[+] Загружаю плейлист...')
        for item in list_items:
            channel_name = get_channel_name(item)
            get_video_download(item, channel_name)
        main()
    elif user_input == "4":
        exit(0)
    else:
        main()

И остается функция main(), в которой и делается первоначальный выбор и осуществляется пользовательский ввод.

Python:
def main():
    get_target_path(input(f'\n[+] Выберите варианты загрузки:\n\t[1] Загрузить видео\n'
                          f'\t[2] Загрузить видео из списка\n\t[3] Загрузить плейлист\n\t[4] Выход\n\t>>> '))

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


Python:
import os.path
import time

import requests
from tqdm import tqdm


def playlist_item(url):
    headers = {
        'authority': 'api.youtubemultidownloader.com',
        'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="98", "Yandex";v="22"',
        'accept': 'application/json, text/javascript, */*; q=0.01',
        'dnt': '1',
        'sec-ch-ua-mobile': '?0',
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/98.0.4758.141 YaBrowser/22.3.3.852 Yowser/2.5 Safari/537.36',
        'sec-ch-ua-platform': '"Windows"',
        'origin': 'https://youtubemultidownloader.net',
        'sec-fetch-site': 'cross-site',
        'sec-fetch-mode': 'cors',
        'sec-fetch-dest': 'empty',
        'referer': 'https://youtubemultidownloader.net/',
        'accept-language': 'ru,en;q=0.9,uk;q=0.8',
    }

    params = {
        'url': url,
        'nextPageToken': '',
    }

    response = requests.get('https://api.youtubemultidownloader.com/playlist', params=params, headers=headers).json()
    list_items = []
    for item in range(0, len(response['items'])):
        list_items.append(response['items'][item]['id'])
    return list_items


def get_channel_name(vid_id):
    headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/98.0.4758.141 Safari/537.36',
        'accept': '*/*',
    }

    params = {
        'key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
        'prettyPrint': 'false',
    }

    json_data = {
        'videoId': vid_id,
        'context': {
            'client': {
                'hl': 'ru',
                'gl': 'RU',
                'remoteHost': '31.173.242.98',
                'deviceMake': '',
                'deviceModel': '',
                'visitorData': 'CgtrdUNhZ3U2VGNEOCiDndSTBg%3D%3D',
                'userAgent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                             'Chrome/98.0.4758.141 Safari/537.36,gzip(gfe)',
                'clientName': 'WEB',
                'clientVersion': '2.20220502.01.00',
                'osName': 'Windows',
                'osVersion': '10.0',
                'originalUrl': 'https://www.youtube.com/watch?v=4MPWVKFaLD8',
                'platform': 'DESKTOP',
                'clientFormFactor': 'UNKNOWN_FORM_FACTOR',
                'configInfo': {
                    'appInstallData': 'CIOd1JMGELiLrgUQmN79EhCUj64FEOqQrgUQw_KtBRCY6q0FELfLrQUQ8IKuBRC7ka4FENSDrgUQ6JCu'
                                      'BRCw7q0FEK_yrQUQgub9EhCR-PwSENi-rQU%3D',
                },
                'userInterfaceTheme': 'USER_INTERFACE_THEME_DARK',
                'timeZone': 'Europe/Moskow',
                'browserName': 'Chrome',
                'browserVersion': '98.0.4758.141',
                'screenWidthPoints': 1137,
                'screenHeightPoints': 870,
                'screenPixelDensity': 1,
                'screenDensityFloat': 1,
                'utcOffsetMinutes': 360,
                'connectionType': 'CONN_CELLULAR_4G',
                'memoryTotalKbytes': '8000000',
                'mainAppWebInfo': {
                    'graftUrl': 'https://www.youtube.com/watch?v=4MPWVKFaLD8',
                    'webDisplayMode': 'WEB_DISPLAY_MODE_BROWSER',
                    'isWebNativeShareAvailable': True,
                },
                'playerType': 'UNIPLAYER',
                'tvAppInfo': {
                    'livingRoomAppMode': 'LIVING_ROOM_APP_MODE_UNSPECIFIED',
                },
                'clientScreen': 'WATCH_FULL_SCREEN',
            },
            'user': {
                'lockedSafetyMode': False,
            },
            'request': {
                'useSsl': True,
                'internalExperimentFlags': [],
                'consistencyTokenJars': [],
            },
            'adSignalsInfo': {
                'params': [
                    {
                        'key': 'dt',
                        'value': '1651838604229',
                    },
                    {
                        'key': 'flash',
                        'value': '0',
                    },
                    {
                        'key': 'frm',
                        'value': '0',
                    },
                    {
                        'key': 'u_tz',
                        'value': '360',
                    },
                    {
                        'key': 'u_his',
                        'value': '5',
                    },
                    {
                        'key': 'u_h',
                        'value': '1080',
                    },
                    {
                        'key': 'u_w',
                        'value': '1920',
                    },
                    {
                        'key': 'u_ah',
                        'value': '1032',
                    },
                    {
                        'key': 'u_aw',
                        'value': '1920',
                    },
                    {
                        'key': 'u_cd',
                        'value': '24',
                    },
                    {
                        'key': 'bc',
                        'value': '31',
                    },
                    {
                        'key': 'bih',
                        'value': '870',
                    },
                    {
                        'key': 'biw',
                        'value': '1121',
                    },
                    {
                        'key': 'brdim',
                        'value': '43,12,43,12,1920,0,1708,991,1137,870',
                    },
                    {
                        'key': 'vis',
                        'value': '1',
                    },
                    {
                        'key': 'wgl',
                        'value': 'true',
                    },
                    {
                        'key': 'ca_type',
                        'value': 'image',
                    },
                ],
            },
        },
        'playbackContext': {
            'contentPlaybackContext': {
                'html5Preference': 'HTML5_PREF_WANTS',
                'lactMilliseconds': '2979',
                'referer': 'https://www.youtube.com/watch?v=4MPWVKFaLD8',
                'signatureTimestamp': 19117,
                'autonavState': 'STATE_OFF',
                'autoCaptionsDefaultOn': False,
                'mdxContext': {},
                'playerWidthPixels': 647,
                'playerHeightPixels': 364,
            },
        },
        'cpn': 'pwy4NMkpT8PY63hl',
        'captionParams': {
            'deviceCaptionsOn': True,
        },
        'attestationRequest': {
            'omitBotguardData': True,
        },
    }

    print('\n[+] Получаю название канала...')
    channel_name = str(requests.post('https://www.youtube.com/youtubei/v1/player', params=params, headers=headers,
                                     json=json_data).json()['videoDetails']['author'])

    for m in ["?", '"', "/", ":", "#", "|", ",", " ?", "?!", "?!", "? ", " / ", " | "]:
        channel_name = channel_name.replace(m, " ")
    print(f'[+] Название канала получено: "{channel_name}"')
    return channel_name


def get_video_download(vid_id, channel_name):
    headers = {
        'authority': 'downloader.freemake.com',
        'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="98", "Yandex";v="22"',
        'dnt': '1',
        'x-cf-country': 'RU',
        'sec-ch-ua-mobile': '?0',
        'x-user-platform': 'Win32',
        'accept': 'application/json, text/javascript, */*; q=0.01',
        'x-user-browser': 'YaBrowser',
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/98.0.4758.141 YaBrowser/22.3.3.852 Yowser/2.5 Safari/537.36',
        'x-analytics-header': 'UA-18256617-1',
        'x-request-attempt': '1',
        'x-user-id': '94119398-e27a-3e13-be17-bbe7fbc25874',
        'sec-ch-ua-platform': '"Windows"',
        'origin': 'https://www.freemake.com',
        'sec-fetch-site': 'same-site',
        'sec-fetch-mode': 'cors',
        'sec-fetch-dest': 'empty',
        'referer': 'https://www.freemake.com/ru/free_video_downloader/',
        'accept-language': 'ru,en;q=0.9,uk;q=0.8',
    }

    print(f'[+] Получаю название и ссылку на видео...')
    response = requests.get(f'https://downloader.freemake.com/api/videoinfo/{vid_id}', headers=headers).json()
    if response['qualities'][0]['qualityInfo']['itag'] == 22:
        video_title = str(response['metaInfo']['title'])
        for m in ["?", '"', "'", "/", ":", "#", "|", ",", " | "]:
            video_title = video_title.replace(m, "")
        url = response['qualities'][0]['url']
        print(f'[+] Название и ссылка получены. Начинаю загрузку: "{video_title}"...')
        if not os.path.isdir(f'{channel_name}'):
            os.mkdir(f'{channel_name}')
            print(f'[+] Создаю папку для сохранения видео...\n')
        else:
            print(f'[+] Папка для сохранения существует...\n')
        req = requests.get(url=url, headers=headers, stream=True)
        total = int(req.headers.get('content-length', 0))
        with open(f'{os.path.join(channel_name, f"{video_title}.mp4")}', 'wb') as file, tqdm(
                desc=f"{video_title[0:int(len(video_title) / 2)]}...",
                total=total,
                unit='iB',
                unit_scale=True,
                unit_divisor=1024,
        ) as bar:
            for data in req.iter_content(chunk_size=1024):
                size = file.write(data)
                bar.update(size)
        print(f'\n[+] Видео сохранено в папку: "{channel_name}".\n[+] Загрузка завершена.\n')
    else:
        user_change = input('\n[+] Нет видео в качестве 720р...\n[+] Загрузить в доступном качестве?:\n'
                            '\t[1]: Да\n\t[2]: Нет\n\t>>> ')
        if user_change == "1":
            video_title = str(response['metaInfo']['title'])
            for m in ["?", '"', "'", "/", ":", "#", "|", ",", " | "]:
                video_title = video_title.replace(m, "")
            url = response['qualities'][0]['url']
            print(f'[+] Название и ссылка получены. Начинаю загрузку: "{video_title}"...')
            if not os.path.isdir(f'{channel_name}'):
                os.mkdir(f'{channel_name}')
                print(f'[+] Создаю папку для сохранения видео...\n')
            else:
                print(f'[+] Папка для сохранения существует...\n')
            req = requests.get(url=url, headers=headers, stream=True)
            total = int(req.headers.get('content-length', 0))
            with open(f'{os.path.join(channel_name, f"{video_title}.mp4")}', 'wb') as file, tqdm(
                    desc=f"{video_title[0:int(len(video_title) / 2)]}...",
                    total=total,
                    unit='iB',
                    unit_scale=True,
                    unit_divisor=1024,
            ) as bar:
                for data in req.iter_content(chunk_size=1024):
                    size = file.write(data)
                    bar.update(size)
            print(f'\n[+] Видео сохранено в папку: "{channel_name}".\n[+] Загрузка завершена.\n')
        elif user_change == "2":
            main()
            return
        else:
            print('[-] Вы ввели чушь. Закрываю программу...')
            exit(0)


def get_target_path(user_input):
    if user_input == "1":
        vid_id = input('\t[+] Введите ссылку на видео\n\t[+] Для выхода в меню введите: ex\n\t>>> ')
        if vid_id == 'ex':
            main()
            return
        while not "https://www.youtube.com" in vid_id:
            vid_id = input('\t[+] Введите ссылку на видео\n\t[+] Для выхода в меню введите: ex\n\t>>> ')
            if vid_id == 'ex':
                main()
                return
        if '&list' in vid_id:
            vid_id = vid_id.split("&")[0].split("=")[-1]
        else:
            vid_id = vid_id.split("=")[-1]
        channel_name = get_channel_name(vid_id)
        get_video_download(vid_id, channel_name)
        main()
    elif user_input == "2":
        while not os.path.isfile(user_path := input("\t[+] Введите путь к списку\n\t[+] Для выхода в меню введите: ex\n"
                                                    "\t>>> ").replace('"', '')):
            if user_path == 'ex':
                main()
                return
            print(f"\n\t[+] Список {user_path} не найден\n")
        with open(f'{user_path}', 'r', encoding='utf-8') as file:
            video_list = file.readlines()
        for video in video_list:
            if '&list' in video:
                vid_id = video.split("&")[0].split("=")[-1]
            else:
                vid_id = video.split("=")[-1].strip()
            if video.strip() == "":
                continue
            else:
                channel_name = get_channel_name(vid_id)
                get_video_download(vid_id, channel_name)
        main()
    elif user_input == "3":
        vid_id = input('\t[+] Введите ссылку на плейлист\n\t[+] Для выхода в меню введите: ex\n\t>>> ')
        if vid_id == 'ex':
            main()
            return
        while not "https://www.youtube.com/playlist" in vid_id:
            vid_id = input('\t[+] Введите ссылку на плейлист\n\t[+] Для выхода в меню введите: ex\n\t>>> ')
            if vid_id == 'ex':
                main()
                return
        list_items = playlist_item(vid_id)
        print(f'[+] Видео в плейлисте: {len(list_items)}\n[+] Загружаю плейлист...')
        for item in list_items:
            channel_name = get_channel_name(item)
            time.sleep(0.3)
            get_video_download(item, channel_name)
            time.sleep(0.3)
        main()
    elif user_input == "4":
        exit(0)
    else:
        main()


def main():
    get_target_path(input(f'\n[+] Выберите варианты загрузки:\n\t[1] Загрузить видео\n'
                          f'\t[2] Загрузить видео из списка\n\t[3] Загрузить плейлист\n\t[4] Выход\n\t>>> '))


if __name__ == "__main__":
    main()

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

Вложения

  • video_youtube_downloader.zip
    3,9 КБ · Просмотры: 468
Последнее редактирование:

Johan Van

Green Team
13.06.2020
350
656
BIT
134
Спасибо. Скрипт работает - но очень медленно.

К сожалению да. Но тут нужно учесть, что со времени написания скрипта изменился код на самом Ютубе. Даже те качалки, которые раньше работали на ура не всегда справляются с загрузкой видео с первого захода. Потому, нужно в данном контексте рассмотреть, возможно, другие варианты получения видео. С Ютубом почти всегда так. Насколько я знаю, даже те библиотеки, которые заточены на скачивание видео, например, pytube, иногда перестают работать после изменения кода. И приходиться ждать когда разработчики внесут необходимые правки, насколько это возможно.
 
  • Нравится
Реакции: satfan

satfan

Green Team
26.06.2022
75
1
BIT
42
К сожалению да. Но тут нужно учесть, что со времени написания скрипта изменился код на самом Ютубе. Даже те качалки, которые раньше работали на ура не всегда справляются с загрузкой видео с первого захода. Потому, нужно в данном контексте рассмотреть, возможно, другие варианты получения видео. С Ютубом почти всегда так. Насколько я знаю, даже те библиотеки, которые заточены на скачивание видео, например, pytube, иногда перестают работать после изменения кода. И приходиться ждать когда разработчики внесут необходимые правки, насколько это возможно.
 

satfan

Green Team
26.06.2022
75
1
BIT
42
Спасибо за ответ.
Поставил расширение в гооогле хром: IDM Integration Module (Скачивание файлов с использованием Internet Download Manager) -- всё летает и можно выбрать качество.
 

satfan

Green Team
26.06.2022
75
1
BIT
42
Спасибо за ответ.
Поставил расширение в гооогле хром: IDM Integration Module (Скачивание файлов с использованием Internet Download Manager) -- всё летает и можно выбрать качество.
Вы не смогли бы написать скрипт для скачивания видео с и с рутубе ?
 

Johan Van

Green Team
13.06.2020
350
656
BIT
134
Вы не смогли бы написать скрипт для скачивания видео с и с рутубе ?

Я как-то пытался. Но Дзен видео хорошо прячет. Пока что не удалось реализовать. Может быть в будущем, как руки дойдут ))
 
  • Нравится
Реакции: satfan

Johan Van

Green Team
13.06.2020
350
656
BIT
134
Спасибо. А с рутуба -- можно сделать ?

Здесь есть статья про скачивание видео с Rutube. Если у них не изменился код, должно все работать. Но с момента написания статьи - не проверял. Это было еще до того, как их За DDoSили. Потому не знаю. Если интересно, можете проверить. Вот ссылка на статью: Статья - Скачиваем видео с Rutube с помощью Python
 
  • Нравится
Реакции: satfan

satfan

Green Team
26.06.2022
75
1
BIT
42
Здесь есть статья про скачивание видео с Rutube. Если у них не изменился код, должно все работать. Но с момента написания статьи - не проверял. Это было еще до того, как их За DDoSили. Потому не знаю. Если интересно, можете проверить. Вот ссылка на статью: Статья - Скачиваем видео с Rutube с помощью Python
Спасибо. Я только начал учить питон. Небольшой вопрос: Есть работающий скрипт
Python:
import os

path = 'E:\Мой Python\__Видеокурсы Python'
rez = sorted(os.listdir(path))
with open("out_ok.txt", "w") as file:
    for n, item in enumerate(rez):
        file.write(f"{n + 1} {item}\n")
print('Список файлов смотрим здесь - out_ok.txt ')

# поиск перечня файлов в папке и запись списка в out_ok.txt
Как в него добавить размер каждого файла в Мб в каждую строку ?
Есть пример но он мне не совсем подходит :
Python:
import os
 
# Имя каталога, из которого
# мы собираемся извлечь наши файлы с учетом их размера
path = 'E:\Мой Python\__Видеокурсы Python'
 
# Получить список всех файлов только в данном каталоге
fun = lambda x : os.path.isfile(os.path.join(path,x))
files_list = filter(fun, os.listdir(path))
 
# Создайте список файлов в каталоге вместе с размером
size_of_file = [
    (f,os.stat(os.path.join(path, f)).st_size)
    for f in files_list
]
# Выполните итерацию по списку файлов вместе с размером
# и распечатайте их один за другим.
for f,s in size_of_file:
    print("{} : {}MB".format(f, round(s/(1024*1024),3)))
 

Johan Van

Green Team
13.06.2020
350
656
BIT
134
Спасибо. Я только начал учить питон. Небольшой вопрос: Есть работающий скрипт
Python:
import os

path = 'E:\Мой Python\__Видеокурсы Python'
rez = sorted(os.listdir(path))
with open("out_ok.txt", "w") as file:
    for n, item in enumerate(rez):
        file.write(f"{n + 1} {item}\n")
print('Список файлов смотрим здесь - out_ok.txt ')

# поиск перечня файлов в папке и запись списка в out_ok.txt
Как в него добавить размер каждого файла в Мб в каждую строку ?
Есть пример но он мне не совсем подходит :
Python:
import os
 
# Имя каталога, из которого
# мы собираемся извлечь наши файлы с учетом их размера
path = 'E:\Мой Python\__Видеокурсы Python'
 
# Получить список всех файлов только в данном каталоге
fun = lambda x : os.path.isfile(os.path.join(path,x))
files_list = filter(fun, os.listdir(path))
 
# Создайте список файлов в каталоге вместе с размером
size_of_file = [
    (f,os.stat(os.path.join(path, f)).st_size)
    for f in files_list
]
# Выполните итерацию по списку файлов вместе с размером
# и распечатайте их один за другим.
for f,s in size_of_file:
    print("{} : {}MB".format(f, round(s/(1024*1024),3)))

Можно сделать так:

Python:
import os


def correct_size(bts, ending='iB'):
    size = 1024
    for item in ["", "K", "M", "G", "T", "P"]:
        if bts < size:
            return f"{bts:.2f} {item}{ending}"
        bts /= size


path = 'E:\Мой Python\__Видеокурсы Python'
rez = sorted(os.listdir(path))
with open("out_ok.txt", "w") as file:
    for n, item in enumerate(rez):
        size = correct_size(os.stat(os.path.join(path, item)).st_size)
        file.write(f"{n + 1} {item} - {size}\n")
print('Список файлов смотрим здесь - out_ok.txt ')

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

size = correct_size(os.stat(os.path.join(path, item)).st_size)

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

По сути, то же самое, что у тебя в коде. Только делается все не в одной функции. Ну и у тебя в коде фильтруются объекты в сканируемой директории на папки и файлы. Что правильно, так как с помощью данного кода размер папки не получить.
 
Последнее редактирование:
  • Нравится
Реакции: satfan

satfan

Green Team
26.06.2022
75
1
BIT
42
Можно сделать так:

Python:
import os


def correct_size(bts, ending='iB'):
    size = 1024
    for item in ["", "K", "M", "G", "T", "P"]:
        if bts < size:
            return f"{bts:.2f} {item}{ending}"
        bts /= size


path = 'E:\Мой Python\__Видеокурсы Python'
rez = sorted(os.listdir(path))
with open("out_ok.txt", "w") as file:
    for n, item in enumerate(rez):
        size = correct_size(os.stat(os.path.join(path, item)).st_size)
        file.write(f"{n + 1} {item} - {size}\n")
print('Список файлов смотрим здесь - out_ok.txt ')

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

size = correct_size(os.stat(os.path.join(path, item)).st_size)

Собирается путь к файлу, считывается информация и забирается его размер. Который потом передается в функцию для коррекции.
Нужно учитывать то, что размер директорий в данном случае будет отображен неверно. То есть, будет показан размер папок как объектов, но не самого содержимого этих папок. Если такие есть у тебя в директории.
Спасибо. Скрипт работает. Только выводит в строках не в Мб.
Вот пример строки:
4 02_Курс программирования на Python 3.6 - 4.00 KiB
 

Johan Van

Green Team
13.06.2020
350
656
BIT
134
Спасибо. Скрипт работает. Только выводит в строках не в Мб.
Вот пример строки:
4 02_Курс программирования на Python 3.6 - 4.00 KiB

Ну так я и говорил про папки, что их размер будет выводиться неправильно. Будет выводиться размер папки как объекта. А не ее содержимого. Когда ты смотришь размер папки в проводнике, ты видишь размер уже с учетом содержимого. Здесь же нет. Здесь чистый размер папки.

Если тебе надо получить размер каждого файла в папке, тогда тебе нужно использовать не функцию listdir, а функцию walk. Она возвращает все объекты из вложенных директорий рекурсивно.
 
  • Нравится
Реакции: satfan

satfan

Green Team
26.06.2022
75
1
BIT
42
Ну так я и говорил про папки, что их размер будет выводиться неправильно. Будет выводиться размер папки как объекта. А не ее содержимого. Когда ты смотришь размер папки в проводнике, ты видишь размер уже с учетом содержимого. Здесь же нет. Здесь чистый размер папки.

Если тебе надо получить размер каждого файла в папке, тогда тебе нужно использовать не функцию listdir, а функцию walk. Она возвращает все объекты из вложенных директорий рекурсивно.
Спасибо за помощь Johan Van. Вы олично знаете Python, а я только начал. Может вам что нужно:
1. Есть пратически все книги по Python
2. Куча видео курсов
3. Прога для скачивания с ютуба (искал креченую 2 дня)
Но это всё толлька Вам в личку .
 

Johan Van

Green Team
13.06.2020
350
656
BIT
134
Спасибо за помощь Johan Van. Вы олично знаете Python, а я только начал. Может вам что нужно:
1. Есть пратически все книги по Python
2. Куча видео курсов
3. Прога для скачивания с ютуба (искал креченую 2 дня)
Но это всё толлька Вам в личку .

Спасибо за предложение ))) У меня в данный момент в библиотеке 752 книги только по тегу python. На русском и английском языках, разных годов и издательств. В начале, перед тем как изучать питона я тоже озаботился скачиванием доступной литературы. Да и сейчас иногда докачиваю то, чего нет в библиотеке. Пользуюсь для структурирования calibre. По поводу курсов.... около 29 курсов из тех, что я пока еще не смотрел чисто по питону, 15 курсов по парсингу, ну и что-то есть в разделе безопасность ))
 
  • Нравится
Реакции: satfan

Johan Van

Green Team
13.06.2020
350
656
BIT
134
Спасибо за помощь Johan Van. Вы олично знаете Python, а я только начал. Может вам что нужно:
1. Есть пратически все книги по Python
2. Куча видео курсов
3. Прога для скачивания с ютуба (искал креченую 2 дня)
Но это всё толлька Вам в личку .

А по поводу проги... тут все просто, если именно для винды. Есть сайт такой, интересный, не особо от кого-то скрывающийся: rsload.net. Там можно найти кучу всего. Единственный его минус в том, что без оплаты доступа скачивание ограничено по скорости. Но, если очень надо, можно и подождать.
 

satfan

Green Team
26.06.2022
75
1
BIT
42
Спасибо за предложение ))) У меня в данный момент в библиотеке 752 книги только по тегу python. На русском и английском языках, разных годов и издательств. В начале, перед тем как изучать питона я тоже озаботился скачиванием доступной литературы. Да и сейчас иногда докачиваю то, чего нет в библиотеке. Пользуюсь для структурирования calibre. По поводу курсов.... около 29 курсов из тех, что я пока еще не смотрел чисто по питону, 15 курсов по парсингу, ну и что-то есть в разделе безопасность ))
Ещё вопрос. Прграмма - бесплатный VPN - что посоветуете ?
 

Johan Van

Green Team
13.06.2020
350
656
BIT
134
Ещё вопрос. Прграмма - бесплатный VPN - что посоветуете ?

По поводу размера директорий. Можно попробовать использовать вот такой код:

Python:
from os import stat, path, walk, listdir
from sys import exit as ex


def correct_size(bts: int, ending='iB') -> str:
    """
    Корректируется размер файла.
    :param bts: размер файла, целое число.
    :param ending: суффикс, добавляемый к метрике.
    :return: строка, корректированный размер файла.
    """
    size = 1024
    for item in ["", "K", "M", "G", "T", "P"]:
        if bts < size:
            return f"{bts:.2f} {item}{ending}"
        bts /= size


def folder_scan(path_dir: str) -> str:
    """
    Определение размера директории. Сканируется директория.
    Определяется размер каждого файла находящегося в ней.
    Суммируется в общей переменной. После завершения сканирования
    возвращается скорректированный размер.
    :param path_dir: строка, путь к директории для сканирования.
    :return: строка, скорректированный размер файла.
    """
    file_size = stat(path_dir).st_size
    for root, dirs, files in walk(path_dir):
        for file in files:
            file_size = file_size + stat(path.join(root, file)).st_size
    return correct_size(file_size)


def list_dir(path_dir: str):
    """
    Сканирование директории. Определение размера файлов и папок.
    Добавление данных об имени и размере в текстовый документ.
    Сортировка полученных объектов на файлы и папки в процессе
    итерации по ним. Если файл, определяем размер сразу. Если папка,
    передаем путь к ней в функцию получения размера папки.
    :param path_dir: строка, путь к директории для сканирования.
    """
    rez = sorted(listdir(path_dir))
    with open("out_ok.txt", "w") as file:
        for n, item in enumerate(rez):
            if path.isdir(path.join(path_dir, item)):
                size = folder_scan(path.join(path_dir, item))
            else:
                size = correct_size(stat(path.join(path_dir, item)).st_size)
            file.write(f"{n + 1} {item} - {size}\n")
    print('Список файлов смотрим здесь - out_ok.txt ')


def main():
    """
    Запрос у пользователя пути к сканируемой директории.
    Запуск функции сканирования с передачей в нее
    полученного от пользователя пути.
    """
    path_dir = input("Введите путь к сканируемой директории: ")
    if not path.exists(path_dir):
        print("Введенной вами директории не существует.")
        ex(0)
    list_dir(path_dir)


if __name__ == "__main__":
    main()

Здесь нужно учесть, что будут возвращаться размеры директорий именно верхнего уровня. Того, до которого может дотянуться listdir, а именно, размер папок в сканируемой директории.
 
  • Нравится
Реакции: satfan

satfan

Green Team
26.06.2022
75
1
BIT
42
По поводу размера директорий. Можно попробовать использовать вот такой код:

Python:
from os import stat, path, walk, listdir
from sys import exit as ex


def correct_size(bts: int, ending='iB') -> str:
    """
    Корректируется размер файла.
    :param bts: размер файла, целое число.
    :param ending: суффикс, добавляемый к метрике.
    :return: строка, корректированный размер файла.
    """
    size = 1024
    for item in ["", "K", "M", "G", "T", "P"]:
        if bts < size:
            return f"{bts:.2f} {item}{ending}"
        bts /= size


def folder_scan(path_dir: str) -> str:
    """
    Определение размера директории. Сканируется директория.
    Определяется размер каждого файла находящегося в ней.
    Суммируется в общей переменной. После завершения сканирования
    возвращается скорректированный размер.
    :param path_dir: строка, путь к директории для сканирования.
    :return: строка, скорректированный размер файла.
    """
    file_size = stat(path_dir).st_size
    for root, dirs, files in walk(path_dir):
        for file in files:
            file_size = file_size + stat(path.join(root, file)).st_size
    return correct_size(file_size)


def list_dir(path_dir: str):
    """
    Сканирование директории. Определение размера файлов и папок.
    Добавление данных об имени и размере в текстовый документ.
    Сортировка полученных объектов на файлы и папки в процессе
    итерации по ним. Если файл, определяем размер сразу. Если папка,
    передаем путь к ней в функцию получения размера папки.
    :param path_dir: строка, путь к директории для сканирования.
    """
    rez = sorted(listdir(path_dir))
    with open("out_ok.txt", "w") as file:
        for n, item in enumerate(rez):
            if path.isdir(path.join(path_dir, item)):
                size = folder_scan(path.join(path_dir, item))
            else:
                size = correct_size(stat(path.join(path_dir, item)).st_size)
            file.write(f"{n + 1} {item} - {size}\n")
    print('Список файлов смотрим здесь - out_ok.txt ')


def main():
    """
    Запрос у пользователя пути к сканируемой директории.
    Запуск функции сканирования с передачей в нее
    полученного от пользователя пути.
    """
    path_dir = input("Введите путь к сканируемой директории: ")
    if not path.exists(path_dir):
        print("Введенной вами директории не существует.")
        ex(0)
    list_dir(path_dir)


if __name__ == "__main__":
    main()

Здесь нужно учесть, что будут возвращаться размеры директорий именно верхнего уровня. Того, до которого может дотянуться listdir, а именно, размер папок в сканируемой директории.
Выдало ошибки:
Введите путь к сканируемой директории: E:\Мой Python\__Видеокурсы Python
Traceback (most recent call last):
File "E:\__Moi_scripti\10_files in a directory\0.py", line 69, in <module>
main()
File "E:\__Moi_scripti\10_files in a directory\0.py", line 65, in main
list_dir(path_dir)
File "E:\__Moi_scripti\10_files in a directory\0.py", line 48, in list_dir
size = folder_scan(path.join(path_dir, item))
File "E:\__Moi_scripti\10_files in a directory\0.py", line 31, in folder_scan
file_size = file_size + stat(path.join(root, file)).st_size
FileNotFoundError: [WinError 3] Системе не удается найти указанный путь: 'E:\\Мой Python\\__Видеокурсы Python\\01_Онлайн курс Python для тестировщика\\Курс\\11 Основы автоматизации тестирования веб-приложений с Selenium WebDriver на Python\\1 Практика, пишем простой тест проверки веб-сайта_files\\488c2a4e544f5d0cea166cfb9e33153247829078.jpg'
 

Johan Van

Green Team
13.06.2020
350
656
BIT
134
Выдало ошибки:
Введите путь к сканируемой директории: E:\Мой Python\__Видеокурсы Python
Traceback (most recent call last):
File "E:\__Moi_scripti\10_files in a directory\0.py", line 69, in <module>
main()
File "E:\__Moi_scripti\10_files in a directory\0.py", line 65, in main
list_dir(path_dir)
File "E:\__Moi_scripti\10_files in a directory\0.py", line 48, in list_dir
size = folder_scan(path.join(path_dir, item))
File "E:\__Moi_scripti\10_files in a directory\0.py", line 31, in folder_scan
file_size = file_size + stat(path.join(root, file)).st_size
FileNotFoundError: [WinError 3] Системе не удается найти указанный путь: 'E:\\Мой Python\\__Видеокурсы Python\\01_Онлайн курс Python для тестировщика\\Курс\\11 Основы автоматизации тестирования веб-приложений с Selenium WebDriver на Python\\1 Практика, пишем простой тест проверки веб-сайта_files\\488c2a4e544f5d0cea166cfb9e33153247829078.jpg'

Ну, здесь всего лишь одна ошибка )) А почему именно так... тут надо разобраться. Может быть это связано с глубиной вложенности файлов. Попробую у себя создать подобный путь и просканировать.
 
  • Нравится
Реакции: satfan

Johan Van

Green Team
13.06.2020
350
656
BIT
134
Выдало ошибки:
Введите путь к сканируемой директории: E:\Мой Python\__Видеокурсы Python
Traceback (most recent call last):
File "E:\__Moi_scripti\10_files in a directory\0.py", line 69, in <module>
main()
File "E:\__Moi_scripti\10_files in a directory\0.py", line 65, in main
list_dir(path_dir)
File "E:\__Moi_scripti\10_files in a directory\0.py", line 48, in list_dir
size = folder_scan(path.join(path_dir, item))
File "E:\__Moi_scripti\10_files in a directory\0.py", line 31, in folder_scan
file_size = file_size + stat(path.join(root, file)).st_size
FileNotFoundError: [WinError 3] Системе не удается найти указанный путь: 'E:\\Мой Python\\__Видеокурсы Python\\01_Онлайн курс Python для тестировщика\\Курс\\11 Основы автоматизации тестирования веб-приложений с Selenium WebDriver на Python\\1 Практика, пишем простой тест проверки веб-сайта_files\\488c2a4e544f5d0cea166cfb9e33153247829078.jpg'

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

01.png
 

Johan Van

Green Team
13.06.2020
350
656
BIT
134
Выдало ошибки:
Введите путь к сканируемой директории: E:\Мой Python\__Видеокурсы Python
Traceback (most recent call last):
File "E:\__Moi_scripti\10_files in a directory\0.py", line 69, in <module>
main()
File "E:\__Moi_scripti\10_files in a directory\0.py", line 65, in main
list_dir(path_dir)
File "E:\__Moi_scripti\10_files in a directory\0.py", line 48, in list_dir
size = folder_scan(path.join(path_dir, item))
File "E:\__Moi_scripti\10_files in a directory\0.py", line 31, in folder_scan
file_size = file_size + stat(path.join(root, file)).st_size
FileNotFoundError: [WinError 3] Системе не удается найти указанный путь: 'E:\\Мой Python\\__Видеокурсы Python\\01_Онлайн курс Python для тестировщика\\Курс\\11 Основы автоматизации тестирования веб-приложений с Selenium WebDriver на Python\\1 Практика, пишем простой тест проверки веб-сайта_files\\488c2a4e544f5d0cea166cfb9e33153247829078.jpg'

Попробую сделать этот же трюк на Windows.
 
  • Нравится
Реакции: satfan
Мы в соцсетях:

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