Перейти к содержимому

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:
        ...
    raise

yt-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.

Похожие руководства