Most scraping projects fail for the same reason: they treat every target the same way. A sitemap and a marketplace product page have nothing in common — one is a static file, the other runs ASN checks, request-rate scoring and TLS fingerprinting. Paying for residential traffic on the first is waste; using a datacenter IP on the second is a guaranteed block. The work is in the classification, not in the proxy list.
Classify the target before you choose a pool
| Target class | Examples | What it checks | Pool to use |
|---|---|---|---|
| Open | Sitemaps, static docs, public APIs, government data | Rate limits only | Direct, or datacenter |
| Light defence | News sites, blogs, small shops | User agent, crude rate limits | Datacenter, rotate on 429 |
| ASN-aware | Search engines, marketplaces, travel | ASN, IP reputation, request rate | Residential, rotating |
| Fingerprint-aware | Social platforms, ticketing, large retailers | ASN + TLS/HTTP2 fingerprint + behaviour | Residential + matching browser profile |
The practical consequence: route per host. A single crawl usually touches a protected HTML page, an unprotected JSON API and an image CDN, and only the first needs a residential exit. Splitting traffic this way commonly cuts residential gigabytes by a third without touching success rate.
Rotate per request, and control the two things the target can see
You cannot control how a site scores IP reputation. You can control requests per IP and request shape. Rotating per request handles the first; realistic headers, a real user agent, cookie persistence and paced intervals handle the second.
# Scrapy: rotation at the middleware level + a humanly possible rate # settings.py CONCURRENT_REQUESTS = 16 CONCURRENT_REQUESTS_PER_DOMAIN = 8 DOWNLOAD_DELAY = 1.0 RANDOMIZE_DOWNLOAD_DELAY = True AUTOTHROTTLE_ENABLED = True AUTOTHROTTLE_TARGET_CONCURRENCY = 4.0 RETRY_TIMES = 2 DOWNLOADER_MIDDLEWARES = { "scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware": None, "myproject.middlewares.RotatingProxy": 543, }
# middlewares.py — new session id per request import uuid class RotatingProxy: def __init__(self, pwd): self.pwd = pwd @classmethod def from_crawler(cls, crawler): return cls(crawler.settings.get("PROXY_PASSWORD")) def process_request(self, request, spider): sid = uuid.uuid4().hex[:12] user = "ACCOUNT-country-us-sid-%s" % sid request.meta["proxy"] = "http://%s:%s@GATEWAY_HOST:PORT" % (user, self.pwd)
sid inside the retry path — a 403 usually means that specific
exit is burned, not that the URL is unreachable.Do not render what you can read
A headless browser is 10 to 50 times the bandwidth of a plain HTTP request, and gigabytes are what you are
buying. Check three things before you reach for Playwright: does the page ship data in JSON-LD or a
__NEXT_DATA__ block, is there a public or internal JSON endpoint the page calls, and does the HTML
already contain what you need without JavaScript. In practice, roughly two thirds of product and listing pages
answer yes to one of those.
# price and stock often sit in structured data, no browser needed import json, re, requests r = requests.get(url, proxies=PROXY, timeout=30) m = re.search(r'', r.text, re.S) if m: data = json.loads(m.group(1)) print(data.get("offers", {}).get("price"))
What a page actually costs
Work in billable gigabytes, not in requests. Take a 400 KB HTML page, fetched once, at a 95% success rate:
| Item | Value | How it is derived |
|---|---|---|
| Page weight | 400 KB | HTML only; images blocked |
| Pages per GB | 2,500 | 1,000,000 KB / 400 KB |
| Attempts per 1,000 usable pages | 1,053 | 1,000 / 0.95 |
| Billable traffic | 0.42 GB | 1,053 × 400 KB |
| Cost at $1/GB | $0.42 per 1,000 pages | Traffic × unit price |
Two things follow. First, retries are not free — every blocked request is paid for and produces nothing, which is why success rate dominates the price comparison. Second, the cheapest optimisation is usually fewer bytes per page, not a cheaper proxy: dropping a headless browser in favour of the embedded JSON often cuts cost by 80% before you negotiate anything.
A crawl loop that survives real targets
- Fetch the cheap way first. Plain HTTP, no browser, per-request rotation.
- Detect the block, not just the status. A 200 page that says "unusual traffic" is a block. Match on known markers, page length and the presence of the field you came for.
- Escalate once. If the field is missing, retry with a new session; if it is still missing, try the country-specific exit; only then consider rendering.
- Record the outcome per host. Success rate, median latency, bytes per usable row. This table tells you which targets deserve residential traffic and which do not.
- Back off on 429. Cut concurrency per domain in half and raise the delay; do not simply add more IPs.
Concurrency, in numbers you can act on
| Concurrency per domain | When it works | Watch for |
|---|---|---|
| 1–2 | Fingerprint-aware sites, first exploratory run | Nothing yet — this is the baseline |
| 4–8 | ASN-aware marketplaces with a 95% success rate | Success rate dropping below 90% |
| 8–16 | Light-defence sites on datacenter exits | 429 bursts; halve and retest |
| 16+ | Your own infrastructure or an authorised test | Anything else — you are the problem |
Treat these as starting points to tune against your own 403 rate, not as limits. The single biggest mistake is raising concurrency to compensate for a low success rate — that makes the block worse, not better.
Frequently asked questions
Do I need residential proxies for web scraping?
Only for targets that defend themselves. Static HTML sites, public APIs and sitemaps do not care where you come from — scraping them through residential traffic is paying a premium for nothing. Marketplaces, search engines, social platforms, travel sites and real-estate portals do run ASN and behaviour checks, and there a residential exit is the difference between 60% and 95% success.
How many concurrent requests can I run?
Concurrency is limited by the target, not by us: one account can run as many parallel requests as your workers need. As a starting point keep 5–10 concurrent requests per domain, watch the 429/403 rate, and raise it in steps. More concurrency on one domain gets you blocked faster than more throughput.
Is rotating better than sticky for scraping?
Rotating, for anything read-only. One request per exit keeps per-IP counts low, which is the strongest signal you can control. Use sticky sessions only when a workflow needs continuity — login, cart, paginated results tied to a session cookie.
How much does scraping cost per 1,000 pages?
At a 400 KB page and a 95% success rate, roughly 0.42 GB of billable traffic per 1,000 usable pages, so about $0.42 at $1/GB. Blocked requests still cost traffic, so your real figure is higher — measure it on your own target rather than trusting a generic estimate.
Do I need a headless browser?
Only when the data is rendered client-side or the site requires JavaScript to issue a token. A headless browser costs 10–50x the bandwidth of a plain HTTP request, so try the API or the embedded JSON first. Many product pages ship price and stock in a __NEXT_DATA__ or JSON-LD block.
What about robots.txt and terms of service?
Read them. Proxies change your origin, not your obligations: only collect public data you have a right to, respect robots directives and rate limits, and do not use residential exits to bypass an access decision a site has already made about you. Lawful basis is the operator's responsibility.
Run your own target list first
Residential from $1/GB billed by traffic, static ISP from $2/IP per month from 15 IPs. No monthly commitment — measure your success rate before you scale.
Create account