6 min di letturaIngegneria

Come abbreviare un URL in Python con la libreria requests

Abbrevia un URL in Python con poche righe di requests: un POST all'API di uno shortener, la lettura del link breve nella risposta, poi retry, idempotenza, bulk e async.

Marius Voß
DevRel · edge infra
Come abbreviare un URL in Python: un POST con requests che invia un URL di destinazione all'API di uno shortener e legge il link breve in risposta, con i passaggi di retry e idempotenza

Abbreviare un URL in Python è un'unica richiesta HTTP POST. Invii il tuo link lungo all'API di uno shortener con la libreria requests, passi la tua API key come token Bearer, e leggi il link breve dalla risposta JSON. Il tutto sta in circa cinque righe, e il resto di questa guida è ciò che aggiungere quando lo fai sul serio: gestione degli errori, idempotenza, bulk e async.

Questa è la versione per sviluppatori della guida generale su come abbreviare un URL: quella copre il flusso da dashboard e browser, questa è codice che puoi incollare in uno script. Gli endpoint e i nomi dei campi qui sotto usano l'API di Elido, ma la forma (un POST con una destinazione, la ricezione di un URL breve) è la stessa nella maggior parte degli shortener moderni, quindi gli schemi si trasferiscono facilmente.

Parti dalla panoramica sull'API gratuita per abbreviare URL se non hai ancora scelto un servizio: illustra la forma della richiesta, il modello di autenticazione e i limiti del piano gratuito che questo articolo assume.

Il modo più veloce: un solo POST con requests

Installa requests, metti la tua API key in una variabile d'ambiente, ed invia (POST) l'URL di destinazione all'endpoint dei link:

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

Tre cose rendono questo codice pronto per la produzione invece che un giocattolo. L'API key proviene dall'ambiente, mai da un valore letterale nel codice sorgente. timeout è impostato, così una connessione bloccata fallisce rapidamente invece di restare in attesa all'infinito. E raise_for_status() trasforma un 4xx o un 5xx in un'eccezione che puoi catturare, invece di lasciare che una chiamata fallita sembri un successo.

La risposta è JSON. Leggi short_url per ottenere il link finito; la maggior parte degli shortener restituisce anche un id da conservare se prevedi di modificare o far scadere il link in seguito.

Gestisci correttamente errori e rate limit

Il percorso felice è cinque righe. Uno script che gira senza supervisione deve sopravvivere a quelli infelici: un 5xx transitorio, un 429 quando vai troppo veloce, un'interruzione di rete. Avvolgi la chiamata in modo che ritenti sugli errori per cui vale la pena ritentare e rinunci su quelli che non avranno mai successo.

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")

Un 401 o un 403 non si risolveranno mai da soli ritentando, quindi finiscono in raise_for_status() e fermano lo script. Un 429 rispetta l'header Retry-After che l'API invia. Un 5xx rallenta in modo esponenziale. L'approfondimento su rate limit e idempotenza spiega perché ritentare alla cieca è il modo per trasformare un'interruzione in due.

Una chiamata Python con requests che invia un URL di destinazione e una Idempotency-Key all'endpoint dei link dello shortener con un token Bearer, l'API che restituisce HTTP 201 con uno short_url, e un ciclo di retry che rallenta sulle risposte 429 e 5xx

Invia una Idempotency-Key per evitare che i retry duplichino

Ecco il bug sottile nel ciclo di retry qui sopra. Se un POST ha successo sul server ma la risposta si perde nel tragitto di ritorno, il tuo codice vede un timeout, ritenta e crea un secondo link breve per lo stesso URL. Una idempotency key risolve questo problema: invia una chiave stabile e unica con ogni richiesta logica, e l'API restituisce il link originale in caso di ripetizione invece di crearne uno nuovo.

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,
)

Genera la chiave una volta per URL e riutilizzala in tutti i retry di quello stesso URL, non a ogni tentativo. Conservala insieme all'URL se il batch stesso potrebbe essere rieseguito. Questa è l'abitudine più importante quando passi dall'abbreviare un solo link all'abbreviarne un elenco, ed è incorporata nelle API e SDK proprio per questo motivo.

Abbreviare URL in blocco (bulk)

Con una shorten() sicura a disposizione, un batch è un ciclo, ma un ciclo che rispetta il rate limit e non perde la corrispondenza tra input e output:

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}")

Mantenere il risultato in un dict indicizzato sull'URL originale rende un fallimento parziale visibile e rieseguibile, non una lacuna silenziosa. Per qualche centinaio di link questa versione sequenziale va benissimo. Per migliaia, la latenza di andata e ritorno si accumula, ed è lì che l'async guadagna il suo posto.

Async su larga scala con httpx

Quando il batch è grande, il collo di bottiglia è l'attesa sulla rete, non Python. Un client async come httpx invia molte richieste in concorrenza mantenendo un tetto a quante sono in volo, così saturi la connessione senza far scattare il rate limit.

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))

Il Semaphore(10) è tutto il trucco: limita le richieste concorrenti a dieci, così vai veloce senza martellare l'API fino a farla rispondere con 429. Calibralo sul limite documentato del tuo piano. Per la forma completa della richiesta, i nomi dei campi e i limiti attuali, la documentazione API è il riferimento, e soluzioni per sviluppatori ha gli SDK se preferisci non scrivere il client da zero.

Abbreviazione in blocco sequenziale che invia una richiesta e attende ogni risposta a turno, confrontata con un approccio async che invia un numero limitato di richieste in concorrenza tramite un semaphore per abbreviare un batch di grandi dimensioni più velocemente

Quale approccio scegliere

Adatta lo strumento alla dimensione del compito. Un link in uno script: la chiamata requests di cinque righe. Un job affidabile che gira su una pianificazione: la versione con retry più idempotenza. Un batch una tantum di poche centinaia: il ciclo sequenziale. Decine di migliaia con una scadenza: httpx con un semaphore.

Qualunque tu scelga, tieni la chiave nell'ambiente, imposta un timeout, e invia una idempotency key su tutto ciò che potrebbe ritentare. Queste tre abitudini sono la differenza tra uno snippet che fa solo scena e uno script che puoi lasciare girare.

Leggi la serie cornerstone

Questo articolo fa parte del cluster engineering. Il punto di partenza è la guida all'API gratuita per abbreviare URL per la forma degli endpoint e l'autenticazione, poi l'articolo su rate limit e idempotenza per comportarsi bene sotto carico. Il riferimento live è la documentazione API.

Articoli correlati sul blog

Domande frequenti

Come abbrevio un URL in Python?

Invia una richiesta HTTP POST all'API di uno shortener di URL usando la libreria requests, passando il tuo URL lungo nel corpo JSON e la tua API key come token Bearer, poi leggi il link breve dalla risposta JSON. Sono circa cinque righe: costruisci gli header, invia (POST) l'URL di destinazione all'endpoint dei link, e stampa response.json()['short_url'].

Posso abbreviare un URL in Python senza una libreria esterna?

Sì. Il modulo urllib.request della libreria standard può inviare un POST JSON senza installare nulla, il che è comodo in un ambiente vincolato. È più verboso di requests: codifichi tu stesso il corpo, imposti manualmente gli header e leggi la risposta, ma non richiede alcun pip install.

Come abbrevio molti URL contemporaneamente in Python?

Scorri la lista ed effettua un POST per ciascun URL, ma invia una Idempotency-Key per ogni URL così un retry non crea mai un duplicato, e rispetta il rate limit dell'API rallentando (backoff) sugli HTTP 429. Per batch di grandi dimensioni, un client async come httpx con un semaphore limitato abbrevia migliaia di URL in concorrenza invece che uno alla volta.

Perché la mia richiesta Python allo shortener di URL restituisce 401?

Un 401 significa che l'API key manca o è errata nell'header Authorization. Verifica di inviare esattamente 'Authorization: Bearer YOUR_API_KEY', che la chiave non sia stata revocata, e che tu l'abbia caricata da una variabile d'ambiente invece di incollarne una scaduta. Un 403 invece significa che la chiave è valida ma non ha lo scope per quell'azione.

Prova Elido

Incolla un URL, ottieni un link breve

Senza registrazione. Il link vive 30 giorni. Iscriviti per conservarlo.

Gratis, nessuna registrazione richiesta · 2 al giorno

Prova Elido

Accorciatore di URL ospitato nell'UE: domini personalizzati, analisi approfondite e API aperta. Piano gratuito - senza carta di credito.

Tag
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

Continua a leggere