Use Shifter with Scrapy
Wire Shifter's residential and ISP proxies into any Scrapy spider via a tiny downloader middleware. Per-request rotation, sticky sessions, and per-spider geo-targeting — all in 20 lines of Python.
Quick Start
Install
pip install scrapy Basic Usage
# settings.py
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.ShifterProxyMiddleware": 350,
"scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware": 360,
}
# middlewares.py
class ShifterProxyMiddleware:
PROXY = (
"customer-USERNAME-country-us-sid-123ABC:"
"PASSWORD@p.shifter.io:443"
)
def process_request(self, request, spider):
request.meta["proxy"] = self.PROXY
# Run as usual:
# scrapy crawl my_spider Features
Examples
Downloader Middleware (Sticky Session)
The standard way to plug a proxy into Scrapy. Add a `sid` to the username and every request from the spider will share one residential IP. Add `country-uk-city-london` to geo-target.
# myproject/middlewares.py
import secrets
class ShifterProxyMiddleware:
"""Routes every Scrapy request through Shifter's residential pool."""
def __init__(self, country="us", city=None, ttl=300):
self.sid = secrets.token_hex(4)
parts = [
"customer-USERNAME",
f"country-{country}",
]
if city:
parts.append(f"city-{city}")
parts.append(f"sid-{self.sid}")
parts.append(f"ttl-{ttl}")
username = "-".join(parts)
self.proxy_url = f"http://{username}:PASSWORD@p.shifter.io:443"
@classmethod
def from_crawler(cls, crawler):
s = crawler.settings
return cls(
country=s.get("SHIFTER_COUNTRY", "us"),
city=s.get("SHIFTER_CITY"),
ttl=s.getint("SHIFTER_TTL", 300),
)
def process_request(self, request, spider):
request.meta["proxy"] = self.proxy_url
# myproject/settings.py
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.ShifterProxyMiddleware": 350,
"scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware": 360,
}
SHIFTER_COUNTRY = "uk"
SHIFTER_CITY = "london" Per-Request Rotation
Don't set a sid — let the gateway rotate IPs on every request. Useful for high-volume scraping of paginated targets where each page should look like a different visitor.
# myproject/middlewares.py
import secrets
class ShifterRotatingMiddleware:
"""Rotates the residential IP on every Scrapy request."""
PROXY_HOST = "p.shifter.io:443"
def process_request(self, request, spider):
# Unique sid per request -> guaranteed new IP for every fetch
unique_sid = secrets.token_hex(6)
username = (
f"customer-USERNAME-country-{spider.country}"
f"-sid-{unique_sid}"
)
request.meta["proxy"] = (
f"http://{username}:PASSWORD@{self.PROXY_HOST}"
)
# myproject/spiders/products.py
import scrapy
class ProductsSpider(scrapy.Spider):
name = "products"
country = "us" # consumed by the middleware
custom_settings = {
"DOWNLOADER_MIDDLEWARES": {
"myproject.middlewares.ShifterRotatingMiddleware": 350,
},
"CONCURRENT_REQUESTS": 32,
}
start_urls = [
f"https://example.com/products?page={i}" for i in range(1, 100)
]
def parse(self, response):
for card in response.css(".product-card"):
yield {
"title": card.css("h2::text").get(),
"price": card.css(".price::text").get(),
"url": response.urljoin(card.css("a::attr(href)").get()),
} Per-Country Spiders (concurrent geo-scraping)
Build one spider class and parameterize the country at run time. Run multiple instances in parallel — each with its own residential IP pool.
# scrapy crawl localized -a country=uk
# scrapy crawl localized -a country=de
# scrapy crawl localized -a country=jp
import scrapy
class LocalizedSpider(scrapy.Spider):
name = "localized"
def __init__(self, country="us", *args, **kwargs):
super().__init__(*args, **kwargs)
self.country = country
self.start_urls = [
f"https://www.example.com/{country}/products",
]
def start_requests(self):
proxy = (
f"customer-USERNAME-country-{self.country}-sid-{self.country}-batch:"
f"PASSWORD@p.shifter.io:443"
)
for url in self.start_urls:
yield scrapy.Request(url, meta={"proxy": proxy}, callback=self.parse)
def parse(self, response):
for product in response.css(".product"):
yield {
"country": self.country,
"title": product.css("h2::text").get(),
"price": product.css(".price::text").get(),
} Scrapy + scrapy-playwright (JS-rendered pages)
When the target needs JavaScript, swap the downloader for scrapy-playwright. Pass the proxy on the launch options — Scrapy still handles scheduling and pipelines.
# pip install scrapy-playwright
# playwright install chromium
# settings.py
DOWNLOAD_HANDLERS = {
"http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
"https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
}
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
PLAYWRIGHT_LAUNCH_OPTIONS = {
"headless": True,
"proxy": {
"server": "http://p.shifter.io:443",
"username": "customer-USERNAME-country-fr-sid-789GHI",
"password": "PASSWORD",
},
}
# spider.py
import scrapy
class JsHeavySpider(scrapy.Spider):
name = "js_heavy"
start_urls = ["https://app.example.com/dashboard"]
def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(
url,
meta={"playwright": True, "playwright_include_page": True},
callback=self.parse,
)
async def parse(self, response):
page = response.meta["playwright_page"]
await page.wait_for_selector(".widget")
widgets = await page.query_selector_all(".widget")
for w in widgets:
yield {"label": await w.text_content()}
await page.close() Frequently asked questions
Common questions about using Shifter with Scrapy.
Write a tiny downloader middleware that sets `request.meta['proxy']` to your Shifter URL, then register it in DOWNLOADER_MIDDLEWARES with a priority lower than 750 (so it runs before HttpProxyMiddleware). Twenty lines of Python — no SDK required.
Generate a fresh sid in process_request — for example `secrets.token_hex(6)` — and embed it in the proxy username. Each request will get a different sid and therefore a different residential IP from Shifter's gateway. No external scrapy-rotating-proxies dependency required.
Use a fixed sid for the entire crawl run. Generate it once at the spider's start (or in the middleware constructor) and reuse it across every request. Add `ttl-N` to extend the IP lifetime to N seconds.
Yes. Pass a `-a country=uk` argument when running a spider, expose it as a setting in custom_settings, or read it inside your middleware. Build the proxy URL with `country-uk` in the username — every request from that spider goes through UK residentials.
Yes. Configure the proxy in PLAYWRIGHT_LAUNCH_OPTIONS — `server`, `username`, `password`. scrapy-playwright forwards them to Playwright's launch call, which handles the basic-auth challenge in headless mode without an extension.
Yes. Scrapy Cloud projects are just Scrapy spiders — your downloader middleware and Shifter proxy URL ship inside your project tarball. Add the credentials as Scrapy Cloud project settings (or env vars) so they're not committed to source.
Start Using Shifter with Scrapy
Plug Shifter's 205M+ residential and ISP proxies into your Scrapy spiders via a 20-line middleware. Per-request rotation, sticky sessions, and full scrapy-playwright support.