Build1 distinct publisher3 min readUpdated
A dev.to post argues four different failures arrive as one event in most scrapers. The useful part is not the backoff curve but the counter you enforce on yourself before the server does.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
A post on dev.to argues that the retry loop in most scrapers is not merely wasteful but is part of what earns the block [1]. The claim is worth an operator's attention because it moves the important decision away from sleep duration and towards classification: a rate limit, a hard block, a TLS reset and a slow origin all arrive as "the request failed", so you burn the run budget on a target that was never going to open while the one failure that would have succeeded gets three attempts and is dropped [2].
The taxonomy has four entries. A 429 carrying a Retry-After header is the cooperative case: sleep the stated duration and continue on the same connection and the same identity, because rotating there is how a temporary throttle becomes a fingerprinted pattern [4]. A 429 with no header is the one place exponential backoff with jitter is genuinely right, and the author says to open at 5 seconds rather than 0.5 [5]. A 403, a 401 or a challenge page is not retryable at all: the same bytes get the same rejection, and the repeat is what converts a temporary block into a durable one, so the move is straight to a fallback identity, a different route, or shelving the URL [6]. Connection resets, timeouts and TLS handshake failures are the class people wrongly file with 403; they are usually noise, worth two immediate retries and then a hard failure [7]. The dividing line the author draws: HTTP rejections tell you something about your request, transport failures usually tell you nothing [8].
The published code is tighter than the prose in one place and looser in two. Its hard-block set includes 407 and 451, neither of which the written taxonomy discusses [4]. Its Retry-After path only fires when the header value passes `isdigit()`, so a date-formatted Retry-After silently falls through to the exponential ladder and you ignore the number the server gave you [5]. That ladder is `min(60, 5 * 2 ** attempt)` plus jitter, which means the 60-second cap engages at attempt four, while the 5xx ladder reaches its 30-second cap at attempt five [10][3]. The transport path sleeps 0.5 seconds and gets two tries, so the whole class costs about one second before you give up on it, against five seconds for the opening sleep on a headerless 429 [2].
The load-bearing argument is the next one. Backoff handles the failure you already have and does nothing about the next one, and on many targets the limit is not time-based at all but a volume budget: N requests, block on N+1, however politely you spaced them [11]. The author's example is a regional grocery chain's stock checker that would 429 politely for about an hour and then flip to a permanent 403 once some invisible total was crossed, which no spacing strategy touches because the thing being counted is volume, not rate [12]. The proposed fix is a rolling per-host counter that refuses before the server does, defaulting to 200 requests in a 3600-second window, or roughly one request every 18 seconds [13][1].
Once that counter exists, a 403 stops being something you react to and becomes a number you write to disk: whatever the counter read when the block landed is the new ceiling for that host, minus margin, and the next run starts already knowing [14]. The author's reason for distrusting the 403 on its own is timing. By the time it arrives you are already benched, the cooldown clock started without telling you, and nothing you do for the next hour counts [15].
Two things to check in your own stack. Whether your classifier honours non-numeric Retry-After values, and whether any host ceiling you have ever hit was recorded anywhere durable rather than lost with the process.
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
The author learned this on a regional grocery chain's stock checker, which would return 429 politely for about an hour and then flip to a permanent 403 once an invisible total was crossed; backoff strategies are useless against it because the thing being counted is volume, not rate.
The proposed fix is a HostBudget class implementing a rolling per-host request counter that refuses before the server does, with defaults max_requests=200 and window_seconds=3600; allow() drops entries older than the window and returns False when the count reaches max.
Once the counter exists, a 403 becomes a signal you record rather than react to: whatever the counter said when the block landed is the new ceiling for that host, minus a healthy margin, written to disk so the next run starts already knowing.
The author states that in this taxonomy "the counter matters more than the backoff".
The sample classifier defines RETRYABLE_STATUS = {429, 500, 502, 503, 504} and HARD_BLOCK = {401, 403, 407, 451}, and returns one of transport, blocked, throttled, server, ok or fatal.
The sample sleep_for function returns int(retry-after) for a throttled response only when the header is present and ra.isdigit(); otherwise min(60, 5 * (2 ** attempt)) + random.uniform(0, 2). For kind "server" it returns min(30, 2 ** attempt) + random.uniform(0, 1); for "transport" it returns 0.5; otherwise 0.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Runnable code, no measurement
The technical substance is verifiable in place: classify(), sleep_for(), HostBudget and fetch() are quoted in full, internally consistent, and their constants and ladder caps can be checked by arithmetic. What is missing is empirical support for the claims that motivate them. The causal assertions — that retries harden blocks, that rotation during a throttle creates a fingerprint, that many targets enforce volume rather than rate limits — rest on one unnamed, undated anecdote about a grocery chain's stock checker, with no block-rate, run-completion or comparative data, and no second source in the cluster.
No adoption evidence supplied
The cluster contains no release, deployment, download, benchmark, usage disclosure or third-party uptake of this taxonomy, the HostBudget pattern, or the referenced RoamProxy examples repository. A single blog post with inline snippets provides nothing to measure adoption against, and inferring uptake from a link to a GitHub repo would be guessing.
Modest overstatement of causal certainty
The post's tone is deliberately unglamorous — three rules and 'Worth twenty minutes' — and it does not claim dramatic gains, which keeps the gap small. It nevertheless states contested mechanisms as settled fact: that retrying unchanged reliably converts temporary blocks into durable ones, that rotating on a throttle produces a fingerprint, and that on many targets a hidden volume counter fires at N+1. Each is presented as a general law on the strength of one anecdote, and the code diverges from the prose in two places, so the confidence conveyed runs somewhat ahead of the evidence shown.
Vendor content marketing for a proxy provider
The post is published on dev.to under the RoamProxy account and ends with an explicit promotion: 'We publish code examples and testing notes for developers who scrape and automate at RoamProxy' plus a link to its examples repository. The advice most favourable to the author's commercial interest — that a hard block is unretryable and should be answered with a different identity or route — is exactly the proxy-rotation product category, and the counter/ceiling advice increases the number of hosts and identities a serious scraping operation needs. The incentive is disclosed rather than hidden, and the code is genuinely usable without buying anything, which moderates the score.
Single vendor source, code checkable, claims not
Confidence is bounded by the cluster shape: one publisher, one item, no corroboration or dissent. What the code does is high-confidence because it is quoted verbatim and its arithmetic can be checked; what the code is claimed to achieve is low-confidence because it rests on unverifiable practitioner narrative from a commercially interested author, with no adoption signal to triangulate against.
build
A retry cap is not a retry budget, and each language breaks it in a different place1 distinct publisher
build
Return the admission record, not the log line: one memory service's case for receipts1 distinct publisher
build
Three clocks, one request: why reset emails duplicate under a 4291 distinct publisher
build
Force the tool call, then hand Lightsail a long-lived key1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 20, 2026