saveapi.org

Скачивание видео из соцсетей на Python

Один GET, один формат JSON, 1.5 кредита за успешный разбор. Здесь про то, что не видно из самого ответа: как приходят смешанные посты, когда выбор рендишена есть, а когда его нет, какие ошибки заслуживают второй попытки и как получить файл, не загружая его в память.

Обновлено

Первый вызов

Одного аргумента timeout уже достаточно, чтобы выбрать здесь httpx: подвисший источник должен уронить ваш запрос, а не держать его. Тридцать секунд — разумный потолок, собственный бюджет шлюза на источник ниже.

Python
import httpx

r = httpx.get(
    "https://api.saveapi.org/v1/download",
    params={"url": "https://www.instagram.com/reel/Cx1a2b3c/"},
    headers={"Authorization": "Bearer sk_live_xxxx"},
    timeout=30,
)
data = r.json()

Читайте medias[], а не угадывайте

В ответе всегда один и тот же массив — было в посте одно видео или десять картинок. В смешанных постах типы сохраняются в порядке поста: карусель из двух фото и клипа даст две записи image и одну video. Ветвитесь по type — и один и тот же код закроет все платформы.

Python
for media in data["medias"]:
    if media["type"] == "video":
        print("video:", media["url"])
    elif media["type"] == "image":
        print("image:", media["url"])
    elif media["type"] == "audio":
        print("track:", media["url"])

Где есть выбор, выберите рендишен

Некоторые источники публикуют несколько рендишенов, и в таких ответах есть formats[] — в каждом готовая ссылка и точный размер в мегабайтах. Ни второго запроса, ни параметра формата: отфильтруйте список на своей стороне и возьмите самый крупный файл, который влезает в ваш потолок.

Python
best = None
for fmt in data.get("formats", []):
    if fmt["size_mb"] <= 45 and (best is None or fmt["size_mb"] > best["size_mb"]):
        best = fmt

url = best["url"] if best else data["medias"][0]["url"]

Повторяйте только то, что лечится повтором

Таймаут или ошибка источника стоят ещё одной попытки. Закрытый пост, удалённый пост или неподдерживаемый домен — нет: второй вызов вернёт тот же ответ, а на тарифицируемых ошибках потратит второй кредит. Разложите коды на два набора и принимайте решение по набору, а не по тексту сообщения.

Python
RETRY = {"UPSTREAM_TIMEOUT", "UPSTREAM_ERROR"}

def download(link: str, attempts: int = 2) -> dict:
    for attempt in range(attempts):
        data = httpx.get(API, params={"url": link}, headers=HEAD, timeout=30).json()
        if data["success"]:
            return data
        if data["error"]["code"] not in RETRY:
            raise RuntimeError(data["error"]["message"])   # qayta urinish yordam bermaydi
    raise RuntimeError("source unavailable")

Следите за заголовками лимита

В каждом ответе есть минутный лимит ключа и его остаток, а на платных ключах ещё и остаток кредитов в пакете. Читайте их из ответа, который у вас уже есть, вместо опроса баланса по таймеру.

Python
print(r.headers["X-RateLimit-Limit"])       # kalitning daqiqadagi chegarasi
print(r.headers["X-RateLimit-Remaining"])   # shu daqiqada qolgani
print(r.headers.get("X-Credits-Remaining")) # paketdagi qolgan kredit

Берите файл потоком, а не в память

Полученный адрес указывает прямо на CDN источника, то есть байты не проходят через API. Пишите его на диск потоком: 4K-ролик в памяти — самый быстрый способ получить убитый контейнер.

Python
with httpx.stream("GET", data["medias"][0]["url"]) as source:
    with open("reel.mp4", "wb") as out:
        for chunk in source.iter_bytes():
            out.write(chunk)

Вопросы перед началом

Есть ли официальный SDK для Python?
Нет, и одному эндпоинту он не особо нужен. Весь клиент — это вызов httpx с заголовком Bearer; обёртка в восемь строк даст вам типизацию и повторы без зависимости, которую надо обновлять.
Проходит ли файл через API?
Нет. Вы получаете адрес, указывающий на CDN источника, и забираете байты напрямую оттуда — поэтому скорость скачивания зависит от этого CDN, а не от нас.
Как узнать, во сколько обошёлся вызов?
В ответе есть credits.spent и credits.remaining. Успешный разбор — 1.5 кредита (история Instagram 5, Rutube 3). Закрытый или отсутствующий пост стоит 0.1, а сбой на нашей стороне не стоит ничего.

Платформы, использованные здесь

Другие руководства

Запустите это руководство со своим ключом

Ключ за минуту. 1 000 бесплатных кредитов, без карты и без срока.

Получить ключ и попробоватьПолный справочник API