Conhecimento

Como Usar Proxies Residenciais com Python (requests, httpx, Scrapy)

Um guia prático sobre proxies residenciais em Python: autenticação, IPs rotativos vs. fixos, geo-targeting, retries, com exemplos para requests, httpx e Scrapy.

Chris Collins

Chris Collins

21 de junho de 2026 · 9 min de leitura

Conectar um proxy residencial em um script Python é uma tarefa de cinco minutos depois que você já viu como fazer. O atrito nunca está no código, está nos detalhes que ninguém anota: como as credenciais codificam a segmentação, quando fazer rotação versus fixar um IP, como lidar com os erros específicos de proxy e as pequenas diferenças entre as bibliotecas requests, httpx e Scrapy.

Este é o guia que eu gostaria de ter tido. Exemplos prontos para copiar e colar que realmente funcionam, para as três bibliotecas que você provavelmente está usando, além das partes que transformam um snippet funcional em um scraper que sobrevive em produção.

Tudo abaixo usa o gateway residencial da Shifter: um único endpoint, p.shifter.io:443, com toda a segmentação codificada no username. Se você usa outro provedor, a estrutura é a mesma; basta trocar o host e o formato das credenciais.

A primeira coisa que você precisa entender

Em um gateway residencial, o username do proxy carrega sua autenticação e sua segmentação. Você não muda de endpoint para trocar de país ou de sessão, você muda a string do username. Um username se parece com isto:

customer-USERNAME-country-us-sid-abc123-ttl-600

Leia da esquerda para a direita: id da conta, depois as flags. country-us segmenta para os EUA. sid-abc123 fixa uma sessão persistente (sticky). ttl-600 mantém esse IP por 600 segundos. Remova sid/ttl e cada requisição faz rotação para um novo IP. A senha é constante. Esse é todo o modelo mental; o resto é apenas encaixar essa string no slot de proxy de cada biblioteca.

Mantenha as credenciais em variáveis de ambiente, nunca fixas no código:

import os
USER = os.environ["SHIFTER_USER"] # seu username de conta
PASS = os.environ["SHIFTER_PASS"]
GATEWAY = "p.shifter.io:443"

requests: a versão de 6 linhas

requests recebe um dicionário proxies. Tanto a chave http quanto https apontam para a mesma URL de proxy http://, isso está correto, o requests tuneliza HTTPS através dele via CONNECT.

import os, requests
USER = os.environ["SHIFTER_USER"]
PASS = os.environ["SHIFTER_PASS"]
GATEWAY = "p.shifter.io:443"
def proxy(country="us"):
url = f"http://{USER}-country-{country}:{PASS}@{GATEWAY}"
return {"http": url, "https": url}
r = requests.get("https://api.ipify.org?format=json", proxies=proxy("us"), timeout=30)
print(r.json()) # {'ip': '<um IP residencial dos EUA>'}

Execute duas vezes e você obterá dois IPs diferentes, porque não há sid, cada requisição faz rotação. Esse é o padrão, e para a maioria dos casos de scraping é exatamente o que você quer.

Rotativo vs sticky, em código

Essa é a distinção que costuma confundir as pessoas, então aqui está de forma concreta. (Para a versão conceitual, veja proxies residenciais sticky vs rotativos.)

Rotativo (novo IP a cada requisição) é o padrão, basta omitir sid:

for _ in range(3):
r = requests.get("https://api.ipify.org", proxies=proxy("us"), timeout=30)
print(r.text) # três IPs diferentes

Sticky (mesmo IP em várias requisições) precisa de um id de sessão e um TTL no username. Use quando um fluxo abrange múltiplas requisições que precisam parecer um único usuário, um login, depois as páginas seguintes:

def sticky_proxy(country="us", session="s1", ttl=600):
url = f"http://{USER}-country-{country}-sid-{session}-ttl-{ttl}:{PASS}@{GATEWAY}"
return {"http": url, "https": url}
s = requests.Session()
p = sticky_proxy(session="checkout-42", ttl=600)
for path in ("/login", "/cart", "/checkout"):
r = s.get(f"https://shop.example{path}", proxies=p, timeout=30)
# as três requisições compartilham um IP por até 600s

Escolha o id de sessão você mesmo, qualquer string única por sessão lógica. A mesma string retorna o mesmo IP até o TTL expirar, então ele faz rotação mantendo suas outras flags.

Segmentação geográfica

Como a segmentação vive no username, geo é apenas mais uma flag. País é a mais comum; estado, cidade e ASN funcionam da mesma forma:

def geo_proxy(country, city=None):
parts = [USER, "country", country]
if city:
parts += ["city", city.lower().replace(" ", "_")]
url = f"http://{'-'.join(parts)}:{PASS}@{GATEWAY}"
return {"http": url, "https": url}
requests.get("https://example.com", proxies=geo_proxy("de")) # Alemanha
requests.get("https://example.com", proxies=geo_proxy("us", "new york")) # Nova York

Nomes de cidades usam underscores para espaços (new_york). Combine flags livremente; a ordem não importa para o gateway.

httpx: mesma ideia, pronto para async

httpx é a escolha moderna quando você quer async ou HTTP/2. O proxy vai no client. Note que o parâmetro é proxy= (singular) nas versões atuais do httpx; versões antigas usavam proxies=.

import os, httpx
USER = os.environ["SHIFTER_USER"]
PASS = os.environ["SHIFTER_PASS"]
GATEWAY = "p.shifter.io:443"
def proxy_url(country="us"):
return f"http://{USER}-country-{country}:{PASS}@{GATEWAY}"
# Sync
with httpx.Client(proxy=proxy_url("us"), timeout=30) as client:
print(client.get("https://api.ipify.org").text)

A versão async é onde o httpx se destaca, distribua muitas requisições simultaneamente, cada uma através de um IP rotativo novo:

import asyncio, httpx
async def fetch(client, url):
r = await client.get(url, timeout=30)
return r.status_code, r.text[:80]
async def main(urls):
async with httpx.AsyncClient(proxy=proxy_url("us")) as client:
return await asyncio.gather(*(fetch(client, u) for u in urls))
urls = ["https://api.ipify.org"] * 10
print(asyncio.run(main(urls))) # 10 requisições simultâneas, com rotação de IP

São dez requisições residenciais simultâneas em uma dúzia de linhas. Cuidado com a concorrência, mais nem sempre é mais rápido, e atacar um único alvo com uma rajada de IPs ainda pode disparar detecção comportamental.

Scrapy: um middleware de proxy

Scrapy é o peso pesado para crawls grandes. A forma limpa de anexar proxies é definir request.meta["proxy"] por requisição; o HttpProxyMiddleware embutido do Scrapy lê isso e cuida do cabeçalho Proxy-Authorization a partir das credenciais na URL.

Um pequeno middleware que faz rotação de geo e dá a cada requisição um IP novo:

middlewares.py
import os
class ResidentialProxyMiddleware:
def __init__(self):
self.user = os.environ["SHIFTER_USER"]
self.password = os.environ["SHIFTER_PASS"]
self.gateway = "p.shifter.io:443"
def process_request(self, request, spider):
country = request.meta.get("country", "us")
request.meta["proxy"] = (
f"http://{self.user}-country-{country}:"
f"{self.password}@{self.gateway}"
)

Habilite-o em settings.py (ele precisa rodar antes do middleware de proxy padrão):

settings.py
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.ResidentialProxyMiddleware": 350,
"scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware": 400,
}

Agora qualquer spider roteia através de IPs residenciais, e você pode segmentar por requisição com Request(url, meta={"country": "gb"}). Para uma sessão sticky, monte o username com sid/ttl exatamente como no exemplo do requests e defina em meta["proxy"].

Lidando com os erros que realmente acontecem

Um scraper que ignora erros de proxy funciona na demonstração e morre durante a noite. Três que você vai encontrar:

  • 407 Proxy Authentication Required, username/senha errados, ou uma flag não reconhecida (um erro de digitação em country ou asn). Corrija a string de credenciais; tentar novamente não vai ajudar.
  • 429 Too Many Requests, o alvo está limitando sua taxa de requisições. Reduza a velocidade e faça rotação de IPs (o que o gateway já faz por requisição quando você não está usando sticky).
  • 502 Bad Gateway, nenhum IP corresponde atualmente ao seu filtro (geralmente geo muito restrito, como país+cidade+asn). Afrouxe uma flag e tente novamente.

Um wrapper mínimo de retry que aplica backoff e desiste de forma limpa:

import time, requests
def get_with_retry(url, proxies, tries=4):
for attempt in range(tries):
try:
r = requests.get(url, proxies=proxies, timeout=30)
if r.status_code in (429, 502, 503):
raise requests.exceptions.RequestException(f"status {r.status_code}")
r.raise_for_status()
return r
except requests.exceptions.RequestException as e:
if attempt == tries - 1:
raise
sleep = 2 ** attempt # 1s, 2s, 4s
print(f"retry {attempt+1}: {e}; sleeping {sleep}s")
time.sleep(sleep)

Backoff exponencial mais rotação por requisição resolve a grande maioria das falhas transitórias. Se você está sendo bloqueado consistentemente em vez de intermitentemente, o problema não são os retries, é a qualidade do IP ou o comportamento da requisição, coberto em como evitar ser bloqueado ao fazer scraping.

Verificando se realmente funciona

Antes de confiar em uma configuração, confirme duas coisas: o IP muda (rotação) e ele está no país correto (geo). Uma verificação rápida:

import requests
r = requests.get("http://ip-api.com/json", proxies=proxy("de"), timeout=30)
data = r.json()
print(data["query"], data["countryCode"]) # espera-se um IP DE

Chame algumas vezes sem um sid e você deve ver IPs diferentes, todos DE. Se o país estiver errado, verifique a grafia da sua flag. Se o IP nunca mudar, você deixou acidentalmente um sid no username.

Uma nota sobre SOCKS5

Tudo acima usa proxies HTTP, o padrão certo para web scraping. Se sua carga de trabalho precisa de SOCKS5 (tráfego não HTTP, ou uma ferramenta que espera isso), o mesmo gateway fala esse protocolo, mude o esquema para socks5h:// e instale requests[socks] (ou use o extra SOCKS do httpx). As compensações estão em HTTP vs proxies SOCKS5; para scraping simples, fique com HTTP.

FAQ

Preciso de um endpoint diferente para cada país? Não. Um único endpoint, p.shifter.io:443, para tudo. País, cidade e sessão mudam pela string do username, não pelo host. Esse é o núcleo do modelo de gateway.

Por que tanto a chave http quanto a https são definidas com uma URL http:// no requests? Porque o requests envia HTTPS através de um proxy HTTP via um túnel CONNECT. O esquema da URL do proxy descreve como você fala com o proxy (HTTP), não com o alvo. Isso está correto e é padrão, não defina a chave https como https://.

Como faço rotação de IPs a cada requisição? Omita sid do username. Sem um id de sessão, o gateway te entrega um IP novo por requisição automaticamente. Você não gerencia uma lista de proxies, a rotação do pool acontece no lado do servidor.

Como mantenho o mesmo IP em um fluxo com múltiplas etapas? Adicione sid-<seu-id>-ttl-<segundos> ao username e reutilize em cada requisição do fluxo. Mesmo id, mesmo IP, até o TTL expirar.

Minhas requisições estão lentas. O proxy é o problema? Geralmente não. IPs residenciais adicionam alguma latência em comparação com uma conexão direta, mas lentidão em escala costuma vir mais de concorrência alta demais, falta de reuso de conexão (use uma Session/Client), ou um alvo lento. Faça profiling antes de culpar o proxy.

Isso funciona também com aiohttp, urllib3 ou pycurl? Sim. Qualquer client que aceite uma URL de proxy autenticada http://user:pass@host:port funciona, a segmentação codificada na credencial é idêntica. requests, httpx e Scrapy são apenas os três mais comuns.

Encerrando

O padrão é o mesmo em todas as bibliotecas: monte a URL http://USER-flags:PASS@p.shifter.io:443, coloque-a no slot de proxy, omita sid para fazer rotação ou adicione-o para fixar. Geo é uma flag, erros são um pequeno loop de retry, e concorrência é o que seu client já faz.

Comece a partir dos snippets acima, aponte-os para o gateway residencial, e você terá código de proxy pronto para produção em uma tarde. Planos e taxas por GB estão na página de preços, e a referência completa de flags está na documentação do gateway.

Pronto para começar?

Experimente os proxies residenciais da Shifter, mais de 205M IPs, mais de 195 países, a partir de $0,75/GB.

Começar