Buscando dados da internet em poucas linhas
A maioria dos programas Python reais eventualmente precisa conversar com algo pela rede — uma API REST, um serviço de meteorologia, um endpoint do GitHub, um download. A biblioteca padrão consegue fazer isso (via urllib), mas a ferramenta de facto no mundo Python é uma biblioteca de terceiros chamada requests. A API é tão mais amigável que vale o único pip install.
pip install requests
Se você ainda não está num ambiente virtual, monte um primeiro — mantém essa instalação escopada ao seu projeto.
Primeiro GET
Três linhas: chame get, confira o status code, olhe o corpo. response.text é o corpo da resposta como string. Truncado aqui porque o JSON completo é longo.
Status codes que vale reconhecer no nível de folha de cola:
- 200 — OK, tudo deu certo.
- 201 — Criado (resposta comum de POST).
- 301 / 302 — redirecionamentos;
requestssegue automaticamente por padrão. - 400 — requisição ruim; algo no que você enviou estava errado.
- 401 / 403 — não autenticado / não autorizado.
- 404 — o recurso não existe.
- 429 — rate limited; desacelere.
- 500 — erro do servidor.
Parseando uma resposta JSON
Quando o endpoint retorna JSON, chame .json() na resposta — parseia o corpo e te entrega um dict (ou list):
Por baixo dos panos, .json() é o mesmo que json.loads(response.text) — só um atalho para o caso comum.
Enviando parâmetros de query
Não grude ?chave=valor&... na URL na mão. Passe um dict para params=:
O requests lida com URL encoding para você — espaços, caracteres especiais, Unicode tudo funciona com segurança.
A URL real que saiu está disponível em response.url, útil para debug.
Requisições POST com corpos JSON
Para enviar dados — criar recursos, submeter formulários, chamar endpoints que modificam — use requests.post:
import requests
payload = {
"title": "Docs update",
"body": "Added HTTP requests page.",
"labels": ["docs"],
}
response = requests.post(
"https://api.example.com/issues",
json=payload,
headers={"Authorization": "Bearer YOUR_TOKEN"},
)
print(response.status_code)
print(response.json())
O argumento json= faz duas coisas: serializa payload para JSON e define Content-Type: application/json. Você poderia fazer na mão com data=json.dumps(payload) e um header explícito, mas json= é o atalho idiomático.
Para dados form-encoded (o tipo que um formulário HTML clássico envia), use data=:
requests.post("https://example.com/login", data={"user": "rosa", "password": "..."})
Headers
Passe qualquer header customizado como dict:
import requests
response = requests.get(
"https://api.example.com/profile",
headers={
"Authorization": "Bearer abc123",
"User-Agent": "my-tool/1.0",
},
)
A maioria das APIs quer um header Authorization para autenticação. O esquema exato (Bearer, Basic, Token) está na documentação delas.
Timeouts não são opcionais
Por padrão, requests espera para sempre por uma resposta. Num programa real, isso transforma um servidor instável num script travado. Sempre passe um timeout:
import requests
try:
response = requests.get("https://api.example.com/slow", timeout=5)
except requests.Timeout:
print("Server took too long.")
O número é em segundos. timeout=5 significa "desista se não tivermos resposta em 5 segundos". Você pode passar uma tupla (connect_timeout, read_timeout) para mais controle.
Tratamento de erros
Dois tipos de problema podem acontecer:
- Erros de nível HTTP (4xx, 5xx) — o servidor respondeu, mas a resposta é de erro.
response.status_codete conta. - Problemas de nível de rede — timeouts, falhas de DNS, hosts inalcançáveis. Esses disparam exceções.
O padrão idiomático combina os dois:
raise_for_status() é no-op para respostas 2xx e dispara exceção nos outros casos. RequestException é a classe base de todo erro que requests dispara — um único catch para "qualquer coisa que deu errado conversando com esse endpoint".
Baixando um arquivo
Para downloads binários grandes, faça streaming da resposta para não ficar na memória:
import requests
url = "https://example.com/large.zip"
with requests.get(url, stream=True, timeout=30) as r:
r.raise_for_status()
with open("large.zip", "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
stream=True diz ao requests para não pré-carregar o corpo. iter_content(chunk_size=...) produz o corpo pedaço por pedaço, que você escreve direto no disco.
Sessions: reuso de conexão e defaults
Se você vai fazer várias requisições ao mesmo serviço, use uma Session. Ela reutiliza a conexão TCP por baixo (mais rápida) e permite definir defaults de uma vez:
import requests
session = requests.Session()
session.headers.update({"Authorization": "Bearer abc123"})
# Every request through this session carries the header.
a = session.get("https://api.example.com/users/1")
b = session.get("https://api.example.com/users/2")
c = session.post("https://api.example.com/users", json={"name": "Rosa"})
Para scripts que batem na mesma API dezenas de vezes, uma session é um ganho notável de velocidade.
Um exemplo realista: pequeno cliente do GitHub
Buscando o último release de um repo:
import requests
def latest_release(owner, repo):
url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest"
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
return {
"tag": data["tag_name"],
"name": data["name"],
"published": data["published_at"],
"url": data["html_url"],
}
release = latest_release("python", "cpython")
print(release)
Menos de quinze linhas: monte a URL, requisite, confira erros, extraia os campos que te importam. Essa é a cara da maioria dos clientes de API que você vai escrever.
E o urllib?
O urllib.request da biblioteca padrão consegue fazer tudo que requests faz — em mais linhas e com ergonomia pior. Se você absolutamente não pode adicionar dependência, está lá:
import json
import urllib.request
with urllib.request.urlopen("https://api.github.com/repos/python/cpython") as r:
data = json.loads(r.read().decode("utf-8"))
print(data["name"])
Para qualquer coisa além de script rápido, requests (ou httpx se você precisa de async) vale a instalação.
Alguns hábitos
- Sempre defina timeout. Sem exceção.
- Use
raise_for_status()em scripts que esperam sucesso — transforma uma resposta ruim numa exceção barulhenta. - Passe dicts para
params=ejson=, não strings montadas na mão. - Envolva chamadas relacionadas numa
Sessionquando você estiver batendo na mesma API muitas vezes. - Logue
response.status_codeeresponse.textao debugar — o corpo geralmente te diz exatamente o que o servidor não gostou.
Próxima: datas e horas
Com requests no seu kit, você pode conversar com qualquer API web moderna, baixar arquivos e construir pequenas integrações entre serviços. Combinado com as páginas de JSON e CSV antes desta, você agora tem o loop completo "busque dados, leia, faça algo, escreva de volta" — o formato de um número enorme de scripts Python reais. A maior parte desses dados tem um timestamp, porém, e a próxima página cobre como o Python representa datas, horas e as armadilhas de fuso horário que vale evitar.
Perguntas frequentes
Como faço uma requisição HTTP em Python?
Instale a biblioteca requests com pip install requests, depois chame requests.get(url) para um GET ou requests.post(url, json=...) para um POST. O objeto response tem .status_code, .text, .json() e .headers. Exemplo: r = requests.get('https://api.example.com/users/1').
Devo usar requests ou urllib?
requests para qualquer coisa que você escreveria na mão — a API é muito mais amigável. urllib é embutido e serve quando adicionar uma dependência é impossível, mas exige mais código para coisas como corpos JSON e sessões. Para produção, a maioria dos times também usa httpx (uma biblioteca compatível com requests com suporte assíncrono).
Como envio JSON numa requisição POST em Python?
Passe json={'key': 'value'} para requests.post(...). O requests serializa o dict para JSON e define Content-Type: application/json para você. Não passe data= e json= juntos — escolha um.
Como trato erros com a biblioteca requests?
Confira response.status_code (200 significa sucesso), ou chame response.raise_for_status() para disparar uma exceção em 4xx/5xx. Problemas de nível de rede (timeouts, falhas de DNS) disparam subclasses de requests.RequestException — capture isso para cobrir os dois.