Crawling thousands of sites is a different workload from scraping one, and defaults tuned for focused crawls hold it back.
A broad crawl visits many domains, often without knowing them in advance. Scrapy's defaults are tuned for crawling a single site, so the bottlenecks move from politeness to throughput and memory. The optimization guide recommends:
- Raise global concurrency: set
CONCURRENT_REQUESTSclose toCONCURRENT_REQUESTS_PER_DOMAINtimes the number of domains in flight, as CPU and memory allow, and raiseSCRAPER_SLOT_MAX_ACTIVE_SIZEwhen more concurrency stops helping. - Keep the scheduler domain-aware:
SCHEDULER_PRIORITY_QUEUE = "scrapy.pqueues.DownloaderAwarePriorityQueue"(the default in current versions; older ones needed it set) picks requests for domains with free download slots, so one slow site can't dominate. - Speed up DNS with a local caching resolver and a larger
REACTOR_THREADPOOL_MAXSIZE, since DNS lookups use threads. - Cut the cost of bad responses: disable or reduce retries, lower
DOWNLOAD_TIMEOUT, setREDIRECT_ENABLED = Falseunless you need redirects, and cap sizes withDOWNLOAD_MAXSIZE. - Consider breadth-first order,
DEPTH_PRIORITY = 1with FIFO queues (SCHEDULER_DISK_QUEUE = "scrapy.squeues.PickleFifoDiskQueue",SCHEDULER_MEMORY_QUEUE = "scrapy.squeues.FifoMemoryQueue"): it reaches many sites shallowly before going deep, and the docs suggest trying it when memory is the bottleneck.JOBDIRkeeps the queue on disk. - Guard memory and traps with
MEMUSAGE_LIMIT_MBandDEPTH_LIMIT(infinite calendars and session-id URLs are classic traps).
When one process isn't enough, scale out: run several Scrapy processes sharing a central queue and dupefilter, typically with the scrapy-redis extension, or partition the URL space (by domain hash) across independent crawlers.
At this scale, politeness is still per site: keep obeying robots.txt and per-domain limits.
Example
# settings.py for a broad crawl
CONCURRENT_REQUESTS = 200 # ~2 per domain x 100 domains
CONCURRENT_REQUESTS_PER_DOMAIN = 2
SCHEDULER_PRIORITY_QUEUE = "scrapy.pqueues.DownloaderAwarePriorityQueue"
REACTOR_THREADPOOL_MAXSIZE = 20
# breadth-first order
DEPTH_PRIORITY = 1
SCHEDULER_DISK_QUEUE = "scrapy.squeues.PickleFifoDiskQueue"
SCHEDULER_MEMORY_QUEUE = "scrapy.squeues.FifoMemoryQueue"
DEPTH_LIMIT = 3
REDIRECT_ENABLED = False
RETRY_ENABLED = False
DOWNLOAD_TIMEOUT = 15
DOWNLOAD_MAXSIZE = 5 * 1024 * 1024 # skip responses over 5 MB
MEMUSAGE_LIMIT_MB = 2048
LOG_LEVEL = "INFO"
ROBOTSTXT_OBEY = True
# $ scrapy crawl broad -s JOBDIR=crawls/broad-1Remember
- Raise global concurrency toward per-domain limit x number of domains
- DownloaderAwarePriorityQueue keeps slow domains from hogging the queue
- DEPTH_PRIORITY=1 + FIFO queues = breadth-first crawling
- Scale out with a shared queue and dupefilter (e.g. scrapy-redis)
Watch out for
- Running a broad crawl with single-site defaults, so it dives deep into the first few sites while most domains wait.