From Library to Service: Scaling the Crawl Without Losing Control
Crawl4AI’s operational problem is to keep a selective web-to-knowledge pipeline responsive when many pages, slow sites, retries, browser processes, and untrusted requests compete for the same resources. This chapter examines how arun_many(), dispatchers, rate limiting, caching, browser reuse, monitoring, and the Docker service coordinate that work. The practical judgment it supports is architectural: distinguish the expressive controls that are appropriate inside a trusted Python process from the constrained controls required at a network boundary, and audit every secondary path that fetches, writes, renders, or displays content.
More concurrency does not automatically mean more throughput. A browser page consumes memory, a provider can reject bursts, a site can interpret a request pattern as abusive, and a consumer can stop reading a stream while tasks continue in the background. The system needs admission control: a way to decide what work may start, how quickly it may proceed, what state it owns, and how it stops.
Scaling is admission control, not just parallelism
For a small set of URLs, a loop around arun() is easy to understand. For many URLs, Crawl4AI exposes arun_many(). The method accepts one run configuration for all URLs or a list of URL-matching configurations, and it can return a complete batch or a stream of results as they finish. By default, it creates a memory-adaptive dispatcher with a rate limiter unless the caller supplies another dispatcher.
The dispatcher sits between the URL list and the crawler. It does not merely launch one task per URL. It puts work into a priority queue, limits the number of active sessions, consults a rate limiter before a crawl, measures memory, and updates task status for monitoring. The default memory-adaptive path can enter pressure mode when memory crosses its threshold. At a critical level it requeues work rather than opening more pages, and it can raise an error if pressure persists beyond its configured wait period. Waiting tasks receive updated priorities so that a long queue does not permanently starve an older URL.
This architecture makes a distinction between capacity and demand. The URL list may contain more work than the browser pool can safely run. A queue absorbs that difference, while a concurrency permit determines how much work crosses into the active browser stage. A fixed SemaphoreDispatcher offers a simpler alternative: it limits concurrent sessions to a chosen number without using memory pressure as the primary feedback signal. The fixed approach is easier to predict when the deployment has stable resource behavior; the memory-adaptive approach can respond to changing conditions, but its decisions depend on the quality of the memory measurement and thresholds.
Streaming adds another ownership question. In batch mode, the caller waits for the dispatcher to finish and receives a list. In streaming mode, results are yielded as tasks complete. If the caller stops consuming early, the dispatcher’s cleanup path cancels active tasks, awaits them, removes queued work belonging to that stream, stops the memory monitor, and stops the task monitor. Without that cleanup, browser pages could continue using resources after the consumer believes the operation has ended. A stream is therefore not only a response format; it is a lifecycle contract between producer and consumer.
Rate limiting controls a different boundary. The dispatcher can insert delays between requests and apply backoff when a server returns rate-limit statuses such as 429 or 503. The delay reduces burst pressure and the retry policy gives transient failures a chance to recover. It does not prove that the request pattern complies with every site’s rules, nor does it guarantee that a retry will be useful. A retry of a consistently blocked or malformed request multiplies work without changing its cause.
Caching and reuse trade freshness for speed
Crawl4AI’s cache modes make the read/write decision explicit. An enabled mode can read and write, a read-only mode can reuse existing results without adding new ones, a write-only mode can populate the cache without reading it, and bypass or disabled modes avoid reuse for different reasons.