Proxy no Python requests: configuração, autenticação e erros

11 min de leitura

Como configurar proxy no Python Requests, autenticar sem vazar senha, separar HTTP de CONNECT, tratar timeout, TLS e 407, e conferir SOCKS5 versus socks5h.

Este texto é para quem precisa mandar o Python Requests por um proxy e conferir o caminho. Os trechos usam dados dummy.

01Configurar um proxy no requests e confirmar o caminho

Os exemplos seguem a documentação oficial de proxies do Requests, conferida em 14 de outubro de 2026. O laboratório local usou Python 3.14.6, Requests 2.34.2, urllib3 2.8.0 e PySocks 1.7.1, com verificação TLS ligada. Ele mostra protocolo. Não mede velocidade, geografia nem qualidade de um IP de provedor.

Cada método aceita um dicionário proxies. As chaves são o esquema da URL de destino. Os valores são a URL do proxy:

import requests

proxies = {
    "http": "http://203.0.113.10:8080",
    "https": "http://203.0.113.10:8080",
}

session = requests.Session()
session.trust_env = False
r = session.get("https://example.com/", proxies=proxies, timeout=(3.05, 27))
print(r.status_code)
  1. Crie uma Session e defina trust_env = False enquanto você escolhe o proxy no código.
  2. Aponte HTTP e HTTPS para o mesmo proxy se o provedor oferece um proxy HTTP com CONNECT.
  3. Passe sempre uma tupla de timeout. Sem isso a chamada pode ficar presa.
  4. Faça um GET pequeno contra um endpoint que você controla.
  5. Leia a resposta e o log do proxy. A página Qual é o meu IP no navegador mede a saída do navegador, não a rota do Python.

No laboratório, o GET direto no origin local devolveu HTTP 200. O mesmo GET pelo proxy aberto em loopback também 200. O proxy viu a forma absoluta GET http://127.0.0.1:53653/ HTTP/1.1. Sem esse rastro você só sabe que alguma coisa respondeu 200.

Requests 2.34.2 aceitou 127.0.0.1:8080 sem esquema. A chamada passou com HTTP 200. Por dentro, prepend_scheme_if_needed completa http://. Escreva o esquema mesmo assim. Sem http:// ou socks5:// a URL fica ambígua quando SOCKS entra no jogo. A documentação estável diz que a URL do proxy precisa incluir o esquema. Inferir HTTP não torna o valor sem esquema uma forma portátil.

Uma chave no formato https://api.interna.exemplo vale só para aquele host, não para subdomínios.

02Separar o proxy HTTP do destino HTTPS

A chave "https" quase sempre aponta para uma URL de proxy http://. Isso é proposital. O urllib3 documenta o túnel: destino HTTP vai em forma absoluta, destino HTTPS vira CONNECT e o TLS nasce depois, no túnel. No laboratório o evento do proxy foi CONNECT localhost:53654 HTTP/1.1 enquanto a URL alvo era https://localhost:53654/. O esquema do destino não é o esquema do endpoint do proxy.

Diagrama: no dicionário proxies a chave é o esquema do destino; destino http:// vira GET em forma absoluta, destino https:// vira CONNECT com TLS dentro do túnel
Diagrama, não captura de tela: o que o proxy registrou no laboratório local para cada esquema de destino

https:// na URL do proxy só entra quando o provedor termina TLS na própria porta do proxy. Isso é outro serviço, não o forward proxy clássico.

A mesma senha ausente não se parece nos dois caminhos:

Sintoma no Requests 2.34.2CamadaO que conferir
Status 407, corpo "Proxy Authentication Required", sem exceptionProxy HTTP, GET em forma absolutaUsuário, senha, percent-encoding. Não é falha de Python.
ProxyError com Tunnel connection failed: 407CONNECT para destino HTTPSA mesma senha, agora no túnel. HTTPProxyAuth não bastou aqui.
Status 401 depois do login válido no proxyServidor de destinoO proxy passou. A aplicação no origin recusou.
ProxyError com ConnectTimeoutErrorTCP até o proxyHost, porta, firewall. O timeout é o connect, não o relógio inteiro da chamada.
ConnectionError com ReadTimeoutError na cadeiaLeitura depois do connectO corpo atrasou. Não use um único número como prazo total.
SSLError / CERTIFICATE_VERIFY_FAILEDTLS no destino, dentro do túnelCA do destino ou de um proxy que intercepta. Não desligue verify.
ConnectionError, nodename nor servname provided em socks5://DNS no clienteHostname sem resolução local. socks5h:// manda o nome ao proxy.

Se você só quer saber se um endpoint responde, o checador de proxy basta para um teste manual. Ele não substitui o rastro do Python. Protocolo não é origem do IP. Um GET 200 não prova anonimato, país, elegibilidade de conta nem acesso universal ao alvo.

03Passar usuário e senha sem expor credenciais

A documentação pede Basic Auth na URL do proxy, com percent-encoding por componente. Caractere especial na senha não entra cru na URL:

from urllib.parse import quote

user = quote("demo", safe="")
password = quote("p@ss:/#%", safe="")
proxies = {
    "http": f"http://{user}:{password}@203.0.113.10:8080",
    "https": f"http://{user}:{password}@203.0.113.10:8080",
}

Sem login, o proxy de autenticação local devolveu 407 como resposta normal no GET HTTP. O mesmo header ausente no HTTPS virou requests.exceptions.ProxyError com OSError('Tunnel connection failed: 407 Proxy Authentication Required'). Os dois 407 não são o mesmo objeto. Uma página genérica de diagnóstico 407 no navegador é outro assunto.

Com os dados dummy já codificados na URL, HTTP e CONNECT HTTPS chegaram a 200 depois que a CA de laboratório foi passada em verify. Senha errada no GET HTTP também ficou em 407, sem exception.

A RFC 7617, seção 2 é clara: o primeiro dois-pontos separa user-id e senha. User-id com dois-pontos é inválido depois da decodificação. Senha pode ter dois-pontos. No laboratório, demo%3Ax na parte de usuário ainda virou 407, porque o decode reconstitui o dois-pontos e o Basic quebra no lugar errado.

HTTPProxyAuth funcionou no GET em forma absoluta (200). No CONNECT, a mesma classe não carregou o header no túnel: de novo ProxyError com 407. Coloque usuário e senha na URL do proxy, já codificados, quando o alvo for HTTPS.

Diagrama: credenciais na URL com quote() passam no GET HTTP e no CONNECT para HTTPS; HTTPProxyAuth passa no GET HTTP e falha com ProxyError 407 no CONNECT
Diagrama dos resultados do laboratório local com dados dummy; nenhuma senha aparece na figura

Não grave senha em variável de ambiente compartilhada nem em repositório. O Requests avisa isso na página de proxies. Logs e figuras deste artigo redigem a senha dummy.

Retries ilimitados para furar 407 não cabem aqui. Tentativa limitada, depois pare e leia o status.

04Entender Session, variáveis de ambiente e trustenv

A documentação estável alerta: session.proxies pode ser sobrescrito pelos proxies do ambiente, os que urllib.request.getproxies devolve. Para mandar de verdade, passe proxies= em cada chamada ou desligue o ambiente.

No processo normal, HTTP_PROXY e HTTPS_PROXY em maiúsculas foram lidas. O GET HTTP saiu pelo proxy aberto. O mesmo processo com session.proxies apontando para um endereço morto ainda usou o ambiente. A chamada por request com proxies={"http": None, "https": None} ignorou o ambiente e foi direto ao origin, 200.

Session.trust_env = False ignorou HTTP_PROXY e HTTPS_PROXY. Também ignorou REQUESTS_CA_BUNDLE. Com trust_env = True, o mesmo bundle fez o HTTPS local passar. Com False, o default verify=True caiu em SSLError / unable to get local issuer certificate. O efeito da CA de ambiente some junto com o proxy de ambiente. Não é um interruptor só de proxy.

CPython 3.14.6, em urllib.request.getproxies_environment, descarta HTTP_PROXY quando REQUEST_METHOD está definido. É o CVE-2016-1000110, o caso CGI. No laboratório, com REQUEST_METHOD=GET e HTTP_PROXY preenchido, getproxies() voltou {}. Isso não é a regra do curl, que trata http_proxy minúsculo de outro jeito. Não misture as duas histórias.

Prepared requests também não herdam o ambiente sozinhas. A documentação de Session pede merge_environment_settings se você montar o PreparedRequest na mão. Fora desse fluxo, trust_env=False e proxies= explícito deixam o teste reproduzível.

05Testar SOCKS5, socks5h e a resolução DNS

SOCKS não vem no Requests puro. A página oficial pede python -m pip install 'requests[socks]', que puxa PySocks 1.7.1. Sem isso a URL socks5:// nem sobe.

proxies = {
    "http": "socks5h://127.0.0.1:1080",
    "https": "socks5h://127.0.0.1:1080",
}

socks5:// resolve o DNS no cliente. socks5h:// manda o hostname ao proxy, no mesmo espírito do curl. Chute a partir de um 200 não prova onde o nome foi resolvido. Precisa de hostname controlado e rastro no proxy.

O laboratório usou lab-origin.invalid, nome que o resolver local não conhece, mapeado só no fixture SOCKS para 127.0.0.1. Com socks5:// o cliente falhou antes de falar com o proxy: ConnectionError / NewConnectionError / gaierror, [Errno 8] nodename nor servname provided, or not known. Zero eventos SOCKS. Com socks5h:// o proxy registrou ATYP 3 e addr=lab-origin.invalid, e o GET voltou 200. Um GET socks5:// para 127.0.0.1 mostrou ATYP 1, IPv4, porque o cliente já tinha o endereço.

Diagrama: com socks5:// o Python resolve o nome e falha com gaierror antes de falar com o proxy; com socks5h:// o proxy recebe ATYP 3 com o hostname e o GET volta 200
Diagrama, não captura de tela: hostname de teste do laboratório local, mapeado só no fixture SOCKS

06Diagnosticar timeout, TLS e autenticação

O quickstart de timeouts diz que o parâmetro não é limite do download inteiro. É espera sem bytes no socket. Sem timeout, a chamada não expira.

A tupla (connect, read) separa as duas esperas. (3.05, 10) não é um orçamento de 13 segundos. Connect curto contra 192.0.2.1 gerou ConnectTimeout com connect timeout=0.4. Pelo proxy morto no mesmo prefixo, a classe visível foi ProxyError com ConnectTimeoutError na cadeia. O connect falhou no proxy, não no origin.

Corpo atrasado depois dos headers: o origin local dormiu 3 segundos no body. Com timeout=(3.05, 0.4) o Requests 2.34.2 não levantou ReadTimeout puro. Levantou ConnectionError cujo texto é Read timed out. e a cadeia traz ReadTimeoutError. Trate a cadeia, não só o nome da exception de fora.

TLS: sem a CA de laboratório, verify=True (default) falhou com SSLCertVerificationError / unable to get local issuer certificate. Com verify no PEM público da CA, o mesmo origin HTTPS respondeu 200. Não use verify=False no código de produção. Proxy que intercepta TLS pede a CA desse proxy, não um atalho.

urllib3.util.retry.Retry com total=3 e status_forcelist=(503,). allowed_methods default é GET, HEAD, OPTIONS, PUT, DELETE, TRACE.

MétodoResposta do originBatidas no originEspera
GET503 com Retry-After: 14 (pedido mais 3 retries)3,023 s
POST5031 (uma vez só)0,002 s

Os 3,023 s do GET não são teto de relógio de 3 segundos. Erro de connect ainda pode retentar POST, porque _is_connection_error não consulta o método. Read e status de POST, não. Ligue replay só em operação segura.

401 no origin depois de proxy autenticado continuou 401. Não confunda com 407.

Diagrama: seis camadas onde uma chamada do Python Requests pode parar, do DNS no cliente ao 401 do origin, com a exception de cada camada e o significado de timeout=(connect, read)
Diagrama, não captura de tela: casos do laboratório local do Requests 2.34.2, sem provedor comercial

07Escolher um endpoint compatível e conferir o resultado

Antes de comprar IP, decida o protocolo que o cliente fala. Requests fala HTTP proxy com CONNECT e, com o extra SOCKS, socks5 / socks5h. Residencial ou datacenter é outra pergunta. A comparação de origem está em proxies residenciais vs. datacenter. Catálogo em proxies residenciais e proxies móveis. Nenhum desses links prova que o seu script Python saiu por aquele IP.

Confira o endpoint no checador se quiser um teste manual. Depois, no código:

  1. trust_env = False no teste.
  2. URL de proxy com esquema explícito.
  3. Usuário e senha com quote(..., safe="") por componente.
  4. Timeout em tupla.
  5. verify com a CA certa se houver interceptação.
  6. Log do proxy: GET absoluto versus CONNECT, ATYP 1 versus 3 no SOCKS.

As fichas abaixo estão publicadas em português neste catálogo e citam HTTP ou SOCKS5. Não passaram por este laboratório. Não são prova de sucesso em produção, geolocalização ou conta.

SOAX: proxies residenciais e móveis
SOAX: proxies residenciais e móveis3.8218

A SOAX vende proxies residenciais e móveis rotativos num pool compartilhado de créditos, com datacenter e ISP nos mesmos planos. O Builder começa em $200/mês + IVA (200 créditos); o tráfego Tier 1 é $3/GB no Builder.

85 países
US$ 3,00
por 1 GB
Visitar site
Bright Data: proxies residenciais e de datacenter
Bright Data: proxies residenciais e de datacenter4.41449

A Bright Data é uma grande rede de proxies para coleta de dados: IPs residenciais, móveis, ISP e de datacenter em cerca de 195 países. O tráfego residencial pode ser pago por uso, sem assinatura, e os endereços de datacenter e ISP são vendidos em pacotes a partir de 10 IPs. O acesso residencial e móvel exige verificação de identidade.

10 países
US$ 5,00
por 1 GB
Oxylabs — proxies residenciais e de datacenter
Oxylabs — proxies residenciais e de datacenter3.9773

A Oxylabs vende proxies residenciais, móveis, ISP e de datacenter em uma única assinatura mensal a partir de US$ 30. Pagamento: cartão, transferência, AliPay, PayPal.

10 países
US$ 0,45
por 1 GB
Decodo (Smartproxy): proxies residenciais e de datacenter
Decodo (Smartproxy): proxies residenciais e de datacenter4.22595

A Decodo (antes Smartproxy) vende proxies residenciais e de datacenter. Residencial: $4/GB sem plano mensal, ou pacotes mensais a partir de $3.75/GB para 3 GB. Pagamentos: cartões, PayPal, Alipay, Google/Apple Pay, cripto (com limites).

83 países
US$ 0,38
por 1 GB

Um 200 no Requests prova que aquele hop falou HTTP. Não prova que o próximo alvo aceita o IP, nem que o navegador na mesma máquina usa a mesma saída.

Como configurar um proxy no Python requests?

Passe um dict proxies com chaves http e https apontando para a URL do proxy, use Session com trust_env False enquanto testa, e coloque timeout em tupla. Confira o log do proxy, não só o status.

Por que https no dict aponta para um proxy http?

A chave é o esquema do destino. O proxy HTTP clássico recebe CONNECT e o TLS começa no túnel. A URL do proxy continua http:// a menos que o provedor exponha TLS na própria porta do proxy.

Como passar usuário e senha sem quebrar caracteres especiais?

Use urllib.parse.quote em cada componente, com safe vazio, e monte a URL. User-id com dois-pontos é inválido no Basic depois do decode. Senha pode ter dois-pontos. HTTPProxyAuth no CONNECT desta versão não substitui a URL.

Qual a diferença entre socks5 e socks5h no Requests?

socks5 resolve o DNS no cliente. socks5h envia o hostname ao proxy. Um 200 não mostra onde o nome foi resolvido. Sem o extra requests[socks] a URL socks nem funciona.

Por que um 407 vira exception no HTTPS e resposta no HTTP?

GET HTTP em forma absoluta devolve 407 como status. CONNECT HTTPS falha o túnel e o Requests empacota ProxyError com a frase Tunnel connection failed 407. Não trate os dois como o mesmo objeto.

timeout=(3, 10) limita a chamada a 13 segundos?

Não. O primeiro valor é connect. O segundo é ociosidade de leitura. Não é prazo total. total=3 no Retry é contagem de novas tentativas, e Retry-After pode alongar a espera.

A página Meu IP no navegador mostra o IP do meu script Python?

Não. Ela mede a saída do navegador. O Requests só entra nesse número se o navegador e o script usarem o mesmo proxy, o que você precisa provar à parte.
  • O que é proxy ISP e quando usar IP fixo

    Proxy ISP explicado: de onde vem o IP, o que o ASN revela, por que IP estático não é o mesmo que sessão sticky residencial e como checar o produto antes de pagar.