Python — Web Scraping
Scrapy vs Playwright for Modern SPAs
Direct answer
Use Playwright when the content you need only exists after JavaScript executes and there is no accessible JSON endpoint; use Scrapy when pages are server-rendered or when the SPA fetches its data from an API you can call directly. In practice I open the browser's network tab first — most SPAs hydrate from JSON endpoints that Scrapy or plain httpx can consume far more cheaply than a fleet of headless browsers.
Single-page apps broke the classic request-and-parse scraping model, and the reflexive fix — render everything in a headless browser — is often the most expensive option on the table. Here is how I actually choose between Scrapy and Playwright on real projects, and the hybrid pattern that usually wins.
Key facts, with sources
- The 2025 Imperva Bad Bot Report found automated traffic surpassed human activity for the first time in a decade, accounting for 51% of all web traffic. (Imperva)
- Bad bots alone made up 37% of all internet traffic in 2024, up from 32% the year before, according to the 2025 Imperva Bad Bot Report. (Business Wire)
- Mordor Intelligence sizes the web scraping market at $1.03 billion in 2025, projected to reach $2.23 billion by 2031 at a 13.78% compound annual growth rate. (Mordor Intelligence)
- Cloudflare's analysis of AI crawler traffic found that about 80% of AI crawling over a recent 12-month period was for model training, versus 18% for search and 2% for user-initiated actions. (Cloudflare)
- Cloudflare data shows Google crawls websites about 14 times per referral click it sends back, while OpenAI's crawl-to-referral ratio was roughly 1,700 to 1 in June 2025, illustrating how much scraping now happens without reciprocal traffic. (Cloudflare)
Start with the network tab, not the framework
The decision is not really Scrapy versus Playwright — it is HTML versus API. Before choosing tools, I load the target page with the browser dev tools open and watch the XHR/fetch requests. Most React, Vue, and Next.js sites pull their data from JSON endpoints, and those endpoints usually return cleaner, better-structured data than anything you could parse out of rendered HTML. If such an endpoint exists, is publicly reachable without authentication, and the site's robots.txt and terms permit automated access, calling it directly with Scrapy or httpx is faster, cheaper, and more stable than rendering.
Only when data is genuinely embedded in client-rendered DOM — no accessible endpoint, or responses that are obfuscated or signed — does a real browser earn its cost. That is a minority of the SPAs I audit.
What Scrapy brings beyond fetching
People compare Scrapy to Playwright as if both were fetchers, but Scrapy's real value is crawl infrastructure: a scheduler with per-domain concurrency limits, automatic throttling, retry middleware, robots.txt enforcement, item pipelines, and feed exports. When a job involves thousands of pages across many domains, that plumbing is what you would otherwise write by hand.
Scrapy is also where politeness settings live in one obvious place — ROBOTSTXT_OBEY, DOWNLOAD_DELAY, and AUTOTHROTTLE_ENABLED belong in every production spider I ship. A headless browser script has none of this by default; you end up rebuilding a worse version of it.
import scrapy
class ProductSpider(scrapy.Spider):
name = "products"
custom_settings = {
"ROBOTSTXT_OBEY": True,
"DOWNLOAD_DELAY": 2.0,
"AUTOTHROTTLE_ENABLED": True,
"CONCURRENT_REQUESTS_PER_DOMAIN": 2,
"USER_AGENT": "catalog-research-bot/1.0",
}
def parse(self, response):
for card in response.css("article.product"):
yield {
"name": card.css("h2::text").get(),
"price": card.css(".price::text").get(),
}
next_page = response.css("a.next::attr(href)").get()
if next_page:
yield response.follow(next_page, self.parse)Where Playwright genuinely earns its keep
When content truly requires JavaScript — infinite scroll that computes items client-side, DOM built from WebSocket messages, or state that only exists after user-like interaction — Playwright is the right tool and a pleasure to use. Its auto-waiting model (wait for a selector, wait for network idle) removes most of the flakiness that plagued older headless setups, and it drives Chromium, Firefox, and WebKit through one API.
The discipline is to treat the browser as a rendering step, not a crawler. Fetch the page, wait for the specific selector you need, grab the HTML or evaluate a small extraction script, and close. Long-lived browser sessions that click around like a ghost user are fragile and much harder to rate-limit responsibly.
from playwright.async_api import async_playwright
async def fetch_rendered(url: str) -> str:
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
await page.goto(url, wait_until="domcontentloaded")
await page.wait_for_selector("article.product")
html = await page.content()
await browser.close()
return htmlThe hybrid pattern that usually wins
On most SPA projects I end up with a two-tier architecture. Tier one uses Playwright sparingly — to discover how the site loads data, to render the small subset of pages that genuinely need JavaScript, or to snapshot a page type once so I can study its API calls. Tier two does the volume work over plain HTTP with Scrapy or httpx against whatever server-rendered pages or JSON endpoints exist.
If you want both inside one framework, the scrapy-playwright integration lets individual Scrapy requests opt into browser rendering while everything else stays on the fast path. That keeps Scrapy's scheduling and politeness machinery in charge of every request, rendered or not, which is exactly where you want rate limiting enforced.
Cost, operations, and the ethics constant
A headless browser typically consumes an order of magnitude more CPU and memory per page than an HTTP fetch, and browser fleets bring their own failure modes — zombie processes, memory growth, version drift between the driver and the browser. If your crawl volume is large, that difference decides your infrastructure bill. HTTP-first designs also parallelize more predictably because concurrency is bounded by connections, not by browser instances.
One thing that does not change between tools: the rules. Robots.txt, terms of service, honest identification, and per-domain rate limits apply equally whether the request comes from Scrapy or Chromium. A browser that renders like a human is not a license to crawl like a machine — if anything, rendered crawling should be slower, because each page load triggers many subresource requests on the target's infrastructure.
When to hire senior help
Bring in senior help when scraped data feeds production features or pricing decisions, because reliability engineering, compliance review, and change monitoring matter far more than the initial extraction script. An experienced engineer will also steer you toward official APIs, licensed feeds, and terms-of-service-respecting designs that avoid legal exposure and rework. If your stack includes React Native + Python + AI, a senior engineer who owns the full product beats coordinating multiple juniors.
Bottom line
Dhairya Senjaliya ships Python — Web Scraping projects worldwide — book a scoping call to discuss your specific situation.
Common pitfalls to avoid
- ✕Scraping without first checking the site's terms of service, robots.txt, and whether an official API or licensed data feed already provides the data lawfully and more reliably
- ✕Sending unthrottled concurrent requests with no politeness delays, which looks like an attack, gets IP ranges banned, and can disrupt the target site's service
- ✕Collecting personal data without a lawful basis under GDPR or CCPA, turning a data project into a regulatory liability
- ✕Coupling parsers tightly to page DOM structure with no output validation or monitoring, so a site redesign silently fills the warehouse with empty or wrong records for weeks
Frequently asked questions
Is Playwright slower than Scrapy for scraping?
Per page, yes — usually dramatically. Playwright launches a real browser that downloads and executes scripts, styles, and images, while Scrapy fetches one HTML document over HTTP. That typically translates to far higher CPU and memory per page and lower safe concurrency. Use Playwright only for pages that genuinely require JavaScript execution, and route everything else through plain HTTP fetching.
Can I use Scrapy and Playwright together in one project?
Yes. The scrapy-playwright integration lets specific Scrapy requests opt into browser rendering while the rest of the crawl runs over plain HTTP. This is the pattern I recommend for mixed sites: Scrapy keeps ownership of scheduling, retries, robots.txt enforcement, and per-domain rate limits, and Playwright is invoked only for the page types that need JavaScript to produce their content.
How do I scrape a React or Vue site without a headless browser?
Open the site with browser dev tools and watch the network tab while the page loads. Most SPAs fetch their content from JSON endpoints, and calling those directly returns cleaner data than parsing rendered HTML. Verify the endpoint is public, permitted by robots.txt and the site's terms, and rate-limit your requests. If the data only exists in client-rendered DOM, then a browser is justified.
Is web scraping legal for our business?
It depends on what you collect and how: scraping publicly available, non-personal data while respecting terms of service and robots.txt is generally lower risk, while bypassing access controls, violating contracts, or harvesting personal data creates real legal exposure. Get jurisdiction-specific legal advice before building revenue on scraped data, and prefer official APIs or licensed datasets where they exist.
Why do scrapers break so often and what does maintenance cost?
Sites change markup, add bot defenses, and restructure pages; with 51% of web traffic now automated, anti-bot systems are aggressive and constantly updated. Plan for ongoing maintenance as a permanent line item, typically a meaningful fraction of the original build cost per year, plus monitoring that detects breakage within hours instead of weeks.
Should we build scrapers in-house or buy data from a vendor?
For a handful of stable, permissively accessible sources, an in-house Python scraper is cheap and flexible. For large-scale or legally sensitive collection, commercial data providers amortize compliance, proxy infrastructure, and maintenance across many customers, which is why the scraping market is growing at roughly 14% annually. Many teams start with a vendor and only insource once volume justifies it.
Bottom line: Dhairya Senjaliya ships Python — Web Scraping projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.