Design a Web Crawler

BFS over the web graph at scale: the URL frontier with politeness queues, dedup with a Bloom filter, crawler traps and freshness, and how to distribute the whole thing by domain.

system-designcrawlerbfscase-study

Prompt

Crawl the web: start from seed URLs, fetch pages, extract links, store the content, and repeat — at the scale of billions of pages, while being polite to each site and keeping content reasonably fresh.

Why this one, and at what level

It's a BFS over the web graph (graphs) turned into a distributed system. SDE-1: the fetch → parse → enqueue loop and a seen-set. SDE-2: the URL frontier, dedup at scale, and politeness. SDE-3 / Staff: distributing the frontier, crawler traps, freshness/re-crawl policy, and prioritization. Asked at Google, Amazon, Microsoft and search/data startups.

1. Requirements

Functional: fetch pages from seeds, extract and follow links, store content for indexing. Non-functional: scalable (billions of pages); polite (respect robots.txt, cap requests per domain); robust (avoid traps, handle failures); fresh (re-crawl changing pages); dedup (don't crawl the same URL/content twice).

2. Architecture

seeds ─▶ URL frontier ─▶ fetchers ─▶ parser ─▶ extract links
          (priority +      (HTTP)      │           │
           per-domain                   ▼           ▼
           politeness)            content store   dedup filter
              ▲                    (S3 / blob)    (Bloom filter / seen-set)
              └──────────────── new, unseen URLs ◀──┘

The URL frontier is the heart of the design — a queue of URLs to crawl that enforces two competing goals at once: priority (crawl important/fresh pages sooner) and politeness (don't hammer one server).

3. The decisions that matter

  • Politeness: never blast a single domain. Maintain per-domain queues with a minimum delay between requests to the same host, and obey robots.txt (cached per domain). A common design: a front set of queues for priority, a back set keyed by domain for politeness, with a mapping between them.
  • Dedup at scale: a billions-URL "have I seen this?" set won't fit in memory as a plain hash set. Use a Bloom filter (tiny, fast, false positives only — occasionally skip a new URL, never re-crawl). Also dedup content (hash the page) to catch the same page under different URLs.
  • Crawler traps & cycles: infinite calendars, session-id URLs, deep dynamic links. Cap URL length and crawl depth per domain, normalize URLs (strip fragments/params), and budget pages per domain.
  • Freshness / re-crawl: pages change at different rates. Re-crawl high-change pages (news) often, static pages rarely — an adaptive policy based on observed change frequency.
  • Distribution: partition the frontier by domain hash so each crawler node owns a set of domains (which also makes politeness local). Cache DNS aggressively — DNS lookups are a surprising bottleneck.

Design drills

Design drills: Web Crawler0/3 done

Whiteboard each one out loud for 5–10 minutes before you reveal what a strong answer covers — the gap between your sketch and the checklist is your study list. Progress is saved on this device.

Core

Track 'already crawled' across billions of URLs without exhausting memory.

Stretch

Stay polite: never overload a single domain while still crawling fast overall.

Core

The crawler gets stuck in an infinite calendar / session-id trap.