When you scrape at any real volume, the bottleneck is rarely your code — it's the target site's rate limiting and IP bans. Rotating residential proxies solve this by routing each request through a different real-user IP. Here's how to wire them into requests and Scrapy in Python, including the sticky-session trick most tutorials skip.
The proxy URL format
A residential proxy is just an authenticated HTTP/SOCKS endpoint. With a pool gateway, you target a country and control session behavior through the username, not separate endpoints:
http://USERNAME_country-us_session-a1b2c3_lifetime-30:[email protected]:1000
Enter fullscreen mode Exit fullscreen mode
-
country-us— exit country (ISO code) -
session-a1b2c3— a sticky-session id; reuse it to keep the same IP, change it to rotate -
lifetime-30— how many minutes that session's IP stays fixed
Basic request through a rotating proxy
import requests
USER = "USERNAME"
PWD = "PASSWORD"
def proxy(country="us", session=None, lifetime=30):
tag = f"_country-{country}"
if session:
tag += f"_session-{session}_lifetime-{lifetime}"
url = f"http://{USER}{tag}:{PWD}@proxy.gproxy.net:1000"
return {"http": url, "https": url}
# New IP on every call (no session id):
r = requests.get("https://api.ipify.org?format=json", proxies=proxy(), timeout=30)
print(r.json()["ip"])
Enter fullscreen mode Exit fullscreen mode
Run it in a loop and you'll see a different IP each time — the gateway rotates automatically when no session id is present.
Sticky sessions: keep one IP across requests
Some flows (login, multi-step checkouts, paginated results behind a cookie) break if your IP changes mid-session. Pin the IP by passing a stable session id:
import uuid
sess = uuid.uuid4().hex[:8] # one id for the whole flow
p = proxy(country="de", session=sess, lifetime=30)
s = requests.Session()
s.proxies.update(p)
s.get("https://example.com/login")
s.post("https://example.com/login", data={...}) # same exit IP
Enter fullscreen mode Exit fullscreen mode
When you want a fresh IP, generate a new session id.
Scrapy integration
Scrapy reads the proxy off each request's meta. A tiny middleware rotates a fresh session per request:
# middlewares.py
import uuid
class ResidentialProxyMiddleware:
USER = "USERNAME"
PWD = "PASSWORD"
def process_request(self, request, spider):
sess = uuid.uuid4().hex[:8]
cc = request.meta.get("country", "us")
request.meta["proxy"] = (
f"http://{self.USER}_country-{cc}_session-{sess}_lifetime-10:"
f"{self.PWD}@proxy.gproxy.net:1000"
)
Enter fullscreen mode Exit fullscreen mode
# settings.py
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.ResidentialProxyMiddleware": 350,
}
Enter fullscreen mode Exit fullscreen mode
Set request.meta["country"] = "gb" in a spider to geo-target specific requests.
A few practical notes
-
Retry on proxy errors. Residential IPs occasionally drop; wrap requests with retries (
urllib3Retry, or Scrapy'sRETRY_ENABLED) so a bad exit doesn't kill a job. -
Respect the target. Rotating IPs is for spreading legitimate load and geo-testing — not for hammering a site past its terms. Add delays and honor
robots.txt. -
SOCKS5 works the same way on port
1002if you need it (socks5://...).
Getting credentials
You need a residential pool that supports country targeting and sticky sessions. I use GProxy — the username-based targeting above is exactly its format, and it's usage-based ($0.90/GB) rather than a fixed monthly seat, which suits burst scraping jobs. Grab a key from the dashboard, drop it into USER/PWD, and the snippets above run as-is.
Happy (responsible) scraping.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.