Статья Скачиваем видео с 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()

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

Вложения

Последнее редактирование:
Попробую сделать этот же трюк на Windows.

Попробовал на Windows. Вот для примера ваша глубина вложенности, плюс рабочий стол: C:\Users\test\Desktop\Мой Python\__Видеокурсы Python\01_Онлайн курс Python для тестировщика\Курс\11 Основы автоматизации тестирования веб-приложений с Selenium WebDriver на Python\1 Практика, пишем простой тест проверки веб-сайта_files.

Все отработало корректно.
 
Попробовал на Windows. Вот для примера ваша глубина вложенности, плюс рабочий стол: C:\Users\test\Desktop\Мой Python\__Видеокурсы Python\01_Онлайн курс Python для тестировщика\Курс\11 Основы автоматизации тестирования веб-приложений с Selenium WebDriver на Python\1 Практика, пишем простой тест проверки веб-сайта_files.

Все отработало корректно.

Для тестирования отсканировал свою директорию с курсами. И все также прошло без ошибок.

Вот, даже список для примера:

1 OSINT Library - 11.80 GiB
2 Безопасность, пентест - 300.25 GiB
3 Боты - 28.28 GiB
4 Веб-разработка - 11.28 GiB
5 Новое видео Ютуб - 25.15 GiB
6 Парсинг - 36.90 GiB
7 Программирование - 168.00 GiB

UPD: Для тестирования взял еще более сложный случай - папку с библиотекой calibre. И снова все без ошибок.
 
Для тестирования отсканировал свою директорию с курсами. И все также прошло без ошибок.

Вот, даже список для примера:

1 OSINT Library - 11.80 GiB
2 Безопасность, пентест - 300.25 GiB
3 Боты - 28.28 GiB
4 Веб-разработка - 11.28 GiB
5 Новое видео Ютуб - 25.15 GiB
6 Парсинг - 36.90 GiB
7 Программирование - 168.00 GiB

UPD: Для тестирования взял еще более сложный случай - папку с библиотекой calibre. И снова все без ошибок.

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

Python:
from os import stat, path, walk, listdir
from pathlib import Path
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))
    file_name = f'{Path(path_dir).name}'
    
    with open(f"{file_name}.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(f'Список файлов смотрим здесь - {file_name}.txt ')


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


if __name__ == "__main__":
    main()
 
  • Нравится
Реакции: satfan
Немного изменил код, добавил получение названия сканированной директории, чтобы файл сохранялся с ее именем.

Python:
from os import stat, path, walk, listdir
from pathlib import Path
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))
    file_name = f'{Path(path_dir).name}'
  
    with open(f"{file_name}.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(f'Список файлов смотрим здесь - {file_name}.txt ')


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


if __name__ == "__main__":
    main()
Опять ошибк:
Введите путь к сканируемой директории: E:\Мой Python\__Видеокурсы Python
Traceback (most recent call last):
File "E:\__Moi_scripti\10_files in a directory\11.py", line 72, in <module>
main()
File "E:\__Moi_scripti\10_files in a directory\11.py", line 68, in main
list_dir(path_dir)
File "E:\__Moi_scripti\10_files in a directory\11.py", line 51, in list_dir
size = folder_scan(path.join(path_dir, item))
File "E:\__Moi_scripti\10_files in a directory\11.py", line 32, 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'
 
Опять ошибк:
Введите путь к сканируемой директории: E:\Мой Python\__Видеокурсы Python
Traceback (most recent call last):
File "E:\__Moi_scripti\10_files in a directory\11.py", line 72, in <module>
main()
File "E:\__Moi_scripti\10_files in a directory\11.py", line 68, in main
list_dir(path_dir)
File "E:\__Moi_scripti\10_files in a directory\11.py", line 51, in list_dir
size = folder_scan(path.join(path_dir, item))
File "E:\__Moi_scripti\10_files in a directory\11.py", line 32, 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 все ОК.
Путь path = 'E:\Мой Python\__Видеокурсы Python' нужно либо экранировать, либо поставить букву r, чтобы не обрабатывались обратные слеши.
то есть, path = r'E:\Мой Python\__Видеокурсы Python'. Это в том случае, если не передавать путь к папке с помощью input, а передавать в коде.
 
Кстати, да. Сразу же не обратил внимания. Уже привык к тому, что в Linux все ОК.
Путь path = 'E:\Мой Python\__Видеокурсы Python' нужно либо экранировать, либо поставить букву r, чтобы не обрабатывались обратные слеши.
то есть, path = r'E:\Мой Python\__Видеокурсы Python'. Это в том случае, если не передавать путь к папке с помощью input, а передавать в коде.
Спасибо. В моём исходном скрипте всё прввильно.
А в Вашем крайнем как применить это правило ?
 
Спасибо. В моём исходном скрипте всё прввильно.
А в Вашем крайнем как применить это правило ?

В моем никак. Если путь передается с помощью ввода, он автоматом попадает в path уже в нужном виде.
 
  • Нравится
Реакции: satfan
В моем никак. Если путь передается с помощью ввода, он автоматом попадает в path уже в нужном виде.
Спасибо. Вы отлично разбираетесь в питоне. Мне тоже Вам хочется быть полезным.
У меня хобби: Спутниковое ТВ и IPTV. Может вам нужны какие плейлисты m3u (Их читает : PotPlayer и VLC плеер)
 
Спасибо. Вы отлично разбираетесь в питоне. Мне тоже Вам хочется быть полезным.
У меня хобби: Спутниковое ТВ и IPTV. Может вам нужны какие плейлисты m3u (Их читает : PotPlayer и VLC плеер)
Ещё у меня проблемка.
Скрипт не срабатывает - 18_Поиск текста в файлах папки:
Python:
# -*- coding: utf-8 -*
import glob

dirname = r'C:\Users\Пк\Desktop\1_Изучаем Python'
line = 'Яндекс Лицей'

for name in glob.glob(f"{dirname}/**/*.m3u"):
    file_info = open(name, mode="r", encoding='utf-8').read()
    if line in fileinfo:
        print(f'Нашел нужную строчку в файле {name}')

# 1_Изучаем Python  - это моя папка
# C:\Users\Пк\Desktop\1_Изучаем Python - это путь к папке
# Яндекс Лицей - это ищем в файлах с расширением .m3u
 
Ещё у меня проблемка.
Скрипт не срабатывает - 18_Поиск текста в файлах папки:
Python:
# -*- coding: utf-8 -*
import glob

dirname = r'C:\Users\Пк\Desktop\1_Изучаем Python'
line = 'Яндекс Лицей'

for name in glob.glob(f"{dirname}/**/*.m3u"):
    file_info = open(name, mode="r", encoding='utf-8').read()
    if line in fileinfo:
        print(f'Нашел нужную строчку в файле {name}')

# 1_Изучаем Python  - это моя папка
# C:\Users\Пк\Desktop\1_Изучаем Python - это путь к папке
# Яндекс Лицей - это ищем в файлах с расширением .m3u

Скиньте мне один или несколько плейлистов в личку. Чуть позже посмотрю. Сейчас немного занят.
 
В личном сообщении на форуме. Запакуй несколько плейлистов в архив и прикрепи к сообщению во вложении. Плейлисты много места не занимают.
Папка путь: C:\Users\Пк\Desktop\2_Мои IPTV
Изменил расширение файла: m3u на txt --- иначе не смог загрузить сюда форум.
Моя почта: mav53@bk.ru
 

Вложения

Последнее редактирование:
Ещё у меня проблемка.
Скрипт не срабатывает - 18_Поиск текста в файлах папки:
Python:
# -*- coding: utf-8 -*
import glob

dirname = r'C:\Users\Пк\Desktop\1_Изучаем Python'
line = 'Яндекс Лицей'

for name in glob.glob(f"{dirname}/**/*.m3u"):
    file_info = open(name, mode="r", encoding='utf-8').read()
    if line in fileinfo:
        print(f'Нашел нужную строчку в файле {name}')

# 1_Изучаем Python  - это моя папка
# C:\Users\Пк\Desktop\1_Изучаем Python - это путь к папке
# Яндекс Лицей - это ищем в файлах с расширением .m3u

В данном коде присутствует не одна проблема, а сразу же несколько. Но об этом по порядку.
Итак, первая проблема, это с путями. Нужно запомнить, что в ОС Windows пути указываются через обратный слеш «\», а в ОС Linux через прямой «/».
В твоем коде ты указываешь путь к директории правильно, для ОС Windows, а вот уже путь к искомому файлу у тебя указан странно:

glob.glob(f"{dirname}/**/*.m3u")

Такое указание путей для Windows не подойдет.

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

01.png


Тогда директории, которые указаны стрелками будут просканированы. А вот сама директория «Курсы», в которой тоже могут находиться файлы и субдиректория «2_Изучаем Python», на картинке они выделены желтым, просканированы уже не будут. «Курсы» потому, что не попадают в путь твоего шаблона, а «2_Изучаем Python» просто находиться глубже, чем путь в шаблоне. Соответственно, будет выполнен только частичный поиск.

Для того, чтобы поиск был рекурсивным, то есть обходил все папки, нужно параметр glob — recursive, установить в значение True.

Теперь по поводу путей. Если ты хочешь, чтобы поиск файлов проходил вне зависимости от того, на какой ОС ты используешь данный код, то желательно и пути указывать не в «жестком» варианте, а с помощью модуля os.path.join или использовать Path из библиотеки pathlib. Разница будет не особо великая. Можно использовать как тот, так и другой вариант.

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

Но, если ты хочешь узнать, сколько вхождений в файле и в какой строке, лучше читать файл построчно в цикле. То есть, использовать не read(), а readlines().

Таким образом, если искать по всему файлу целиком, код будет таким:

Python:
import sys
from glob import glob
from pathlib import Path


def scan(path_dir: str, req: str):
    """
    Выполняется итерация по каталогам(рекурсивно) и ищется
    файл по заданному шаблону. После того как файл найден,
    он открывается для чтения.
    Выполняется условие, по поиску вхождения в текст файла.
    Если найден, печатаем где. Если нет, двигаемся дальше.

    :param path_dir: строка, путь к директории для сканирования.
    :param req: строка, слово или предложение, вхождение которого нужно найти.
    """
    try:
        print("")
        for name in glob(str(Path(path_dir) / "**" / "*.m3u"), recursive=True):
            with open(name, "r", encoding='utf-8') as resp:
                if req in resp.read():
                    print(f"Найдено совпадение в файле: {name}")
    except KeyboardInterrupt:
        print("\nGood By!")


def main():
    try:
        path_dir = input("Введите путь к директории для сканирования: ")
        req = input("Введите искомое слово или словосочетание: ")
        if not Path(path_dir).exists():
            print("Введенного пути не существует!")
            sys.exit(0)
        scan(path_dir=path_dir, req=req)
    except KeyboardInterrupt:
        print("\nGood By!")


if __name__ == "__main__":
    main()

Ну, а если искать сколько вхождений и в какой строке, то таким:

Python:
import sys
from glob import glob
from pathlib import Path


def scan(path_dir: str, req: str):
    """
    Выполняется итерация по каталогам(рекурсивно) и ищется
    файл по заданному шаблону. После того как файл найден,
    он открывается для чтения построчно. В цикле выполняется
    итерация по каждой строке, и проверяется вхождение
    искомого слова или предложения.
    Если вхождение найдено, добавляем строку с номером и текстом
    во временный список.
    После того как цикл завершиться, проверяем какова длина
    списка. Если она больше нуля, печатаем сколько совпадений
    и выводим каждую строку, в которой найдено совпадение.

    :param path_dir: строка, путь к директории для сканирования.
    :param req: строка, слово или предложение, вхождение которого нужно найти.
    """
    try:
        for name in glob(str(Path(path_dir) / "**" / "*.m3u"), recursive=True):
            lines = []
            with open(name, "r", encoding='utf-8') as resp:
                for n, line in enumerate(resp.readlines()):
                    if req in line:
                        lines.append(f"Строка: {n+1}, {line.strip()}")
                if len(lines) > 0:
                    print(f'\n[+] Совпадений: {len(lines)}, в файле: {name}')
                    for ln in lines:
                        print(f"   - {ln}")
    except KeyboardInterrupt:
        print("\nGood By!")


def main():
    try:
        path_dir = input("Введите путь к директории для сканирования: ")
        req = input("Введите искомое слово или словосочетание: ")
        if not Path(path_dir).exists():
            print("Введенного пути не существует!")
            sys.exit(0)
        scan(path_dir=path_dir, req=req)
    except KeyboardInterrupt:
        print("\nGood By!")


if __name__ == "__main__":
    main()

Ну и соответственно, в первом случае вывод будет такой:

02.png


Во-втором, такой:

03.png


Такие вот дела.
 
  • Нравится
Реакции: satfan
В данном коде присутствует не одна проблема, а сразу же несколько. Но об этом по порядку.
Итак, первая проблема, это с путями. Нужно запомнить, что в ОС Windows пути указываются через обратный слеш «\», а в ОС Linux через прямой «/».
В твоем коде ты указываешь путь к директории правильно, для ОС Windows, а вот уже путь к искомому файлу у тебя указан странно:

glob.glob(f"{dirname}/**/*.m3u")

Такое указание путей для Windows не подойдет.

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

Посмотреть вложение 64489

Тогда директории, которые указаны стрелками будут просканированы. А вот сама директория «Курсы», в которой тоже могут находиться файлы и субдиректория «2_Изучаем Python», на картинке они выделены желтым, просканированы уже не будут. «Курсы» потому, что не попадают в путь твоего шаблона, а «2_Изучаем Python» просто находиться глубже, чем путь в шаблоне. Соответственно, будет выполнен только частичный поиск.

Для того, чтобы поиск был рекурсивным, то есть обходил все папки, нужно параметр glob — recursive, установить в значение True.

Теперь по поводу путей. Если ты хочешь, чтобы поиск файлов проходил вне зависимости от того, на какой ОС ты используешь данный код, то желательно и пути указывать не в «жестком» варианте, а с помощью модуля os.path.join или использовать Path из библиотеки pathlib. Разница будет не особо великая. Можно использовать как тот, так и другой вариант.

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

Но, если ты хочешь узнать, сколько вхождений в файле и в какой строке, лучше читать файл построчно в цикле. То есть, использовать не read(), а readlines().

Таким образом, если искать по всему файлу целиком, код будет таким:

Python:
import sys
from glob import glob
from pathlib import Path


def scan(path_dir: str, req: str):
    """
    Выполняется итерация по каталогам(рекурсивно) и ищется
    файл по заданному шаблону. После того как файл найден,
    он открывается для чтения.
    Выполняется условие, по поиску вхождения в текст файла.
    Если найден, печатаем где. Если нет, двигаемся дальше.

    :param path_dir: строка, путь к директории для сканирования.
    :param req: строка, слово или предложение, вхождение которого нужно найти.
    """
    try:
        print("")
        for name in glob(str(Path(path_dir) / "**" / "*.m3u"), recursive=True):
            with open(name, "r", encoding='utf-8') as resp:
                if req in resp.read():
                    print(f"Найдено совпадение в файле: {name}")
    except KeyboardInterrupt:
        print("\nGood By!")


def main():
    try:
        path_dir = input("Введите путь к директории для сканирования: ")
        req = input("Введите искомое слово или словосочетание: ")
        if not Path(path_dir).exists():
            print("Введенного пути не существует!")
            sys.exit(0)
        scan(path_dir=path_dir, req=req)
    except KeyboardInterrupt:
        print("\nGood By!")


if __name__ == "__main__":
    main()

Ну, а если искать сколько вхождений и в какой строке, то таким:

Python:
import sys
from glob import glob
from pathlib import Path


def scan(path_dir: str, req: str):
    """
    Выполняется итерация по каталогам(рекурсивно) и ищется
    файл по заданному шаблону. После того как файл найден,
    он открывается для чтения построчно. В цикле выполняется
    итерация по каждой строке, и проверяется вхождение
    искомого слова или предложения.
    Если вхождение найдено, добавляем строку с номером и текстом
    во временный список.
    После того как цикл завершиться, проверяем какова длина
    списка. Если она больше нуля, печатаем сколько совпадений
    и выводим каждую строку, в которой найдено совпадение.

    :param path_dir: строка, путь к директории для сканирования.
    :param req: строка, слово или предложение, вхождение которого нужно найти.
    """
    try:
        for name in glob(str(Path(path_dir) / "**" / "*.m3u"), recursive=True):
            lines = []
            with open(name, "r", encoding='utf-8') as resp:
                for n, line in enumerate(resp.readlines()):
                    if req in line:
                        lines.append(f"Строка: {n+1}, {line.strip()}")
                if len(lines) > 0:
                    print(f'\n[+] Совпадений: {len(lines)}, в файле: {name}')
                    for ln in lines:
                        print(f"   - {ln}")
    except KeyboardInterrupt:
        print("\nGood By!")


def main():
    try:
        path_dir = input("Введите путь к директории для сканирования: ")
        req = input("Введите искомое слово или словосочетание: ")
        if not Path(path_dir).exists():
            print("Введенного пути не существует!")
            sys.exit(0)
        scan(path_dir=path_dir, req=req)
    except KeyboardInterrupt:
        print("\nGood By!")


if __name__ == "__main__":
    main()

Ну и соответственно, в первом случае вывод будет такой:

Посмотреть вложение 64490

Во-втором, такой:

Посмотреть вложение 64491

Такие вот дела.
Спасибо огромное. Вы мне целую лекция прочитали. Всё отлично работает. Впервые получаю такю грамотную поддержку.
 
Спасибо автору за работу. Всё работает из коробки, без проблем, а значит написано грамотно и с душой)
 
Мы в соцсетях:

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