# Yandex Disk Protected Folder Downloader Скрипт для рекурсивного скачивания большой публичной папки Яндекс Диска, защищённой паролем. Он получает список объектов через веб-API Яндекс Диска, сохраняет структуру каталогов и скачивает каждый файл отдельно через `wget`. ## Возможности - чтение всех пользовательских настроек из файла `.env` рядом со скриптом; - использование готового cookie `passToken` либо автоматическое получение токена по паролю через Chromium и Playwright; - постоянный профиль Chromium с повторным использованием cookies; - рекурсивный обход вложенных каталогов; - двухэтапная работа: сначала полный список, затем скачивание; - сохранение полного списка в `yandex-download-manifest.jsonl`; - исключение служебного элемента текущего каталога из расчёта `offset`; - сохранение исходной структуры каталогов; - продолжение прерванной загрузки через `wget --continue`; - автоматический пропуск файлов, размер которых уже совпадает с серверным; - проверка итогового размера каждого скачанного файла; - паузы между запросами прямых ссылок и периодические длительные паузы; - повторные HTTP-запросы при временных ошибках `429`, `500`, `502`, `503` и `504`; ## Как работает двухэтапная загрузка ### Этап 1 — получение полного списка Скрипт быстро обходит страницы каталога: ```text 0 → 40 → 80 → 120 → ... → completed=True ``` На этом этапе: - `download-url` не запрашивается; - `wget` не запускается; - файлы не скачиваются; - для каждого файла сохраняются серверные метаданные и относительный каталог; - вложенные каталоги также обходятся полностью; - служебное описание текущей папки не учитывается при увеличении `offset`. После успешного завершения обхода создаётся: ```text OUTPUT_DIR/yandex-download-manifest.jsonl ``` Manifest записывается атомарно: сначала создаётся временный файл `.tmp`, после чего он заменяет предыдущий manifest. Пример одной строки: ```json {"relative_directory":".","item":{"name":"FL_9J1915234BP_1654_40013103010803_V001_S.frf","type":"file","size":793122,"path":"public_hash:/ODIS/Flashdaten/Brand-A/FL_9J1915234BP_1654_40013103010803_V001_S.frf"}} ``` ### Этап 2 — скачивание файлов После получения `completed=True` скрипт начинает скачивание по полностью собранному плану: 1. проверяет наличие локального файла; 2. сравнивает его размер с серверным; 3. пропускает полностью скачанный файл; 4. для отсутствующего или неполного файла получает временный `download-url`; 5. запускает `wget --continue`; 6. проверяет размер после завершения. Текущая версия сохраняет manifest для контроля и последующего анализа, но при новом запуске всё равно заново выполняет этап 1 и получает актуальный список с Яндекса. ## Требования Поддерживается Linux с Python 3. Необходимые пакеты: ```bash sudo apt install -y python3 python3-requests wget ``` Playwright и Chromium для автоматического получения `passToken`: ```bash python3 -m pip install --user playwright python3 -m playwright install chromium ``` ## Настройка Все основные параметры находятся в файле sample.env ### `TARGET_URL` Полная публичная ссылка на нужный каталог: ```dotenv TARGET_URL="https://disk.yandex.ru/d/cWn4RgSdFcSZXw/ODIS/Flashdaten/Brand-A" ``` Для вложенного каталога необходимо указывать полный путь после идентификатора публичной ссылки. ### `PUBLIC_LINK_PASSWORD` Пароль публичной ссылки: ```dotenv PUBLIC_LINK_PASSWORD="реальный пароль публичной ссылки" ``` Если оставить значение пустым, программа попытается использовать cookies из постоянного профиля Chromium. Если действующего `passToken` нет и запуск производится из терминала, пароль будет запрошен без отображения введённых символов. ### OUTPUT_DIR Каталог, куда будут сохранены файлы: ```python OUTPUT_DIR = "/mnt/hdd1-4TB/ODIS/Flashdaten/Brand-V" ``` Каталог будет создан автоматически, если у пользователя есть права на его создание. Внутри него также могут появиться: ```text yandex-download-manifest.jsonl yandex-captcha-url.txt ``` ### Настройки Playwright ```dotenv PLAYWRIGHT_PROFILE_DIR="./playwright-profile" PLAYWRIGHT_HEADLESS="false" PLAYWRIGHT_TIMEOUT="120" ``` - `PLAYWRIGHT_PROFILE_DIR` — постоянный профиль Chromium; - `PLAYWRIGHT_HEADLESS=false` — показывает окно браузера и позволяет вручную пройти CAPTCHA; - `PLAYWRIGHT_HEADLESS=true` — запускает Chromium без окна; при CAPTCHA программа остановится; - `PLAYWRIGHT_TIMEOUT` — максимальное время ожидания получения `passToken`, в секундах. Относительный путь профиля вычисляется относительно каталога, где находится `.env` и скрипт. ### Задержки между страницами ```dotenv PAGE_DELAY_MIN="0.5" PAGE_DELAY_MAX="1.5" ``` Эти задержки используются только на этапе 1 между запросами `fetch-list`. После перехода на двухэтапную схему страницы перечисляются подряд и больше не разделяются длительным скачиванием файлов. Поэтому состояние пагинации живёт значительно меньше времени. ### Задержки между файлами ```dotenv FILE_DELAY_MIN="1.5" FILE_DELAY_MAX="3.5" ``` Случайная пауза перед каждым запросом `download-url` на этапе 2. ### Длительные паузы ```dotenv COOLDOWN_EVERY_FILES="100" COOLDOWN_MIN="60" COOLDOWN_MAX="120" ``` После каждых 100 запросов `download-url` программа дополнительно ждёт от 60 до 120 секунд. Чтобы отключить длительные паузы: ```dotenv COOLDOWN_EVERY_FILES="0" COOLDOWN_MIN="0" COOLDOWN_MAX="0" ``` ### HTTP-таймауты и повторы ```dotenv CONNECT_TIMEOUT="30" READ_TIMEOUT="120" HTTP_RETRIES="5" ``` - `CONNECT_TIMEOUT` — таймаут установки соединения; - `READ_TIMEOUT` — таймаут чтения ответа API; - `HTTP_RETRIES` — число автоматических повторов временных ошибок. ## Пример вывода ### Получение токена ```text [AUTH] Открываю защищённую ссылку в Chromium... [AUTH] Используется passToken из сохранённого профиля. ``` Либо: ```text [AUTH] Поле пароля найдено (input[type="password"]). [AUTH] Ввожу пароль публичной ссылки... [AUTH] Пароль введён; отправляю форму... [AUTH] passToken получен. ``` ### Этап 1 ```text [INFO] Открываю исходную ссылку... [INFO] Папка открыта. [INFO] Корневой hash: public_hash:/ODIS/Flashdaten/Brand-A [INFO] Этап 1/2: получаю полный список файлов... [DIR ] /mnt/hdd1-4TB/ODIS/Flashdaten/Brand-A [PAGE] получено API=41, дочерних=40, служебных=1, offset=0->40, completed=False [WAIT] 0.8 с перед следующей страницей [PAGE] получено API=10, дочерних=9, служебных=1, offset=12080->12089, completed=True [INFO] Полный список получен: 12089 файлов. [INFO] Manifest сохранён: /mnt/hdd1-4TB/ODIS/Flashdaten/Brand-A/yandex-download-manifest.jsonl ``` ### Этап 2 ```text [INFO] Этап 2/2: начинаю скачивание по сохранённому списку... [SKIP] /mnt/hdd1-4TB/ODIS/Flashdaten/Brand-A/FL_0DL300014F_3123_idMA_sw.frf (2.14 MiB) [GET ] /mnt/hdd1-4TB/ODIS/Flashdaten/Brand-A/FL_9J1915234BP_1654_40013103010803_V001_S.frf (774.53 KiB) [WAIT] 2.6 с перед запросом ссылки 2026-08-03 URL:https://s684vla.storage.yandex.net/rdisk/... [793122/793122] ``` ### Итоговая статистика ```text ================ РЕЗУЛЬТАТ ================ Найдено файлов: 12089 Скачано сейчас: 529 Уже было скачано: 11560 Ошибок: 0 Известный общий объём: 49.04 GiB =========================================== ``` ## CAPTCHA При большом количестве запросов Яндекс может вернуть CAPTCHA. Скрипт распознаёт ответ: ```json { "type": "captcha" } ``` После этого загрузка немедленно прекращается. Будет выведена ссылка: ```text ЯНДЕКС ПОТРЕБОВАЛ CAPTCHA Открой ссылку ниже в обычном браузере с того же внешнего IP: https://disk.yandex.ru/showcaptcha?... ``` Ссылка также сохраняется в файл: ```text /mnt/hdd1-4TB/ODIS/Flashdaten/Brand-V/yandex-captcha-url.txt ``` ## Ошибка `409 Conflict` Внутренний API `/public/api/fetch-list` может вернуть `409 Conflict`, если серверное состояние пагинации изменилось или стало недействительным. Двухэтапная схема значительно снижает вероятность такой ошибки, потому что все страницы каталога запрашиваются до начала скачивания файлов. `409` не следует считать признаком конца каталога. Если он всё же возник на этапе 1: 1. не удаляйте уже скачанные файлы; 2. повторно запустите программу; 3. скрипт получит новый `sk` и начнёт перечисление заново; 4. на этапе 2 существующие файлы будут пропущены. Полным считается только обход, завершившийся серверным признаком: ```text completed=True ``` ## Коды завершения Скрипт использует следующие коды выхода: | Код | Значение | | ----: | ------------------------------------------ | | `0` | загрузка завершена без ошибок | | `1` | загрузка завершена с ошибками | | `2` | ошибка конфигурации или отсутствует `wget` | | `75` | Яндекс потребовал CAPTCHA | | `130` | процесс остановлен через `Ctrl+C` | Проверить код после завершения: ```bash echo $? ```