As of October 7, 2026, Shifter has the largest pool of live US IPs, more than Oxylabs and Bright Data, at a fraction of the cost.Shifter now has the largest pool of live US IPs

See benchmarks

Scraping Frameworks With Built-In Proxy Management: What Each One Handles

Scrapy, Crawlee, Colly, Playwright, Puppeteer and Selenium compared on proxy support: rotation, sessions and credentials, and what a rotating gateway changes.

Engineering · Updated November 9, 2026 · 7 min read
setrotatesessionCrawleeScrapyCollyPlaywrightPuppeteerSelenium
Six tools, three proxy jobs. The blue cells are handled for you.
Knowledge
On this page
  1. The short answer
  2. Crawlee: proxies as a managed resource
  3. Scrapy: one proxy per request, rotation is yours
  4. Colly: a simple round-robin switcher
  5. Playwright and Puppeteer: isolation per context
  6. Selenium: browser-wide, and awkward with passwords
  7. What a rotating gateway changes
  8. Wiring it up
  9. Which one to choose
  10. Sources and references

Almost every scraping framework lets you set a proxy. Far fewer manage proxies for you: rotating them, retiring the blocked ones, and keeping a session on one IP. If you use residential proxies, knowing which part the framework handles and which part the gateway handles saves a lot of glue code.

This guide compares six common tools on three questions: how you set the proxy, whether the tool rotates proxies itself, and whether it ties a session to one proxy. Then it shows the small amount of code each one needs behind a rotating gateway.

The short answer

ToolLanguageHow you set the proxyRotates on its ownTies a session to a proxy
CrawleeNode.jsProxyConfigurationYes: round robin, custom function, tiersYes, through its session pool
ScrapyPythonrequest.meta["proxy"] or environment variablesNo, needs your own middlewareNo
CollyGoSetProxy or SetProxyFuncYes: round-robin switcherNo
PlaywrightNode.js, Python, Java, .NETAt launch or per browser contextNoPer context
PuppeteerNode.jsLaunch flag or per browser contextNoPer context
SeleniumManyBrowser-wide optionNoNo

Crawlee is the only one that treats proxies as something to manage rather than a setting. The browser tools give you isolation per context, which is the building block for sessions, but the rotation logic is yours.

Crawlee: proxies as a managed resource

Crawlee’s ProxyConfiguration takes a list of proxy URLs and rotates through them “in a round-robin fashion”. Instead of a list you can pass a function that picks the proxy for each request. A third mode, tiered proxies, starts with the cheapest tier (often no proxy at all), moves up a tier when a site blocks the crawler, and drops back down when the lower tier works again.

The part that matters most is sessions. When the crawler runs with its session pool, every session keeps the same proxy: in the docs’ words, later calls “with the same sessionId will always return the same proxyUrl”. When Crawlee decides a session is blocked, it retires it and the next session gets a different proxy. Cookies follow the session, so the IP and the cookies stay together.

Scrapy: one proxy per request, rotation is yours

Scrapy’s HttpProxyMiddleware reads the proxy from request.meta["proxy"], and falls back to the http_proxy and https_proxy environment variables. Credentials can sit in the URL, as in http://username:password@host:port. That is all it does. Picking a different proxy per request, noticing bans and resting dead proxies are left to you or to an extension such as scrapy-rotating-proxies, which reads a list of proxies and treats non-200 or empty responses as bans by default.

Our Scrapy integration guide covers the basic setup, and the post on rotating residential proxies in Scrapy goes deeper into middleware.

Colly: a simple round-robin switcher

Colly, the Go framework, has SetProxy for one proxy and SetProxyFunc for a function that chooses one per request. Its proxy package includes RoundRobinProxySwitcher, which “rotates ProxyURLs on every request” and accepts http, https and socks5 URLs. There is no session concept: if two requests must share an IP, you decide that in your own function.

Playwright and Puppeteer: isolation per context

Browser automation tools do not rotate anything, but they let each browser context use its own proxy, which is how you run many separate sessions in one browser.

  • Playwright takes a proxy option with server, username and password, either at launch for the whole browser or on newContext() for one context. See our Playwright integration guide for the full setup.
  • Puppeteer sets a proxy with the --proxy-server launch flag, or per context with createBrowserContext({ proxyServer }). Neither takes credentials: you answer the proxy’s login challenge with page.authenticate().

Give each context its own session and you get one IP and one cookie jar per context, which is what a logged-in flow needs.

Selenium: browser-wide, and awkward with passwords

Selenium sets the proxy through browser options, so it applies to the whole browser. With Chrome the usual route is the --proxy-server switch, which has no place for a username and password. The common workarounds are a small browser extension that answers the login challenge, or a local forwarding proxy without a password that passes traffic on to your provider with credentials. Selenium Wire, once the popular fix, was archived in January 2024 and is no longer maintained.

What a rotating gateway changes

Framework rotation was designed for lists of proxies: a hundred IP:port pairs that you cycle, test and retire. A rotating residential gateway works differently. You connect to one host and port, and the gateway chooses the exit IP. That moves most of the work out of your code:

  • Rotation happens at the gateway, so a single proxy URL is enough. A round-robin list of one entry is just a setting.
  • Sessions are requested in the username. With Shifter you add sid-<id> to keep one exit IP and ttl-<seconds> to say how long to hold it, as explained in sticky vs rotating proxies.
  • Location is also part of the username, so a crawler can target a country or city per request without a second proxy list.

So the job of the framework shrinks to one thing: map its own idea of a session to the session ID in the proxy username.

Wiring it up

Crawlee. Pass a function that builds the username from Crawlee’s session ID. Session IDs can contain characters such as underscores, so keep only letters and digits:

import { CheerioCrawler, ProxyConfiguration } from 'crawlee';

const proxyConfiguration = new ProxyConfiguration({
  newUrlFunction: (sessionId) => {
    const sid = sessionId
      ? `-sid-${String(sessionId).replace(/[^a-zA-Z0-9]/g, '')}-ttl-600`
      : '';
    return `http://customer-USERNAME${sid}:PASSWORD@p.shifter.io:443`;
  },
});

const crawler = new CheerioCrawler({
  proxyConfiguration,
  useSessionPool: true,
  persistCookiesPerSession: true,
  async requestHandler({ request, $ }) {
    console.log(request.url, $('title').text());
  },
});

await crawler.run(['https://example.com/']);

When Crawlee retires a blocked session, the next session ID gives a new sticky IP on its own.

Scrapy. A small downloader middleware sets the proxy before Scrapy’s own proxy middleware runs. Requests that carry a session value in meta share one IP; the rest rotate:

# middlewares.py
class GatewayProxyMiddleware:
    def process_request(self, request, spider):
        sid = request.meta.get("session")
        user = "customer-USERNAME"
        if sid:
            user += f"-sid-{sid}-ttl-600"
        request.meta["proxy"] = f"http://{user}:PASSWORD@p.shifter.io:443"

# settings.py
DOWNLOADER_MIDDLEWARES = {
    "myproject.middlewares.GatewayProxyMiddleware": 350,
}

Playwright. One context per session:

const context = await browser.newContext({
  proxy: {
    server: 'http://p.shifter.io:443',
    username: 'customer-USERNAME-sid-cart1-ttl-600',
    password: 'PASSWORD',
  },
});

Which one to choose

Choose the framework for the site and the team, not for its proxy features. Crawlee is the strongest pick when you want blocking, sessions and proxy tiers handled for you in Node.js. Scrapy remains the fastest way to crawl large numbers of plain HTML pages in Python, and its proxy gap is a ten-line middleware. Playwright or Puppeteer are the right tools when pages need a real browser, and contexts give you clean sessions. Selenium works too, with more effort around credentials.

Behind a rotating gateway, all six reduce to the same pattern: one proxy URL, and a session ID in the username wherever requests must share an IP. For more on putting this into a production crawler, see our web scraping use case.

Sources and references

Frequently asked questions

Which scraping framework has the most complete built-in proxy management?

Crawlee. Its ProxyConfiguration rotates a list of proxies, can pick a proxy per request with your own function, can move up through tiers of proxies when a site starts blocking, and ties each session to one proxy so cookies and IP stay together. Scrapy, Playwright, Puppeteer and Selenium accept a proxy but leave rotation to you.

Does Scrapy rotate proxies on its own?

No. Scrapy's HttpProxyMiddleware sends each request through the proxy in request.meta['proxy'] or the http_proxy and https_proxy environment variables. Rotating between proxies needs your own middleware or a third-party extension such as scrapy-rotating-proxies.

Do I need framework proxy rotation if my provider gives me a rotating gateway?

Usually not. A rotating residential gateway changes the exit IP for you behind one host and port. What you still need from the framework is session handling: sending the same session ID in the proxy username for every request that must share one IP, such as a login or a checkout.

Why can't I put a username and password in Chrome's proxy flag?

Chrome's --proxy-server switch takes a host and port only. Puppeteer answers the proxy's login challenge with page.authenticate, and Playwright takes a username and password in its proxy option. Selenium has no built-in equivalent for Chrome, so people use a small browser extension or a local forwarding proxy.

Ready to get started?

Try Shifter's residential proxies, 205M+ IPs, 195+ countries, from $0.10/GB.

Get Started