Handbooks / System Design Case Studies / Chapter 8
Design a Web Crawler
13 pages · ~18 min✓ Reviewed
Builds on Design a News Feed like Instagram. Next up: Design Cloud File Storage like Dropbox.
Problem Statement, Scope and Requirements
1. Problem and Scope (Web Crawler)
- Crawl the public web starting from seed URLs
- Store pages so a search index can use them later
- Example system: a Google-style Web Crawler
- Ranking
- Indexing
- JS-heavy rendering
- Login-walled pages
2. Requirements
- Seed intake: accept starting URLs
- Fetch HTML pages
- Extract links from each page
- Dedupe URLs and content
- Respect
robots.txt - Recrawl by change rate
| Non-functional | Target |
|---|---|
| Politeness | max 1 request/sec per host |
| Robustness | survive bad HTML, timeouts, traps |
| Extensibility | plug in new content types and parsers |
| Freshness | changing pages recrawled sooner |
| Scalability | ~400 pages/sec avg, ~1,200 peak |
High-Level Architecture and Crawl Loop
1. Pipeline and Components (Web Crawler)
- Seedsstart URLs
- URL frontierwhat to crawl next
- DNS resolverhost → IP
- FetcherHTTP GET
- Content storeraw body
- ParserHTML → text
- Link extractora, link tags
- Filter / dedupback to frontier
URL Frontier: Priority and Queues
The frontier of the Web Crawler: ~400 pages/sec average, ~1,200 peak, 10B known URLs waiting.
1. Responsibilities
- Hold every URL still to crawl
- Order by priority: important pages first
- Enforce politeness: max 1 request/sec per host
- Survive restarts: no lost or duplicated queue
- Hand fetchers the next URL on demand
2. Two-Tier Design
Politeness and robots.txt Handling
Web Crawler: stay at 1 request/sec per host while the fleet runs ~400 pages/sec average, ~1,200 peak.
1. The Politeness Rule
- One connection per host at a time, never parallel
- Delay = max(1s, 10x last fetch time), or the host's
Crawl-delayif larger - Fetch took 3s → wait 30s; fetch took 200 ms → wait 1s
- Host not yet allowed → park its back queue, serve another host
- Pop hostearliest next_allowed_at
- Robots checkcache, 24h
- Fetch1 connection
- Set next_allowed_atnow + delay
DNS Resolution and Fetcher Design
1. DNS Is the Bottleneck
Running example: the Web Crawler at 1,200 fetches/sec peak, where every new host needs a lookup.
- Synchronous lookups take 10-200 ms and block a worker
- Run a custom async resolver inside the fetcher
- Keep a local cache of ~10M entries
- Honor each record's TTL; expire entries when it runs out
- A cache hit costs microseconds, not a network round trip
Parser and Link Extraction
1. What the Parser Does
- Runs after the fetcher stores the body in the content store (Web Crawler)
- Validate
Content-Typeis text/html; skip images, PDFs, zips - Detect the encoding before decoding the bytes
- Extract text for the index
- Extract links from
a href,link href, andlink rel=canonical
- Fetchbody stored
- Parse queuedecouples
- Parser pooltype, charset, DOM
- Normalizecanonical form
- Filterrules
- Dedup, frontiernew URLs only
URL Deduplication: Seen-URL Test
1. Why Check Every Link
In the Web Crawler, the seen-URL test sits between the link extractor and the frontier. It runs on every extracted link.
- Goal: never re-enqueue any of the 10B+ URLs already known
- Load: ~50 links/page x 400 pages/sec = 20,000 checks/sec
- Each check must be fast (in-memory or one KV read)
- A wrong "seen" answer loses a page; a wrong "new" answer only wastes a fetch
2. Exact Store and Bloom Filter
Content Deduplication with Fingerprints
1. Why Dedup Content
- ~=30%== of web pages are duplicates or near-duplicates
- Sources: mirrors, tracking params, printer views
- URL dedup cannot catch them: the URLs differ, the body is the same
- Goal: skip parsing, indexing and storage for pages we already hold
2. Exact and Near Duplicates
- Exact: store SHA-256 of the body in a KV store
- A hit means skip parsing and indexing
Recrawl Scheduling and Freshness
1. The Goal and the Tiers
- Goal: maximize fresh pages under a fixed budget of ~400 fetches/sec
- Pages do not change equally: spend fetches where change is likely
- Wasted fetch = page re-downloaded that did not change
| Tier | Pages | Recrawl |
|---|---|---|
| Hot | News homepages | hourly |
Distributed Crawler and Crawler Traps
1. Partition and Route (Web Crawler)
- Partition by hash(host) on a consistent hash ring
- Each node owns whole hosts, never part of one
- Politeness state and robots cache stay local to the owner
- No cross-node lock needed for the 1 request/sec per host rule
- Parser on node Aextracts link
- hash(host)owner = node C
- Kafka topicpartitioned by host hash
- Node C frontierback queue for host
Scaling, Failure Handling, Trade-offs and Interview Questions
1. Scale-out (Web Crawler)
| Today | 10x growth | |
|---|---|---|
| Peak fetch rate | 1,200 pages/sec | ~12,000 pages/sec |
| Fetcher nodes | ~10 (60 connections each) | ~100 |
| Bandwidth | ~1 Gbps | ~10 Gbps |
| Storage | 100 TB/month raw, ~30 TB gzip |
Web Crawler Cheatsheet
Night-before notes for the Web Crawler design: numbers, frontier, politeness, dedup, recrawl, scale and traps.
1. Numbers
| Quantity | Estimate |
|---|---|
| Volume | 1B pages/month |
| Average rate | 400/sec |
| Peak rate (3x) | 1,200/sec |
| Raw storage |
Check yourself
A crawler runs 10 workers that all pop from one shared queue. Host X has 500 URLs queued next to each other. What goes wrong, and what is the fix?
- Several workers pop X's URLs at the same moment, so X gets parallel requests and the 1 request/sec rule is broken.
- X may rate-limit with 429s or block the crawler.
- Fix: route each URL to a back queue by
hash(host), so one host lives in one queue. - Give each back queue to a single worker, and use a heap keyed by next-allowed-time to pick the next host.