Most web scrapers work on day one and die within a month. The site changes a class name, an anti-bot wall appears, pagination shifts — and the script that "worked" quietly returns half a dataset. After building 300+ production scrapers, the pattern behind the reliable ones is consistent. This guide walks through it.
Before parsing any HTML, open your browser's network tab and watch what the page actually loads. A large share of modern sites fetch their content from a JSON API — and scraping that API directly is faster, more stable, and immune to CSS redesigns. Only fall back to HTML parsing when no structured endpoint exists.
# The order of preference for any target:
# 1. Hidden JSON API (network tab) → fastest, most stable
# 2. Embedded JSON (__NEXT_DATA__ etc.) → stable, one request
# 3. Server-rendered HTML → parse with BeautifulSoup
# 4. JavaScript-rendered page → last resort: browser automation
Plain requests announces itself as a script: its TLS handshake looks nothing like a browser's, and modern anti-bot systems fingerprint exactly that. For protected targets I default to curl_cffi, which impersonates real browser TLS fingerprints, with httpx for friendlier APIs:
from curl_cffi import requests
resp = requests.get(
"https://example.com/listings",
impersonate="chrome", # browser-grade TLS fingerprint
timeout=30,
)
resp.raise_for_status()
Full browser automation (Playwright, Selenium) is the heaviest tool in the box — use it when content genuinely renders client-side, not as a first resort. A browser costs 100× the resources of an HTTP request.
The two places scrapers silently lose data are pagination edges and transient failures. Treat both explicitly:
A scraper's job is a clean dataset, not a pile of responses. Before writing output: enforce a schema (required fields present, types correct), deduplicate on the record key, and fail loudly when the extraction rate drops — 200 items from a page that usually yields 1,000 means the site changed, and you want an alert, not a quiet half-file. Ship CSV or JSON with consistent field names your spreadsheet or database can ingest directly.
Reliable scraping is polite scraping: throttle request rates, cache what you've already fetched, collect only publicly available data, and honor the legal basics — terms of service, copyright, and data-protection rules like GDPR where they apply. Slow and steady also gets blocked less.
It depends on the target: curl_cffi or httpx for HTTP work (curl_cffi when anti-bot TLS fingerprinting is involved), BeautifulSoup for HTML parsing, and Playwright or Selenium only when content is rendered client-side by JavaScript.
Use a browser-grade TLS fingerprint (curl_cffi), realistic headers, moderate request rates, retries with backoff, and rotating proxies when the volume requires it. Most blocks come from obviously non-browser traffic patterns.
Scraping publicly available data is generally permissible, but you must comply with the target site's terms of service and applicable laws, including copyright and data-protection rules like GDPR/CCPA. Never scrape data behind logins or paywalls without authorization.
I build scrapers, Actors, and data pipelines as a service — fixed quote, fast turnaround.
Start a project →