yt-dlp из Python: встроенный API, опции, хуки и запуск на сервере
Автор Команда TunelioОпубликовано
yt-dlp — это пакет Python, поэтому его можно импортировать, а не вызывать через оболочку: yt_dlp.YoutubeDL(opts).extract_info(url) отдаёт метаданные и, при желании, файл. В этом руководстве: словарь опций, форматы без скачивания, хуки прогресса, обработка ошибок, как не заморозить async-сервер, нужный Docker-образ — и сколько это стоит в CPU на видео, когда вы запускаете это для других людей.
Установка
python3 -m pip install -U "yt-dlp[default]"
# плюс ffmpeg на машине (слияние / аудио) и JS-рантайм, например Deno (челленджи YouTube)Минимальная загрузка
import yt_dlp
opts = {
"format": "bv*+ba/b", # лучшее видео + лучшее аудио, слитые (нужен ffmpeg)
"merge_output_format": "mp4",
"outtmpl": "downloads/%(title)s.%(ext)s",
"noplaylist": True,
"quiet": True,
}
with yt_dlp.YoutubeDL(opts) as ydl:
ydl.download(["https://youtu.be/dQw4w9WgXcQ"])У каждого флага командной строки есть ключ словаря; соответствие описано в yt_dlp/YoutubeDL.py (docstring класса) и yt_dlp/options.py. Если сомневаетесь, запустите CLI с нужными флагами и --print-json и скопируйте имена опций.
Метаданные и форматы без скачивания
import yt_dlp
with yt_dlp.YoutubeDL({"quiet": True, "skip_download": True}) as ydl:
info = ydl.extract_info("https://youtu.be/dQw4w9WgXcQ", download=False)
print(info["title"], info["duration"])
for f in info["formats"]:
if f.get("vcodec") != "none" and f.get("acodec") != "none": # прогрессивные (видео+аудио в одном файле)
print(f["format_id"], f.get("height"), f.get("ext"), f.get("filesize_approx"))
best_audio = max((f for f in info["formats"] if f.get("vcodec") == "none"), key=lambda f: f.get("abr") or 0)
print("audio:", best_audio["format_id"], best_audio["url"][:60], "...")extract_info возвращает тот же JSON, что CLI печатает с -j: форматы с прямыми ссылками googlevideo, превью, субтитры, главы. Ссылки подписаны под IP и клиент, которые их запросили, и истекают примерно через шесть часов — отдавайте их ffmpeg или загрузчику на той же машине, а не пользователям.
Хуки прогресса и логирование
def on_progress(d):
if d["status"] == "downloading":
print(d.get("_percent_str"), d.get("_speed_str"), end="\r")
elif d["status"] == "finished":
print("\nготово, постобработка:", d["filename"])
class QuietLogger:
def debug(self, msg): pass
def warning(self, msg): print("WARN", msg)
def error(self, msg): print("ERR", msg)
opts = {"progress_hooks": [on_progress], "logger": QuietLogger(), "format": "bv*+ba/b"}Обработка ошибок
from yt_dlp.utils import DownloadError, ExtractorError
try:
with yt_dlp.YoutubeDL(opts) as ydl:
info = ydl.extract_info(url, download=False)
except DownloadError as e:
msg = str(e)
if "Sign in to confirm" in msg: # бот-стена → IP/cookies/PO-токен, см. руководство по бот-проверке
...
elif "HTTP Error 403" in msg: # см. руководство по 403
...
elif "Private video" in msg or "Video unavailable" in msg:
...
raiseyt-dlp оборачивает почти всё в DownloadError с исходным сообщением внутри; сопоставляйте по тексту. Для плейлистов задайте "ignoreerrors": True, чтобы один мёртвый элемент не прерывал запуск (такие элементы возвращаются как None).
Cookies, прокси и другие production-опции
opts = {
"cookiefile": "/secrets/cookies.txt", # формат Netscape; только запасной аккаунт
"proxy": "socks5h://user:pass@exit.example:1080",
"source_address": "0.0.0.0", # принудительный IPv4 (флаг -4)
"extractor_args": {"youtube": {"player_client": ["tv", "web"]}},
"sleep_interval_requests": 1,
"retries": 3,
"socket_timeout": 30,
}Правила те же, что в командной строке: cookies из приватного окна запасного аккаунта, резидентные выходы вместо дата-центровых, IPv4, если не уверены в чистоте IPv6-диапазона. Подробности — в руководствах по cookies, прокси и 403.
Внутри async-сервера (FastAPI, aiohttp, Telegram-боты)
import asyncio
from fastapi import FastAPI
app = FastAPI()
_sem = asyncio.Semaphore(4) # ограничить параллельные извлечения — каждое ≈ 1 с CPU
def _extract(url: str) -> dict:
with yt_dlp.YoutubeDL({"quiet": True, "skip_download": True}) as ydl:
return ydl.extract_info(url, download=False)
@app.get("/info")
async def info(url: str):
async with _sem:
data = await asyncio.to_thread(_extract, url) # никогда не вызывайте yt-dlp в event loop
return {"title": data["title"], "duration": data["duration"]}yt-dlp синхронный и тяжёлый по CPU: на наших серверах одно извлечение YouTube стоит около 1,2–1,3 секунды CPU, в основном на выполнение JavaScript плеера. Вызванный напрямую в async-обработчике, он замораживает все остальные запросы; выполняйте его в пуле потоков или процессов и ограничивайте параллельность, иначе сервис ляжет при нескольких запросах в секунду.
Docker
FROM python:3.12-slim
RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg curl unzip ca-certificates \
&& rm -rf /var/lib/apt/lists/* \
&& curl -fsSL https://deno.land/install.sh | DENO_INSTALL=/usr/local sh
RUN pip install --no-cache-dir "yt-dlp[default]" fastapi uvicorn
COPY app.py /app/app.py
USER 1000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--app-dir", "/app"]Три зависимости времени выполнения: yt-dlp, ffmpeg и JavaScript-рантайм. Пересобирайте образ хотя бы ежемесячно (см. руководство по обновлению) — контейнер с трёхмесячным yt-dlp будет падать на большинстве видео. Запускайте от непривилегированного пользователя с собственным каталогом кеша.
Стоимость и надёжность в масштабе
- CPU: ~1,2–1,3 с на извлечение на современном ядре, на каждый запрос (JS плеера выполняется заново). Десять запросов в секунду — это дюжина ядер только под yt-dlp.
- Репутация IP: сервер, извлекающий сотни видео в день с одного адреса, упрётся в бот-стену; в итоге вы запускаете прокси и сервисы PO-токенов.
- Поломки: каждое изменение YouTube — редеплой; почему, объясняет руководство по обновлению.
- Трафик: медиа, которое вы ретранслируете пользователям, проходит через ваш сервер дважды.
Именно поэтому существуют хостинговые API. Tunelio (наш сервис) выполняет извлечение и возвращает ссылку на скачивание на каждый запрос, так что ваш Python остаётся одним HTTP-вызовом, а в контейнере нет ни ffmpeg, ни рантайма, ни прокси. Для скрипта на ноутбуке встроенный API выше — правильный инструмент. Новые аккаунты получают 100 бесплатных кредитов.
Источники
- README yt-dlp — раздел «Embedding yt-dlp» и справочник опций в yt_dlp/YoutubeDL.py.
- Wiki yt-dlp — Extractor args (player_client) и руководство по PO-токенам.
- Наши измерения (парк извлечения Tunelio, 2026): CPU на извлечение, доля бот-стен на дата-центровых IP.
Частые вопросы
Вызывать CLI yt-dlp через subprocess или импортировать?
Импорт чище и даёт структурированные данные; subprocess изолирует падения и память. В любом случае выполняйте вне event loop и ограничивайте параллельность.
Можно получить прямую ссылку на видео без скачивания?
Да: extract_info(url, download=False) возвращает каждый формат с его URL. Они привязаны к вашему IP/клиенту и истекают примерно через шесть часов, так что используйте их на сервере сразу.
Почему приложение FastAPI зависает, пока работает yt-dlp?
yt-dlp блокирующий и тяжёлый по CPU. Оберните его в asyncio.to_thread (или пул процессов) и ограничьте параллельные извлечения семафором.
Какие ключи опций соответствуют каким флагам?
Большинство флагов становятся ключами в snake_case (--merge-output-format → merge_output_format). Авторитетный список — docstring класса YoutubeDL в yt_dlp/YoutubeDL.py.