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! ![]()
Vá Direto ao Assunto…
- O que é a biblioteca requests e como instalar
- Requisição GET com requests
- Requisição POST e envio de JSON com json=
- Timeout: por que usar sempre
- Tratamento de erros: raise_for_status, ConnectionError, Timeout e HTTPError
- Session: reaproveitando conexão, cookies e headers
- Autenticação por token no header
- Download de arquivo em streaming
- Quando usar cada recurso do requests
- Erros comuns
- Exercícios resolvidos
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 objetoResponse. É 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? ![]()
Que tal receber 30 dias de conteúdo direto na sua Caixa de Entrada?
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 geraConnectTimeout; o segundo,ReadTimeout. As duas herdam deTimeout. - O
timeoutnã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:
- Use o requests para coletar páginas no tutorial de web scraping com BeautifulSoup
- Aprofunde a conversão de dados em JSON no Python
- Leia a seção Advanced Usage da documentação (sessões, adaptadores e retentativas)
Se ficou com alguma dúvida, fique à vontade para deixar um comentário no box aqui embaixo! Será um prazer te responder! ![]()
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.
"Porque o Senhor dá a sabedoria, e da sua boca vem a inteligência e o entendimento" Pv 2:6