6 min de leituraEngenharia

Como Encurtar um URL em Python com a Biblioteca requests

Encurte um URL em Python com algumas linhas de requests: POST para uma API de encurtador, leia o link curto de volta, depois adicione retries, idempotência, lote e async.

Marius Voß
DevRel · edge infra
Como encurtar um URL em Python: um POST com requests a enviar um URL de destino para uma API de encurtador e a ler de volta um link curto, com passos de retry e idempotência

Encurtar um URL em Python é um único HTTP POST. Envia o seu link longo para a API de um encurtador com a biblioteca requests, passa a sua chave de API como um Bearer token, e lê o link curto a partir da resposta JSON. A coisa toda cabe em cerca de cinco linhas, e o resto deste guia é o que acrescentar quando o está a fazer a sério: tratamento de erros, idempotência, lote, e async.

Esta é a versão para programadores do guia geral de como encurtar um URL - esse cobre o painel e o fluxo do navegador, este é código que pode colar num script. Os endpoints e os nomes de campos abaixo usam a API da Elido, mas a forma (fazer POST de um destino, receber de volta um URL curto) é a mesma na maioria dos encurtadores modernos, por isso os padrões são transportáveis.

Comece pela visão geral da API gratuita de encurtador de URL se ainda não escolheu um serviço - ela expõe a forma do pedido, o modelo de autenticação, e os limites do nível gratuito que este artigo assume.

A Forma Mais Rápida: Um POST com requests

Instale o requests, coloque a sua chave de API numa variável de ambiente, e faça POST do URL de destino para o endpoint de 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

Três coisas tornam isto pronto para produção em vez de um mero brinquedo. A chave de API vem do ambiente, nunca um literal no código-fonte. O timeout está definido, para que uma ligação suspensa falhe rapidamente em vez de bloquear para sempre. E o raise_for_status() transforma um 4xx ou 5xx numa exceção que pode apanhar, em vez de deixar uma chamada falhada parecer um sucesso.

A resposta é JSON. Leia short_url para obter o link finalizado; a maioria dos encurtadores também devolve um id que deve guardar se planear editar ou expirar o link mais tarde.

Trate Erros e Limites de Taxa Corretamente

O caminho feliz são cinco linhas. Um script que corre sem supervisão precisa de sobreviver aos infelizes: um 5xx transitório, um 429 quando está a ir depressa demais, uma falha de rede. Envolva a chamada para que repita nos erros que vale a pena repetir e desista nos que nunca vão resolver-se sozinhos.

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

Um 401 ou 403 nunca se resolve sozinho por repetição, por isso esses caem para raise_for_status() e param o script. Um 429 respeita o cabeçalho Retry-After que a API envia. Um 5xx recua exponencialmente. O aprofundamento sobre limites de taxa e idempotência explica por que repetir às cegas é como se transforma uma indisponibilidade em duas.

Uma chamada requests em Python a enviar um URL de destino e uma Idempotency-Key para o endpoint de links do encurtador com um Bearer token, a API a devolver HTTP 201 com um short_url, e um ciclo de retry a recuar perante respostas 429 e 5xx

Envie uma Idempotency-Key para as Repetições Não Duplicarem

Aqui está o bug subtil no ciclo de retry acima. Se um POST for bem-sucedido no servidor mas a resposta se perder no caminho de volta, o seu código vê um timeout, repete, e cria um segundo link curto para o mesmo URL. Uma chave de idempotência resolve isso: envie uma chave estável e única com cada pedido lógico, e a API devolve o link original numa repetição em vez de criar um novo.

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

Gere a chave uma vez por URL e reutilize-a entre repetições desse mesmo URL - não por tentativa. Guarde-a junto ao URL se o próprio lote puder ser executado de novo. Este é o hábito mais importante quando passa de encurtar um link para encurtar uma lista, e está incorporado nos API e SDKs exatamente por este motivo.

Encurte URLs em Lote

Com um shorten() seguro em mãos, um lote é um ciclo - mas um ciclo que respeita o limite de taxa e não perde o mapeamento entre entrada e saída:

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

Manter o resultado num dicionário indexado pelo URL original significa que uma falha parcial é visível e pode ser reexecutada, não uma lacuna silenciosa. Para algumas centenas de links esta versão sequencial é suficiente. Para milhares, a latência de ida e volta acumula-se, e é aí que o async ganha o seu lugar.

Async em Escala com httpx

Quando o lote é grande, o estrangulamento é a espera pela rede, não o Python. Um cliente assíncrono como o httpx envia muitos pedidos em simultâneo mantendo um teto sobre quantos estão em curso, para que sature a ligação sem disparar o limite de taxa.

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

O Semaphore(10) é o truque todo: limita os pedidos concorrentes a dez para que avance depressa sem sobrecarregar a API com 429s. Ajuste-o ao limite documentado do plano. Para a forma completa do pedido, os nomes dos campos, e os limites atuais, os documentos da API são a referência, e solutions for developers tem os SDKs caso prefira não construir o cliente à mão.

Encurtamento em lote sequencial a enviar um pedido e a esperar por cada resposta por sua vez, comparado com uma abordagem assíncrona a enviar um número limitado de pedidos em simultâneo através de um semaphore para encurtar um lote maior mais depressa

Qual Abordagem Escolher

Ajuste a ferramenta ao tamanho da tarefa. Um link num script: a chamada requests de cinco linhas. Uma tarefa fiável que corre num horário: a versão com retry e idempotência. Um lote pontual de algumas centenas: o ciclo sequencial. Dezenas de milhares com prazo apertado: httpx com um semaphore.

Seja qual for a sua escolha, mantenha a chave no ambiente, defina um timeout, e envie uma chave de idempotência em qualquer coisa que possa repetir. Esses três hábitos são a diferença entre um excerto que serve para demonstrar e um script que pode deixar a correr.

Leia a Série de Artigos-Pilar

Este artigo integra-se no agrupamento de engenharia. O ponto de partida é o guia da API gratuita de encurtador de URL para a forma do endpoint e a autenticação, depois o artigo de limites de taxa e idempotência para se comportar bem sob carga. A referência viva são os documentos da API.

Relacionado no Blog

Perguntas frequentes

Como encurto um URL em Python?

Envie um HTTP POST para a API de um encurtador de URL com a biblioteca requests, passando o seu URL longo no corpo JSON e a sua chave de API como um Bearer token, depois leia o link curto a partir da resposta JSON. São cerca de cinco linhas: construa os cabeçalhos, envie o URL de destino para o endpoint de links, e imprima response.json()['short_url'].

Posso encurtar um URL em Python sem uma biblioteca externa?

Sim. O urllib.request da biblioteca padrão consegue fazer POST de JSON sem instalar nada, o que é útil num ambiente restrito. É mais verboso do que requests - codifica o corpo você mesmo, define os cabeçalhos manualmente, e lê a resposta - mas não precisa de nenhum pip install.

Como encurto vários URLs de uma vez em Python?

Percorra a lista e faça POST de cada um, mas envie uma Idempotency-Key por URL para que uma repetição nunca crie um duplicado, e respeite o limite de taxa da API recuando perante um HTTP 429. Para lotes grandes, um cliente assíncrono como httpx com um semaphore limitado encurta milhares de URLs em simultâneo em vez de um de cada vez.

Por que motivo o meu pedido de encurtador de URL em Python está a devolver 401?

Um 401 significa que a chave de API está em falta ou incorreta no cabeçalho Authorization. Confirme que está a enviar 'Authorization: Bearer YOUR_API_KEY' exatamente, que a chave não foi revogada, e que a carregou a partir de uma variável de ambiente em vez de colar uma expirada. Um 403, em vez disso, significa que a chave é válida mas não tem âmbito para essa ação.

Experimente Elido

Cole uma URL, obtenha um link curto

Sem cadastro. O link vive 30 dias. Cadastre-se para mantê-lo para sempre.

Grátis, sem necessidade de registo · 2 por dia

Experimente o Elido

Encurtador de URL hospedado na UE: domínios personalizados, análises profundas e API aberta. Plano gratuito - sem cartão de crédito.

Tags
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

Continuar lendo