5 хв читанняІнженерія

Як скоротити URL у Python за допомогою бібліотеки requests

Скоротіть URL у Python кількома рядками requests: POST-запит до API скорочувача, зчитування короткого посилання у відповіді, а далі - повторні спроби, ідемпотентність, масова обробка та асинхронність.

Marius Voß
DevRel · edge infra
Як скоротити URL у Python: POST-запит requests надсилає цільову адресу до API скорочувача та зчитує коротке посилання у відповіді, з кроками повторної спроби та ідемпотентності

Скоротити URL у Python - це один HTTP POST-запит. Ви надсилаєте своє довге посилання до API скорочувача за допомогою бібліотеки requests, передаєте свій API-ключ як Bearer-токен і зчитуєте коротке посилання з JSON-відповіді. Усе це вміщується приблизно у п'ять рядків, а решта цього гайду - те, що варто додати, коли ви робите це по-справжньому: обробку помилок, ідемпотентність, масову обробку та асинхронність.

Це версія для розробників загального гайду про те, як скоротити URL - той матеріал охоплює роботу через дашборд і браузер, а цей - код, який можна вставити у скрипт. Ендпоінти та назви полів нижче стосуються API Elido, але сама форма (POST з адресою, у відповідь - коротке посилання) однакова у більшості сучасних скорочувачів, тож ці патерни переносяться без змін.

Почніть з огляду безкоштовного API скорочувача URL, якщо ви ще не обрали сервіс - там викладено форму запиту, модель авторизації та ліміти безкоштовного тарифу, які ця стаття вважає відомими.

Найшвидший спосіб: один POST через requests

Встановіть requests, покладіть свій API-ключ у змінну середовища й надішліть POST з цільовою URL-адресою на ендпоінт links:

import os
import requests

resp = requests.post(
    "https://api.elido.app/v1/links",
    headers={"Authorization": f"Bearer {os.environ['ELIDO_API_KEY']}"},
    json={"destination_url": "https://example.com/a-very-long-path?with=params"},
    timeout=10,
)
resp.raise_for_status()
print(resp.json()["short_url"])   # -> https://s.elido.me/ab12cd

Три речі роблять це продакшн-якісним рішенням, а не іграшкою. API-ключ береться зі змінної середовища, а не з буквального рядка в коді. Встановлено timeout, тож зависле з'єднання швидко провалиться замість того, щоб блокувати виконання назавжди. І raise_for_status() перетворює 4xx чи 5xx на виняток, який можна перехопити, а не дозволяє провальному виклику виглядати як успішний.

Відповідь - це JSON. Зчитайте short_url для готового посилання; більшість скорочувачів також повертають id, який варто зберегти, якщо плануєте пізніше редагувати чи скасовувати посилання.

Правильно обробляйте помилки та ліміти швидкості

Щасливий шлях - п'ять рядків. Скрипт, що працює без нагляду, має пережити нещасливі: тимчасовий 5xx, 429, коли ви йдете надто швидко, мережевий збій. Обгорніть виклик так, щоб він повторював спробу на помилках, які варто повторювати, і здавався на тих, які ніколи не виправляться.

import os
import time
import requests

def shorten(destination: str, *, retries: int = 3) -> str:
    headers = {"Authorization": f"Bearer {os.environ['ELIDO_API_KEY']}"}
    for attempt in range(retries):
        resp = requests.post(
            "https://api.elido.app/v1/links",
            headers=headers,
            json={"destination_url": destination},
            timeout=10,
        )
        if resp.status_code == 429:
            time.sleep(int(resp.headers.get("Retry-After", 2)))
            continue
        if resp.status_code >= 500:
            time.sleep(2 ** attempt)   # exponential backoff
            continue
        resp.raise_for_status()        # 4xx other than 429 -> raise
        return resp.json()["short_url"]
    raise RuntimeError(f"shorten failed after {retries} attempts")

401 чи 403 ніколи не виправляться самі повторними спробами, тож вони провалюються до raise_for_status() і зупиняють скрипт. 429 враховує заголовок Retry-After, який надсилає API. 5xx відступає назад експоненційно. Матеріал про ліміти швидкості та ідемпотентність детально пояснює, чому сліпі повторні спроби перетворюють один збій на два.

Виклик Python requests, що надсилає цільову URL-адресу та Idempotency-Key на ендпоінт links скорочувача з Bearer-токеном, API повертає HTTP 201 з short_url, а цикл повторних спроб відступає назад при відповідях 429 і 5xx

Надсилайте Idempotency-Key, щоб повторні спроби не дублювали

Ось тонка помилка в циклі повторних спроб вище. Якщо POST-запит успішно виконався на сервері, але відповідь загубилася на шляху назад, ваш код бачить таймаут, повторює спробу і створює другий короткий лінк для тієї самої URL-адреси. Ключ ідемпотентності виправляє це: надсилайте стабільний, унікальний ключ з кожним логічним запитом, і API повертає оригінальне посилання при повторі замість того, щоб карбувати нове.

import uuid

key = str(uuid.uuid4())   # one key per URL you want shortened once
resp = requests.post(
    "https://api.elido.app/v1/links",
    headers={
        "Authorization": f"Bearer {os.environ['ELIDO_API_KEY']}",
        "Idempotency-Key": key,
    },
    json={"destination_url": "https://example.com/launch"},
    timeout=10,
)

Генеруйте ключ один раз на кожну URL-адресу й повторно використовуйте його при повторних спробах для тієї самої адреси - не для кожної окремої спроби. Зберігайте його поруч із URL-адресою, якщо саму партію можуть перезапустити. Це найважливіша звичка, коли ви переходите від скорочення одного посилання до скорочення списку, і саме вона закладена в API та SDK з цієї причини.

Скорочення URL-адрес у масовому режимі

Маючи безпечну функцію shorten(), партія - це просто цикл, але цикл, що враховує ліміт швидкості й не втрачає зв'язок між вхідними та вихідними даними:

urls = [
    "https://example.com/spring-sale",
    "https://example.com/newsletter",
    "https://example.com/docs/getting-started",
]

results = {}
for url in urls:
    try:
        results[url] = shorten(url)
    except Exception as err:      # log and keep going; one bad URL should not sink the batch
        results[url] = f"ERROR: {err}"

for original, short in results.items():
    print(f"{short}\t{original}")

Зберігання результату у словнику, ключем якого є оригінальна URL-адреса, означає, що часткова невдача видима й піддається повторному запуску, а не є мовчазною прогалиною. Для кількох сотень посилань ця послідовна версія цілком підходить. Для тисяч затримка кожного запиту-відповіді накопичується, і саме тут асинхронність себе виправдовує.

Асинхронність у масштабі з httpx

Коли партія велика, вузьким місцем стає очікування мережі, а не Python. Асинхронний клієнт на кшталт httpx надсилає багато запитів одночасно, водночас утримуючи стелю на кількість запитів у польоті, тож ви насичуєте з'єднання, не порушуючи ліміт швидкості.

import asyncio
import os
import httpx

async def shorten_all(urls: list[str], concurrency: int = 10) -> dict[str, str]:
    headers = {"Authorization": f"Bearer {os.environ['ELIDO_API_KEY']}"}
    limit = asyncio.Semaphore(concurrency)
    out: dict[str, str] = {}

    async with httpx.AsyncClient(timeout=10) as client:
        async def one(url: str) -> None:
            async with limit:
                r = await client.post(
                    "https://api.elido.app/v1/links",
                    headers=headers,
                    json={"destination_url": url},
                )
                out[url] = r.json()["short_url"]

        await asyncio.gather(*(one(u) for u in urls))
    return out

# asyncio.run(shorten_all(my_urls))

Semaphore(10) - це весь трюк: він обмежує кількість одночасних запитів десятьма, тож ви прискорюєтеся, не закидуючи API 429-ми відповідями. Налаштуйте це значення під задокументовані ліміти вашого тарифу. Повну форму запиту, назви полів і актуальні ліміти дивіться в документації API, а рішеннях для розробників знайдете SDK, якщо не хочете писати клієнт вручну.

Послідовне масове скорочення, що надсилає один запит і чекає на кожну відповідь по черзі, порівняно з асинхронним підходом, який надсилає обмежену кількість запитів одночасно через semaphore, щоб скоротити великий пакет швидше

Який підхід обрати

Підбирайте інструмент під розмір завдання. Одне посилання в скрипті: п'ятирядковий виклик requests. Надійне завдання, що виконується за розкладом: версія з повторними спробами й ідемпотентністю. Разова партія з кількох сотень: послідовний цикл. Десятки тисяч у дедлайн: httpx із semaphore.

Хоч би що ви обрали, тримайте ключ у змінній середовища, встановлюйте таймаут і надсилайте ключ ідемпотентності на все, що може повторюватися. Ці три звички - різниця між сніпетом для демо і скриптом, який можна залишити працювати.

Читайте серію опорних матеріалів

Цей матеріал входить у блок про інженерію. Відправна точка - гайд про безкоштовний API скорочувача URL щодо форми ендпоінтів і авторизації, а далі - матеріал про ліміти швидкості та ідемпотентність щодо коректної поведінки під навантаженням. Живий довідник - документація API.

Пов'язане в блозі

Поширені запитання

Як скоротити URL у Python?

Надішліть HTTP POST-запит до API скорочувача URL за допомогою бібліотеки requests, передавши вашу довгу URL-адресу в тілі JSON, а ваш API-ключ - як Bearer-токен, а потім зчитайте коротке посилання з JSON-відповіді. Це приблизно п'ять рядків: зберіть заголовки, надішліть POST з цільовою адресою на ендпоінт links і виведіть response.json()['short_url'].

Чи можна скоротити URL у Python без зовнішньої бібліотеки?

Так. urllib.request зі стандартної бібліотеки може надіслати POST з JSON без встановлення чогось додатково, що зручно в обмеженому середовищі. Це багатослівніше за requests - тіло запиту доводиться кодувати самостійно, заголовки встановлювати вручну, а відповідь зчитувати вручну - але pip install не потрібен.

Як скоротити багато URL-адрес одразу у Python?

Пройдіться циклом по списку й надішліть POST для кожної адреси, але додавайте Idempotency-Key на кожну URL-адресу, щоб повторна спроба ніколи не створювала дублікат, і дотримуйтеся ліміту швидкості API, відступаючи назад при HTTP 429. Для великих партій асинхронний клієнт на кшталт httpx з обмеженим semaphore скорочує тисячі URL-адрес одночасно, а не по одній.

Чому мій запит на скорочення URL у Python повертає 401?

401 означає, що API-ключ відсутній або неправильний у заголовку Authorization. Перевірте, що ви надсилаєте саме 'Authorization: Bearer YOUR_API_KEY', що ключ не відкликано, і що ви завантажили його зі змінної середовища, а не вставили прострочений. 403, натомість, означає, що ключ дійсний, але не має прав (scope) для цієї дії.

Спробуйте Elido

Вставте URL - отримайте коротке посилання

Без реєстрації. Посилання живе 30 днів. Зареєструйтесь, щоб зберегти назавжди.

Безкоштовно, без реєстрації · 2 на день

Спробуйте Elido

URL-скорочувач із хостингом у ЄС: власні домени, глибока аналітика, відкритий API. Безкоштовний тариф - без кредитної картки.

Теги
how to shorten a url in python
python url shortener
shorten url python requests
url shortener api python
python requests post json
bulk shorten urls python

Читати далі