

Dovydas Vėsa
Last updated on
2026-06-01
12 min read
Following web scraping best practices is the difference between a pipeline that runs reliably for months and one that breaks the moment a target site changes a CSS class or tightens its bot traffic control algorithms. Whether you're collecting pricing data, monitoring news, or building training datasets, every scraper eventually runs into the same wall: rate limits, CAPTCHAs, IP bans, and dirty data.
This guide covers everything you need to build web scrapers that are fast, efficient, legally legitimate, and production-ready. We'll walk through technical hygiene, legal obligations, tool selection, and the infrastructure decisions – proxies, headless browsers, and scraper APIs – that separate amateur scraping workflows from professional data extraction pipelines.
If you're new to scraping, we recommend starting with our complete guide to web scraping before diving into optimization techniques.
Web scraping is the automated process of extracting data from websites. A scraper typically sends requests to a webpage, downloads the HTML or rendered dynamic content, and parses specific elements like prices, product names, reviews, or search results.
At small scale, scraping process can appear simple, but at production scale, most scrapers are not bulletproof and tend to fail fairly quickly for various reasons. Here's why:
Bot control is more advanced. Sites now fingerprint not only browser headers, but also TLS handshakes, mouse movement patterns, canvas rendering, and JavaScript execution behavior. A basic requests call stands out immediately.
Dynamic rendering is everywhere. Most modern websites tend to render critical content client-side via React, Vue, or Angular. Static HTML parsers receive near-empty results.
IP-based blocking is more aggressive. Many web administrators block entire ASN IP ranges associated with cloud providers, even instantly flagging datacenter IPs outright.
Frequent structure changes. Web scrapers that rely on CSS selectors or XPath expressions break the moment a developer renames a class or restructures the DOM, which are more frequent than ever.
Understanding these points of failure is the first step. The next is building around them thoughtfully.
Implementing the following best practices web scraping is guaranteed to improve your success rates, reduce server load, and ensure higher data quality.
Before writing a single line of web scraping code, check whether the website owner already provides the data you need in a machine-readable format.
Look for a public REST or GraphQL API. Many platforms (major social networks, search engines, e-commerce marketplaces) provide official data endpoints, often rate-limited but stable and legally unquestionable.
Check for bulk data downloads. Government portals, academic repositories, and open data initiatives frequently publish CSV, JSON, or XML dumps updated on a predictable schedule.
Inspect the browser's Network tab in DevTools while browsing the site. Many single-page apps fetch data from an internal API that you can call directly – cleaner, faster, and far more resilient than parsing HTML.
Even if an API requires a paid subscription, the savings in maintenance costs often break even with the initial investment in the long run. That said, web scraping should generally be your fallback option when official structured data access isn't available.
Every proper web server publishes a robots.txt file at its root directory (e.g., https://example.com/robots.txt). These files instruct search engine robots which paths are allowed or forbidden, and may specify a crawl delay.
User-agent: *
Disallow: /admin/
Disallow: /checkout/
Crawl-delay: 10Respecting robots.txt is both a technical advice and an ethical one. While it is not legally binding in most jurisdictions, ignoring it increases the likelihood of IP bans from website owners and may create unnecessary load on the target website.
Luckily, you can also use robots.txt programmatically. For example, in Python the built-in urllib.robotparser module handles this without any third-party dependencies:
from urllib.robotparser import RobotFileParser
rp = RobotFileParser()
rp.set_url("https://example.com/robots.txt")
rp.read()
if rp.can_fetch("MyBot/1.0", "https://example.com/products"):
# proceed
passFlooding a site with hundreds of requests per second is the single fastest way to get blocked – and it's inconsiderate to say the least. Even if you have legitimate business reasons to scrape, flooding a server affects real users. Here are a few rate-limiting ideas for your scraping activities:
Introduce delays between requests. A fixed delay of 1–3 seconds is a reasonable start. Randomize it (e.g., random.uniform(1, 3)) to avoid predictable bot patterns.
Schedule web crawlers during off-peak hours. We recommend running intensive jobs between midnight and 6 AM site's local time. Less impact and often fewer "are you a robot?" messages.
Limit concurrency. Using async, it's easy to fire 200 concurrent requests, so cap concurrency to 5–10 instances.
Even with respectful rate limits, the same IP address sending successive requests to the same domain will trigger thresholds. Sites track request frequency, patterns, and cumulative volume per IP. Once blocked, that IP may stay blacklisted by the website owner indefinitely.
IP rotation solves this by distributing requests across a pool of proxy servers, making your traffic indistinguishable from organic visitors. There are three main proxy types to know:
| Proxy type | Use case | Access reliability |
| Datacenter | High volume, cost-saving | Medium |
| Residential | Reliable access to difficult targets | High |
| ISP | Speed + residential-like trust | Very high |
| Mobile | Geo-targeting, hardest to distinguish | Highest |
Oxylabs Proxies cover all these categories with a pool of over 177 million residential IPs, making it straightforward to integrate rotation into any scraping stack – whether you're using requests, Scrapy, or a headless browser. Rotating properly means each request (or session) draws a fresh IP from your pool, so total request counts never build up against a single address.
Using a proxy with your requests is as simple as just a few lines of code:
import requests
proxies = {
"http": "http://customer-USERNAME:PASSWORD@pr.oxylabs.io:7777",
"https": "http://customer-USERNAME:PASSWORD@pr.oxylabs.io:7777"
}
response = requests.get("https://ip.oxylabs.io/location", proxies=proxies)Websites don't only analyze IP addresses. They also inspect browser fingerprints and HTTP headers.
Using the same request headers repeatedly creates detectable patterns, so you should rotate:
User-agent header strings
Accept-Language headers
Referer values
Browser fingerprints
Session cookies when appropriate
Keep in mind that current bot traffic control systems compare browser versions, operating systems, screen dimensions, rendering behavior, and even TLS fingerprints.
This is why many projects relying on larger scale web scraping operations move from simple HTTP clients toward browser-based automation tools or simpler libraries like fake-useragent to pull up-to-date, realistic strings automatically.
Most websites today don't serve all of its content in the initial HTML response and loads it after JavaScript executes – through API calls, lazy loading, infinite scroll, etc. In that case, a plain HTTP client like requests will only scrape a skeleton page with little to no scraped data.
Headless browsers solve this by running a full browser engine (Chromium, Firefox, WebKit) without a graphical interface, executing JavaScript exactly as a real browser would. Best known browser automation tools you should keep your eye out for are Selenium, Playwright, and Puppeteer.
However, on top of creating the browsing automation script yourself, handling custom masking measures is a whole another challenge to deal with. To skip the hassle, tools like Oxylabs Headless Browser are built as ready-to-use solutions that handle all advanced techniques automatically – rotating fingerprints, patching detection, managing browser instances at scale – so you get real browser rendering without the maintenance headaches of running your own custom browser fleet.
Honeypots are traps embedded in web pages specifically to catch automated crawlers. The most common one is a hidden link or form field that is invisible to legitimate users (hidden via CSS display: none or visibility: hidden) but caught by a bot that follows commands blindly. To avoid honeypots:
Consider CSS element visibility. Skip links that are not visible to users. In Playwright or Selenium, check element visibility before interacting with it.
Don't fill hidden forms. Verify that all fields you're populating are visible.
Check for CSS-hidden navigation. Inspect stylesheets to identify elements hidden with display: none, opacity: 0, or off-screen positioning.
A scraper without error handling is not a pipeline – it's a script. Production web scrapers face an endless variety of failures: network timeouts, connection resets, rate-limit responses, server errors, redirects to login pages, CAPTCHA challenges, and structural changes that cause parsers to return nothing where scraped data is expected.
To make your web web scraping projects more reliable, consider these robust error handling mechanisms:
Classify errors. Separate retryable errors (network timeout, 429, 503) and terminal ones (404, auth failure, target website structure change).
Implement exponential backoff with jitter. On retryable failures, wait 2^attempt + random_jitter seconds before retrying, but keep a reasonable ceiling (e.g., 60 seconds).
Set a retry ceiling. After X failed attempts (typically 3–5), log the failure and move on to not stop your pipeline.
Log everything. Request URL, response code, response time, and retry count – you'll need this data to diagnose failures at scale.
Here's a an example of these implemented in Python:
import time, requests, random
def fetch_with_retry(url, max_retries=5):
for attempt in range(max_retries):
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
return response
except requests.exceptions.HTTPError as e:
if e.response.status_code in (429, 503):
wait = (2 ** attempt) + random.uniform(0, 1)
time.sleep(wait)
else:
raise
except requests.exceptions.RequestException:
time.sleep(2 ** attempt)
raise Exception(f"Max retries exceeded for {url}")Every redundant HTTP request costs time, money, and goodwill with the target website server. If your pipeline processes the same websites multiple times (re-parsing, format changes, or pipeline reruns) caching raw responses is a very efficient optimization to avoid unnecessary requests:
Cache raw HTML/JSON responses. Store with a timestamp and invalidate after a your chosen TTL appropriate to the data's change frequency.
Use HTTP cache headers. Cache-Control, ETag, and Last-Modified headers. On subsequent requests, send If-None-Match or If-Modified-Since, and if the content hasn't changed, you'll get a 304 with no body.
Separate fetching from parsing. Store raw responses, then parse them in a separate step. This lets you re-parse after fixing a bug without re-fetching duplicate data you already had anyway.
Even a web scraper that successfully fetches pages can produce unusable data if parsing and validation are not set up correctly. Field values may be missing, distorted, inconsistently formatted, or encoded incorrectly. Efficient data extraction best practices include:
Schema validation. Define expected fields, types, and constraints. Python's Pydantic or JavaScript's Zod are among the most popular options.
Normalizing formats. Dates, currencies, phone numbers, and addresses come in dozens of formats. Standardize to ISO 8601, minor currency units, E.164, and a canonical address schema during the extraction process.
Stripping and cleaning strings. HTML entities, non-breaking spaces (\xa0), zero-width characters, and BOM markers can cause bugs later. Scan for them and clean up attentively.
Deduplication. Use canonical URLs (strip UTM parameters, normalize trailing slashes) and consider content hashing to prevent duplicate entries.
A scraper that ran perfectly three months ago may be silently returning empty records today because the site added a data-testid attribute and removed the class your selector relied on. Without monitoring, you won't know until a downstream system flags incomplete data, so in that case:
Track record counts and completeness per run. If a run returns 20% fewer records than last week, trigger an alert before data users notice.
Use CSS selectors that are change-resistant. Prefer semantic attributes (aria-label, data-product-id) over auto-generated class names that change with every build.
Set up alerting. Route scraper metrics to a dashboard (Grafana, Datadog) with alerts on error rate, empty-field rate, and run duration.
There's a lot of grey area between legality and data collection, so technical proficiency and legal compliance must go hand-in-hand. Even a scraper that works perfectly can still expose its user to legal and regulatory risk if not used thoughtfully and carefully.

Web content can be protected by copyright the moment it is created, so scraping and republishing that content in its original form will lead to infringement most of the time. However, several factors limit copyrights:
First is Fair Use (US) and Fair Dealing (UK/AU/CA). Transformative uses, commentary, criticism, and research may qualify. The key differentiators are the purpose of use, the nature of the work, the amount of data collected, and the effect on the market for the original.
You should also remember that factual data is not copyrightable. A database of prices, stock tickers, or sports scores is factual – but the specific creative expression of that data (chart design, editorial annotations) may be.
Lastly, the EU's sui generis database right protects substantial investment in database creation, independent of whether the content itself is copyrightable or not.
The practical takeaway here is that scraping facts to power your own analysis can be seen as defensible, but reproducing someone else's article, product descriptions, or creative work verbatim is not.
If you operate in or target EU web pages, GDPR applies to even publicly available data, so "Public" doesn't mean "free to use for any purpose". On top of that, key obligations apply when scraping personal data:
Establish a lawful basis. Legitimate interest is frequently cited, but your interest must not overstep the data subject's rights.
Purpose limitation. Data collected for one purpose cannot be repurposed.
Data minimization. Collect only what you need. Don't scrape names, emails, and profile photos if you only need job title names.
Data subject rights. Individuals can request access or deletion, even for data scraped from public sources.
Data retention. Employ TTL and don't keep personal data longer than necessary.
Almost every website's Terms of Service prohibits automated access to sensitive data. However, ignoring a target website's terms is not directly illegal, but the consequences can include account termination and IP banning, civil claims for breach of contract, and in some jurisdictions – claims under computer access laws.
The practical standard is that if you're scraping valuable data at scale for commercial purposes and from a site that actively prohibits it, consult a lawyer immediately. For lower-stakes, research-oriented, non-republishing use cases, the legal risk is generally low but as time shows – never zero.
Selecting the right tool depends heavily on the scale of your project and the complexity of the target website. Here we present the most popular and widely used web scraping tools for almost all needs.
Scrapy is the gold standard for Python-based large-scale web crawling. It's an asynchronous framework with built-in support for various integrations, pipelines, item loaders, sitemap web crawlers, and export to structured formats CSV, JSON, and databases.
Scrapy's architecture makes it easy to plug in proxy rotation, user-agent rotation, and retry logic as reusable components. See our Scrapy web scraping tutorial for a full walkthrough of building a production-ready spider.
Use Scrapy when:
You need to crawl thousands to millions of pages
Targets serve fully rendered HTML (no JavaScript requirement)
You want a structured codebase that's easy to maintain
These tools automate real browsers and are essential for sites with rendered dynamic content.
Selenium is the most mature option, with support for Python, Java, C#, Ruby, and JavaScript. Its WebDriver API may be wordy for some, but it's widely understood. See our Selenium web scraping guide for practical examples.
Puppeteer is Google's Node.js library for controlling Chromium. It's tightly integrated with the Chrome DevTools Protocol and excellent for tasks like PDF generation, performance profiling, and scraping Chrome-specific sites. Our Puppeteer tutorial covers setup and how to handle common anti-scraping challenges.
Playwright is the modern async-first, multi-browser (Chromium, Firefox, WebKit) choice with a clean API. It handles complex interactions like file uploads, multi-tab flows, and network interception with ease. Read our Playwright web scraping guide to learn more.
Use these when pages rely on:
JavaScript rendering
Infinite scroll
Other complex interactions HTTP clients can't replicate
While Selenium, Puppeteer, and Playwright give you browser control, running them at scale against sites with serious bot detergents is a different challenge. Bot traffic control systems fingerprint automated browser behavior, such as the navigator.webdriver flag, missing browser plugins, predictable canvas fingerprints, and unnatural event timing.
Managed automated browser like Oxylabs Headless Browser address this by handling browser fingerprint rotation, CAPTCHAs, and session management automatically. You get a fully rendered page delivered as HTML without building and maintaining the whole stack yourself.
Use a managed headless browser when:
The target website flags basic browser fingerprints
You need CAPTCHA handling integrated into your flow
You want to scrape JavaScript-heavy pages without managing browser infrastructure.
BeautifulSoup is a Python library for parsing HTML and XML documents. You still have to pair it with requests or another HTTP client for fetching, but its intuitive API makes it the go-to choice for straightforward extraction tasks.
For complex multi-page crawls, Scrapy can serve you better. For JavaScript-rendered dynamic content, you need a headless browser. But for quick, precise data collection, BeautifulSoup is hard to beat. Our BeautifulSoup parsing tutorial covers everything from basic tag selection to extracting data and advanced parsing patterns.
Use BeautifulSoup when:
The page content is available in static HTML
Your use case is simple (e.g. a specific data point in multiple pages)
You want readable, beginner-friendly code
Building and maintaining the full web data access stack (such as proxy rotation, browser fingerprinting, CAPTCHA handling, retry logic, header rotation, etc.) is a significant time and resource investment. For most teams, it's the difference between getting the scraped data when you need it and missing out on critical web intelligence.
An API abstract all of this scraping process – you send a URL and you get back rendered, clean HTML text file. The API handles the everything from start to finish without you needing to script or maintain almost anything from your side.
Use a web scraping API when:
You need to scrape at scale without building and maintaining infrastructure
Target websites use multi-layered systems for stop bot traffic (e-commerce, travel, real estate)
You want reliable access without manually managing a proxy pool
Resources are more valuable for data processing than on scraping infrastructure maintenance
For example, Oxylabs Web Scraper API supports JavaScript rendering, geo-targeting, custom headers, and structured data extraction accessible via a single HTTP endpoint and a simple JSON payload:
import requests
payload = {
"source": "universal",
"url": "https://example.com/products",
"render": "html",
"geo_location": "United States",
}
response = requests.post(
"https://realtime.oxylabs.io/v1/queries",
auth=("username", "password"),
json=payload,
)
print(response.json()["results"][0]["content"])Reliable and ethically acceptable web scraping process is a stack of deliberate decisions: respecting the target, rotating infrastructure, addressing failures correctly, keeping your scraped data clean, and handling the scraped data responsibly. All tips in this guide can be mixed and matched so that most web access, parsing, and data-quality challenges become solvable before they become emergencies.
When the complexity of managing proxies, headless browsers, and handling CAPTCHAs starts to outweigh the value of building it yourself, it may be the right time to look out for trustworthy scraper APIs and proxy infrastructure, as those tend to pay for themselves by letting your team focus on what to do with the desired data rather than how to get it.
This article is for informational purposes only and any information contained herein does not constitute legal advice. Accordingly, before engaging in any scraping activities, you should get appropriate professional legal advice regarding your specific situation.
Please be aware, that third-party tools mentioned in this article are not owned or controlled by Oxylabs. Each third-party provider is responsible for its own software and services. Consequently, Oxylabs will have no liability or responsibility to you regarding those services. Please review carefully the third-party's policies and practices and/or conduct due diligence before accessing or using any third-party services.
Yes, web scraping is legal in many cases, but it depends on what data you collect, how you collect it, and how you use it. Laws vary by country or jurisdiction, and scraping personal data, copyrighted content, or restricted pages can create legal risks.
Forget about complex web scraping processes
Choose Oxylabs' advanced web intelligence collection solutions to gather real-time public data hassle-free.
About the author

Dovydas Vėsa
Technical Content Researcher
Dovydas Vėsa is a Technical Content Researcher at Oxylabs. He creates in-depth technical content and tutorials for web scraping and data collection solutions, drawing from a background in journalism, cybersecurity, and a lifelong passion for tech, gaming, and all kinds of creative projects.
All information on Oxylabs Blog is provided on an "as is" basis and for informational purposes only. We make no representation and disclaim all liability with respect to your use of any information contained on Oxylabs Blog or any third-party websites that may be linked therein. Before engaging in scraping activities of any kind you should consult your legal advisors and carefully read the particular website's terms of service or receive a scraping license.



Get the latest news from data gathering world
Scale up your business with Oxylabs®
Proxies
Advanced proxy solutions
Data Collection
Datasets
Resources
Innovation hub
Forget about complex web scraping processes
Choose Oxylabs' advanced web intelligence collection solutions to gather real-time public data hassle-free.