fix(downloaders): Deno runtime, свежий yt-dlp, честный health-check, смоук-тест
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 2m36s

Возрастные YouTube-видео не скачивались: с cookies yt-dlp отбрасывает клиент
android и остаётся web, которому нужно решить n-challenge, а в образе стоял
Node 20 при требуемом минимуме 22 ("JS runtimes: node-20.19.2 (unsupported)").
Ставим Deno — рекомендованный yt-dlp рантайм и один статический бинарник.

Instagram падал с "empty media response" одинаково с cookies и без них —
дело было не в сессии, а в устаревшем экстракторе: слой pip был закеширован
на yt-dlp 2026.06.09. Поднимаем нижнюю границу до 2026.7.4.

/cookies/check помечал проблемой с cookies ЛЮБОЙ сбой, из-за чего на поломку
JS-рантайма прилетел алерт про протухшие cookies и увёл разбор не туда.
Теперь ответ содержит status: ok | cookies_invalid | extraction_failed |
no_cookies, и админ-бот шлёт разные сообщения. Разбор ответа в bot.py
сохраняет совместимость со старым форматом без поля status.

Добавлен smoke_test.py — гоняет реальные ссылки (включая обе регрессии выше)
через запущенные сервисы и печатает таблицу. Запускать после каждой правки.

Схема получения cookies переведена с крона на разовый ручной экспорт:
cookies-cron/ -> cookies/, удалены скрипты с анти-паттерном
`--cookies-from-browser BROWSER --cookies FILE`, который wiki yt-dlp прямо
запрещает и который сам ломал YouTube-сессию ротацией. Ключевые cookies живут
около года, поэтому обновление по расписанию не нужно — триггером служит алерт
health-check. deliver_cookies.sh только доставляет файлы по scp.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
vrubelroman 2026-07-26 00:31:16 +00:00
parent 5092d882e5
commit d597a5e1c5
19 changed files with 373 additions and 733 deletions

View file

@ -1,51 +1,7 @@
# Инструкция по получению cookies для Instagram
## Вариант 1: Использование расширения браузера (рекомендуется)
Актуальная инструкция живёт в одном месте — [`cookies/README.md`](../cookies/README.md).
1. Установите расширение для экспорта cookies:
- Chrome: [Get cookies.txt LOCALLY](https://chrome.google.com/webstore/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc)
- Firefox: [cookies.txt](https://addons.mozilla.org/en-US/firefox/addon/cookies-txt/)
2. Откройте Instagram и войдите в свой аккаунт: https://www.instagram.com
3. Кликните на расширение и выберите "Export cookies.txt"
4. Сохраните файл как `instagram_cookies.txt` в корень проекта (там же, где docker-compose.yml)
## Вариант 2: Ручной экспорт через DevTools
1. Откройте Instagram в браузере и войдите: https://www.instagram.com
2. Откройте DevTools (F12) → вкладка Application/Storage → Cookies → https://www.instagram.com
3. Скопируйте нужные cookies в формате Netscape:
```
# Netscape HTTP Cookie File
.instagram.com TRUE / FALSE 1735689600 sessionid YOUR_SESSION_ID
.instagram.com TRUE / FALSE 1735689600 csrftoken YOUR_CSRF_TOKEN
```
4. Сохраните в файл `instagram_cookies.txt`
## Вариант 3: Использование yt-dlp для экспорта
```bash
# Экспорт cookies из браузера Chrome
yt-dlp --cookies-from-browser chrome --cookies instagram_cookies.txt https://www.instagram.com
# Или из Firefox
yt-dlp --cookies-from-browser firefox --cookies instagram_cookies.txt https://www.instagram.com
```
## Важно!
- Файл должен называться `instagram_cookies.txt`
- Разместите его в корне проекта (рядом с docker-compose.yml)
- Cookies имеют срок действия - возможно, потребуется обновлять их периодически
- Не коммитьте файл в git (он уже добавлен в .gitignore)
После добавления файла перезапустите контейнер:
```bash
docker compose restart bot
```
Там описано: когда обновлять (по алерту `🍪⚠️ ПРОАКТИВНАЯ ПРОВЕРКА COOKIES` из админ-бота, а не по расписанию), как правильно экспортировать куки расширением для `cookies.txt` и как доставить их на прод через `deliver_cookies.sh`.
**Не используйте `yt-dlp --cookies-from-browser ... --cookies file.txt`** — этот способ раньше был описан здесь, но wiki yt-dlp его прямо запрещает: он ломает сессию, и куки протухают («cookies have likely been rotated in the browser as a security measure»).

View file

@ -11,27 +11,9 @@
### 1. Настройка cookies
Перед запуском сервиса необходимо получить cookies Instagram. Есть несколько способов:
Перед запуском сервиса нужно положить `instagram_cookies.txt` в папку `instagram-downloader/`.
#### Способ 1: Через скрипт (рекомендуется)
```bash
cd instagram-downloader
./get_instagram_cookies.sh
```
Скрипт попросит выбрать браузер и автоматически извлечет cookies.
#### Способ 2: Обновление существующих cookies
```bash
cd instagram-downloader
./update_instagram_cookies.sh
```
#### Способ 3: Вручную
См. подробные инструкции в `INSTAGRAM_COOKIES_INSTRUCTIONS.md`
Куки экспортируются вручную из браузера расширением для `cookies.txt` — пошаговая процедура в [`cookies/README.md`](../cookies/README.md). Скрипты автоматического экспорта через `yt-dlp --cookies-from-browser` убраны: этот способ ломает сессию, куки после него быстро протухают.
### 2. Запуск сервиса
@ -71,6 +53,21 @@ curl http://localhost:5556/health
}
```
### POST /cookies/check
Проверка, работают ли cookies прямо сейчас: делает лёгкий `extract_info` (без скачивания) по публичному тестовому ролику. Используется фоновым мониторингом в `bot.py`.
**Ответ:**
```json
{
"cookies_present": true,
"cookies_valid": false,
"detail": "текст ошибки от yt-dlp"
}
```
`cookies_valid: null` означает, что файл с куками не найден. Тестовый URL переопределяется переменной `INSTAGRAM_COOKIE_TEST_URL`.
### POST /download/stream
Скачивание видео с Instagram.
@ -93,40 +90,22 @@ curl http://localhost:5556/health
## Обновление cookies
Cookies Instagram имеют ограниченный срок действия. Рекомендуется обновлять их раз в несколько недель.
Ключевые куки Instagram живут около года, поэтому обновлять их по расписанию не нужно. Триггер на обновление — алерт `🍪⚠️ ПРОАКТИВНАЯ ПРОВЕРКА COOKIES` из админ-бота (за состоянием следит фоновая проверка в `bot.py`, которая раз в 30 минут дёргает `POST /cookies/check`).
Для обновления:
```bash
cd instagram-downloader
./update_instagram_cookies.sh
```
После обновления перезапустите сервис:
```bash
docker compose restart instagram-downloader
```
Процедура экспорта и доставки — в [`cookies/README.md`](../cookies/README.md). Перезапускать сервис после обновления не нужно: `yt-dlp` читает файл с диска при каждом запросе.
## Troubleshooting
### Сервис не может скачать видео
1. Проверьте наличие файла `instagram_cookies.txt` в папке `instagram-downloader/`
2. Проверьте срок действия cookies (они могут истечь)
3. Обновите cookies через скрипт `update_instagram_cookies.sh`
1. Проверьте состояние куков: `curl -s -X POST localhost:5556/cookies/check`
2. Проверьте наличие файла `instagram_cookies.txt` в папке `instagram-downloader/` и что в нём есть `sessionid`
3. Если `cookies_valid: false` — обновите куки по инструкции в [`cookies/README.md`](../cookies/README.md)
4. Проверьте логи: `docker compose logs instagram-downloader`
### Cookies истекли
### Cookies есть и не истекли по сроку, но всё равно `login required`
Если видите ошибку "Instagram cookies истекли", выполните:
```bash
cd instagram-downloader
./update_instagram_cookies.sh
```
Затем перезапустите сервис.
Instagram умеет блокировать сессию на своей стороне — тогда в логах видно `Main webpage is locked behind the login page` при формально валидном `sessionid`. Лечится только повторным экспортом куков. Провоцирует такую блокировку активная автоматическая работа с аккаунтом (например, постоянно открытая вкладка Instagram, непрерывно ходящая в сеть), поэтому после экспорта браузер лучше закрывать.
## Структура файлов
@ -136,9 +115,7 @@ instagram-downloader/
├── Dockerfile # Образ Docker
├── docker-compose.yml # Конфигурация для отдельного запуска
├── requirements.txt # Python зависимости
├── get_instagram_cookies.sh # Скрипт для получения cookies
├── update_instagram_cookies.sh # Скрипт для обновления cookies
├── INSTAGRAM_COOKIES_INSTRUCTIONS.md # Подробные инструкции по cookies
├── INSTAGRAM_COOKIES_INSTRUCTIONS.md # Отсылка к cookies/README.md
└── README.md # Этот файл
```

View file

@ -206,12 +206,24 @@ INSTAGRAM_COOKIE_TEST_URL = os.getenv(
)
# Маркеры, по которым сбой однозначно относится к cookies/авторизации.
# Всё остальное считаем поломкой извлечения (версия yt-dlp, изменения на стороне
# Instagram) и про cookies ничего не утверждаем — иначе мониторинг уводит
# диагностику не туда: например, "empty media response" воспроизводится и без cookies.
INSTAGRAM_COOKIE_INVALID_MARKERS = (
'login required',
'locked behind the login page',
'rate-limit reached',
)
@app.route('/cookies/check', methods=['POST'])
def cookies_check():
"""Проверяет, рабочие ли сейчас Instagram cookies (для проактивного мониторинга)."""
cookies_file = Path(os.getenv('INSTAGRAM_COOKIES_FILE', 'instagram_cookies.txt'))
if not cookies_file.exists():
return jsonify({'cookies_present': False, 'cookies_valid': None,
'status': 'no_cookies',
'detail': 'cookies file missing'}), 200
ydl_opts = {
@ -224,10 +236,16 @@ def cookies_check():
try:
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.extract_info(INSTAGRAM_COOKIE_TEST_URL, download=False)
return jsonify({'cookies_present': True, 'cookies_valid': True, 'detail': ''}), 200
return jsonify({'cookies_present': True, 'cookies_valid': True,
'status': 'ok', 'detail': ''}), 200
except Exception as e:
return jsonify({'cookies_present': True, 'cookies_valid': False,
'detail': str(e)[-500:]}), 200
err = str(e)
detail = err[-500:]
if any(marker in err.lower() for marker in INSTAGRAM_COOKIE_INVALID_MARKERS):
return jsonify({'cookies_present': True, 'cookies_valid': False,
'status': 'cookies_invalid', 'detail': detail}), 200
return jsonify({'cookies_present': True, 'cookies_valid': None,
'status': 'extraction_failed', 'detail': detail}), 200
@app.route('/download/stream', methods=['POST'])

View file

@ -1,79 +0,0 @@
#!/bin/bash
# Скрипт для получения cookies Instagram через yt-dlp из Firefox
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
COOKIES_FILE="$SCRIPT_DIR/instagram_cookies.txt"
BROWSER="firefox"
# Если запущено с sudo, используем HOME реального пользователя
if [ -n "$SUDO_USER" ]; then
REAL_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6)
export HOME="$REAL_HOME"
echo "⚠️ Обнаружен sudo, использую домашнюю директорию пользователя: $HOME"
fi
echo "Получение cookies Instagram из Firefox..."
echo "Файл cookies будет сохранен в: $COOKIES_FILE"
echo "Получаю cookies..."
# Используем таймаут для предотвращения зависания
timeout 15 yt-dlp \
--cookies-from-browser "$BROWSER" \
--cookies "$COOKIES_FILE" \
--no-download \
--quiet \
"https://www.instagram.com" 2>&1 | head -20
EXIT_CODE=$?
if [ $EXIT_CODE -eq 124 ]; then
echo ""
echo "⚠️ Процесс получения cookies превысил таймаут (15 сек)"
echo " Проверяю, был ли создан файл cookies..."
elif [ $EXIT_CODE -ne 0 ] && [ $EXIT_CODE -ne 124 ]; then
echo ""
echo "⚠️ Процесс завершился с кодом $EXIT_CODE"
echo " Проверяю, был ли создан файл cookies..."
fi
if [ -f "$COOKIES_FILE" ]; then
# Проверяем, что файл содержит данные (не только заголовки)
COOKIE_LINES=$(grep -v '^#' "$COOKIES_FILE" | grep -v '^$' | wc -l)
if [ "$COOKIE_LINES" -gt 0 ]; then
echo ""
echo "✅ Cookies успешно сохранены в $COOKIES_FILE"
echo " Найдено строк с cookies: $COOKIE_LINES"
echo ""
echo "Если cookies смонтированы с хоста (см. docker-compose.yml/docker-compose.prod.yml),"
echo "перезапуск не нужен — yt-dlp читает файл заново при каждом запросе."
else
echo ""
echo "❌ Ошибка: файл cookies создан, но не содержит данных"
rm -f "$COOKIES_FILE"
exit 1
fi
else
echo ""
echo "❌ Ошибка: файл cookies не был создан"
echo ""
echo "Возможные причины:"
echo "1. Firefox не найден или недоступен"
echo "2. Проблемы с правами доступа к файлу cookies браузера"
echo "3. Cookies не найдены в браузере"
echo ""
echo "Проверка:"
if [ -n "$SUDO_USER" ]; then
REAL_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6)
FIREFOX_DIR="$REAL_HOME/.mozilla/firefox"
else
FIREFOX_DIR="$HOME/.mozilla/firefox"
fi
if [ -d "$FIREFOX_DIR" ]; then
echo " ✓ Директория Firefox найдена: $FIREFOX_DIR"
else
echo " ✗ Директория Firefox не найдена: $FIREFOX_DIR"
echo " Попробуйте запустить скрипт БЕЗ sudo: ./get_instagram_cookies.sh"
fi
exit 1
fi

View file

@ -1,5 +1,5 @@
Flask==3.0.0
flask-cors==4.0.0
yt-dlp>=2024.12.13
yt-dlp>=2026.7.4
gunicorn==21.2.0

View file

@ -1,47 +0,0 @@
#!/bin/bash
# Обновление cookies Instagram через браузер
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
COOKIES_FILE="$SCRIPT_DIR/instagram_cookies.txt"
echo "Обновление cookies Instagram..."
echo ""
echo "Выберите браузер:"
echo "1) Chrome"
echo "2) Firefox"
echo "3) Edge"
echo "4) Opera"
read -p "Введите номер (1-4): " browser
case "$browser" in
1) BROWSER="chrome" ;;
2) BROWSER="firefox" ;;
3) BROWSER="edge" ;;
4) BROWSER="opera" ;;
*)
echo "Неверный выбор"
exit 1
;;
esac
echo ""
echo "ВАЖНО: Перед обновлением cookies убедитесь, что вы авторизованы в Instagram в выбранном браузере!"
echo ""
read -p "Нажмите Enter для продолжения или Ctrl+C для отмены..."
echo "Обновляю cookies из $BROWSER..."
echo "Файл cookies будет сохранен в: $COOKIES_FILE"
yt-dlp --cookies-from-browser "$BROWSER" --cookies "$COOKIES_FILE" --no-download https://www.instagram.com 2>&1 | head -10
if [ -f "$COOKIES_FILE" ]; then
echo ""
echo "✅ Cookies успешно обновлены в $COOKIES_FILE"
echo ""
echo "Для применения изменений перезапустите instagram-downloader сервис:"
echo " cd $(cd "$SCRIPT_DIR/.." && pwd) && docker compose restart instagram-downloader"
else
echo ""
echo "❌ Ошибка: файл cookies не был создан"
exit 1
fi