Web Scraping Without Getting Blocked: Proxy Rotation, User-Agents, and Headers
A comprehensive guide to avoiding IP bans and captchas when web scraping. Proxy rotation strategies, browser fingerprinting, and anti-detection techniques.
The Arms Race of Web Scraping
Websites are spending billions on anti-bot technology. Cloudflare, DataDome, Akamai, and PerimeterX are deployed across most major sites. If you're scraping without anti-detection measures, you're leaving money on the table.
This guide covers the techniques that actually work in 2026.
Why Scrapers Get Blocked
Sites detect scrapers through multiple signals:
Detection Signal Weight
─────────────────────────────────────
IP behavior (rate, volume) 40%
Browser fingerprint 30%
Request headers 15%
JavaScript challenges 10%
Behavioral patterns 5%
No single signal gets you blocked. It's the combination.
Strategy 1: Proxy Rotation
Basic Rotation
import random
import requests
proxies = [
"http://proxy1:8080",
"http://proxy2:8080",
"http://proxy3:8080",
]
def fetch(url):
proxy = random.choice(proxies)
try:
return requests.get(url, proxies={"http": proxy, "https": proxy})
except:
proxies.remove(proxy) # Remove dead proxies
return fetch(url) # Retry with another
Problem: Sites detect patterns in rotation. Random is predictable.
Smart Rotation
class SmartRotator:
def __init__(self, proxy_pool):
self.pool = proxy_pool
self.usage = {} # Track requests per proxy
def get_proxy(self, url):
"""Select best proxy for a specific URL."""
domain = urlparse(url).netloc
# Avoid using same proxy for same domain too often
available = [
p for p in self.pool
if self.usage.get(p, {}).get(domain, 0) < 3
]
if not available:
available = self.pool
self.usage.clear() # Reset counters
proxy = random.choice(available)
# Track usage
self.usage.setdefault(proxy, {})
self.usage[proxy][domain] = self.usage[proxy].get(domain, 0) + 1
return proxy
Geo-Targeted Rotation
Match proxy geography to target:
def get_proxy_for_target(target_url):
"""Select proxy in same region as target server."""
target_country = geoip(target_url)
region_proxies = pool.get_proxies_by_country(target_country)
if region_proxies:
return random.choice(region_proxies)
return random.choice(pool.all) # Fallback
Strategy 2: Browser Fingerprinting
HTTP clients (requests, Scrapy) have obvious fingerprints. Modern scraping requires full browsers.
Playwright with Stealth
from playwright.sync_api import sync_playwright
from playwright_stealth import stealth_sync
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
# Apply stealth patches
stealth_sync(page)
# This makes headless Chrome look like a real Chrome
page.goto("https://bot.sannysoft.com")
# Without stealth: 14/17 detection tests FAIL
# With stealth: 2/17 detection tests FAIL
What Stealth Modifies
- WebDriver flag (removed)
- Chrome runtime (masked)
- Permissions (normalized)
- WebGL fingerprint (realistic)
- Canvas noise (added)
- Font list (realistic)
Randomizing Viewport
def random_viewport(page):
sizes = [
{"width": 1920, "height": 1080},
{"width": 1366, "height": 768},
{"width": 1536, "height": 864},
{"width": 1440, "height": 900},
]
size = random.choice(sizes)
page.set_viewport_size(size)
Strategy 3: Request Headers
Real browsers send consistent, ordered headers:
BASE_HEADERS = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
}
Common Mistakes
- Missing Accept-Encoding — Bots often omit this
- Wrong order — Browsers have specific header ordering
- Consistent headers — Always identical is suspicious
- Missing Sec- headers* — Modern browsers all send these
Strategy 4: Human Mimicry
Random Delays
import time
import random
def human_delay():
"""Wait like a human would."""
delay = random.gauss(3, 1) # Normal distribution
delay = max(1, min(10, delay)) # Clamp
time.sleep(delay)
Mouse Movements
async def human_like_navigation(page, url):
await page.goto(url)
# Scroll like a human
for _ in range(random.randint(2, 5)):
await page.mouse.wheel(0, random.randint(200, 600))
await page.wait_for_timeout(random.randint(500, 2000))
# Move mouse randomly
for _ in range(random.randint(3, 8)):
x = random.randint(100, 1800)
y = random.randint(100, 900)
await page.mouse.move(x, y, steps=10)
await page.wait_for_timeout(random.randint(100, 300))
Strategy 5: Captcha Handling
When captchas do appear:
class CaptchaHandler:
def __init__(self):
self.captcha_count = 0
def handle(self, page):
"""Detect and respond to captchas."""
# Option 1: Rotate proxy and retry
if self.captcha_count < 3:
self.captcha_count += 1
return "rotate_proxy"
# Option 2: Use solving service
# captcha_solver = CapSolver(api_key)
# return captcha_solver.solve(page)
# Option 3: Slow down dramatically
return "slow_down"
Putting It All Together
class AntiDetectionScraper:
def __init__(self, proxy_pool):
self.rotator = SmartRotator(proxy_pool)
self.captcha = CaptchaHandler()
def scrape(self, url):
proxy = self.rotator.get_proxy(url)
browser = self._create_browser(proxy)
try:
page = browser.new_page()
stealth_sync(page)
random_viewport(page)
human_delay()
response = page.goto(url, wait_until="networkidle")
if "captcha" in response.url.lower():
action = self.captcha.handle(page)
if action == "rotate_proxy":
browser.close()
return self.scrape(url) # Retry with new proxy
human_like_navigation(page)
return page.content()
finally:
browser.close()
Recommended Tool Stack
| Tool | Purpose |
|---|---|
| Playwright | Browser automation |
| playwright-stealth | Anti-detection patches |
| Pineapple Proxy | Residential proxy pool |
| CapSolver | Captcha solving |
| Scrapy | Large-scale scraping framework |
| Celery + Redis | Queue management |
Testing Your Setup
Use these sites to verify your anti-detection:
- https://bot.sannysoft.com — Browser fingerprint tests
- https://amiunique.org — Browser uniqueness
- https://iphey.com — Bot detection score
- https://pixelscan.net — Bot detection analysis
Conclusion
Getting blocked is not a question of if, but when — unless you implement proper anti-detection. Proxy rotation, browser fingerprinting, header management, and human mimicry together create a scraper that's indistinguishable from a real user.
Pineapple Proxy provides the residential proxy infrastructure to power your scraping operation. Browse our proxies or see our plans.
Related Posts
How to Check If a Proxy Is Working: Ping, Headers, and Anonymity Tests
Learn how to test proxy servers for connectivity, speed, and anonymity. From simple ping checks to advanced header analysis.
SEO Monitoring with Proxies: Track Your Rankings from 50+ Locations
How to use proxies for accurate SEO rank tracking across multiple geographies. Strategies for local SERP monitoring and avoiding Google penalties.
SOCKS5 Proxy Setup Guide for Telegram, Discord, and P2P Applications
A step-by-step guide to configuring SOCKS5 proxies for Telegram, Discord, torrent clients, and other popular applications across Windows, macOS, and Linux.