Files
2026-08-03 21:44:03 +03:00

316 lines
13 KiB
Markdown

# 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 $?
```