Biblioteca Requests em Python: Guia Prático de Requisições HTTP

Cansado de programar?

Conheça a melhor e mais completa formação de Python e Django e sinta-se um programador verdadeiramente competente. Além de Python e Django, você também vai aprender Banco de Dados, SQL, HTML, CSS, Javascript, Bootstrap e muito mais!

Quero aprender Python e Django de Verdade! Quero aprender!
Suporte

Tire suas dúvidas diretamente com o professor

Projetos práticos

Projetos práticos voltados para o mercado de trabalho

Prática profissional

Formação moderna com foco na prática profissional

Resposta rápida

requests é a biblioteca mais usada para fazer requisições HTTP em Python: consumir APIs, baixar arquivos e enviar dados. Instale com pip install requests e use requests.get(url, timeout=10). O retorno é um Response, com .status_code, .json() e .text.

1
2
3
4
5
import requests

resposta = requests.get("https://jsonplaceholder.typicode.com/todos/1", timeout=10)
print(resposta.status_code)  # 200
print(resposta.json())       # {'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}

Resumo em 30 segundos:

  • requests.get() busca dados; requests.post(url, json=dados) envia JSON.
  • Sempre passe timeout: sem ele, o programa pode esperar para sempre.
  • 404 e 500 não geram exceção: chame resposta.raise_for_status().
  • params= monta a query string; headers= envia cabeçalhos (como o token).
  • Use requests.Session() para várias chamadas ao mesmo servidor.

Salve salve Pythonista!

Quase todo sistema moderno conversa com outro por HTTP: o seu script consulta a cotação do dólar, o seu bot publica mensagens, a sua aplicação busca o CEP do cliente. Em Python, a ferramenta padrão para isso é a biblioteca requests.

Neste guia você vai aprender a fazer GET e POST, enviar filtros e cabeçalhos, mandar JSON, tratar erros de rede do jeito certo, reaproveitar conexões com Session, autenticar com token e baixar arquivos grandes sem estourar a memória. No final há erros comuns com a mensagem real e exercícios resolvidos.

Todos os exemplos foram executados com requests 2.34.2 (a versão mais recente no PyPI em setembro de 2026) e Python 3.13, contra as APIs públicas de teste JSONPlaceholder e httpbin.org.

Então… Bora pro post! :rocket:

Vá Direto ao Assunto…

O que é a biblioteca requests e como instalar

O que é a biblioteca requests? requests é uma biblioteca externa de Python para fazer requisições HTTP (GET, POST, PUT, PATCH, DELETE) de forma simples. Ela cuida de montar a URL, codificar os dados, manter cookies e decodificar a resposta, entregando tudo em um objeto Response. É a forma mais comum de consumir APIs REST em Python.

A biblioteca padrão do Python tem o módulo urllib.request, mas ele é verboso. O requests resolve o mesmo problema com uma API bem mais direta, e por isso virou o padrão da comunidade. Ele não vem instalado com o Python, então crie um ambiente virtual e instale:

1
pip install requests

Para conferir a versão instalada:

1
2
3
import requests

print(requests.__version__)
1
2.34.2

A versão 2.34 exige Python 3.10 ou mais novo. A documentação oficial fica em requests.readthedocs.io.

Requisição GET com requests

O GET é o método para buscar dados. Você chama requests.get() com a URL e recebe um objeto Response, que carrega tudo o que o servidor devolveu.

1
2
3
4
5
6
7
8
9
import requests

resposta = requests.get("https://jsonplaceholder.typicode.com/users/1", timeout=10)

print(resposta.status_code, resposta.ok, resposta.reason)
print(resposta.headers["Content-Type"])

usuario = resposta.json()
print(usuario["name"], "-", usuario["email"])
1
2
3
200 True OK
application/json; charset=utf-8
Leanne Graham - [email protected]

Os atributos que você mais vai usar do Response:

Atributo ou método O que devolve
resposta.status_code O código HTTP (200, 201, 404, 500…)
resposta.ok True se o código for menor que 400
resposta.json() O corpo JSON convertido em dict ou list
resposta.text O corpo como str (HTML, texto, JSON cru)
resposta.content O corpo como bytes (imagens, PDFs, arquivos)
resposta.headers Os cabeçalhos da resposta, sem diferenciar maiúsculas
resposta.url A URL final, já com a query string e depois de redirecionamentos

O .json() usa o mesmo formato do módulo json da biblioteca padrão: objetos viram dicionários e arrays viram listas. Se quiser revisar essa conversão, veja o nosso post sobre como manipular JSON no Python.

Quando a resposta é HTML (uma página web, e não uma API), use .text e entregue o conteúdo para um parser. É exatamente o que fazemos no tutorial de web scraping com Python e BeautifulSoup.

Parâmetros na URL com params

APIs costumam aceitar filtros na query string (?userId=1&_limit=3). Em vez de concatenar strings, passe um dicionário em params e deixe o requests montar e codificar a URL:

1
2
3
4
5
6
7
8
9
10
11
import requests

resposta = requests.get(
    "https://jsonplaceholder.typicode.com/posts",
    params={"userId": 1, "_limit": 3},
    timeout=10,
)
print(resposta.url)

for post in resposta.json():
    print(post["id"], post["title"][:40])
1
2
3
4
https://jsonplaceholder.typicode.com/posts?userId=1&_limit=3
1 sunt aut facere repellat provident occae
2 qui est esse
3 ea molestias quasi exercitationem repell

Chaves com valor None são omitidas da URL, e espaços e acentos são codificados automaticamente.

Cabeçalhos com headers

Cabeçalhos HTTP informam ao servidor quem está pedindo e em que formato. Passe um dicionário em headers:

1
2
3
4
5
6
7
import requests

cabecalhos = {"User-Agent": "meu-app/1.0", "Accept": "application/json"}
resposta = requests.get("https://httpbin.org/headers", headers=cabecalhos, timeout=10)

recebidos = resposta.json()["headers"]
print(recebidos["User-Agent"], recebidos["Accept"])
1
meu-app/1.0 application/json

O endpoint /headers do httpbin devolve os cabeçalhos que ele recebeu, o que é ótimo para conferir o que o seu código está enviando de verdade.

Requisição POST e envio de JSON com json=

O POST envia dados para o servidor, normalmente para criar um recurso. Para APIs REST, use o parâmetro json=: ele converte o dicionário em JSON e já define o cabeçalho Content-Type: application/json.

1
2
3
4
5
6
7
8
9
10
11
12
import requests

novo_post = {"title": "Aprendendo requests", "body": "Post criado via Python", "userId": 1}
resposta = requests.post(
    "https://jsonplaceholder.typicode.com/posts",
    json=novo_post,
    timeout=10,
)

print(resposta.status_code)
print(resposta.json())
print(resposta.request.headers["Content-Type"])
1
2
3
201
{'title': 'Aprendendo requests', 'body': 'Post criado via Python', 'userId': 1, 'id': 101}
application/json

O código 201 Created indica que o recurso foi criado, e a API devolveu o objeto com o id gerado. O atributo resposta.request guarda a requisição que foi enviada, útil para depurar.

Diferença entre data= e json=

Esta é a dúvida mais comum sobre o POST. O data= envia os campos como formulário HTML; o json= envia um documento JSON. Veja o que o servidor recebe em cada caso:

1
2
3
4
5
6
7
8
9
import requests

dados = {"nome": "Ana", "idade": 30}

r1 = requests.post("https://httpbin.org/post", data=dados, timeout=10)
print(r1.json()["form"], r1.json()["json"])

r2 = requests.post("https://httpbin.org/post", json=dados, timeout=10)
print(r2.json()["form"], r2.json()["json"])
1
2
{'idade': '30', 'nome': 'Ana'} None
{} {'idade': 30, 'nome': 'Ana'}

Com data=, os campos chegam como formulário e a idade virou a string '30'. Com json=, chegam como JSON e a idade continua sendo número. Regra prática: API REST usa json=; formulário de login de site usa data=. Se você passar data e json juntos, o json é ignorado.

PUT, PATCH e DELETE

Os outros métodos seguem o mesmo formato:

1
2
3
4
5
6
7
8
9
10
11
12
import requests

url = "https://jsonplaceholder.typicode.com/posts/1"

r = requests.put(url, json={"id": 1, "title": "novo", "body": "x", "userId": 1}, timeout=10)
print("PUT", r.status_code, r.json())

r = requests.patch(url, json={"title": "so o titulo"}, timeout=10)
print("PATCH", r.status_code, r.json()["title"])

r = requests.delete(url, timeout=10)
print("DELETE", r.status_code, r.json())
1
2
3
PUT 200 {'id': 1, 'title': 'novo', 'body': 'x', 'userId': 1}
PATCH 200 so o titulo
DELETE 200 {}

PUT substitui o recurso inteiro, PATCH altera só os campos enviados e DELETE remove. O JSONPlaceholder simula essas operações sem gravar nada, por isso é seguro testar à vontade. Se você quiser construir o outro lado (a API), veja como usar o FastAPI para construir APIs.

Está curtindo esse conteúdo? :thumbsup:

Que tal receber 30 dias de conteúdo direto na sua Caixa de Entrada?

Sua assinatura não pôde ser validada.
Você fez sua assinatura com sucesso.

Assine as PyDicas e receba 30 dias do melhor conteúdo Python na sua Caixa de Entrada: direto e sem enrolação!

Timeout: por que usar sempre

Por padrão, o requests não tem tempo limite. Se o servidor aceitar a conexão e nunca responder, a sua chamada fica parada para sempre, e junto com ela o seu script, a sua fila de tarefas ou a sua página web. A documentação oficial diz que praticamente todo código de produção deveria usar timeout em praticamente todas as requisições.

O endpoint /delay/5 do httpbin demora 5 segundos para responder. Com timeout=2, a chamada desiste antes:

1
2
3
4
5
6
import requests

try:
    requests.get("https://httpbin.org/delay/5", timeout=2)
except requests.exceptions.Timeout as erro:
    print(type(erro).__name__, ":", erro)
1
ReadTimeout : HTTPSConnectionPool(host='httpbin.org', port=443): Read timed out. (read timeout=2)

Dois detalhes importantes:

  • Você pode passar uma tupla timeout=(3, 10): 3 segundos para conectar e 10 para ler. Estourar o primeiro gera ConnectTimeout; o segundo, ReadTimeout. As duas herdam de Timeout.
  • O timeout não é o tempo total do download: ele conta quanto tempo o servidor fica sem mandar nenhum byte. Um download grande que chega aos poucos não é interrompido.

Tratamento de erros: raise_for_status, ConnectionError, Timeout e HTTPError

Aqui mora a maior confusão de quem está começando: um 404 ou um 500 não é um erro para o requests. O servidor respondeu, então a biblioteca devolve o Response normalmente:

1
2
3
4
import requests

resposta = requests.get("https://jsonplaceholder.typicode.com/posts/9999", timeout=10)
print(resposta.status_code, resposta.ok, resposta.json())
1
404 False {}

Se o seu código seguir em frente e fizer resposta.json()["title"], vai quebrar com um KeyError que não diz nada sobre a causa real. Para transformar respostas 4xx e 5xx em exceção, chame raise_for_status() logo depois da requisição:

1
2
3
4
5
6
7
8
import requests

try:
    resposta = requests.get("https://httpbin.org/status/404", timeout=10)
    resposta.raise_for_status()
except requests.exceptions.HTTPError as erro:
    print("HTTPError:", erro)
    print(erro.response.status_code)
1
2
HTTPError: 404 Client Error: NOT FOUND for url: https://httpbin.org/status/404
404

A exceção carrega a resposta em erro.response, então dá para ler o código e o corpo de erro que a API mandou.

As exceções do requests

Todas as exceções que a biblioteca lança herdam de requests.exceptions.RequestException. As que você vai tratar no dia a dia:

Exceção Quando acontece Mensagem real (resumida)
ConnectionError Domínio inexistente (DNS), conexão recusada, sem internet Max retries exceeded ... Failed to resolve ou Connection refused
Timeout (ConnectTimeout, ReadTimeout) O servidor não conectou ou não respondeu dentro do timeout Read timed out. (read timeout=2)
HTTPError raise_for_status() com código 4xx ou 5xx 404 Client Error: NOT FOUND for url: ...
JSONDecodeError .json() em uma resposta que não é JSON Expecting value: line 1 column 1 (char 0)
MissingSchema URL sem http:// ou https:// No scheme supplied. Perhaps you meant https://...?
RequestException Classe-mãe de todas as anteriores -

Para ver as mensagens de ConnectionError na prática:

1
2
3
4
5
6
7
import requests

for url in ["https://dominio-que-nao-existe-xyz123.com.br", "http://localhost:9"]:
    try:
        requests.get(url, timeout=5)
    except requests.exceptions.ConnectionError as erro:
        print(type(erro).__name__, ":", erro)
1
2
ConnectionError : HTTPSConnectionPool(host='dominio-que-nao-existe-xyz123.com.br', port=443): Max retries exceeded with url: / (Caused by NameResolutionError("HTTPSConnection(host='dominio-que-nao-existe-xyz123.com.br', port=443): Failed to resolve 'dominio-que-nao-existe-xyz123.com.br' ([Errno -2] Name or service not known)"))
ConnectionError : HTTPConnectionPool(host='localhost', port=9): Max retries exceeded with url: / (Caused by NewConnectionError("HTTPConnection(host='localhost', port=9): Failed to establish a new connection: [Errno 111] Connection refused"))

O “Max retries exceeded” assusta, mas não significa que o requests tentou várias vezes: é só o texto padrão da camada de conexão (urllib3). O que interessa está no final: Failed to resolve (DNS) ou Connection refused (nada escutando naquela porta).

Um padrão completo de tratamento

Juntando tudo, esta é uma função reaproveitável que você pode copiar para os seus projetos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests

def buscar_json(url, **kwargs):
    try:
        resposta = requests.get(url, timeout=(3, 10), **kwargs)
        resposta.raise_for_status()
        return resposta.json()
    except requests.exceptions.HTTPError as erro:
        print(f"A API respondeu com erro {erro.response.status_code}")
    except requests.exceptions.Timeout:
        print("A API demorou demais para responder")
    except requests.exceptions.ConnectionError:
        print("Não foi possível conectar (DNS, rede ou servidor fora do ar)")
    except requests.exceptions.RequestException as erro:
        print(f"Falha inesperada na requisição: {erro}")
    return None

print(buscar_json("https://jsonplaceholder.typicode.com/todos/1"))
print(buscar_json("https://httpbin.org/status/500"))
print(buscar_json("https://api-que-nao-existe-xyz123.com.br"))
1
2
3
4
5
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
A API respondeu com erro 500
None
Não foi possível conectar (DNS, rede ou servidor fora do ar)
None

Repare na ordem dos except: Timeout vem antes de ConnectionError. Isso importa porque ConnectTimeout herda das duas classes, e o Python usa o primeiro except que casar. O RequestException fica por último, como rede de segurança. Se quiser revisar como try/except e a hierarquia de exceções funcionam, veja o nosso guia de tratamento de erros e exceções no Python.

Session: reaproveitando conexão, cookies e headers

Cada requests.get() avulso abre uma conexão nova. Quando você faz muitas chamadas ao mesmo servidor, uma requests.Session() reaproveita a conexão TCP (mais rápido), guarda os cookies entre as chamadas e deixa você definir cabeçalhos uma vez só:

1
2
3
4
5
6
7
8
9
10
import requests

with requests.Session() as sessao:
    sessao.headers.update({"User-Agent": "meu-app/1.0"})

    sessao.get("https://httpbin.org/cookies/set/sessionid/abc123", timeout=10)
    print(sessao.cookies.get("sessionid"))

    resposta = sessao.get("https://httpbin.org/cookies", timeout=10)
    print(resposta.json())
1
2
abc123
{'cookies': {'sessionid': 'abc123'}}

O servidor definiu um cookie na primeira chamada e a sessão o enviou sozinha na segunda, como um navegador faria. O with garante que as conexões sejam fechadas no final. A sessão aceita os mesmos métodos do módulo: sessao.get(), sessao.post(), sessao.put() e assim por diante. O timeout continua sendo por chamada: a Session não tem um timeout padrão.

Autenticação por token no header

A maioria das APIs modernas usa um token enviado no cabeçalho Authorization, no formato Bearer <token>. O endpoint /bearer do httpbin confere se o cabeçalho chegou:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import os
import requests

token = os.environ.get("API_TOKEN", "meu-token-secreto")

resposta = requests.get(
    "https://httpbin.org/bearer",
    headers={"Authorization": f"Bearer {token}"},
    timeout=10,
)
print(resposta.status_code, resposta.json())

sem_token = requests.get("https://httpbin.org/bearer", timeout=10)
print(sem_token.status_code)
1
2
200 {'authenticated': True, 'token': 'meu-token-secreto'}
401

Sem o cabeçalho, a API responde 401 Unauthorized. Três cuidados:

  • Nunca escreva o token no código-fonte (ele acaba no Git). Leia de uma variável de ambiente, como no os.environ.get() acima.
  • Com várias chamadas, defina o token na sessão: sessao.headers["Authorization"] = f"Bearer {token}".
  • Para autenticação HTTP Basic (usuário e senha), use o parâmetro auth: requests.get(url, auth=("ana", "senha123"), timeout=10).

Consumir APIs é só uma parte do trabalho de um programador Python; a outra é saber estruturar um projeto inteiro em volta disso. Essa base completa é o que você constrói na Jornada Python:

Download de arquivo em streaming

Por padrão, o requests baixa o corpo inteiro para a memória assim que a resposta chega. Para arquivos grandes (vídeos, backups, datasets), use stream=True e grave em blocos com iter_content():

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests

url = "https://httpbin.org/bytes/102400"
total = 0

with requests.get(url, stream=True, timeout=10) as resposta:
    resposta.raise_for_status()
    with open("arquivo.bin", "wb") as arquivo:
        for bloco in resposta.iter_content(chunk_size=8192):
            arquivo.write(bloco)
            total += len(bloco)

print(f"{total} bytes gravados")
1
102400 bytes gravados

O arquivo é aberto em modo binário ("wb"), porque o conteúdo são bytes, não texto. Com stream=True, só um bloco de 8 KB fica na memória por vez, seja o arquivo de 100 KB ou de 10 GB. O with no requests.get() devolve a conexão ao final, o que é obrigatório quando você usa streaming. Para mais sobre modos de abertura, veja como manipular arquivos utilizando Python.

Quando usar cada recurso do requests

Situação Use Exemplo
Buscar dados de uma API requests.get() + .json() requests.get(url, timeout=10).json()
Filtrar resultados pela URL params= params={"userId": 1}
Criar um recurso em API REST requests.post(url, json=...) json={"title": "x"}
Enviar formulário HTML (login de site) data= data={"usuario": "ana"}
Enviar token de acesso headers= com Authorization {"Authorization": f"Bearer {token}"}
Muitas chamadas ao mesmo servidor requests.Session() with requests.Session() as s:
Baixar arquivo grande stream=True + iter_content() for bloco in r.iter_content(8192):
Garantir que 4xx/5xx virem exceção raise_for_status() resposta.raise_for_status()
Ler uma página HTML .text + BeautifulSoup BeautifulSoup(r.text, "html.parser")

Erros comuns

Estes são os erros que mais aparecem com quem começa a usar requests, com a última linha real do traceback.

Biblioteca não instalada (ModuleNotFoundError)

1
import requests
1
ModuleNotFoundError: No module named 'requests'

Correção: o requests não faz parte da biblioteca padrão. Rode pip install requests no mesmo ambiente (venv) que executa o seu script. Se o erro continuar, confira com python -m pip install requests, que garante que o pip é o do Python em uso.

URL sem https:// (MissingSchema)

1
2
3
import requests

requests.get("jsonplaceholder.typicode.com/todos/1", timeout=10)
1
requests.exceptions.MissingSchema: Invalid URL 'jsonplaceholder.typicode.com/todos/1': No scheme supplied. Perhaps you meant https://jsonplaceholder.typicode.com/todos/1?

Correção: toda URL precisa do esquema. Escreva https://jsonplaceholder.typicode.com/todos/1.

Chamar .json() em uma resposta que não é JSON (JSONDecodeError)

1
2
3
import requests

requests.get("https://httpbin.org/html", timeout=10).json()
1
requests.exceptions.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

Correção: essa URL devolve HTML. Confira resposta.headers["Content-Type"] antes ou use resposta.text. Esse erro também aparece quando a API devolve uma página de erro em HTML (um 502 do proxy, por exemplo), então chame raise_for_status() antes do .json().

Ignorar o status code (KeyError)

1
2
3
4
import requests

resposta = requests.get("https://jsonplaceholder.typicode.com/posts/9999", timeout=10)
print(resposta.json()["title"])
1
KeyError: 'title'

O post 9999 não existe: a API respondeu 404 com corpo {}, o requests não reclamou e o erro estourou longe da causa. Correção: chame resposta.raise_for_status() logo depois da requisição, que troca esse KeyError enigmático por HTTPError: 404 Client Error.

Esquecer o timeout (programa travado ou ReadTimeout não tratado)

Sem timeout, uma API lenta deixa o programa parado indefinidamente, sem nenhuma mensagem. Com timeout mas sem try/except, o programa encerra com:

1
requests.exceptions.ReadTimeout: HTTPSConnectionPool(host='httpbin.org', port=443): Read timed out. (read timeout=2)

Correção: passe sempre timeout e capture requests.exceptions.Timeout para decidir o que fazer (tentar de novo, avisar o usuário, registrar em log).

Exercícios resolvidos

Tente resolver cada exercício antes de abrir a solução. Todos os códigos foram executados contra as APIs públicas e as saídas conferidas. Quer mais prática? Veja a nossa página de exercícios de Python resolvidos.

Exercício 1. Busque todos os usuários em https://jsonplaceholder.typicode.com/users, mostre quantos são e o nome dos 3 primeiros.

Ver solução
1
2
3
4
5
6
7
8
9
import requests

resposta = requests.get("https://jsonplaceholder.typicode.com/users", timeout=10)
resposta.raise_for_status()
usuarios = resposta.json()

print(len(usuarios))
for usuario in usuarios[:3]:
    print(usuario["name"])

Saída:

1
2
3
4
10
Leanne Graham
Ervin Howell
Clementine Bauch

O .json() devolveu uma lista de dicionários, então len() e o fatiamento [:3] funcionam como em qualquer lista.

Exercício 2. Usando params, busque em https://jsonplaceholder.typicode.com/todos as tarefas concluídas do usuário 1 (userId=1 e completed=true). Mostre a URL final e quantas tarefas vieram.

Ver solução
1
2
3
4
5
6
7
8
9
10
11
import requests

resposta = requests.get(
    "https://jsonplaceholder.typicode.com/todos",
    params={"userId": 1, "completed": "true"},
    timeout=10,
)
resposta.raise_for_status()

print(resposta.url)
print(len(resposta.json()))

Saída:

1
2
https://jsonplaceholder.typicode.com/todos?userId=1&completed=true
11

O completed foi passado como a string "true" porque é assim que essa API espera o valor na URL. Se você passar o booleano True, o requests escreve True com maiúscula, e a API não reconhece o filtro.

Exercício 3. Crie um post em https://jsonplaceholder.typicode.com/posts com título, corpo e userId=7, enviando JSON. Mostre o status code e o id retornado.

Ver solução
1
2
3
4
5
6
import requests

dados = {"title": "Meu primeiro POST", "body": "Enviado com requests", "userId": 7}
resposta = requests.post("https://jsonplaceholder.typicode.com/posts", json=dados, timeout=10)

print(resposta.status_code, resposta.json()["id"])

Saída: 201 101

201 significa “criado”. O json= cuidou da conversão e do cabeçalho Content-Type.

Exercício 4. Escreva uma função buscar_json(url) que devolva o JSON da resposta ou None em caso de erro, imprimindo uma mensagem diferente para erro HTTP, timeout e falha de conexão. Teste com uma URL válida, com https://httpbin.org/status/404 e com um domínio inexistente.

Ver solução
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests

def buscar_json(url):
    try:
        resposta = requests.get(url, timeout=5)
        resposta.raise_for_status()
        return resposta.json()
    except requests.exceptions.HTTPError as erro:
        print(f"Erro HTTP {erro.response.status_code}")
    except requests.exceptions.Timeout:
        print("A API demorou demais para responder")
    except requests.exceptions.ConnectionError:
        print("Falha de conexão")
    return None

print(buscar_json("https://jsonplaceholder.typicode.com/todos/1"))
print(buscar_json("https://httpbin.org/status/404"))
print(buscar_json("https://api-que-nao-existe-xyz123.com.br"))

Saída:

1
2
3
4
5
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
Erro HTTP 404
None
Falha de conexão
None

O raise_for_status() é o que faz o 404 cair no primeiro except. Sem ele, a função devolveria {} como se tudo tivesse dado certo.

Exercício 5. Usando uma Session, defina o cabeçalho Authorization: Bearer abc123 uma única vez e faça duas chamadas a https://httpbin.org/bearer, mostrando o status e o token recebido pelo servidor.

Ver solução
1
2
3
4
5
6
7
import requests

with requests.Session() as sessao:
    sessao.headers["Authorization"] = "Bearer abc123"
    for _ in range(2):
        resposta = sessao.get("https://httpbin.org/bearer", timeout=10)
        print(resposta.status_code, resposta.json()["token"])

Saída:

1
2
200 abc123
200 abc123

Os cabeçalhos definidos em sessao.headers são enviados em todas as requisições feitas pela sessão.

Exercício 6. Baixe https://httpbin.org/bytes/50000 em streaming, gravando em dados.bin em blocos de 4096 bytes, e mostre quantos bytes foram gravados.

Ver solução
1
2
3
4
5
6
7
8
9
10
11
import requests

tamanho = 0
with requests.get("https://httpbin.org/bytes/50000", stream=True, timeout=10) as resposta:
    resposta.raise_for_status()
    with open("dados.bin", "wb") as arquivo:
        for bloco in resposta.iter_content(chunk_size=4096):
            arquivo.write(bloco)
            tamanho += len(bloco)

print(f"{tamanho} bytes baixados")

Saída: 50000 bytes baixados

Com stream=True, o corpo não é carregado de uma vez: cada volta do for traz no máximo 4096 bytes.

Exercício 7 (estilo prova). Uma API REST espera receber os dados no corpo da requisição em formato JSON, com o cabeçalho Content-Type: application/json. Qual chamada atende a esse requisito sem nenhum código extra?

a) requests.post(url, data=dados)
b) requests.post(url, json=dados)
c) requests.post(url, params=dados)
d) requests.post(url, headers=dados)

Ver solução

Resposta: b. O json= serializa o dicionário e define o Content-Type: application/json. O data= envia como formulário (application/x-www-form-urlencoded), o params= coloca os dados na URL e o headers= enviaria o dicionário como cabeçalhos.

Exercício 8 (estilo prova). O que o código abaixo imprime, sabendo que a URL responde com status 404?

1
2
3
4
import requests

resposta = requests.get("https://httpbin.org/status/404", timeout=10)
print(resposta.status_code, resposta.ok)

a) Nada: a linha 3 lança requests.exceptions.HTTPError
b) 404 False
c) 404 True
d) Nada: a linha 3 lança requests.exceptions.ConnectionError

Ver solução

Saída: 404 False

Resposta: b. Um 404 é uma resposta válida do servidor, então o requests não lança exceção. O ok é False porque o código é 400 ou maior. A HTTPError só apareceria se o código chamasse resposta.raise_for_status(), e ConnectionError só acontece quando não há resposta nenhuma (DNS, rede, servidor fora do ar).

Quer aplicar requests em projetos reais, consumindo APIs e automatizando tarefas? Conheça o nosso curso de Python completo.

Conclusão

Neste guia de requests, você aprendeu:

✅ GET e POST - buscar e enviar dados com requests.get() e requests.post()
✅ params, headers e json= - filtros na URL, cabeçalhos e corpo JSON
✅ timeout - nunca deixar o programa esperar para sempre
✅ Tratamento de erros - raise_for_status(), HTTPError, Timeout e ConnectionError
✅ Session e token - reaproveitar conexão e autenticar
✅ Streaming - baixar arquivos grandes em blocos

Próximos passos:

Se ficou com alguma dúvida, fique à vontade para deixar um comentário no box aqui embaixo! Será um prazer te responder! :wink:

Perguntas frequentes

Como fazer uma requisição GET em Python com requests?

Instale com pip install requests, importe e chame requests.get(url, timeout=10). O retorno é um objeto Response: use resposta.status_code para ver o código HTTP, resposta.json() para converter um corpo JSON em dicionário ou lista e resposta.text para ler o corpo como texto.

Qual a diferença entre data= e json= no requests.post?

json=dados converte o dicionário em JSON e define o cabeçalho Content-Type: application/json, que é o que a maioria das APIs REST espera. data=dados envia os campos como formulário (application/x-www-form-urlencoded), como um formulário HTML. Se a API espera JSON e você usa data=, ela recebe os campos no lugar errado e todos os valores viram texto.

Por que sempre usar timeout no requests?

Porque, sem timeout, o requests espera a resposta para sempre: se o servidor travar, o seu programa trava junto. A própria documentação recomenda usar timeout em praticamente todas as requisições de produção. Use timeout=10 (segundos) ou uma tupla (conexão, leitura), como timeout=(3, 10), e trate requests.exceptions.Timeout.

O requests lança erro quando a API retorna 404 ou 500?

Não. Uma resposta 404 ou 500 é uma resposta válida para o requests: ele devolve o objeto Response com status_code 404 e ok igual a False, sem exceção. Para transformar códigos 4xx e 5xx em exceção, chame resposta.raise_for_status(), que lança requests.exceptions.HTTPError.

Como enviar um token de autenticação com requests?

Coloque o token no cabeçalho Authorization: requests.get(url, headers={'Authorization': f'Bearer {token}'}, timeout=10). Se várias chamadas usam o mesmo token, crie uma requests.Session() e defina sessao.headers['Authorization'] uma vez. Nunca escreva o token direto no código: leia de uma variável de ambiente.

Para que serve requests.Session()?

Uma Session reaproveita a conexão TCP entre requisições para o mesmo servidor (mais rápido), guarda cookies automaticamente e permite definir cabeçalhos e autenticação uma única vez para todas as chamadas. Use com with requests.Session() as sessao: para que as conexões sejam fechadas no final.

Qual exceção o requests lança quando o site não existe ou está fora do ar?

Lança requests.exceptions.ConnectionError, tanto para falha de DNS (domínio inexistente) quanto para conexão recusada. Se o servidor existe mas demora a responder, a exceção é requests.exceptions.Timeout. Todas as exceções da biblioteca herdam de requests.exceptions.RequestException, que serve para capturar qualquer falha de rede de uma vez.

Em uma questão de prova, qual parâmetro do requests.get envia filtros na URL, como ?userId=1?

O parâmetro params. Ao chamar requests.get(url, params={'userId': 1}), o requests monta a query string e a URL final fica url?userId=1. O parâmetro data envia dados no corpo, json envia JSON no corpo e headers envia cabeçalhos, nenhum deles altera a URL.

Começe agora sua Jornada na Programação!

Não deixe para amanhã o sucesso que você pode começar a construir hoje!

#newsletter Olá :wave: Curtiu o artigo? Então faça parte da nossa Newsletter! Privacidade Não se preocupe, respeitamos sua privacidade. Você pode se descadastrar a qualquer momento.