84 stars on GitHub and counting. Yozh Crawler + Scraper is free, open-source, and built in public — give us a star if it earns its place in your stack.

Proxy rotation that survives: sticky sessions vs per-request

Rotate on every request and you break logins and carts; pin one IP and you eat 429s. How to choose a rotation model by the shape of the job — and the failure modes that quietly waste your proxy budget.

Roman
Proxy rotation that survives: sticky sessions vs per-request

Proxy rotation looks trivial until it isn’t. Rotate on every request and your login flow breaks halfway through; pin a single IP and the target rate-limits you into a wall of 429s. Most scrapers pick one mode globally and then fight the symptoms forever.

This guide breaks down the two rotation models, how to match them to the shape of a job rather than the target, the config that makes rotation predictable, and the failure modes that quietly drain your proxy budget.

Two ways to rotate

Per-request rotation

Every request leaves on a fresh IP from the pool. It maximises spread, which is exactly what you want for stateless, parallel reads — product pages, search results, anything where each URL is independent. The cost: you can’t hold a session, a cart, or anything that needs the server to recognise you across requests.

Sticky sessions

The same exit IP is held for a window — usually 1 to 30 minutes, or until you drop it. This is what makes multi-step flows work: log in, add to cart, paginate behind a cursor, follow a checkout. The trade-off is concentration: one IP doing many requests is a louder signal than many IPs doing one each.

Match the model to the job, not the site

The useful question isn’t “is this site hard?” — it’s “does a unit of work span more than one request?”

  • Independent reads (one URL = one result) → per-request rotation, high concurrency.
  • Stateful flows (login, cart, cursor pagination) → one sticky session for the whole flow, then drop it.
  • Search-then-detail → sticky for the search and its result pages, rotate per detail page.
  • Rate-limited APIs → sticky per logical “client”, sized so each IP stays under the documented limit.

A minimal rotating setup

With a gateway endpoint, the rotation policy is usually just a tag in the username — a shared pool versus a session id that pins the exit. Nothing exotic:

import requests

# Per-request: the pool rotates the exit IP on every call
ROTATING = "http://user-pool:pass@gw.proxy.net:7000"

# Sticky: a session id pins one IP for the flow
def sticky(session_id):
    return f"http://user-session-{session_id}:pass@gw.proxy.net:7000"

# stateless reads -- rotate hard
for url in product_urls:
    requests.get(url, proxies={"https": ROTATING}, timeout=20)

# one login flow -- hold a single exit
s = requests.Session()
s.proxies = {"https": sticky("cart-8842")}
s.post(LOGIN, data=creds)
s.get(CART)        # same IP the server already trusts

The pattern is the same whatever the provider: choose the pool when work is independent, choose a stable session id when state has to survive between requests.

The failure modes that waste budget

  • Rotating mid-session. A new IP between “add to cart” and “checkout” reads as session hijacking — instant challenge, and you paid for two IPs to fail one flow.
  • Never rotating. A sticky IP left running for hours behaves like a bot to any rate model. Drop the session the moment the flow ends.
  • Ignoring geo. An exit in the wrong country gets a different page — or a block — before bot detection even runs. Pin the region the job needs.
  • Retrying on the dead IP. When a request fails on a sticky session, retrying on the same exit just burns it further. Rotate first, then retry.

How Yozh handles it

Yozh Scraper treats rotation as part of the job spec, not a global switch. Independent fetches draw from the rotating pool with concurrency caps; a flagged flow holds a sticky exit until it finishes, then releases it. A failed request rotates before it retries, so a single bad IP never poisons a run. You describe the work; the proxy policy follows from its shape.