DIMA.CLOUD
RUEN

Как мы подняли свой сервис транскрибации YouTube-видео

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

Задача

Мне нужен был собственный микросервис для вытаскивания транскриптов из YouTube-видео. Не очередной внешний сайт, не ручной процесс, не «скачай файл и передай агенту», а свой стабильный endpoint.

У сервиса было две цели:

  • веб-интерфейс для человека: вставить ссылку на YouTube и получить текст;
  • API для агентов вроде OpenClaw и Hermes, чтобы они могли сами получать транскрипт по ссылке на видео.

Финальная точка входа:

https://app.dima.cloud/transcribe/

API для агентов:

GET https://app.dima.cloud/transcribe/api/transcript?url=YOUTUBE_URL
GET https://app.dima.cloud/transcribe/api/transcript.txt?url=YOUTUBE_URL

Первый прототип

На старте было два файла: Python-скрипт и React UI. Python-скрипт уже умел пробовать два способа получения субтитров:

  • youtube-transcript-api
  • yt-dlp

Но UI был непригоден для self-hosted сценария: он не вызывал наш backend, а пытался идти в сторонний API прямо из браузера. Поэтому мы заменили это на более простую и управляемую архитектуру:

FastAPI backend
простая HTML-страница
systemd
nginx route

Что было развернуто

На сервере сервис живет здесь:

/opt/youtube-transcribe-service

Запускается через systemd:

youtube-transcribe.service

Внутри слушает локально:

127.0.0.1:8092

Nginx проксирует публичный путь:

/transcribe/ -> 127.0.0.1:8092

JSON endpoint возвращает структуру такого вида:

{
  "success": true,
  "transcript": "...",
  "language": "en",
  "word_count": 3215,
  "char_count": 17150,
  "video_id": "z02Y-1OvWSM",
  "method": "ytdlp"
}

Plain text endpoint возвращает только текст транскрипта, что удобнее для агентов.

Первая проблема: nginx и внутренний порт

Сначала путь /transcribe редиректил на неправильный URL:

https://app.dima.cloud:2443/transcribe/

Причина была в том, что HTTPS server block внутри nginx слушал внутренний порт 127.0.0.1:2443, а перед ним стоял внешний роутинг. Nginx сформировал absolute redirect с внутренним портом.

Решение: убрать редирект и сделать так, чтобы оба пути отдавали страницу:

/transcribe
/transcribe/

Главная проблема: YouTube не любит VPS IP

Самая большая сложность оказалась не в FastAPI, nginx или UI. Главная проблема была в egress IP.

Сервер выходит в интернет с VPS/cloud IP. YouTube часто режет такие адреса. Типовые ошибки:

Sign in to confirm you're not a bot
YouTube is blocking requests from your IP
HTTP Error 429: Too Many Requests

Это проявлялось и в youtube-transcript-api, и в yt-dlp. То есть проблема была не в одной конкретной библиотеке.

Попытка 1: cookies из браузера

Первый бесплатный обход: экспортировать YouTube cookies из браузера и положить их на сервер:

/opt/youtube-transcribe-service/cookies.txt

Сервис умел использовать этот файл через переменную:

YOUTUBE_TRANSCRIPT_COOKIE_PATH=/opt/youtube-transcribe-service/cookies.txt

Метод временно помогал, но быстро проявился его главный недостаток: cookies ротируются и инвалидируются. yt-dlp прямо сообщил:

The provided YouTube account cookies are no longer valid.
They have likely been rotated in the browser as a security measure.

Вывод: cookies — полезный временный workaround, но не production-архитектура. Их нужно регулярно обновлять, особенно после смены VPN, IP или подозрительной активности.

Попытка 2: reverse SOCKS через Mac

Вторая бесплатная попытка: пустить серверный трафик через мой Mac.

сервер -> SSH reverse SOCKS -> Mac -> интернет

Когда Mac выходил в интернет напрямую, YouTube видел домашний IP, а не VPS. Это работало.

Но если Mac был подключен к VPN на этом же сервере, получалась петля:

сервер -> Mac -> VPN -> тот же сервер -> YouTube

Вывод: технически красиво, но хрупко. Mac должен быть включен, туннель должен жить, VPN может ломать маршрут. Для сервиса, которым пользуются агенты, это не подходит.

Попытка 3: Webshare static proxies

Дальше мы проверили тестовый список Webshare из 10 static proxies.

Каждый прокси прогонялся через реальные тесты:

  • отвечает ли IP-check;
  • видит ли YouTube список субтитров;
  • может ли скачать реальный файл субтитров.

Результат: ни один из 10 не подошел. Часть получала 429 Too Many Requests, часть не могла скачать usable formats, часть таймаутилась.

Интересная деталь: некоторые прокси могли показать список субтитров через --list-subs, но падали именно на скачивании timedtext-файлов.

Вывод: static/datacenter proxies для этой задачи почти бесполезны.

Рабочее решение: DataImpulse Residential Proxy

Рабочим решением оказался residential proxy от DataImpulse.

Проверка через сервер:

curl --proxy "http://user:pass@gw.dataimpulse.com:823/" https://api.ipify.org

Внешний IP стал residential, не VPS. После этого yt-dlp смог скачать реальные .vtt субтитры:

  • контрольное видео z02Y-1OvWSM;
  • проблемное видео GEzbesM_X3U.

Production-сервис был переключен на:

YOUTUBE_PROXY_URL=http://user:pass@gw.dataimpulse.com:823/

После перезапуска:

systemctl restart youtube-transcribe.service

публичные endpoints начали работать стабильно.

Упрощение интерфейса

Изначально в UI были настройки языка и метода:

language: ru / en / auto
method: api / ytdlp / auto

Но на практике они только мешали. Почти всегда пользователю нужен лучший доступный транскрипт. Поэтому интерфейс был упрощен:

YouTube URL -> Получить -> Транскрипт или ошибка

Backend сохранил обратную совместимость для агентов, но в UI осталась только ссылка.

Итоговая архитектура

Пользователь или агент
        ↓
https://app.dima.cloud/transcribe/
        ↓
FastAPI на VPS
        ↓
yt-dlp / youtube-transcript-api
        ↓
DataImpulse Residential Proxy
        ↓
YouTube timedtext/subtitles
        ↓
текст транскрипта

Что я понял

  • Сделать UI и API для транскриптов просто.
  • Стабильно получать YouTube subtitles с VPS — сложно.
  • Главная проблема не библиотека, а IP-репутация.
  • Cookies помогают, но быстро становятся операционным долгом.
  • Домашний туннель работает, но не подходит для автономного сервиса.
  • Static proxies не решают задачу.
  • Residential proxy решает.

Что стоит добавить дальше

  • Кэширование транскриптов по video_id, чтобы не тратить proxy-трафик повторно.
  • Rate limiting для агентов.
  • API key, потому что endpoint публичный.
  • Отдельный proxy healthcheck.
  • Очередь задач для длинных видео.
  • Более точные ошибки: no subtitles, proxy timeout, YouTube 429, invalid URL.

Финальный вывод

Сервис транскрибации YouTube-видео — это не столько про Python-библиотеку, сколько про правильный выход к YouTube.

Без residential/mobile proxy решение будет ломаться на cookies, VPN, 429 и bot checks. С residential proxy оно становится рабочим микросервисом, который можно дать как людям, так и агентам.