Adaptive throttling means your scraper changes its own request delay or concurrency based on what the target server does: slower when latency climbs or errors appear, faster when responses are healthy. A fixed rate ignores all of that. Done well, adaptive throttling gets you more pages per hour with fewer 429s, fewer bans and a bill you can predict.
This guide covers the algorithms, the signals to watch, the limits you should never cross, and how to cap cost when retries go wrong.
TL;DR
- Adaptive throttling adjusts delay or concurrency from observed latency and response codes. Fixed rates do not react to server capacity.
- Treat
429and503as hard signals: honorRetry-After, back off exponentially, and add jitter. - Adapt within bounds. Set a minimum delay, a maximum delay, and a ceiling taken from robots.txt and the site's terms.
- Retries cost money on metered APIs. Put a spending cap in front of the loop, not behind it.
- Raise concurrency gradually and watch status counts, retry counts and latency.
Table of Contents
- What adaptive throttling actually does
- Signals worth reading
- Controllers that work
- A worked example
- Staying inside the site's limits
- Throttling and your bill
- Perspective
- Putting it into a pipeline
- Try it with a cap on
- FAQ
- Sources
What adaptive throttling actually does
A fixed rate says "one request every two seconds" no matter what. If the server slows down under load, you keep pushing. If it is idle, you leave throughput on the table.
Adaptive throttling closes the loop. Scrapy's AutoThrottle extension computes each download slot's delay from observed response latency, aiming for a target average number of concurrent requests per remote site, and constrains the result with bounds. A fixed delay stays constant and can produce bursts.
The Spatie crawler documentation describes the same idea with an exponential moving average: delay rises when the server responds slowly, falls when responses speed up, and never leaves the configured minimum and maximum.
The common ingredients:
- A signal: latency (smoothed with an EWMA), status codes, or both.
- A controller: a rule that turns the signal into a new delay or concurrency value.
- Bounds: a floor and a ceiling so the controller cannot run away in either direction.
Signals worth reading
Latency is the earliest warning. Errors are the loudest. Watch both, per domain, not globally.
Scrapy's optimization guidance recommends monitoring counts of 429 and 503 responses, ban-page responses, retry counts and download latency while you raise concurrency gradually. It also separates global concurrency, per-domain concurrency and download delay. Per-domain is the one that matters for politeness: exceeding the rate a target tolerates can cause throttling, errors or bans, and can make the crawl slower overall.
Two signals deserve special handling:
- HTTP 429. Per MDN, it means the client sent too many requests in a given period, and the response may include
Retry-After. Retry-After. MDN documents it as either a number of seconds or an HTTP date, and as a minimum wait before the next request, notably on429and503. Your controller should never schedule a retry earlier than this value.
A false "success" is also a signal. A 200 that returns a CAPTCHA or block page is a ban in disguise. Classify it as a failure so the controller backs off. This is why we return typed failure cases instead of a bare body: an agent or pipeline can branch on blocked versus timeout versus ok without parsing HTML.
Controllers that work
Latency-target control
The AutoThrottle approach: pick a target concurrency, measure latency, set delay to roughly latency divided by target concurrency, clamp to bounds. It is simple, stable and good for steady crawls.
AIMD
Additive increase, multiplicative decrease, the same family used in TCP congestion control. The aioscraper rate limiter implements it per group: failures multiply the interval, and after successive successes the interval shrinks by a fixed step. It can also honor Retry-After. AIMD reacts fast to trouble and recovers cautiously, which is the right asymmetry when the other side can ban you.
Backoff with jitter
When a request is throttled, retry later, and later again if it keeps failing. AWS retry guidance recommends exponential backoff with full jitter, with longer base delays for throttling errors than for transient ones. The formula is a uniformly random value multiplied by a capped exponential delay.
Jitter matters when you run many workers. AWS explains that full jitter spreads clients across the backoff window so they do not retry at the same instant and recreate the overload. The same page distinguishes adaptive retry mode, which lowers the rate of initial requests after throttling, from standard mode, which mainly delays retries. For scraping you want the adaptive behavior: backoff on one request is not enough if the next fifty go out at full speed.
It also says non-retryable errors (authorization, validation, not found) should be returned immediately. Do not spend a retry budget on a 404.
For jitter modes and cost caps in detail, see our exponential backoff guide for scraping and the retries playbook.

A worked example
Suppose you crawl one domain with a floor of 1 second and a ceiling of 30 seconds, starting at 2 seconds.
| Event | Controller action | New delay |
|---|---|---|
| 5 successes, latency steady | Subtract fixed step | 1.5 s |
| Latency doubles | Raise delay toward latency / target concurrency | 3 s |
429 with Retry-After: 20 | Pause domain 20 s, then multiply delay | 6 s |
Block page served as 200 | Count as failure, multiply delay | 12 s |
| 10 clean responses | Step down gradually | 11 s, then lower |
The exact numbers are yours to tune. The shape is what counts: quick to slow down, slow to speed up, always clamped.
Staying inside the site's limits
Adaptive does not mean "as fast as the server survives". Scrapy's maintainers stress that the target site's tolerated rate, not your hardware, is the governing constraint. Practical defaults:
- Read robots.txt and translate
Crawl-delayandRequest-rateinto your floor delay and concurrency ceiling. Our notes on robots.txt compliance and robots meta tags cover the edge cases. - Use a documented API or bulk export when one exists.
- Never rotate identities to dodge a
429. Slowing down is the fix; evasion is a different problem with legal weight. The FTC recognizes legitimate bot uses such as search crawling, monitoring and market analysis, while judging deceptive or unfair conduct case by case under the FTC Act. If your project touches personal data or contractual terms, get advice from counsel.
If you are building the server-side counterpart, this walkthrough of Redis and Lua rate limiting shows what limits look like from the other end, which helps you predict how targets will respond. For crawler politeness defaults, see polite crawling and ethical scraping with rate limits.
Throttling and your bill
On a metered scraping API, every retry can be a charge. A runaway loop, a bad deploy or a stuck agent turns adaptive logic into a spending problem.
The research here is consistent. Zuplo's API cost protection guide argues for hard caps by default on free and self-service plans, with explicit opt-in before overages on paid tiers, and says absolute quotas and spend caps protect against runaway retries and accidental loops more reliably than monitoring alone.
Practical rules:
- Cap before you retry. Give each job a maximum retry count and a maximum spend.
- Separate rate from quota. Rate limits protect capacity per second; quotas protect revenue per month. Our piece on rate limits vs quotas explains why you need both.
- Do not add parallelism to beat a quota. Extra workers help only when you are latency-bound, not quota-bound.
- Cap the backoff. A 20-second ceiling with spending caps is a sane starting point; see scraping rate limits.
Perspective
We built Gyrence around one belief: a scraper should tell you what happened, not guess. Web data is a storm, and the worst outcome is a pipeline that keeps hammering through it and bills you for the privilege.
That is why every call returns a typed, discriminated-union response, including failures. When a fetch is blocked, rate-limited or empty, your agent sees that as data it can reason about, then slow down, retry within a budget, or stop. Adaptive throttling is only as good as the signals feeding it, and hidden failure modes starve it.
Spending caps and predictable cost per call finish the job. Backoff logic will have bugs. The cap means a bug costs you a bounded amount, not a surprise invoice.
Glen
Putting it into a pipeline
A throttled web-data workflow usually runs in this order: Search to find candidate URLs, Map to list a domain's URL graph, Fetch to clean pages to markdown for RAG ingestion, and Extract to pull schema-guided JSON. Throttle at the domain level across all of them, since Map and Fetch hit the same server. If sitemaps are huge, this sitemap guide covers what to do past 50,000 URLs.
When evaluating any scraping API, including alternatives to the one you use today, ask:
- Does it expose rate-limit and failure states as typed responses, or hide them behind generic errors?
- Does it work with MCP clients and agent frameworks?
- Is extraction bundled, or billed separately?
- Can you set a hard spending cap?
Try it with a cap on
Gyrence offers a Free plan, Pay-As-You-Go metering, and paid tiers: Standard at $75/month, Growth at $299/month and Scale at $549/month. Extraction is bundled with no separate AI charge, and spending caps are part of the billing controls. Compare plans on the Gyrence pricing page. If you need to watch pages for changes and get alerts, look at WebDoppler.
FAQ
What is the difference between rate limiting and throttling?
Rate limiting is the cap a server enforces on you, often returning 429. Throttling is what your client does to stay under it. Adaptive throttling tunes that behavior from live feedback instead of a hard-coded number.
Should I retry every failed request?
No. Retry transient failures and throttling responses, with exponential backoff and jitter. Return authorization, validation and not-found errors immediately, since retrying them wastes time and credits.
Does more concurrency make scraping faster?
Only when the work is latency-bound and the target tolerates it. If the limit is a quota or the site's tolerated rate, extra workers produce more 429 responses and a slower crawl. Raise concurrency gradually and watch error counts.
How do I honor Retry-After in an adaptive controller?
Parse it as seconds or an HTTP date, pause that domain until the time passes, then resume at a higher delay than before. Treat it as a minimum wait, never a suggestion.
Sources
- AutoThrottle extension, Scrapy documentation
- Crawl behavior, Spatie crawler documentation
- Optimization, Scrapy documentation
- 429 Too Many Requests, MDN
- Retry-After header, MDN
- Retry behavior, AWS SDKs and Tools
- Retry behavior, AWS General Reference
- aioscraper rate limiter source
- API Cost Protection, Zuplo
- Social Media Bots and Deceptive Advertising, FTC report
- 6 Production Decisions for Engineers on API Rate Limits and Redis+Lua

