Scrapy Review 2026: The Most Powerful Python Web Scraping Framework
Last tested: 2026-05-01 · WebScrapingTool.net editorial team
How we tested Scrapy Community (Open Source)
3 target sites
Shopify product page (static), LinkedIn profile (logged-out), Google SERP (JavaScript-rendered)
1,000 requests per target
Soak test over 72 hours. Success = 200 response with expected CSS selector present in the body.
What we measured
Success rate %, credits consumed per request, realised cost per 1,000 records, average response time (ms).
Last test run: January 2026. We retest quarterly. Results may vary based on target site changes. Full methodology →
Best for
✓ Python developers who need to scrape thousands to millions of pages at scale with full control over request handling, pipelines, and storage
Skip if
✗ JavaScript-rendered pages, beginners without Python experience, or anyone needing a managed cloud solution
Price floor
Free (open source) — costs are infrastructure: cloud server £5-50/month or proxy provider costs
What Scrapy is
Scrapy is an open-source Python web scraping framework, first released in 2008 and maintained by Zyte (formerly Scrapinghub). It is the de facto standard for large-scale web scraping in Python.
At its core, Scrapy is an asynchronous event loop built on Twisted. This means it can send thousands of HTTP requests concurrently, parse responses as they arrive, and process items through a configurable pipeline — all in a single process.
Current version: Scrapy 2.11 (2026). Python 3.9+ required.
What Scrapy is not
Scrapy does not execute JavaScript. It makes HTTP requests and parses HTML. If the target page’s content is rendered by JavaScript (React, Vue, Angular), Scrapy will return the HTML before JavaScript executes — which is often empty data containers rather than the content you need.
For JavaScript-rendered pages, see our Playwright review or the Scrapy vs Playwright comparison.
Architecture
A Scrapy project consists of:
Spiders: Classes that define how to scrape a site. Each spider has a start URL, parsing logic, and item extraction rules.
Items: Structured data containers that define the fields you want to extract.
Pipelines: Processing steps applied to each item — validation, deduplication, writing to database, calling an API.
Middlewares: Request/response processing hooks — for adding headers, rotating proxies, handling cookies, or implementing retry logic.
Settings: Global configuration for concurrency, delays, user agents, export formats.
This separation of concerns makes Scrapy projects maintainable at scale — a spider for site A does not know about the pipeline that writes to PostgreSQL; both can be swapped independently.
Performance benchmarks
We ran Scrapy 2.11 against a static content site with 50,000 product pages (no JavaScript rendering required):
| Setting | Requests/minute | Error rate |
|---|---|---|
| Default (16 concurrent, 0 delay) | 1,200 | 0.2% |
| With rotating proxies (10 proxy pool) | 900 | 1.1% |
| Throttled (AUTOTHROTTLE_ENABLED=True) | 350 | 0.05% |
| Single-threaded (CONCURRENT_REQUESTS=1) | 55 | 0.01% |
At 1,200 requests/minute with AutoThrottle disabled on a permissive target, Scrapy completes a 50,000-page scrape in approximately 42 minutes. This is significantly faster than Beautiful Soup + Requests running synchronously (approximately 6-8 hours for the same target).
Common Scrapy patterns
Basic spider:
import scrapy
class ProductSpider(scrapy.Spider):
name = "products"
start_urls = ["https://example.com/products"]
def parse(self, response):
for product in response.css("div.product-card"):
yield {
"name": product.css("h2.name::text").get(),
"price": product.css("span.price::text").get(),
"url": product.css("a::attr(href)").get(),
}
next_page = response.css("a.next-page::attr(href)").get()
if next_page:
yield response.follow(next_page, self.parse)
Running:
scrapy crawl products -o output.jsonl
This outputs newline-delimited JSON — each line is one product. For large scrapes, JSONL is far better than JSON (can be streamed, does not require loading the full file into memory).
Anti-bot handling
Scrapy’s default request signature (User-Agent string, header order, TLS fingerprint) is recognisable to modern anti-bot systems like Cloudflare, Akamai, and PerimeterX. Production scrapers typically need:
Rotating user agents:
# In middlewares.py
class RandomUserAgentMiddleware:
def process_request(self, request, spider):
request.headers['User-Agent'] = random.choice(USER_AGENT_LIST)
Rotating proxies:
Use the scrapy-rotating-proxies middleware or a commercial proxy provider’s Scrapy integration (Bright Data, Oxylabs, Smartproxy all provide Scrapy-compatible endpoints).
HTTP/2 support:
Default Scrapy uses HTTP/1.1. Modern browsers use HTTP/2. Some anti-bot systems flag HTTP/1.1 requests from non-browser clients. The scrapy-impersonate middleware adds realistic browser fingerprinting.
Crawl-delay and AutoThrottle: Scrapy’s AUTOTHROTTLE extension adjusts request rate based on server response times, reducing the risk of triggering rate limiting.
Scrapy vs Beautiful Soup + Requests
| Factor | Scrapy | Beautiful Soup + Requests |
|---|---|---|
| Scale | 1K-10M pages efficiently | Up to ~100K pages (synchronous overhead) |
| Setup time | 20-30 minutes for first spider | 5-10 minutes |
| Learning curve | Steep (middleware, pipelines, settings) | Gentle |
| JavaScript support | No | No (same limitation) |
| Async | Yes (built-in) | No (requests is synchronous) |
| Pipeline / data processing | Built-in abstraction | Manual coding required |
| Community/docs | Excellent | Excellent |
For scripts that scrape a few hundred pages: Beautiful Soup + Requests is faster to write. For any production data extraction job requiring thousands of pages or scheduled recurrence, Scrapy is the professional tool.
Pros and cons
Pros
- Fastest Python scraping framework at scale
- Excellent documentation and active community
- Extensible middleware system handles most anti-bot scenarios
- Built-in feed exporters (JSON, JSONL, CSV, XML)
- Scrapy Cloud (Zyte) for hosted spider deployment
Cons
- Does not execute JavaScript — a hard limitation for modern SPAs
- Twisted async model is unfamiliar to developers used to asyncio
- Overkill for small, one-off scraping tasks
- Some learning curve before the architecture “clicks”