O módulo urllib.request permite abrir URLs e fazer requisições HTTP usando apenas a biblioteca padrão. Ele oferece urlopen(), objetos Request e uma arquitetura extensível de handlers para redirects, autenticação, cookies, proxies e HTTPS.
Embora bibliotecas como Requests ou HTTPX sejam mais convenientes em aplicações grandes, urllib.request é útil em scripts portáveis, instaladores, ferramentas administrativas e ambientes onde dependências externas não são desejadas. Este guia mostra GET, POST, JSON, downloads limitados, TLS, redirects, proxies, erros e proteção contra URLs não confiáveis.
Primeira requisição GET
from urllib.request import urlopen
with urlopen("https://www.python.org/", timeout=10) as response:
print(response.status)
print(response.headers.get_content_type())
data = response.read(4096)
A resposta funciona como context manager e expõe status, headers e url. O corpo é retornado como bytes. Você precisa determinar o encoding antes de transformá-lo em texto.
charset = response.headers.get_content_charset() or "utf-8"
text = data.decode(charset, errors="replace")
O guia de codecs no Python explica por que bytes e texto devem ser tratados separadamente.
Use sempre timeout
Sem timeout, uma conexão pode bloquear o programa por muito tempo. O parâmetro cobre operações bloqueantes de conexão e leitura de protocolos suportados.
with urlopen(url, timeout=10) as response:
body = response.read(1_000_000)
O timeout não substitui um limite de tamanho. Um servidor pode responder continuamente dentro do prazo. Leia em blocos e interrompa quando o total ultrapassar o máximo permitido.
Criando um objeto Request
Request permite definir método e headers.
from urllib.request import Request, urlopen
request = Request(
"https://api.example.com/items",
headers={
"Accept": "application/json",
"User-Agent": "MinhaFerramenta/1.0",
},
method="GET",
)
with urlopen(request, timeout=10) as response:
body = response.read(500_000)
Identifique o cliente honestamente. Não copie um User-Agent de navegador para contornar regras de acesso. Respeite termos, limites e o arquivo robots quando aplicável.
Query strings corretas
Use urllib.parse.urlencode() em vez de concatenar parâmetros manualmente.
from urllib.parse import urlencode
params = urlencode({"q": "python seguro", "page": 2})
url = f"https://example.com/search?{params}"
O artigo sobre urllib.parse no Python cobre quoting, parsing e validação de URLs.
POST com formulário
from urllib.parse import urlencode
from urllib.request import Request, urlopen
form = urlencode({"name": "Ana", "active": "1"}).encode("ascii")
request = Request(
"https://example.com/form",
data=form,
headers={"Content-Type": "application/x-www-form-urlencoded"},
method="POST",
)
with urlopen(request, timeout=10) as response:
result = response.read(100_000)
Quando data é fornecido sem método explícito, o padrão passa a ser POST. Declarar o método deixa a intenção clara.
Enviando e recebendo JSON
import json
from urllib.request import Request, urlopen
payload = json.dumps({"title": "Exemplo"}).encode("utf-8")
request = Request(
"https://api.example.com/items",
data=payload,
headers={
"Content-Type": "application/json",
"Accept": "application/json",
},
method="POST",
)
with urlopen(request, timeout=10) as response:
raw = response.read(500_000)
result = json.loads(raw.decode("utf-8"))
Valide o Content-Type antes de assumir JSON e limite a resposta. O guia de APIs REST no Python apresenta validação de status, retries e autenticação.
Tratamento de HTTPError e URLError
from urllib.error import HTTPError, URLError
try:
with urlopen(request, timeout=10) as response:
body = response.read(100_000)
except HTTPError as error:
error_body = error.read(20_000)
print(error.code, error.reason)
except URLError as error:
print(f"Falha de rede: {error.reason}")
except TimeoutError:
print("Tempo esgotado")
HTTPError representa uma resposta HTTP com erro e também funciona como objeto de resposta, permitindo ler um corpo limitado. URLError cobre falhas de resolução, conexão, TLS e protocolos.
Downloads com limite
Evite response.read() sem limite para arquivos externos. Verifique o cabeçalho e conte os bytes realmente lidos.
from pathlib import Path
MAX_BYTES = 50 * 1024 * 1024
with urlopen(url, timeout=20) as response:
declared = response.headers.get("Content-Length")
if declared and int(declared) > MAX_BYTES:
raise ValueError("Arquivo declarado é muito grande")
total = 0
with Path("download.tmp").open("wb") as output:
while chunk := response.read(64 * 1024):
total += len(chunk)
if total > MAX_BYTES:
raise ValueError("Download ultrapassou o limite")
output.write(chunk)
Grave em arquivo temporário, valide hash ou formato e mova atomicamente para o destino. Veja o guia de hashlib no Python para verificar SHA-256.
Conteúdo comprimido
urllib.request não descomprime automaticamente toda resposta. Ao solicitar gzip, valide o header e limite também o tamanho descomprimido.
import gzip
encoding = response.headers.get("Content-Encoding", "").lower()
raw = response.read(2_000_000)
if encoding == "gzip":
data = gzip.decompress(raw)
else:
data = raw
Para dados não confiáveis e grandes, prefira descompressão incremental com limite. O artigo de gzip no Python detalha proteção contra expansão excessiva.
TLS e certificados
HTTPS usa validação segura por padrão. Para uma CA privada, forneça um SSLContext confiável.
import ssl
context = ssl.create_default_context(cafile="empresa-ca.pem")
with urlopen(request, timeout=10, context=context) as response:
body = response.read(100_000)
Não desative hostname nem validação de certificado. Corrija a cadeia de confiança. O guia de ssl no Python explica os riscos de CERT_NONE.
Redirects e headers sensíveis
O handler padrão segue redirects HTTP. Analise o URL final em response.url. Credenciais e headers sensíveis não devem ser encaminhados para domínios inesperados. Use add_unredirected_header() para um header que não pode acompanhar redirects ou implemente um HTTPRedirectHandler restritivo.
Redirects 301 e 302 podem transformar POST em GET, imitando navegadores. Os códigos 307 e 308 preservam o método. Quando a operação tem efeito, revise explicitamente essa política.
Desabilitando redirects automáticos
from urllib.request import build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
opener = build_opener(NoRedirect())
Ao permitir redirects, imponha quantidade máxima e valide esquema, hostname e porta de cada destino. Isso é essencial quando parte do URL vem do usuário.
Proxies do ambiente
O opener padrão pode ler http_proxy, https_proxy e configurações do sistema. Em servidores e tarefas sensíveis, não deixe essa dependência implícita.
from urllib.request import ProxyHandler, build_opener
opener = build_opener(ProxyHandler({})) # sem proxy autodetectado
Para proxy explícito, forneça um dicionário e mantenha credenciais fora do código. Variáveis de ambiente não confiáveis podem desviar tráfego.
Autenticação Basic e Digest
HTTPBasicAuthHandler e HTTPDigestAuthHandler integram autenticação ao opener. Basic apenas codifica usuário e senha; ele exige HTTPS. Restrinja credenciais ao URI correto para evitar envio a outros hosts. Python 3.14 adicionou suporte a SHA-256 no handler Digest.
Proteção contra SSRF
Não passe diretamente uma URL fornecida pelo usuário para urlopen(). O módulo também abre esquemas como file:, data: e FTP. Uma aplicação pode acabar lendo arquivos locais ou acessando serviços internos.
Faça parsing, permita apenas https, normalize hostname, resolva DNS, bloqueie IPs privados, loopback, link-local e metadata de nuvem, e valide novamente após cada redirect. Considere DNS rebinding e diferenças entre o hostname validado e o endereço usado na conexão.
Retries seguros
urllib.request não implementa uma política completa de retry. Repita somente erros transitórios e métodos idempotentes, com backoff e limite. Um POST pode ter sido processado mesmo se a resposta não chegou. Use idempotency key quando a API oferecer suporte.
Erros comuns
Os principais problemas são omitir timeout, ler resposta sem limite, confiar no encoding, desativar TLS, seguir redirects sem validar destino, vazar Authorization, aceitar qualquer esquema, depender de proxy do ambiente e repetir POST cegamente.
Boas práticas
Crie Request explícito, configure timeout, limite corpo e descompressão, valide status e tipo, use HTTPS com contexto seguro, trate redirects, controle proxies e feche respostas com with. Para aplicações complexas, prefira um cliente HTTP mantido com pooling, retries e limites mais completos.
Conclusão
urllib.request oferece um cliente HTTP funcional sem dependências externas. Ele é adequado para scripts e integrações controladas, desde que timeouts, limites, TLS, redirects e URLs externas sejam tratados explicitamente. A arquitetura de handlers permite personalização, mas também exige compreender o comportamento de cada etapa.
Consulte a documentação oficial de urllib.request e o RFC 9110 sobre semântica HTTP.







