Web Scraping Best Practices
Web scraping best practices come down to five habits: collect only public data you are allowed to use, keep the request rate low and steady, retry failures with exponential backoff (3-5 attempts, honoring Retry-After), validate every record so a changed page layout is caught the same day, and spread large jobs over rotating IPs instead of hammering a site from one. A scraper built this way breaks less, costs less and gets blocked less. ProxyField's rotating residential proxies give each request a new IP from 412M+ in 195+ countries, from $1.95/GB; start with the Easy Trial: 1 GB for $2.95.
- residential IPs
- 412M+
- from, traffic never expires
- $1.95/GB
- Easy Trial
- 1 GB for $2.95
On this page 10 sections
- The checklist
- Respect the site you scrape
- Retries done right
- What to do when a scraper run fails
- How to handle website HTML structure changes
- Web scraping techniques, from basic to advanced
- Common web scraping challenges and how to fix them
- Best practices for contact information extraction
- Proxy best practices for scraping
- Web Scraping Best Practices FAQ
The checklist
| Area | Practice | Why |
|---|---|---|
| Permission | Read the site's terms and robots.txt; scrape public pages only | Avoids legal and contract trouble, and avoids pages the site asks bots to skip |
| Load | One request at a time per site to start, about one request per second or slower; raise only if the site copes | Keeps the site fast for its users and keeps you under rate limits |
| Source | Prefer an official API or the JSON the page loads over parsing HTML | Fewer bytes, stabler fields |
| Fetching | Plain HTTP first; a headless browser only for pages that need JavaScript | A browser downloads scripts, styles and images too, so each page costs far more time, memory and traffic |
| Errors | Timeouts on every request; retries with backoff for 429, 5xx and network errors only | Recovers from blips without turning a block into a flood |
| Change | Stable selectors, schema validation, alerts on empty fields | Catches layout changes before bad data spreads |
| State | Checkpoint progress; make runs resumable and idempotent | A crash costs minutes, not the whole run |
| Identity | Realistic, consistent headers and sessions; rotate IPs per request or per session | One IP sending thousands of requests is the first thing sites limit |
| Data | Collect only the fields you need; protect personal data | Data minimization is a legal principle in the EU and UK, and good hygiene everywhere |
| Monitoring | Log status codes, success rate, records per page and run time | You see a problem in the numbers before a customer sees it in the data |
Respect the site you scrape
The basics protect both the site and your scraper:
-
Check the rules first
Robots.txttells crawlers which paths the owner wants left alone (the format is standardized in RFC 9309), and the terms of use may restrict automated access. Whether these bind you is a legal question; the overview is in is web scraping legal. -
Scrape public pages only
Do not log in to collect data you are not entitled to, and do not work around access barriers.
-
Keep the load low
Start at about one request per second per site, slow down if responses slow down, scrape off-peak, and slow down at the first 429 or 503.
-
Cache and scrape incrementally
Do not re-download pages that have not changed; use sitemaps'
lastmod,ETagorLast-Modifiedheaders and conditional requests. -
Identify the job when you can
A descriptive user agent with a contact address lets a site owner reach you instead of blocking you.
Retries done right
A request can fail for reasons a retry fixes (a timeout, a dropped connection, a busy server) and reasons it cannot (a page that does not exist, a request the site refuses). Retry only the first kind, with growing waits.
Which status codes to retry:
| Response | Retry? | What to do |
|---|---|---|
| Timeout, connection reset, DNS hiccup | Yes | Back off and retry, up to 3-5 times |
429 Too Many Requests | Yes, after waiting | Honor Retry-After; lower your rate; see how to fix 429 Too Many Requests |
500, 502, 503, 504 | Yes | Back off; a 503 may also carry Retry-After; see 502 and 504 gateway errors |
403 Forbidden | Not with the same request | Slow down, check headers and session, try a new IP; see 403 Forbidden from Cloudflare |
400, 401, 404, 410 | No | Fix the request or drop the URL |
How to wait between attempts. Use exponential backoff with jitter: wait about 1 s, then 2, 4, 8 and so on up to a cap, and randomize each wait so many workers do not retry in step. If the response has a Retry-After header, wait that long instead (RFC 9110, Retry-After). Stop after 3-5 attempts and record the URL as failed.
Retry only safe requests. GET and HEAD can be repeated without side effects. Do not retry a POST blindly: if the first one reached the server, a retry may submit a form twice.
In Python, Requests does not retry failed requests by default, but urllib3's Retry adds backoff, status-code retries and Retry-After support to a session:
import requests
from requests.adapters import HTTPAdapter
from urllib3.util import Retry
retry = Retry(
total=5, # at most 5 retries
backoff_factor=1, # exponential waits between attempts
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET", "HEAD"], # never POST
respect_retry_after_header=True,
)
session = requests.Session()
session.mount("https://", HTTPAdapter(max_retries=retry))
session.mount("http://", HTTPAdapter(max_retries=retry))
resp = session.get("https://books.toscrape.com/", timeout=(10, 30)) # connect, read timeoutsWhen retries run out, Requests raises requests.exceptions.RetryError (the "Max retries exceeded" message). Catch it, log the URL and move on: one failed page should never stop the run. Always set a timeout; without one, a request can hang for a very long time and a retry never starts.
What to do when a scraper run fails
Design the run so a failure costs little:
- Checkpoint. Store which URLs are done (in a database table, a file, or the framework's job directory) and skip them on restart.
- Separate the failures. Put URLs that failed after all retries in a "failed" list and re-run them later, at a slower rate.
- Make writes idempotent. Upsert by a stable key (the URL or the product ID) so re-running does not create duplicates.
- Alert on the numbers. Alert when the success rate drops, records per page fall to zero, or the run takes twice as long as usual.
- Keep evidence. Save the HTML (or a screenshot) of a page that failed to parse; it shows at once whether the layout changed or you got a block page.
Then read the failure: a wave of 429 or 403 means you are going too fast or from too few IPs; timeouts point to the network or the proxy; parse errors with 200 responses mean the page changed or you received a block page with a normal status.
How to handle website HTML structure changes
Layouts change without notice, and a scraper that silently writes empty prices is worse than one that crashes. Build for it:
-
Use the most stable source on the page
Structured data in
<script type="application/ld+json">(schema.org Product, Offer, Article) and embedded JSON state change far less often than visible markup. -
Choose robust selectors
Prefer IDs,
data-*attributes anditemprop, short paths anchored on meaningful classes, and text labels ("Price:") over long chains likediv > div:nth-child(3) > span. -
Keep fallbacks
Try two or three selectors for a critical field, in order, and log which one matched.
-
Validate every record
Check types and ranges (a price is a positive number, a title is not empty) with a schema; reject and count bad records instead of saving them.
-
Alert on drift
A sudden rise in empty fields or a drop in items per page is the signal that the layout changed.
-
Test against saved pages
Keep a few saved HTML samples and run the parser on them in CI, so a selector change is tested before it runs.
Web scraping techniques, from basic to advanced
| Technique | What it is | When you need it |
|---|---|---|
| HTTP request + HTML parser | Download the page and select fields (Requests and Beautiful Soup, fetch and Cheerio) | Static pages: the default |
| Calling hidden APIs | Request the JSON endpoints the page itself uses | Pages that load data with JavaScript |
| Headless browser | Render the page in Chromium with Playwright, Puppeteer or Selenium | Data that exists only after scripts run, clicks or scrolls; see headless browser scraping |
| Crawling | Follow links from a start page with a queue and deduplication | Collecting every page of a section or a site |
| Sessions and cookies | Keep cookies and one IP for a sequence of requests | Multi-step flows such as pagination tied to a session |
| Concurrency and async | Many requests in flight, with per-site limits | Large jobs on deadlines |
| Rotating proxies | A new IP per request or per session, by country or city | Rate limits per IP, regional prices and results |
| Incremental scraping | Re-scrape only what changed since the last run | Daily monitoring of large catalogs |
Basic scraping is one script that fetches and parses; advanced scraping adds the rest of the table: queues, retries, sessions, browsers where needed, proxies and monitoring.
Common web scraping challenges and how to fix them
| Challenge | Fix |
|---|---|
| Content loaded by JavaScript | Call the page's JSON endpoint; otherwise render with a headless browser |
Rate limits (429) | Slow down, honor Retry-After, spread requests over rotating IPs |
Blocks (403, CAPTCHA pages) | Lower the rate, send consistent browser headers, keep sessions; read how anti-bot systems detect scrapers |
| Pagination and infinite scroll | Follow "next" links or the page's API parameters; scroll in a browser only if you must |
| Layout changes | Structured data, robust selectors, validation and alerts |
| Duplicates | Normalize URLs, deduplicate by key, upsert |
| Regional differences | Request from an IP in the country or city whose prices or results you need |
| Scale and cost | Plain HTTP where possible, block images in browsers, scrape incrementally |
Best practices for contact information extraction
Names, email addresses and phone numbers of people are personal data in the EU and the UK even when they are published on a public page, so the GDPR and the UK GDPR apply to collecting them (Regulation (EU) 2016/679). Practices that keep a contact-data project defensible:
- Prefer business contact points. Generic company addresses and phone numbers published for inquiries (sales@, info@) carry less risk than personal ones.
- Define the purpose and the fields before you start, and collect nothing else (data minimization).
- Have a lawful basis. For B2B outreach this is usually legitimate interests, which needs a documented balancing test; France's regulator, the CNIL, lists measures for scraping under that basis, such as defining the data in advance and excluding sites that object to scraping (CNIL focus sheet).
- Tell people. When you collect personal data not from the person, Article 14 GDPR requires you to inform them, generally within one month.
- Follow the outreach rules. Email marketing has its own rules: the CAN-SPAM Act in the US (FTC compliance guide) and the ePrivacy rules in the EU, which the UK applies through PECR.
- Secure and delete. Store contact data securely, honor objections and deletion requests, and delete data you no longer need.
ProxyField's acceptable use allows collecting public web data; it does not allow data behind logins you are not entitled to, or fraud. Collecting personal contact data (emails, phone numbers) needs a lawful basis, and that is the customer's responsibility. Public company data is fine. Spam is forbidden in any case.
Proxy best practices for scraping
- Match rotation to the job. Rotate per request for independent pages (product pages, search results); keep a sticky session for flows that need one IP (pagination tied to cookies, multi-step forms). ProxyField sessions can stay on one IP from 30 s to 36 h.
- Target the location you are measuring. Prices, search results and ads differ by country and city; request from there.
- Do not use IPs to hide a too-fast scraper. More IPs at the same rate still overload the site; pace first, then rotate.
- Budget traffic. Residential proxies are billed per GB: measure the bytes per page, block images and fonts in browsers, and avoid re-downloading unchanged pages.
ProxyField residential proxies cost $2.95/GB for 1 GB down to $1.95/GB from 1,000 GB, with no subscription; the traffic never expires, and all purchases are final. See rotating proxies and residential and mobile proxy pricing.
This article describes general practice and is not legal advice.
Web Scraping Best Practices FAQ
What are web scraping best practices?
ProxyField's rotating residential proxies (412M+ residential IPs, from $1.95/GB) cover one of the web scraping best practices, spreading large jobs over many IPs. The others: scrape only public data you may use, keep the rate low, retry failures 3-5 times with exponential backoff and Retry-After, validate every record, and add checkpoints and monitoring so a failed run is quick to resume.
What are the best practices for extracting contact information with web scraping?
Treat names, emails and phone numbers as personal data: define the purpose and fields first, prefer generic business contact points, document a lawful basis, inform people as Article 14 GDPR requires, and follow email marketing rules such as CAN-SPAM. Collect nothing you do not need and delete what you no longer use.
What happens if a scraping request fails?
Your client gets an error status (such as 429 or 503) or an exception (timeout, connection reset). A well-built scraper retries the retryable ones with backoff, records the URL as failed after the last attempt and continues with the next page.
How should I retry failed requests when scraping?
Retry only timeouts, connection errors, 429 and 5xx responses, with exponential backoff and random jitter (about 1, 2, 4, 8 s up to a cap), and wait for Retry-After when the server sends it. Retry GET requests, not POST, and stop after 3-5 attempts.
How many times should you retry a failed request?
Three to five attempts covers most temporary failures. If a URL still fails, park it in a failed list and retry it later at a slower rate, rather than raising the retry count, which only adds load during a block.
Which status codes should I retry?
Retry 429, 500, 502, 503 and 504, plus network errors and timeouts. Do not retry 400, 401, 404 or 410, and do not repeat a 403 unchanged: slow down and change the session or IP first.
Should I retry POST requests?
Not automatically. A POST can change something on the server, such as submitting a form, so a retry after a timeout may do it twice; retry it only when the endpoint is idempotent or uses an idempotency key.
Does Retry-After matter when scraping?
Yes: it is the server telling you how long to wait, in seconds or as a date, usually on 429 and 503 responses. Honor it instead of your own backoff; urllib3's Retry does this when respect_retry_after_header is on, which is its default.
What should I do when a scraper run fails?
Resume from a checkpoint instead of starting over, and re-run only the URLs that failed. Then read the cause: many 429 or 403 responses mean too fast or too few IPs, timeouts point to the network, and parse errors on 200 responses mean the page changed.
How do I handle website HTML structure changes when scraping?
Read structured data such as JSON-LD where it exists, use stable selectors with fallbacks, validate each record against a schema, and alert when empty fields rise. Test the parser on saved HTML samples so a change is caught before it reaches your data.
Show 4 more questionsShow fewer questions
What are common web scraping challenges and how do I fix them?
The common ones are JavaScript-rendered content, rate limits and blocks, layout changes, duplicates and regional differences. The fixes, in that order: call the JSON endpoint or use a headless browser; slow down, retry with backoff and rotate IPs; use stable selectors and validation; deduplicate by key; and request from an IP in the right country or city.
What is the difference between basic and advanced web scraping?
Basic scraping is one script that downloads pages and parses them. Advanced scraping adds what large or protected jobs need: hidden API calls, headless browsers only where required, queues and concurrency, sessions, rotating proxies, incremental runs and monitoring.
What are the best practices for scaling web scraping projects?
Use a queue with per-site rate limits, keep plain HTTP as the default, and use browsers only for the few pages that need them. Make runs resumable and writes idempotent, monitor success rates, and size your proxy traffic from measured bytes per page.
What web scraping techniques are there?
The core techniques are HTTP requests with an HTML parser, calling the JSON APIs a page uses, headless browser rendering and crawling with a link queue. Larger jobs add session and cookie handling, concurrent and incremental scraping, and rotating proxies for rate limits and regional data.
Related pages
Easy Trial: 1 GB for $2.95.
Try for the price of 1 GB, no subscription, traffic never expires.