Here is everything I know about scraping

Every request tells a story.
I spent 7 years learning to read it.

I started scraping in 2018. Since then I have built hundreds of production spiders across five companies, and spent that time understanding how modern anti-bot systems decide what to trust. This guide is what actually held up in production.

JA4+ TLS Fingerprinting Scrapy Playwright Akamai Cloudflare DataDome Kasada F5 Shape AI Scraping MCP Tools curl_cffi Camoufox Scrapling
6
Anti-bots
86
Libraries
4
Detection layers
6
Decision steps

Paste into Claude, ChatGPT or Cursor. The full download already carries the graph ahead of the prose; the two smaller files are for when you only want the structure.

Scroll
★ About the author
7 years of production scraping
Asad Ikram
Data Engineer & Scraping specialist

I started scraping the web in 2018. Since then I have spent 7+ years building production scrapers at scale, and have gone up against every major anti-bot system in use today.

Chevening Scholar 2024/25, MSc Data Analytics with Distinction.

I built this guide to share everything I know about scraping properly, the bypasses, the failures, the patterns that hold up in production. No guesswork, no generic tutorials.

Portfolio LinkedIn
🕷️
500+
Production spiders built
📊
50M+
Data points extracted
🏢
5+
Companies with production scrapers
🎓
2024
Chevening Scholar, UK Govt
01 Attack strategy

The scraping
decision flow

Walk steps in order. Stop at the first win. Complexity and cost increase right. Most production scraping is solved at steps 1–3.

Thumb rule · before you walk the flow
API or scrape? Five lines to score.

The flow below assumes you have already decided to collect the page yourself. Often that is the wrong assumption, and the argument that follows is opinion against opinion. Below are the five lines worth scoring for both routes — the vendor's official API and scraping the site — and for each one, the metric that settles it and the reason that metric beats the obvious one. The lines are the same everywhere. The answer is different for every source, which is exactly why it is worth measuring rather than debating.

Line 01
Coverage
Does the route carry the fields you actually need? An API returns what the vendor chose to expose; a page shows what they chose to publish, and those sets are rarely the same. Score it per source, not per programme.
Measure
fields returned ÷ fields needed, per source
Count fields, not sources. A route that reaches every source and carries none of your fields scores 100% on the obvious metric and zero on the useful one. This is also the only line that can be zero, and a zero here makes the other four irrelevant.
Line 02
Lead time
How long from deciding you want a source to holding a row you can use. The best single measure of whether the collection function is improving rather than merely growing, and the one most teams never track — it needs two timestamps nobody thought to write down.
Measure
days from source approved → first usable row
Take the median over your last ten sources, not the first one. The first is always slow and tells you nothing about the machine. If the median stays flat while headcount rises, you are buying coverage rather than building it.
Line 03
Cost
What a thousand records you can actually act on cost, all-in. “Usable” carries the sentence — a record nobody trusts cost more than nothing, because somebody acted on it.
Measure
fully loaded spend ÷ 1,000 usable records
Cost per GB and cost per call both hide the failures: you pay for the blocked request, the retry, and the row you later discarded. Include engineer hours — the largest line usually appears as headcount, so it escapes the comparison entirely. Worked numbers in §13.
Line 04
Reliability
Not uptime — delivery. Two numbers: how many sources sit inside their refresh window, and how many runs reported success while delivering nothing.
Measure
% inside refresh SLA + silent-failure rate
Score coverage as a delta against the last good run, not as a level. A source falling from 40,000 rows to 4,000 with every field populated passes every not-zero check ever written. The failure that costs you is the one your dashboard calls green.
Line 05
Leverage
How much of the estate rides on something shared, and how much of it repairs itself. The leading indicator for the other four: this is the line that tells you what the next twelve months cost.
Measure
% of sources on a shared template · % of breaks auto-repaired
The question underneath it: does source two hundred cost less to add than source twenty did? If yes, you have leverage. If no, you are adding sources linearly, and that stops scaling before the roadmap does.
How to actually use it

Run both routes down all five, per source, on your own numbers — not a vendor benchmark and not a blog's. Start with coverage, because it is the only line that can return zero and a zero there makes the rest academic. Then read the five together rather than totting up a winner: the first four tell you how last month went, and only the fifth tells you what next year costs.

In production the answer is often both: take the API for the fields it has and scrape the delta. That hands you something you cannot buy — where the two carry the same field and disagree, your parser is wrong until proven otherwise. The API stops being a data source and becomes a correctness oracle.

One thing these five lines deliberately do not score: whether you are allowed to. Terms of service, rate limits, personal data and the relevant case law sit above the scorecard, not inside it — and a route that scores well on all five is still the wrong route if it is one you should not take. Start at Read this first.

Recon first (Step 0): Before picking a step from the flow, capture a real session through Burp Suite and run PortSwigger's MCP server with Claude Code. One prompt traces the entire cookie lifecycle (_abck, cf_clearance, datadome, reese84), identifies sensor payload endpoints, and tells you which step from the flow below will actually work for this target. What used to be a 4-hour manual walk through HTTP history is now a 2-minute prompt. Once a session is captured, have the agent summarise endpoints before reading requests one at a time. A HAR file grouped by method, origin and path template, with numeric IDs, UUIDs, long hex strings and ISO dates folded into placeholders, turns hundreds of entries into a short list of candidate APIs, and open-source MCP tooling now does this offline without fetching anything. Treat the result as a map, not a client: such views drop query strings, headers and bodies by design, so the token chain that unlocks each endpoint still has to be traced, and nothing captured in a browser reveals server-side logic.
Asad's Priority Order, start left, move right only when needed
Step 1
📱
Mobile API
HTTPToolkit
Frida · mitmproxy
Step 2
🔍
XHR Endpoint
Chrome DevTools
Burp + MCP · webclaw
Step 3
🗃️
JSON in HTML
__NEXT_DATA__
chompjs · Parsel
Step 4
⚡
HTTP Scraping
curl_cffi
Scrapy · Scrapling
Step 5
🌐
C++ Browser
Camoufox
CloakBrowser
Step 6
☁️
Managed API
Bright Data
Zyte · Firecrawl
Rule #1, Asad's priority: Never start at Step 5. The mobile app often hits the same backend with zero anti-bot. Confirmed on a major retailer, a direct GraphQL endpoint bypassed all HTML anti-bot protection entirely. Find the API first, always. When no API turns up, check the page source before you reach for a browser, and search it for one value you can see on the page rather than skimming. Expect to find it inside a script tag more often than in the markup. On 390 top homepages with readable content, JSON-LD appeared on 215, the Next.js App Router stream (self.__next_f.push) on 33, __NEXT_DATA__ on 29, Nuxt on 13 and Redux-style state on 9. App Router payloads are split across many chunks: join them, decode each as a JSON string, then pull known field names with a regular expression, because the format is internal to React and a dedicated parser failed on the pages tried. Nuxt 3 keeps its data in a __NUXT_DATA__ array of indexes. Those are homepage counts from one run, so check your own target pages.
Before we go deeper: The flow above tells you what order to try things. But to understand why those steps exist in that order, and what happens when you skip one, you need to understand how detection actually works. The next section breaks down every signal anti-bots collect, starting at the TCP handshake.

First, place the target on the difficulty ladder

Before any of that, do the cheapest thing that saves the most pain: work out which rung the target sits on, because most teams plan for the easy rung and discover the hard one in week two, after a deadline is already promised. Difficulty is not a spectrum so much as five discrete tiers, each with a giveaway tell and a rough effort, and knowing the tier up front tells you which tools you will actually need and how long it will honestly take.
Tier 1 · Open
The data is just sitting there
What it is: a public API, or clean data right in the HTML. Tell: you can see the data in "view source" or hit a public endpoint directly. Effort: an afternoon. This is the rung you hope for and should always check for first, before assuming a fight. Make that check per site, and per page type, before choosing a tool: request robots.txt, fetch one product page with a plain client, and look for the price in the raw HTML. On 18 European shops named in one practitioner’s 71-site survey, a single re-check found all seven Italian retailers plus Thomann, Otto and Rueducommerce serving prices in plain HTML, while six UK, French and German majors refused the plain request outright. Treat that as one snapshot from one location, not a market rate. When the site is open, take your URL list from the sitemap that robots.txt declares, with its last-modified dates, rather than crawling the navigation; it is cheaper, it tells you what changed, and it is the list the site chose to publish. Expect category and search pages to need a browser more often than product pages do.
Tier 2 · Light defense
Works fine, until you speed up
What it is: rate limits, User-Agent checks, basic bot filtering. Tell: it works, then starts throwing 429s or blocks once you scale. Effort: real headers and rate control, a day or two. The fix is to slow down and look ordinary, not to reach for heavy machinery. For a plain HTTP client, “look ordinary” does not mean pasting in a browser’s user agent. Replace the library default first, because it is the string most often refused, then name your client, include “bot” and give a contact URL or email. On 472 popular homepages fetched once with httpx, the library default got a full page on 353, a copied Chrome string on 375 and a descriptive bot string with contact details on 378, so the browser string bought nothing on average while Wikipedia, W3C and several Meta domains refused it outright. That is one run from one location, on sites already known to accept plain requests, so treat it as a sensible default rather than a guarantee, and check the page you received against what a browser shows.
Tier 3 · Dynamic
Empty in source, full in the browser
What it is: the data loads through JavaScript, behind XHR or GraphQL. Tell: "view source" looks empty but the page is full when it renders. Effort: drive a real browser, or (better) find the API behind the page and call it directly, which is usually faster and more stable than rendering. A lightweight engine sits between those two options, and it is worth trying only after you test it on your own pages. In one run over 26 real homepages that needed JavaScript, jsdom produced at least half of headless Chromium’s text on 3, happy-dom on 1 and Lightpanda on 12, and the full browser was the only working option on the other 12. The memory saving is real, roughly 250 MB for jsdom and 35 MB for Lightpanda against about 470 MB for Chromium on a small demo page, but it pays only where the page actually builds. Treat a light engine as a rung you qualify per site, not a default, and keep the browser for the rest. Do not expect a light DOM to clear an anti-bot challenge, even on a page it builds: some challenges look for the engine by name. All six DataDome plv3 builds in one vendor teardown read window._virtualConsole and window._userAgent, both jsdom internals, and window.Buffer, a Node global, next to navigator.webdriver and a native-code check on atob; by the author’s count jsdom fails four of those five just by existing. Route challenged pages to a real browser or a sanctioned data source, and keep the light engine for pages nothing guards. The teardown is from TakionAPI, which sells DataDome bypass, and has not been independently replicated. Test the engine’s wait settings before you judge it. That run used Lightpanda’s default, which returns at the load event, before many JavaScript apps have built anything; a retest of 28 of the same sites with --wait-until networkidle built 21 and 22 of them over two runs, against 12 on the default, while a wait for every script to finish ran into the 15-second limit on pages that keep polling. On the sites it built it was roughly twice as fast as headless Chromium with a quarter to a third of the memory, smaller gains than the vendor’s own demo-page figures. That is one tester, two runs and one location, with the raw data not published, so qualify the flag on your own targets as you would the engine itself.
Tier 4 · Fingerprinting
Flagged before anyone reads your request
What it is: TLS/JA3 and browser-fingerprint checks. Tell: your code is blocked instantly, but the site loads fine in your own browser. Effort: engine-level stealth (impersonation or a rebuilt browser). Rotating proxies alone will not do it, because the block is about who you look like, not where you are coming from.
Tier 5 · Behavioural
It watches how you move
What it is: Akamai, DataDome, Cloudflare at full strength, scoring mouse, timing, and the path you take. Tell: you pass once, then get flagged the moment you scale or move predictably. Effort: where most teams stall. Looking human over the whole session beats being fast, and this is the rung the rest of this guide spends most of its time on.
The single most useful habit this ladder gives you is honest scoping: decide the rung before you start, not after. A target that turns out to be Tier 4 or 5 needs a fundamentally different toolchain and timeline than a Tier 1 job, and the classic failure is quoting an afternoon for what is really a month because nobody checked which wall was there. The tiers are also cumulative, a Tier 5 site is doing everything the lower tiers do as well, so you climb the ladder in order rather than skipping to the top.

Sweep the impersonation profile before you escalate the tool

One of the cheapest wins available, and almost nobody does it. The reflex when a request-based attempt fails is to escalate: curl_cffi did not work, so reach for a browser. That reflex skips a step that costs minutes and often removes the need for the browser entirely.

A 2026 write-up of a production escalation put numbers on it. The team held request, headers, cookies, rate and IP conditions constant and changed only the impersonation profile. Apply the same one-variable rule when a request fails silently. A refusal at the TLS layer often shows up as a reset or a timeout rather than a status code, which looks exactly like a network fault, so retry from the same host with a browser-impersonating client before you blame the proxy. If the impersonating client succeeds and the plain one keeps failing, you have a client-stack problem; if both fail, it is probably not the handshake. Expect this mostly on targets that already fight back: one vendor census found TLS filtering on 13.8% of landing pages overall but on sites that also rate-limited it co-occurred 78.1% of the time, against 13.2% elsewhere, though that census used the same two clients for both checks. Against one Cloudflare deployment, the generic Chrome profile returned almost no usable responses while Firefox reached roughly 80%. Behind DataDome on a server-rendered route, chrome120 returned 0 usable responses out of 6, and firefox144 returned usable content in about 7 attempts out of 10. Same library, same everything else, different profile.

Read those as a dated snapshot of two specific deployments rather than a rule about Firefox, because the useful lesson is procedural. A protection provider rejects a connection pattern, not a library, and the pattern that fails today is not the only pattern the library can produce. Before you accept the cost of a browser, sweep the profiles you already have and change one variable at a time. Check which problem you have before you escalate at all. A response with no text in it is a JavaScript problem; a challenge page or a 403 is a blocking problem, and a browser is not a guaranteed fix for the second. In one September 2026 run over 544 real homepages from the Tranco top 1,000, a headless Chromium load got past a block that stopped a plain httpx request on 14% of sites, but was itself blocked on another 5% where the plain request worked, eBay and Zillow among them, and 9% blocked both. A browser is a different identity, not a stronger one. Treat those as homepage figures from one run, one location and one client configuration; deeper pages and other networks can move them. The version of this that saves the most time is written down: keep the profile that worked in your target notes, because the next engineer on that domain will otherwise repeat the whole sweep.
How much of the web is actually gated, measured rather than guessed. The largest published audit of access controls, covering 24,898 of the most-visited sites across 230 countries and 110 industries, found that only 18.5% carry no technical barrier at all and that half run two or more layered together. WAFs appear on more than nine sites in ten, and most of those were never chosen deliberately: they arrive switched on with the CDN. Yet the same audit found 88% of landing pages still sitting in the two lowest difficulty tiers. Both numbers are true, and holding them together is the whole discipline of the ladder above. The presence of a barrier tells you almost nothing about the effort required. Only probing the endpoint you actually need does that, and the landing page is frequently not that endpoint.
8 field notes on strategy Reported from practice. Click any card to open it.
+
Aug 2026 / API reversing
Why Facebook's JSON Responses Start With an Infinite Loop
Inspect Facebook's XHR responses and the body does not begin with JSON. It begins with for (;;); — an infinite loop. It is not obfuscation and it is not anti-scraping. It is a defence against JSON hijacking, a flavour of cross-site script inclusion, and knowing why it is there tells you exactly how to handle it.
💡 The client strips nine bytes and parses; loaded as a script, it hangs before reaching the data
The attack it kills. The same-origin policy stops an attacker's page from reading a cross-origin XHR response, but nothing stops that page from doing <script src="…/api/…">. The browser fetches it with the victim's cookies and executes the body as JavaScript. Around 2006 to 2009, if the body was a bare JSON array, Firefox let you override the Array constructor or define setters on Object.prototype and read every value as the literal was constructed. That is how Jeremiah Grossman extracted Gmail contact lists in 2006.

Why the prefix works. Loaded as a script, the response loops forever before it ever reaches the data. Fetched by the real application, the client strips the first nine bytes and calls JSON.parse.

The practical note for anyone consuming these endpoints. Strip the prefix, do not fight it. And note that a top-level object like {"payload": …} was never exploitable, because it is a syntax error when loaded as a script — the prefix is applied everywhere because blanket application is cheaper than auditing which endpoints return arrays. Other codebases use )]}', for the same reason. An unfamiliar prefix on a JSON body is usually a security artefact, not an attempt to stop you.
+
Aug 2026 / Technique
Ask For Markdown and a Lot of Sites Will Just Give It To You
This guide covers publishers serving markdown to named crawler user agents. It has not covered the technique from the collector's side: send Accept: text/markdown instead of Accept: text/html and a growing set of origins return a stripped document with no navigation, no scripts and no styling. Cloudflare shipped this zone-wide as an opt-in feature in February 2026, converting at the edge rather than at origin, free on paid plans.
💡 The response carries a token-count header, so you can plan chunking before you parse
The numbers. Cloudflare reports token reduction of up to 80% against the HTML equivalent, and every converted response carries an x-markdown-tokens header estimating the token count of the markdown body. For a RAG ingestion pipeline that is a budgeting primitive arriving free in a header you were already reading.

It is a one-request probe. Send the header, look for markdown in the response and the token header alongside it. If it is there, you have skipped HTML parsing, boilerplate stripping and script execution in a single move — on a site that decided to hand it to you. Vercel documents the same pattern for Next.js, and several documentation platforms serve it while deliberately keeping the variant out of search indexes.

The caveat that keeps it honest. A February 2026 survey of coding agents found only about three of seven negotiate for markdown at all; the rest still ask for HTML. Support is real but partial on both sides, so treat it as a fast path to try first, never as something to depend on.
+
Aug 2026 / Framing
Access Was the 2008 Problem. Legibility Is the 2026 One.
Scrapy has been solving the same problem since 2008 and still does it well: get past the wall, get past the JavaScript, get the page. It did not lose users. Crawl4AI reached 75,000 GitHub stars in three years, growing far faster than Scrapy ever did, and not because it scrapes better. Because a different problem appeared next to the old one.
💡 A raw HTML dump full of nav bars is not a failure to access. It is a failure to read.
The split is clean once stated. Access asks whether you can reach the page at all, and for most of the web that was largely settled a decade ago. Legibility asks whether a model can use what you collected without drowning in it, and that question barely existed when Scrapy was designed because the consumer was a database, not a context window. Crawl4AI's entire pitch is that second problem: strip the chrome, convert to something citable, hand the model text it can actually use. The two tools are not competing, they answer different questions, which is why both keep growing.

Read it alongside the RAG acquisition-layer argument above and the tool-surface economics in the agentic section, and it is the same claim arriving from three directions: the expensive failure in 2026 is rarely the fetch. It is boilerplate competing for similarity with your actual content, payload you pay to re-read, and corpora nobody can audit.

One extension worth thinking about, from Nimble's founder. Systems are starting to accumulate knowledge about what they found. The more interesting question is whether they can accumulate knowledge about how to find it — better source selection, better retrieval paths, a sharper sense of where the relevant information for a given domain tends to live. Those are two different kinds of memory and both compound. It is the same instinct as keying scraper memory to the platform rather than the URL: the durable asset is not the record you extracted, it is the route you learned to it.
+
Aug 2026 / Recon
The Protection Is On the Endpoint, Not On the Site
A Kasada-protected property site, approached the usual way: inspect the API, lift cookies from a browser session, replay. It failed on signatures, tokens and dynamic validation, as Kasada tends to. So instead of fighting the protection layer, they went reading the JavaScript bundles to see how the frontend itself fetched data.
💡 Two endpoints, near-identical data, only one of them defended
The site exposed /searches, the primary listing endpoint, and /map-listings, which returned largely the same property data plus some additional fields. Only /searches carried the protection. The whole architecture collapsed from browser, then cookies, then tokens, then signatures, then protected API down to direct API, structured JSON, database. No browser automation, no cookie management, no session persistence, no token extraction, and it ran continuously afterwards.

Why this generalises further than it looks. Anti-bot is usually deployed as a rule on a route, not a property of a domain, and the rules are written by people under deadline. Map views, autocomplete, mobile endpoints, embed widgets, sitemap-adjacent JSON, RSS and print views all commonly serve the same underlying records through paths nobody thought to cover. The map endpoint in particular tends to be richer, because a map needs coordinates and metadata a list view does not.

So make endpoint enumeration a step, not an afterthought. Before you accept that a target needs a full browser stack, read the bundles and list every route the frontend can call, then test each one cold with a plain HTTP client. This costs an hour and regularly turns a Kasada or Akamai problem into a JSON problem. It is also the cheapest thing in the escalation playbook above, which is exactly why it should come before you reach for a browser.
+
Aug 2026 / Method
The Endpoint Only Fires If You Make the Page Ask For It
A worked walkthrough of finding a retailer's internal API, and the reason most people conclude there isn't one. On a server-rendered listing, page one is already baked into the HTML, so the JSON call never happens on a plain load. It only fires when the frontend needs data it does not already have.
💡 Open DevTools, then paginate, filter, sort or scroll. The API appears when the page runs out of data.
Four details worth stealing, all of which cost an afternoon to rediscover.

1. Warm up before you deep-link. Navigating straight to page two of a protected listing gets you blocked, and the symptom is misleading: an HTTP/2 protocol error, which reads like a network fault and is actually the anti-bot refusing you at the connection. The fix is to load the homepage first so the session collects _abck and bm_sz, then navigate to the target within the same session. Same lesson as the session-continuity material above: the cookie is not decoration, it is the thing that makes the next request legible.

2. Listen, do not scrape. The capture is a page.on("response") handler that filters on content type (application/json and the +json variants), drops the analytics noise by host (Google, Facebook, OneTrust), deduplicates by HTTP method plus base path so you get one row per endpoint rather than one per request, and writes the bodies to disk for reading afterwards. Twenty lines, and it is the difference between finding the endpoint and guessing at selectors.

3. When a click does nothing, click in the DOM. Client-side pagination often ignores a normal automation click. page.eval_on_selector(sel, "el => el.click()") dispatches it from inside the page and gets the handler to fire.

4. The credential you were about to go hunting for may not exist. In the worked example the internal API turned out to be a proxied Algolia endpoint served from the retailer's own domain, with the credentials injected server-side. There was no key to extract, because the site was authenticating on your behalf. Always check whether the endpoint is first-party plumbing in front of a third-party search service before you start reverse-engineering a token.

Read this against the agent-built-scrapers section above. The single largest finding there was a toolchain that launched a real browser and registered no response listener at all, keeping only page.content(). This is the twenty lines it was missing, and the reason that omission decides the whole strategy. The Lab #112, The Web Scraping Club
+
API-First Scraping
Skip the HTML. Hit the API. 50× Faster.
Open DevTools Network → Fetch/XHR before writing any code. Half the time the page calls a JSON API directly. 50× faster, 100× less memory, zero browsers launched.
💡 Rule: open DevTools Network tab before writing any code
The technique: open Chrome DevTools → Network tab → filter by Fetch/XHR. Reload the page. Look for requests returning JSON. Right-click → Copy → Copy as cURL. Run that cURL command. If you get the same data back, you have found the internal API. What to look for: GraphQL endpoints (POST to /graphql or /api/graphql), REST endpoints (GET to /api/v2/products, etc.), __NEXT_DATA__ (Next.js embeds full page state in a JSON script tag, no request needed, just parse the HTML). Benefits: bypasses most anti-bot because APIs typically have weaker protection than HTML endpoints, returns clean structured data instead of HTML you need to parse, no browser needed, runs at full HTTP speed. When this fails: auth cookies required, the API uses rotating tokens, or the site detects API scraping specifically.

What to watch next: the new QUERY method (RFC 10008, June 2026). One reason so many JSON APIs are POST endpoints is that the read carries a filter object too large for a URL, so developers drop it in a POST body. GraphQL does this for every query. The cost is that a POST is opaque to caches and intermediaries: it cannot be cached or safely retried, and nothing in the method tells you it was only a read. HTTP now has a verb for exactly this, QUERY, which carries a body like POST but is declared safe and idempotent like GET, with a matching Accept-Query response header advertising which query formats a resource speaks. It is a Proposed Standard, not yet widespread (framework support is landing, e.g. an open Spring PR), but two things matter for a scraper. First, as targets adopt it, a QUERY endpoint is an even cleaner signal than a POST that you have found a read-only data API. Second, because QUERY responses are explicitly cacheable, a polite scraper that respects cache headers can cut fetches the way it never could against POST. Watch the Allow header for QUERY alongside GET and HEAD.

When the internal API answers in an obfuscated shape: ProtoJSON. Finding the endpoint is sometimes the easy half. Large sites (Google properties are the textbook case) do not return clean labelled JSON, they serialise Protocol Buffers as deeply nested, positional JSON arrays with no keys, often prefixed with an anti-hijacking junk string such as )]}' that you must strip before parsing. The data is all there, but a field is addressed by its path through the array, not by a name, so a star rating might live at P[7][1][15] and an English translation at P[7][2][15][1][0]. Two things make this tractable. First, treat the leading junk prefix as a known quantity and slice it off before json.loads. Second, map the indices once and encode them as named constants, and this is a task an LLM is genuinely good at: hand it a sample response alongside the rendered page and ask it to align visible values to array paths, then freeze the mapping into a parser. The indices are effectively a private schema, so they can shift without notice, which means the index map belongs behind the same field-coverage monitoring and self-healing trigger as any brittle selector. It is the backend-API strategy carried to its conclusion: you traded HTML parsing for array-path parsing, which is faster and more stable, but it is still a contract the site can change.

Sep 2026 update. A practitioner report on Google Maps (n=1, no code published) follows this recipe: scroll a local-business search, catch the internal batch-RPC call in Fetch/XHR, then strip query parameters one at a time until you find the smallest request that still returns the positional JSON. Claimed ~10× faster than a browser, unmeasured. Scraping Maps is against Google’s terms, and business phone numbers and names can be personal data under GDPR; check before collecting leads.
+
Learning Path
Scraping Tutorials Teach the Wrong Things First
Most start with BeautifulSoup on static HTML. Real scraping is JS-rendered, sessions, rate limits, dynamic APIs. Better: DevTools → XHR replication → Scrapy → anti-bot.
💡 DevTools → XHR replication → Scrapy → anti-bot, in that order
The typical tutorial sequence: install Beautiful Soup, parse static HTML, extract data. This teaches the wrong mental model. Real production scraping involves: JavaScript renderingmost modern sites build their UI client-side, the HTML you fetch is an empty shell, Sessions and authcookies, CSRF tokens, login flows, Rate limiting and backoffexponential backoff, per-domain limits, Dynamic selectorssites change their HTML structure, you need adaptive extraction. The right learning sequence: DevTools Network tab → understand how data flows between client and server → learn to replicate XHR requests with requests/curl_cffi → Scrapy for structure and scale → fingerprinting and anti-bot bypass last. Understanding what actually happens when a browser loads a page is more valuable than memorising BeautifulSoup APIs.
+
Mental Model
Understanding Beats Tools Every Time
Not curl_cffi, not Playwright, not a $300/mo plan. Understanding how detection works is the real advantage. Tools change. Detection evolves. Understanding transfers.
💡 "Tools change. Detection evolves. Understanding is what transfers."
The mental model shift: most scrapers think in terms of tools ("which library bypasses Cloudflare?"). Experienced scrapers think in terms of signals ("which signals is my scraper leaking that Cloudflare can detect?"). The difference: tool-thinkers update their library when it breaks. Signal-thinkers understand why the library broke and can fix it themselves or identify the correct replacement. Signals Cloudflare checks: JA4 TLS fingerprint, HTTP/2 SETTINGS frames, navigator properties (webdriver, plugins, languages), Canvas hash, WebGL renderer, timing patterns, IP reputation. If you know which signal you are leaking, you can fix it regardless of which library you are using. This understanding also transfers to new anti-bots, the signals are similar across vendors even though the implementations differ.

The six steps above tell you what order to try. But to know which step to stop at, and why skipping ahead costs you days, you first need to understand how the detection actually works. Let's go deeper.

02 The anatomy of detection

Before you send a single byte,
you've already been judged.

The moment your scraper opens a TCP connection to a CDN, a fingerprinting pipeline triggers. By the time your HTTP request body arrives, four independent scoring systems have already assigned you a trust score. Here's exactly what each one measures, and why defeating just one is never enough.

The fundamental insight: Anti-bots don't make binary decisions. They assign a continuous trust score across all four layers simultaneously. A perfect TLS fingerprint with a datacenter IP and machine-like mouse movement still fails, just at a different layer. The only winning strategy is addressing all four at once. If the eight-layer atlas feels like too much to hold at once, reduce it to four questions that you should be able to answer for every client you run: what it claims to be (User-Agent and declared identity), how it connects (the TLS and HTTP/2 handshake), where it connects from (address type and reputation), and how it behaves (pacing, scrolling, input). A former detection engineer at one of the largest CDNs uses exactly this split and notes that most of these checks resolve before the page loads. That makes them the front line, while in-page JavaScript and side-channel fingerprinting come later. Treat the split as an insider’s framing rather than a measured weighting, since no numbers come with it. It also flags where the stack is heading. An agent driving a real browser passes the first three almost by default. The open question becomes who sent the agent, and today’s request signatures name the vendor that built it, not the person it works for.
Concretely, here is the whole dossier a server holds before your JavaScript executes even one line. A modern browser is not one program but several processes cooperating, a coordinator process for tabs and navigation and cookies, a renderer for HTML and JS and the DOM, a GPU process for compositing, and a network process that actually speaks to the server, all wired together over inter-process communication. Only the renderer runs page JavaScript, and it runs late. By the time it does, the network process has already completed a handshake and a request, so the server has already collected, with zero cooperation from your code: your IP address and its reputation, your TLS fingerprint (JA3/JA4) from the ClientHello, your HTTP headers and their exact values, the header order and casing, the ALPN and cipher list, your Client Hints (sec-ch-*), and your request timing and rate. That is seven independent signals scored before a single line of your JS runs, which is why a flawless in-page fingerprint cannot save a client whose network-layer story does not match: if the headers announce Chrome 140 while the TLS handshake is Python's OpenSSL, the contradiction is decided before your first document.querySelector. The practical takeaway threads through this whole section, the JavaScript layer is the last thing a server learns about you, not the first, so a scraper that only manages its in-page fingerprint has already lost the connection it never knew it was being judged on.
This layered picture is not just the folklore of this guide, it is where the peer-reviewed research lands too. A 2025 integrative literature review of a dozen years of survey-bot studies (Karumathil and Tripathi, in AIS Transactions on Human-Computer Interaction) reached two conclusions worth carrying over to any bot problem, not just fraudulent survey respondents. First, there is no silver bullet: detection is inherently cyclical, defenders ship a check, bot authors adapt, defenders ship the next check, and no single technique stays decisive. Second, the only durable answer is multimodal quality control, layering independent protections and then checking them for consistency against each other, which is the academic phrasing of the same coherence principle the detection stack above is built on. The same logic generalises straight to the abuse a scraper impersonates or triggers, new-account fraud, account takeover, credential stuffing: any one signal can be spoofed in isolation, so the defensive value lives in the cross-checks between them, and correspondingly the offensive difficulty lives in keeping every layer telling one story at once. Expect the same fusion from small sites that have nothing more than their access logs. A defender does not need a vendor to combine request rate, header anomalies, a JA4 that disagrees with the declared browser, and navigation shape into one score; a short rule-based script does it. Keep each signal unremarkable on its own, because a fused score is dominated by clients that are odd on several axes at once: in one synthetic demonstration with 24 clients and a 0.62 review threshold, the four flagged clients scored 0.83 to 0.87 while every other client stayed at or below 0.39. That demonstration published no code, weights or real traffic, so read the shape of the result, not its numbers.

Layer 1, TLS Fingerprinting: The Handshake That Betrays You

This fires before a single HTTP byte is exchanged. Understanding it is non-negotiable.

Origin 2017 · Salesforce Research
JA3, The First Fingerprint
When any HTTPS client connects, it sends a TLS ClientHello message. JA3 extracts five fields from it and MD5-hashes the combination:

TLS Version + Cipher Suites + Extensions + Elliptic Curves + Curve Formats

This produced a stable 32-char hex hash. Python's requests library has always had the same JA3 hash. Every major anti-bot catalogued it. By 2021, your Python scraper was identifiable before the first HTTP header.

JA3's weakness: Chrome started randomising TLS extension order in 2022. Same browser, different JA3 every session. The fingerprint became unstable and unreliable.
2023 · FoxIO · Replaces JA3
JA4+, The Unbreakable Standard
JA4 was engineered specifically to survive Chrome's randomisation. Instead of hashing raw extension order, it sorts extensions alphabetically and removes GREASE values before hashing. The result is stable regardless of Chrome's ordering.

JA4 format: t13d1516h2_8daaf6152771_b0da82dd1658
, t13 = TLS 1.3, d = DTLS, 1516 = cipher count+length hash, h2 = ALPN (HTTP/2), remainder = extension hash

JA4+ extends this with: JA4H (HTTP header fingerprint), JA4X (X.509 certificate), JA4SSH (SSH handshake), JA4T (TCP window + options). Cloudflare deployed it in a Rust crate at CDN edge. Akamai in an EdgeWorker. Both fire before your request reaches origin.
HTTP/2 · Wireshark Observable
HTTP/2 Frame Fingerprinting
Even with a perfect JA4 hash, HTTP/2 itself leaks your client identity. The SETTINGS frame that every HTTP/2 client sends at connection start has parameters that vary by implementation:

HEADER_TABLE_SIZE, MAX_CONCURRENT_STREAMS, INITIAL_WINDOW_SIZE, MAX_FRAME_SIZE, MAX_HEADER_LIST_SIZE

Chrome's exact values are documented. Python's httpx sends different values. curl sends different values. The ordering of these settings, the window update frame sizes, and the HPACK compression decisions all create a secondary fingerprint that cannot be spoofed without rewriting the HTTP/2 clientwhich is exactly what curl_cffi does.
2024+ · Emerging Standard
QUIC / HTTP/3 Fingerprinting
As HTTP/3 adoption grows, JA4Q and QUIC Initial packet fingerprinting are being deployed. QUIC's handshake carries its own fingerprint surface: connection ID length, transport parameters, initial packet number, token presence.

Chrome's QUIC stack differs from libcurl's QUIC implementation differs from Python's aioquic. Each leaves a unique signature in the Initial packets.

Current status: JA4+ covers QUIC. Cloudflare has begun collecting QUIC fingerprints. Not yet widely enforced for blocking, but the infrastructure is live. Tools like curl_cffi are actively implementing QUIC parity.
python
# Test your actual JA4 fingerprint against tls.browserleaks.com
import requests
from curl_cffi import requests as cffi

# ❌ requests, exposes Python/urllib3 JA4, blocked immediately
r1 = requests.get("https://tls.browserleaks.com/json")
print(r1.json()["ja4"])
# → t13d1516h2_8daaf6152771_b0da82dd1658  (Python fingerprint, catalogued, blocked)

# ✓ curl_cffi, emits Chrome 124's exact JA4 hash, HTTP/2 frames, cipher order
r2 = cffi.get(
    "https://tls.browserleaks.com/json"–
    impersonate="chrome124"  # also: chrome110, chrome107, safari17
)
print(r2.json()["ja4"])
# → t13d1517h2_c4b4b4b4b4b4_aaaaaaaaaa   (Chrome 124 fingerprint, passes)

# Also check HTTP/2 fingerprint
print(r2.json()["http2"])  # Chrome's exact SETTINGS frame values
Practical · How to actually spoof TLS in 2026
From theory to working code

All the JA4+ research is academic until you ship it. Three tiers of solution, in order of how often you should reach for each:

Tier 1 · 80% of cases
Use a TLS-impersonating HTTP client
curl_cffi (Python), tls-client (Go), noble-tls, hrequests. One line of code, exact Chrome/Firefox JA4. Drop-in replacement for requests.

curl_cffi.requests.get(url, impersonate="chrome131")
Tier 2 · Scrapy projects
Plug a stealth middleware in
scrapy-stealth adds TLS + HTTP/2 fingerprinting + proxy rotation + fingerprint cycling to existing Scrapy spiders via DOWNLOADER_MIDDLEWARE. Per-request engine switching keeps simple URLs fast.

meta={"stealth": {"profile": "chrome_147"}}
Tier 3 · Hardest targets
Browser with C++ patches
When TLS spoofing alone fails (Akamai extension probes, Kasada toString checks, behavioural ML), reach for Camoufox, rayobrowse, or CloakBrowser. C++ binary patches ship a real-browser TLS stack along with everything else.

Cost: 200MB+ memory per browser instance
⚠ Common mistakes
1. Spoofing User-Agent without TLS. If your UA says Chrome but JA4 says Python urllib3, you flag faster than no spoofing at all, the mismatch is the signal.
2. Forgetting HTTP/2 SETTINGS frames. Even perfect JA4 fails if your HTTP/2 SETTINGS (header table size, max concurrent streams, initial window size) do not match the browser you claim to be. curl_cffi and tls-client handle this; rolling your own usually does not.
3. Using stale impersonation profiles. Chrome 120 fingerprints in 2026 are themselves suspicious, real users rolled forward. Keep impersonate="chrome131" or newer.
What "impersonate a browser" means at the byte level, and why a real browser fingerprint is not one value but a set of consistent tells. The reason a default HTTP client gets a 403 it cannot explain is that the ClientHello is built by its TLS library (OpenSSL for requests, Go's crypto/tls for Go clients) with that library's own cipher order, extension set, and ordering, a fingerprint that matches no shipping browser. Tools in the curl-impersonate lineage (and the Go uTLS library that pioneered this) do not tweak that ClientHello, they construct it manually, byte by byte, to reproduce a specific browser's exact handshake. Knowing the tells they have to match is what lets you tell a convincing impersonation from a leaky one.
The per-browser discriminators
GREASE presence, curves, and extension order
A handful of stable signals separate the real stacks. GREASE (RFC 8701: random sentinel values like 0x0a0a a client injects so servers stay tolerant of unknown values) is present in Chrome, Edge, and Safari, but Firefox sends none, so a ClientHello that claims to be Chrome with no GREASE is self-contradictory. Safari advertises curves the others usually omit (for example secp521r1) and orders its suites and extensions distinctly. Chrome and Edge are both Chromium but not byte-identical. The renegotiation-info extension (0xff01) is present in stock browsers and a common omission in spoofs. Each TLS library has its own defaults (Chrome uses BoringSSL, Firefox uses NSS), and the gap between those defaults and a browser's exact bytes is exactly what a fingerprint database scores.
Why pinning a profile is fragile
Match the whole stack, and keep it current
Impersonation libraries pin to a named profile (HelloChrome_131 and the like) because a known-good fingerprint is safer than auto-updating to an untested one, but that stability cuts both ways. Real Chrome rolled forward and randomises its extension order per connection since 2023, so a frozen profile drifts from the live browser, and named uTLS CVEs have shown pinned fingerprints leaking subtle inconsistencies (an ECH cipher choice that real Chrome never makes) on a fraction of connections. Two rules follow. Keep the profile current, a 2024 Chrome hash in 2026 is itself a tell. And keep it coherent across the whole stack: the TLS fingerprint, the HTTP/2 SETTINGS frame, the header wire order, and the User-Agent must all name the same browser, because a Chrome JA3 under a Firefox User-Agent is an instant contradiction. The same contradiction applies when a script with a Python or Go handshake sends a copied Chrome User-Agent: the header names a browser the connection plainly is not. On a single 472-homepage run with httpx, that pairing earned no more full pages than an honest bot string (375 against 378), and it drew 400s from Facebook, WhatsApp and Meta domains and 403s from W3C, MySQL and LG where the honest string was served. If you are not going to make the handshake match, do not let the header claim a browser at all. One run, one vantage point and homepages only, so read the counts as indicative, not as a rate you can plan against.
Two TLS practicalities that trip people up: the MITM-proxy fingerprint, and TLS 1.3 session resumption. First, if you route traffic through an intercepting proxy (Burp, mitmproxy, and the like) the target never sees your browser's handshake. Such a proxy runs two independent TLS connections, your browser to the proxy, then the proxy to the server on its own stack, and the server only ever sees the second one, while the browser-grade HTTP headers (including the User-Agent) ride on top unchanged. So the server reads a request that says Chrome or Firefox over a handshake that is plainly the proxy's Java or Go TLS library, and that mismatch alone is enough to get silently downgraded. The practical symptom is worth memorising: auth or session endpoints intermittently failing with vague generic errors and no CAPTCHA ever shown, because an invisible risk-based check just failed closed. Before assuming an endpoint is broken, echo your real outbound handshake against a fingerprint service (tls.peet.ws and similar) and confirm it looks like the browser you are claiming.
Second, do not read too much into a fingerprint that changes between two runs of the same client. TLS 1.3 session resumption makes this happen legitimately: the first connection to a host has no cached session so the client offers a session_ticket extension, and the second reuses the ticket and offers pre_shared_key instead, so the ClientHello bytes, and therefore the JA3 hash, genuinely differ between first and repeat visits. It is not randomness or a timestamp, it is normal protocol behaviour that real browsers and proxies both exhibit, so a differing hash on a repeat test does not mean your spoofing broke. This is also why no serious detector allowlists one exact hash: real browsers already do not produce a single stable fingerprint (Chrome has randomised its TLS extension order since early 2023, which is part of why JA4 sorts extensions before hashing), one vendor reports on the order of fifteen million distinct JA4s across hundreds of millions of real users in a single hour, and a routine client-library update has been documented raising a mobile app's block rate by several percent purely from legitimate users' fingerprints shifting. The takeaway for a scraper is calibrating: the target is to land inside the broad cloud of plausible modern-browser fingerprints, not to clone one canonical hash byte-for-byte, because the detector is scoring closeness among many inputs, not string-matching a single approved value.
The reframe that changes what TLS is for: reputation, not resemblance. Everything above asks one question, does this handshake resemble a real browser. That question has a shelf life, because resemblance is cheap to fake, curl-impersonate and the uTLS crowd reproduce a browser's JA4 byte for byte, so a pure resemblance check eventually flags nothing. A sharper question is not what a fingerprint looks like but what it does: behaviour is not a property of the handshake, it is a property of everything that handshake has ever been seen doing. Research built from inside a residential proxy network (where a node sees the firehose of JA3/JA4 handshakes and the SNI each one reaches for) turns TLS from a per-connection shape check into a reputation layer, and the tell that falls out is promiscuity. A real browser's fingerprint touches the handful of domains its human actually visits; a tool fires the same barrel at a bank, a sneaker drop, a social API, and an airline in a row, and nobody browses like that. You judge a handshake by how lit-up it is across everything you have watched it do, not by one connection in isolation.
The uncomfortable finding
It is not fake Chrome, it is real Chrome being driven
In that corpus about 56% of connections carried a Chrome/Chromium TLS fingerprint, almost exactly Chrome's real market share, which looks reassuring until you read the destinations: a large majority of that "Chrome" was automation infrastructure, social-media APIs and attestation endpoints, anti-bot gauntlets being ground through (DataDome, ThreatMetrix, Turnstile, Queue-it), retail scalping, ticket bots, and the dead giveaway, domains no human browser ever visits (anti-detect-browser and sneaker-tool phone-home endpoints). The reason it fingerprints as genuine Chrome is that it is genuine Chrome: headless Chrome under Playwright or Puppeteer, Electron, Android WebView, and the anti-detect browsers, which are Chromium forks. The moment an adversary stops faking a browser and starts driving one, the handshake cannot tell the driver from the driven, or from a real person, which is exactly why that route is chosen.
One test that still separates them, and the limit
The permutation check, and where TLS runs out
There is a clean discriminator between driven-real-Chrome and cheap static fakes. Since Chrome 110 the browser shuffles its own ClientHello extension order on every connection, so one Chrome JA4 fans out into around a hundred distinct JA3s over repeated visits. Genuine Chrome traffic permutes correctly essentially all the time; a static impersonator with a perfect JA4 but a fixed hello never permutes, and that stillness is the tell (mature impersonation libraries can reproduce the shuffle, but the lazy tier does not). The honest limit is the lesson, though: TLS reputation is excellent at naming the software and catching the tier that never tried, but once the answer comes back "real Chrome, permuting correctly," the handshake has told you everything it can. It can say this is Chrome; it cannot say this is a human's Chrome. That single question, is there a person here, is exactly the one the network layer cannot answer, which is precisely why a JS challenge, hardware probing, and behavioural checks exist above it.
JA4 is not as stable as its reputation suggests, and the instability has a stealth cost worth understanding. The session-resumption wrinkle above is a specific case of a broader problem: JA4 counts and hashes every extension it sees, including ephemeral ones the client only sends in certain states. On a TLS 1.3 resumption a browser adds pre_shared_key (0x0029) and often padding (0x0015), which bumps the extension count and changes the hash, so the same browser on the same machine emits two different JA4s (for example a ...1516... variant with no key and a ...1517... variant with the pre-shared key, note the 16 becoming 17). A proposed refinement, JA4E, strips the ephemeral extensions before hashing to recover one stable per-client identity. From the evasion side this reframes the target: staying coherent now means aligning your TLS state with your application state. If a site has already handed you a session cookie, your HTTP layer is claiming to be a returning visitor, so a cold TLS handshake with no pre_shared_key is a contradiction between the two layers, exactly the kind of cross-signal mismatch the rest of this section is about. A returning-user HTTP state riding a first-time TLS handshake is a tell.
Two more sharp edges for anyone building or defeating a TLS fingerprint. First, GREASE: modern browsers inject deliberately random, meaningless values into the handshake (so servers stay tolerant of unknown parameters), and JA4 as commonly implemented ignores them. You should not try to compute the GREASE value, because Chrome randomises it every request, but its mere presence is signal a stricter fingerprint could flag, and a client that never emits GREASE where a real Chrome would is as odd as one that gets it wrong. Second, a concrete capture-side trap: as of Chrome 151, GREASE is also injected into the signature_algorithms extension, so a fingerprint parser that does not filter GREASE out of that field before hashing will compute a fresh, wrong JA4 on every single request from the same client. If you run a TLS-capture site or a fingerprint database, filter GREASE across every extension, not just the cipher and extension lists, or your own corpus will look like an infinite population of one-off clients. And note the ceiling of the format itself: JA4 (and JA4+) does not natively capture HTTP/2, HTTP/3, or QUIC transport parameters, which is why the HTTP/2 frame-ordering fingerprint (often called the Akamai HTTP2 fingerprint) is tracked separately rather than folded in. When you test whether a target filters on the handshake, change one layer at a time: the same client with only the TLS profile swapped, then HTTP/2 switched on or off, then header order. A two-client comparison that swaps everything at once tells you the stack was refused, not which layer refused it. The most-quoted prevalence figure has exactly that limit. The 13.8% of 11,100 landing pages reported as TLS-fingerprinted came from one plain aiohttp request, which speaks HTTP/1.1, against one browser-impersonating curl_cffi request, scored as blocked on any status of 400 or above or any error, once per site. Read it as the share of sites that refused a plain Python stack but served a browser-shaped one, not as a count of handshake checks, and probe your own endpoint before spending a week on JA4.

Below the handshake: the layer most stealth work never reaches. TLS is where this section starts because it is where most practitioners start, but it is not actually the bottom. The TCP SYN packet that opens the connection carries its own signature: initial window size, maximum segment size, window scaling, the exact set and ordering of TCP options, and the initial time-to-live. Those values come from the operating system's network stack, not from your HTTP library and not from your TLS library, which is precisely what makes them useful to a defender. A request can present a flawless Chrome-on-Windows story at every layer you control and still contradict itself at layer 4, because the packet was assembled by a Linux server kernel. Passive OS fingerprinting from SYN packets is old technique, and it is now being applied to proxy egress specifically: the question stops being what your client claims and becomes what kind of machine actually sent the first packet. It is a small share of blocks compared with IP reputation and behaviour, but it is the residue that survives everything else, and it is invisible from inside the browser.
Layer 2, JavaScript Fingerprinting: The Page That Interrogates You

2026 update: Microsoft Edge now silently returns navigator.webdriver = false for AI-agent-driven Playwright sessions, and Google patched out the most common CDP-detection technique in V8. The browser vendors that wrote the automation-transparency rules quietly stopped enforcing them. Any bypass or detection strategy pivoting on these flags should treat them as unreliable. See the Innovation Feed card "The Vendors That Wrote the Detection Rules" for details.

Once your TLS passes, the page loads its anti-bot script. This is a 500KB+ obfuscated interrogation that runs dozens of tests in parallel.

Most stable signal · GPU dependent
Canvas + WebGL Fingerprinting
The page draws invisible shapes, gradients, and text using canvas.getContext('2d') then calls canvas.toDataURL(). The exact pixel output varies by:

, GPU manufacturer and model (NVIDIA vs AMD vs Intel)
, Driver version and sub-pixel rendering
, OS-level font rendering (Windows ClearType vs macOS CoreText)
, Canvas size and DPI scaling

A headless Chromium with no GPU produces a software-rendered canvas with a known hash. Botaaurus and CloakBrowser spoof this at the C++ level by injecting slight noise into the pixel values before toDataURL() returns, enough to vary the hash while remaining visually identical.
GPU vendor string · Renderer string
WebGL Fingerprinting
WebGL exposes the GPU through gl.getParameter(gl.RENDERER) and gl.getParameter(gl.VENDOR). Real Chrome returns something like ANGLE (Intel, Intel(R) UHD Graphics 620 Direct3D11 vs_5_0 ps_5_0).

Headless Chrome returns a generic string or crashes on WebGL entirely. Anti-bots cross-reference: if WebGL says "Intel UHD 620" but Canvas hash shows software rendering, that's a contradiction, you're flagged.

WebGL extensions list is also fingerprinted. Real GPUs expose 30–40 extensions. Software renderers expose a different subset. The exact combination is GPU-specific and stable across sessions.
Deterministic · Hard to spoof
AudioContext Fingerprinting
The page creates an AudioContextgenerates a sine wave through an OscillatorNoderuns it through a DynamicsCompressorNodeand reads the output buffer values. The floating-point output depends on:

, CPU architecture (x86 vs ARM floating-point precision)
, Operating system audio stack
, Audio driver implementation

Headless environments often return 0.0 across the buffer (no audio context), or a software-emulated value that differs from hardware. CloakBrowser patches this at the Chromium C++ audio rendering layer. Keep the audio surface in proportion. The rendering probe needs no permission and finishes in roughly 5 to 50 ms, so assume it runs on every protected page and that its output has to match the OS and CPU your other signals claim. Real users do not all produce one clean value, though: Safari 17 adds noise in Private Browsing, Brave randomises the output and Tor disables Web Audio, so an unusual hash is weaker evidence than an inconsistent one. The microphone is a separate question. Reading raw input through getUserMedia() with gain control, noise suppression and echo cancellation switched off needs a secure context and an explicit user grant, so it is not a silent anti-bot signal. It matters on liveness and identity-verification flows, where the media stream itself is what gets judged.
Runtime patches exposed · 2026 standard
Function.toString() Detection
This is why playwright-stealth fails against Kasada in 2026.

When JS patches a native function, for example, navigator.webdriverit replaces the getter with a custom function. Calling Function.prototype.toString.call(getter) on the patched function returns function () { [custom code] } instead of function () { [native code] }.

Kasada specifically tests dozens of native functions this way. playwright-stealth patches them in JavaScript, so toString() reveals the patch.

Check when your stealth layer last shipped before trusting it. puppeteer-extra-plugin-stealth is still installed about 835,000 times a week (npm, September 2026), yet its latest release, 2.11.2, dates from 1 March 2023, when Chrome was on version 110, and its repository has not been pushed since July 2024. Every evasion it ships is a JavaScript override of the kind described above, so each Chrome release since then is a surface it has not been updated for, and the overrides themselves are inspectable. If a Puppeteer scraper starts failing, run the same target with the plugin removed before adding more patches. Hosted alternatives move the fingerprint work into the browser build instead: Browserless’s BAP SDK exposes stealth as an endpoint change to /stealth/bql and humanizes typing with a random 50–200 ms per keystroke by default, but it publishes no pass rates or patch list, so treat it as a claim to test on your own targets. PatchRight fixes this at the Python source levelbefore Chrome even starts. There's no JS to inspect.
The harvest-and-replay threat (Castle Research, April 2026): Real canvas hashes from real Macs are now commercially traded. Services like Bablosoft's PerfectCanvas render canvas on a real remote GPU and inject the result into headless sessions. A "passing" canvas hash is not proof of a real browser, it may be a replayed hash from a legitimate device. Detection systems are responding by pairing canvas probes with harder-to-replay signals: WASM SIMD CPU timing, behavioural Bezier-curve physics, and controlled variability in their own probe scripts. Full report.
Akamai specific · 60 probes in 2026
Chrome Extension Probing (Akamai)
Akamai's sensor.js fetches 60 known Chrome extension resource URLs using fetch('chrome-extension://[id]/manifest.json'). Real Chrome browsers have at least a few extensions installed (ad blockers, password managers, etc.).

A headless browser returns net::ERR_FAILED on all 60 requests simultaneously, a statistically impossible result for a real user. The extension IDs probed include:

cjpalhdlnbpafiamejdnhcphjbkeiagm (uBlock Origin)
hdokiejnpimakedhajhdlcegeplioahd (LastPass)
nngceckbapebfimnlniiiahkandclblb (Bitwarden)

Fix: CloakBrowser loads real extension profiles. You install 1Password or Bitwarden into it so some probes return real manifest data.
CDP timing leaks · Protocol signals
Headless / CDP Detection
Beyond navigator.webdriverCDP-controlled browsers expose themselves through subtler signals:

Timing: CDP's Runtime.enable command leaves a timing gap between page parse and script execution that doesn't exist in real Chrome.
Execution context: window.cdc_adoQpoasnfa76pfcZLmcfl_Array and similar artifacts left by ChromeDriver are checked.
Permission API: Real Chrome returns realistic permission states. ChromeDriver returns defaults inconsistent with a "normal" browser.
Plugins: Headless Chrome has zero plugins. Real Chrome always has at least the PDF viewer plugin.

Camoufox's solution: Uses Mozilla's Juggler protocol, which sits below CDP entirely, none of these artifacts exist.
How the top tier weaponises the gap between two layers: the Akamai escalation trap. The layers above are usually described as independent checks you pass one at a time. The hardest deployments do the opposite, they cross-check one layer against another so that passing each in isolation still fails. A cold-start agentic mapping of a live Akamai sensor laid this out cleanly, and it is worth internalising because it explains why a technically perfect client payload can still earn a permanent block.
The trap
TLS at the edge cross-checks the behavioural payload
Akamai evaluates the connection's TLS fingerprint at the edge before any client JavaScript runs. A consumer TLS stack (for example Safari emulated through curl_cffi) gets a 200 carrying the sec-cpt challenge; an automation stack (stock Node or Puppeteer Chromium) gets an immediate 403 and never sees the challenge. The trap is what happens next. Even if a real Chromium driven by a stealth tool solves sec-cpt and serialises a mathematically perfect roughly 2.2KB behavioural payload to validate the _abck cookie, the edge still refuses it when the TLS fingerprint on that same connection reads as the automation tool. Perfect behaviour over the wrong TLS escalates to a permanent 403 for the session. The lesson the guide repeats applies at its sharpest here: every layer must agree at once, a flawless payload over a mismatched handshake is worse than useless.
The anti-hook
The "clean iframe" defeats window-object patches
Stealth tools commonly patch native APIs on the page's window (overriding navigator.webdriver, wrapping DOM methods in a Proxy, replacing toString). Akamai sidesteps all of it by injecting a hidden zero-size iframe and pulling pristine API references straight from iframe.contentWindow, so it can call the original native functions even when the main window is heavily spoofed. It pairs this with Function.prototype.toString checks for the [native code] marker and Object.getOwnPropertyDescriptor inspection of getters that should not exist, then ships an integrity hash to a separate anti-hook endpoint before the main telemetry is even sent. Practical consequence: patching the visible window is not enough, your patches must survive being compared against a clean realm the page can conjure at will.
The reference open-source fingerprinter: FingerprintJS, and the one line in its own README that every scraper should read. If you want to actually see what a fingerprinter collects rather than read about it, the canonical starting point is FingerprintJS, the most widely used open-source (MIT) browser-fingerprinting library. It runs entirely client-side, queries a broad set of browser and device attributes, and hashes them into a single stable visitorId, and the property that makes fingerprinting matter at all is easy to demonstrate with it: open its demo, then reopen in incognito or after clearing browser data, and the identifier is unchanged, because none of the signals it reads live in cookies or local storage. Running it against your own scraper is the fastest honest audit of how identifiable your setup is, and it pairs naturally with the local BotD and CreepJS harness described just below.
Identification is not detection
FingerprintJS vs BotD: same company, different jobs
Keep two of this vendor's libraries straight, because they answer different questions. FingerprintJS does identification, is this the same browser I saw before, producing a stable visitor ID for deduplication and tracking. BotD, from the same team and referenced just below, does bot detection, is this browser automated at all. A scraper is exposed on both axes at once: FingerprintJS-style signals link your sessions together across IPs (the rotation-inversion problem from the proxy section), while BotD-style checks flag the automation itself. Beating one does nothing for the other, which is why a coherent identity and a clean automation surface are separate pieces of work. Linking runs on a different rule from detection, so test it separately. Assume a client-side ID stored in localStorage is the strongest link a defender holds: it ties every session from a reused profile together whatever the IP does. Without it, assume hardware is the fallback. One open-source fraud console, OpenFraudMonitoring v1.3, joins a new visit to a known device only when the GPU renderer or device model, the screen geometry, and the CPU count with device memory all match exactly one stored device, and it states outright that IP cannot establish a link. That cuts both ways: identical cloud machines can collapse into a single device, while one changed group splits a real machine in two. It is one project with no published accuracy figure, so treat it as a readable example of the logic, not as how commercial vendors weigh it.
The admission in the README
Client-side fingerprints can be spoofed, so the hard version moves server-side
The most useful thing in the FingerprintJS documentation, from a scraper's point of view, is its own stated limitation: because the fingerprint is generated and processed in the browser, it is openly described as vulnerable to spoofing and reverse engineering, and the open-source accuracy is "significantly lower" than the commercial tier. That commercial version closes the gap precisely by moving work out of reach, combining the client signals with server-side processing over 100-plus signals plus network-level data, and validating that signals were not tampered with or replayed. That single design choice is the whole detection arms race in miniature: anything computed in the browser you can eventually forge, so serious systems anchor the verdict where your code cannot reach, which is the same lesson as the tanh and coherence sections, viewed from the vendor's side.
A measured warning: the JavaScript-surface score tells you almost nothing about whether you are caught. It is tempting to judge a stealth setup by how clean it looks to an in-page fingerprint probe, but that score and the actual block decision are only loosely related. The signal that flips many verdicts lives below JavaScript, in the driver binary. A practitioner who stood up the open-source detectors locally (BotD and CreepJS) and ran stock Selenium, selenium-stealth, and undetected-chromedriver against real Chrome found the gap directly: selenium-stealth cleaned up roughly 94 percent of the JS-surface checks, yet BotD still labelled it "selenium," because the stealth layer spoofs in-page properties but never strips the ChromeDriver binary's $cdc_ markers, which is exactly what BotD keys on. Only undetected-chromedriver, which patches those markers out of the binary, passed outright.
The counterintuitive part
Naive stealth can make you more detectable
The same harness showed selenium-stealth's getter-based spoofing adding two CreepJS "lies" that plain Selenium did not have. When you override a property with a JavaScript getter, the override is itself a detectable artifact: a function where a native value belongs, a toString that does not read as [native code], a descriptor that should not exist. A consistency-checking detector counts each of those as evidence of tampering. So a half-measure that patches the visible surface while leaving the binary tell in place can score worse than no stealth at all, because you have removed nothing that mattered and added new lies to catch.
The operating rule
Fix the layer the detector actually reads
This is why the library section rates binary-patching and below-CDP tools (undetected-chromedriver while it held, nodriver, the source-patched browsers) above JS-injection stealth on hard targets. The rule generalises past Selenium: identify the layer a given detector actually keys on (driver binary markers, the TLS handshake, the automation protocol) and fix it there, rather than polishing a JavaScript surface the detector may barely weight. And measure it: a local BotD or CreepJS harness turns "I think this is stealthy" into a before-and-after number, which is the only way to notice when a patch made things worse.
A reproducible way to rank stealth setups, and one CDP gotcha it exposed. An open build-in-public benchmark (stealthbench) scores automation configs against self-hosted detectors on localhost and commits every result, and its matrix is a useful sanity check on the folklore. Across a Sannysoft-style panel, BotD, CreepJS, and rebrowser-bot-detector, the runtime-patching CDP configs came out on top: undetected-chromedriver and its successor nodriver (which drives Chrome straight over the DevTools Protocol with no Selenium) passed every panel cleanly and occasionally aced the full tells panel, while stock Selenium and selenium-stealth got dinged on the same handful of automation giveaways every time, and Camoufox sat just behind the Chrome leaders. One honest wrinkle worth keeping in mind when you read any such table: a Firefox tool legitimately cannot answer Chrome-specific tests, so it runs a smaller denominator rather than failing, and a benchmark that treats that absence as a fail understates it. The methodological point is the durable one, a new detector that confirms your existing ranking is evidence the ranking was measuring something real, not noise.
The bug that build surfaced is a genuine trap for anyone reading signals back over the DevTools Protocol. Driving Chrome via CDP, a call like evaluate(..., return_by_value=True) can silently drop falsy return values if the client's unwrap logic tests truthiness (an if result.value: style check), so a script that legitimately returns false, 0, or "" comes back as nothing. This is quietly catastrophic for stealth work, because the values a bot detector traffics in are exactly the falsy ones: navigator.webdriver is supposed to read false on a good setup, and if false silently vanishes you cannot tell a passing config from a broken read. The clean fix is to stop trusting the transport's unwrap and force a round-trip: wrap the probe in JSON.stringify(...) on the page and JSON.parse (or json.loads) the returned string, so 0, false, "", and nested objects all survive verbatim (the one edge is a script evaluating to undefined, which stringifies to a bare word rather than valid JSON, so treat a non-string result as an unusable signal). The general lesson beyond this one library: when you read detection signals through any automation transport, verify the transport does not reshape your values before you trust a single result, because a corrupted read looks exactly like a passing check.
A second, larger benchmark reaches the same verdict and adds two methodology lessons worth stealing. An independent fifteen-tool run against live detectors sorted the open-source Python automation stack cleanly, and the pattern held: the tools that passed the strict referee mostly won by rebuilding the browser rather than patching flags onto a normal Chrome, engine-level Firefox and Chromium builds (camoufox, a patched-Chromium binary, a self-hosted stealth Chromium serving real-device fingerprints) plus a stealth-fetcher wrapper, none of which leave a HeadlessChrome user agent or anything that reads as CDP automation. Everything that still leaves navigator.webdriver true sat at the bottom, and the middle tier was, in the author's phrase, one user-agent string away from a clean pass. That is the same rebuild-beats-patch conclusion from the matrix above, reached with a different tool set, which is exactly the kind of agreement that tells you the ranking measured something real.
The two transferable lessons are about honest measurement. First, scope: that benchmark tested only the JavaScript fingerprint surface and said so, on the reasoning that transport (TLS and HTTP/2) depends on the browser's own network stack rather than the Python library wrapping it, timing is a behavioural question, and proxy reputation is about your IP, so swapping libraries only moves the JS-surface needle. Knowing which layer a score actually covers is the difference between a number you can use and one that misleads you, a tool can ace a JS-only bench and still die on TLS or IP. Second, a concrete artefact trap the author controlled for: Playwright's bundled headless Chromium reports a software WebGL renderer (SwiftShader), which is a headless artefact of that binary, not a tell the tool introduced, so pointing every Chrome-based tool at the real installed Chrome (channel="chrome") is what makes the comparison measure stealth rather than which Chromium happened to ship. The general habit both of these teach is to calibrate against outside referees rather than your own probe, cross-checking against independent bot-detection pages that return a structured verdict keeps your weighting honest. Run the plain browser on the same targets, headful and headless, before you credit any stealth tool with a pass, and report the control row next to the tool row. A stealth win only counts on the targets where the control fails. In one October 2026 hands-on review of a patched-Firefox tool, vanilla Playwright failed all five vendor-protected sites headless, but headful it loaded the Cloudflare, Akamai, DataDome and PerimeterX sites and failed only Kasada. The tool passed all five, so the evidence that it beats plain automation rests on one Kasada page, tested once from a home residential IP. Most of the headless gap is the headless tell itself, which this section already shows is cheap to remove. Without that control row, a five-for-five table reads as five wins when it may be one.
A new hardware tell shipping in Chrome: the CPU Performance API. Sites have always wanted to know how powerful your device is, historically by running a timing benchmark, which is noisy and easy to perturb. Chrome is standardising a shortcut. Reading navigator.cpuPerformance returns a stable integer tier from 1 (low) to 4 (high), with 0 for unknown, a coarse hardware class handed over with no benchmark, no opt-out, and no cross-origin limit. There is also a managed-Chrome policy, CpuPerformanceTierOverride, that pins the reported tier — which is worth knowing in both directions: a supported way to set the value without patching anything, and a reason a detector cannot treat the tier as ground truth. It is a WICG proposal, deliberately bucketed (each tier must cover a few hundred CPU models) to cap the entropy it adds, but Mozilla still estimates it at one to three extra bits per user and WebKit declined to implement it, which tells you how the other engines weigh the tradeoff. There is even a Chrome Enterprise policy to override the value (0 to 4).
Why a fixed number helps the defender
A tier you cannot benchmark your way out of
For a real user the tier is just a stable fact about their machine. For automation it is a fresh consistency check that costs the detector nothing. Because the value is declared by the browser rather than measured, a stealth setup cannot earn it by behaving well, it either matches the rest of the device story or it does not. And because the buckets are time-invariant, the same machine reports the same tier across visits and across origins, so it composes with every other signal a fingerprint already carries. A single coarse number is weak alone, which is the whole design, but it is one more axis that has to agree.
Where it bites a spoofed box
Cross-check the tier against the rest of the silicon story
The interesting move for a detector is to cross the advertised tier against the other hardware signals and look for a machine telling two stories about itself. Report navigator.hardwareConcurrency of 16 (spoofed) on a 2-vCPU cloud box whose cpuPerformance lands at a low tier, and the two claims disagree loudly. Push further and a smart WASM micro-benchmark (SIMD timings, feature detection) fingerprints the actual silicon and either corroborates the tier or exposes the lie. The practical lesson for a scraper is the one this section keeps arriving at: if you spoof one hardware property you now own all of them, the tier, the core count, and the measured compute all have to tell a single coherent story, and a headless fleet on undersized cloud instances is exactly where they stop doing so.
A worked example of what "spoof one thing and you now own all of them" looks like in practice. It is worth seeing the coherence principle as a real remediation report reads it, because the failures are never where a beginner looks. Take a headless desktop Chromium configured to present as an Android mobile Chrome (claimed identity: Chromium / Android / mobile / Chrome, with the CPU architecture field left empty). To a first-order fingerprint it looks plausible. A full audit finds it contradicting its own story in at least seven independent places at once, and every one of them is a separate subsystem the operator forgot had an opinion.
The contradictions an audit surfaces
Seven subsystems, one incoherent device
The CPU architecture, exposed through a WASM relaxed-SIMD instruction-selection oracle, reads as x86 desktop, not the ARM an Android device would run. navigator.maxTouchPoints is 0, but a phone reports one or more touch points. The Opus audio codec ships its desktop complexity default, a mobile build tunes it differently. The Widevine DRM stack and PaymentRequest service are missing or stubbed, where a real Android Chrome exposes both. The WebGL renderer string names desktop silicon, and the media power-efficiency profile matches a plugged-in desktop, not a phone. The automation protocol (CDP) is independently detectable on top. No single one of these is exotic; together they describe a device that cannot exist.
Why the fix order is the lesson
Config is cheap, coherence is architectural
The instructive part of such a report is the remediation ladder. The cheapest wins are config and build flags: disabling the CDP automation domain, patching the Opus complexity constant, forcing maxTouchPoints to a mobile value, shipping a real Widevine CDM. But the deep fixes are architectural and cannot be faked from JavaScript: to make the SIMD CPU oracle agree with the ARM claim you have to actually run on ARM64 hardware or a VM, and a JS override of PaymentRequest or the GPU renderer leaves its own tamper artifact that a coherence check then catches. That is the whole thesis of this section in one page: the shallow tells are a checklist you can grind through, but the ones that decide the verdict require the environment to genuinely be what it claims, which is why a desktop box pretending to be a phone loses no matter how many properties it patches. Treat a hosted Android persona the same way: check the subsystems it does not list before you trust it on a mobile-gated target. Browserless’s emulationOs=android, documented in October 2026, sets the user agent, Client Hints, a phone viewport and pixel ratio, touch input, motion sensors, battery and network signals, Android system fonts and a mobile GPU identity. Its documentation does not mention CPU architecture, Widevine or PaymentRequest, the very signals in the audit above that only real ARM hardware or a VM fixes, and the sessions run on cloud servers whose architecture is not stated. Probe the relaxed-SIMD oracle, maxTouchPoints and the codec defaults yourself from inside a session. Also spell the value exactly: the documentation says any other value, including macOS in the wrong case, is silently ignored and the session stays Linux.
The purest example of "it is harder to lie than to tell the truth": your browser does math differently on each operating system. Most fingerprint signals leak because a browser exposes a property it could, in principle, be patched to fake. This one is different, because the browser is not the thing computing the answer. IEEE 754 fixes how a floating-point number is stored but does not require transcendental functions like sin, cos, or tanh to be correctly rounded to the last bit. Every operating system ships its own math library (glibc on Linux, libsystem_m on macOS, UCRT on Windows) with its own polynomial coefficients and rounding, so the same call returns bits that differ in the last place depending on the OS underneath.
Why tanh in particular
The one JavaScript math function that leaks the OS
For most of Math, V8 bundles its own implementation, statically linked and identical on every OS, so sin, cos, and pow give the same bits everywhere and leak nothing. Math.tanh is the exception. Since Chrome 148, V8 stopped computing tanh with its own bundled routine and now calls std::tanh, which reads the host OS math library. The result: on Chrome 148 and later, Math.tanh of the right input returns Linux bits on Linux and Mac bits on Mac, and a Linux server spoofing macOS is caught the instant a page evaluates it, because the browser genuinely cannot change the answer, the OS produced it. Chrome 147 and earlier do not leak here, which itself pins a version range.
The wider surface
CSS trig leaks everywhere, and the edges are checked
JavaScript Math is a tell in essentially one place, but CSS trig functions leak everywhere, because the rendering engine calls the host math library directly for every sin, cos, tan, and inverse. A defender can probe those and also the domain edges, where implementations diverge more loudly: asin(2) is out of domain and resolves to zero on a real Mac (the NaN is clamped), not the ninety degrees a naive reproduction returns. The lesson for a scraper is the sharpest version of this whole section's theme. You cannot patch your way to a coherent lie here, because the signal is produced below the browser, by hardware and the OS, so the only setup that survives a math probe is one whose claimed OS is the OS it runs on. Spoofing the User-Agent is free; making the silicon agree is not.
The subtle trap in math fingerprinting: the expected values decay, because "Chrome" was never a stable identity for a floating-point result. There is a second lesson here that cuts the other way, and it bites defenders and naive spoofers alike. These math results are not browser constants; they shift whenever an engine changes how it does the arithmetic, which happens far more often than fingerprint tables assume. V8 has been steadily replacing its bundled 1993-era fdlibm port with LLVM's libc, and each migration moves bits: Chrome 149 and 150 disagree on roughly 4.5% of sampled inputs to the migrated functions, every disagreement being the old implementation off by one unit in the last place. The change is permanent in a specific way, correct rounding has only one answer, so once an engine gets there the wrongness that made it distinctive is gone, and every engine that arrives spits out identical bits. The wrongness was the signal. The practical fallout is that a fingerprint library holding a table of "what Chrome returns" goes stale silently: run a current CreepJS-style table against a freshly migrated Chrome and a large share of the math rows no longer match its Chrome column, and some now match Firefox instead. The trig rows went stale back in Chrome 110 for the same reason. The takeaway for both sides: the useful key is not "Chrome" but the exact build, chrome_149 and chrome_150 are different fingerprints, and treating an engine family as one stable value is how a detector starts misclassifying its own real users, and how a spoofer targets a value the real browser stopped returning two versions ago.
Two functions are the exception that proves the OS-not-engine point: Math.pow and Math.tanh never used V8's math library at all (pow sits behind a use_std_math_pow flag defaulting true; tanh is one line calling std::tanh), so both go straight to the operating system's libm, ucrtbase on Windows, glibc on Linux. That is why Math.pow(17, 13) returns 9904578032905936 on Windows and 9904578032905938 on Linux, same browser, same version, and why a table that files tanh(0.123) under a single "Chrome" value gets it wrong on one of the two OSes. The vivid case: because that call now hits the system libm, Linux Chrome returns a value a stale table labels as Chrome while Windows Chrome returns one the same table labels as Safari, identical browser and version, two different engine labels, purely from the OS underneath. It is the cleanest possible proof that these values measure the platform, not the browser string, and that any fingerprint keyed on the browser name is standing on sand.
The counterintuitive corollary at the HTTP layer: sometimes sending fewer headers is what gets you through. The instinct when a plain HTTP client is blocked is to make it look more like a browser, copy the full navigation header bundle a real Chrome sends, accept-language, the sec-fetch-* set, sec-ch-ua, and so on. On some detectors (DataDome is the reported case) that is exactly backwards. Those headers exist because a browser is rendering a page and running JavaScript. Send the full bundle from a client that plainly is not executing any JS, with no telemetry, no canvas, no timing to justify them, and the mismatch is the tell: the detector flags the combination of browser-grade request metadata with a non-browser client, not any single header. A documented case had a controlled one-variable-at-a-time matrix (proxy, JA3 profile, header order, cache-control all changed in isolation) return a 403 every time, until the header set itself was cut back to a lean, honest handful (user-agent, priority, cache-control), at which point the same endpoint returned 200 and real HTML, no browser and no JS involved. The principle generalises the coherence theme one layer up from TLS and hardware: more apparent realism is not more human when the signals you add have nothing behind them. A client that honestly sends almost nothing is more coherent, and often less suspicious, than one wearing a full browser costume it cannot back up, so match your header set to what your client can actually justify rather than to the longest bundle you can copy. Assume a header costume on a non-browser handshake is caught at the edge, before any header value is weighed. Sending Chrome 154’s complete header set, including sec-ch-ua, the sec-fetch-* family and priority, through stock macOS curl 8.7.1 still produced JA4 t13d497h2, 49 cipher suites and 7 extensions, where current Chrome presents t13d1517h2; its HTTP/2 pseudo-headers also arrived as method, scheme, authority, path rather than Chrome’s method, authority, scheme, path. Either difference alone separates the client from the browser it claims to be. That is one measurement from one curl build; other builds and TLS libraries produce other hashes, none of them Chrome’s.
The other way headers betray you: not what you send, but the order you send it in. You can copy Chrome's header values perfectly and still be flagged, because a real browser emits its headers in a stable, characteristic sequence and an HTTP library emits them in its own. Same values, different order, and the order itself is a fingerprint. This is sharpest on HTTP/2, which lowercases every header so that casing stops being a usable signal, at which point the pseudo-header order carries the weight. Chrome sends its pseudo-headers as :method :authority :scheme :path; most HTTP libraries emit a different order entirely (a common one is :method :path :scheme :authority), and that mismatch is exactly why a request with otherwise flawless headers still dies against Akamai. The trap is that you often cannot fix it from your own code: the library reorders your headers underneath you, and on the classic Python stack urllib3 reorders again below that, so setting the headers in a dictionary does not help because something downstream reshuffles them before they hit the wire. The reliable fixes are the ones that own the whole emission path: use curl_cffi, which impersonates Chrome's full profile with header and pseudo-header order included, or drive a real browser. Right values in the wrong order is not a disguise, it is a tell, and it is one of the most common reasons a hand-built HTTP client that looks perfect on paper fails the moment it meets a serious anti-bot.

How an extension list gets built, and why it reads as evidence of a person. The anti-bot vendors that probe for browser extensions are exploiting a gap the browser deliberately left. Chrome exposes no API that reports which extensions are installed, and that is an intentional anti-fingerprinting decision. Individual extensions can still be detected one at a time, and enough of those checks in sequence reconstructs a list.

The main technique is resource probing. An extension declares web_accessible_resources, files it will serve to any page that asks, so a script can request a known file path under chrome-extension:// with the extension's fixed identifier and learn from success or failure whether it is present. Run that against a catalogue of popular extensions and you have an enumerated list.

The signal is unusually good for a defender on two counts. It carries real entropy, because the combination of a dozen installed extensions is close to unique, and it is stable, because people rarely add or remove them, so the same list returns tomorrow. The second count is the one that decides tool choice: an extension list is decent positive evidence of a human, since automated browsers almost never ship with any. That is the reasoning behind the vendor probe covered in the Akamai profile, and it is why the browsers that clear it do so by loading genuine password-manager profiles rather than by patching a response. An empty extension surface is not neutral. It is a statement.
Layer 2.6, Side Channels And State Leaks: The Signals Nobody Thinks To Patch

Timing side channel · navigator.storage
Incognito Detection By Write Speed
A recent revival of incognito detection skips feature checks entirely and times the storage backend. In a normal window navigator.storage is backed by disk, in a private window it is backed by RAM, and writing to RAM is measurably faster than writing to disk. Write and flush a single byte, time it, repeat a few times to weed out noise, and a flush under roughly a tenth of a millisecond gives away a private session.

The lesson is broader than incognito: a detector does not need to read a property you spoofed, it can measure a physical consequence of your environment that no fingerprint patch touches. Timing is not on most people's spoofing checklist.
Implementation leak · IndexedDB ordering
When An Undefined Edge Case Becomes A Fingerprint
A 2026 Firefox flaw is the cleanest example of how a tiny implementation detail turns into an identifier. The browser returned IndexedDB database names in hash-table iteration order rather than a canonical sorted order. That ordering was stable and unique enough to correlate a user across sessions, across private windows, even across a Tor "New Identity" reset, all without a single cookie.

The takeaway for anyone building scrapers: stealth is not a setting you flip on. Any API behaviour, any implementation quirk, any undefined edge case can become a fingerprint. You cannot enumerate them in advance, which is why coherence across the whole environment matters more than patching individual tells.
The countermeasure that actually holds
Isolate At The Process Level
Both signals above survive the usual defence of a fresh browser profile per identity, because they leak from shared process state, not from the profile. Memory-state artifacts, timing baselines, and hash-table orderings live below the profile boundary. If several scraping identities share one browser process, those hidden signals quietly become a correlation key tying your "separate" identities together.

The fix is to stop isolating at the profile level and isolate at the process level: one identity, one process, and ideally one host with a coherent fingerprint, rather than many personas multiplexed through a single long-lived browser. It costs more to run. It is also the only isolation the deeper signals respect.
Hardware timing · No permission needed
Disk Latency As A Tracking Vector
The timing family goes deeper than storage backends. A technique making the rounds measures the microscopic latencies of the machine's SSD itself. Every site and active tab generates a slightly different load pattern on the disk subsystem, and ordinary JavaScript can read those timing variations through default web APIs. In principle a script can infer activity in other tabs from the disk-contention signature alone.

The two properties that make this matter for a scraper: it needs zero permissions, browsers do not prompt for disk access, and neither an ad blocker nor a private window mitigates it. Until vendors fuzz or round these timings there is no clean client-side fix. The general lesson repeats: hardware leaves a signature your fingerprint patch never touches, and a farm of identical VMs can look too identical at the disk layer.
Spoof contradiction · Client Hints
sec-ch-ua Is Deterministic, Not Random
The sec-ch-ua header looks like noise: "Chromium";v="149", "Not A Brand";v="24". It is not random, it is a pure function of the Chromium major version. The brand ordering, the greasey "Not A Brand" label, and the version permutation are all seeded from the major version number over fixed lookup tables, no rand(), no timestamp. Every machine on the same Chromium version produces an identical header.

That determinism is a trap if you spoof carelessly. Hand-roll a sec-ch-ua that does not match the algorithm for the Chrome version you are claiming, and you have manufactured a contradiction a detector can check with one lookup. If you set the version, derive the header from it rather than copying one from a different build.
The pattern across all of these
Why These Keep Appearing
Storage timing, disk latency, IndexedDB ordering, Client Hints seeding: none of these is on a typical spoofing checklist, and that is exactly why they work. They are second-order signals, derived from physics or from an implementation detail rather than from a property you thought to override.

You cannot enumerate them all in advance. The defensible posture is not to chase each new tell, it is to keep the whole environment internally consistent and let real hardware speak for itself wherever you can, rather than presenting a hand-assembled identity that has to be right on every one of a thousand axes at once.

Layer 2.5, WebAssembly Fingerprinting: The Layer Below Your Stealth Browser

Build-time leak · Nobody patches this
Hyphenation Dictionary Detection
Chromium needs a hyphenation dictionary bundled at build time on Windows and Linux (Android and macOS handle it at OS level). Most people forking Chromium don't know this. The benefit for detection: many stealth Chromium forks literally cannot hyphenate words, and that is visible from JavaScript.

The probe: set hyphens: auto on a narrow container, render a known word like "hyphenation", read the rendered width or screenshot via Canvas. A real Chrome on Windows produces hy-phen-ation. A custom fork without the dictionary produces no break, or the wrong break.

Affected stealth browsers: anything built from a custom Chromium source that skipped the hyphenation step, which is most of them. Real CloakBrowser and properly-built forks include it, hand-rolled patches usually don't.

Mitigation: confirm your build ships the dictionary for every language you claim to support, or run a real Chrome binary under XVFB. Verify with the live PoC: joe12387.github.io/hyphenation-dictionary-poc · github source
CPU fingerprinting · DataDome internal research
WASM SIMD probes the CPU itself
WebAssembly SIMD (Single Instruction Multiple Data) gives browsers access to 128-bit vector operations that map directly to CPU instructions. Anti-bots ship tiny WASM modules that execute SIMD ops in deterministic patterns and time them. The results reveal vector register width, NEON vs SSE vs AVX availability, and microarchitecture quirks unique to the CPU model.

Why this matters: stealth browsers like Camoufox, CloakBrowser, PatchRight patch what the browser reports. WASM SIMD probes the actual CPU. A real Mac with M2 chip can't be spoofed to look like an Intel laptop because the SIMD timing fingerprint is generated by the silicon, not by the browser.

Source: Anthony Manikhouth (DataDome bot detection engineer), blog.azerpas.com, May 2026.
High-resolution timing · The enabling primitive
SharedArrayBuffer via WASM gives 17× timer precision
Chrome floors performance.now() at 100µs on non-isolated pages to prevent Spectre-style timing attacks. But one line of JavaScript breaks that: new WebAssembly.Memory({shared:true}).buffer returns a real SharedArrayBuffer on any page, no special headers required.

Paired with a MessageChannel ping-pong loop in a hidden iframe driving Atomics.add(), you get a counter incrementing at 100,000 Hz, distinguishing steps around 6µs. That's 17× finer than the timer Chrome intends you to have.

Why anti-bots love this: micro-timing patterns (canvas render time, JS jitter, animation frame variance) differ between humans and bots at sub-millisecond scale. WASM shared memory makes that measurable on every page, not just cross-origin-isolated ones. Reported to Chrome as crbug 40057687, marked Won't Fix.

Source: Manuel (brokenbrowser.com).
Bypass implications
Why your stealth browser still leaks
WASM fingerprinting sits in a blind spot of most stealth tools. The signal flow:

1. Anti-bot ships a WASM module with SIMD ops and a high-resolution timer built from WebAssembly.Memory({shared:true}).
2. The module runs natively, no JS hooks to intercept, no Function.toString() traces to leak.
3. CPU microarchitecture + timing patterns are POSTed back as part of the bot scoring payload, often alongside the canvas hash.

What this defeats: Camoufox (Firefox C++ patches), CloakBrowser (49 Chromium patches), PatchRight, undetected-chromedriver, Nodriver, Pydoll. All of them patch JS APIs and binary internals, but none patches the WASM execution layer.

What still works: real hardware diversity. Different physical machines produce different SIMD fingerprints naturally. The future of stealth scraping is less about better lies and more about real hardware in real consumer locations, which is exactly what residential proxies on real ISP IPs already approximate.
CVE-2026-6770 · April 2026 · Process memory leak
IndexedDB Iteration Order: The Fingerprint Nobody Patched
Firefox stored IndexedDB database names using internal UUID mappings in a global hash table shared across all origins within the same browser process. When a site calls indexedDB.databases(), the names come back in hash table iteration order, which is deterministic and stable for the lifetime of that process. Two unrelated sites see the same ordering and can use it to silently link a user's activity across domains — no cookies, no shared storage, no user interaction required.

The fingerprint persisted across reloads, new private windows, and even Tor Browser's "New Identity" resets. Only a full browser restart cleared it. Fixed in Firefox 150 / Tor Browser 15.0.10 (April 21, 2026).

Scraper implication: If you run multiple scraping identities inside the same browser process (shared Camoufox instance, same Firefox PID), an anti-bot can correlate them using this ordering as a stable session token — regardless of proxy rotation, cookie isolation, or fingerprint patching. The signal is below every stealth layer.

Rule: Isolate scraping identities at the process level, not just the profile level. One identity = one browser process. Verify your Camoufox build is on Firefox 150+.
Ref: CVE-2026-6770 · mfsa2026-30 · SecurityAffairs writeup

Layer 3, Network Identity: The Five Vectors That Must Agree

This is the layer most people underinvest in. Beginners pour effort into header spoofing and fingerprint patching while running through a flagged IP, and then wonder why nothing works. It is backwards. If your IP reputation is bad, no amount of header spoofing will save your requests, the request is scored down before your carefully crafted headers are ever read. Infrastructure beats code here: a plain HTTP client on a clean residential IP routinely outperforms a perfectly patched browser on a flagged datacenter IP. Get the network identity right first, then worry about everything above it. There is a sixth vector, and it is the one you never get to declare. The five above are claims you make about where you are, and a competent setup makes them agree; latency is not a claim but a measurement the server takes itself. A proxy inserts a hop the server’s transport clock never paid for, so the round trip it times and the one your traffic makes diverge, and nothing you configure subtracts that leg. One edge detector scores exactly this in a few lines of Fastly VCL, dividing two counters the Linux kernel already keeps, minimum RTT over smoothed RTT: 1.0–0.7 reads as a normal link, 0.7–0.3 as an unstable one, 0.3–0.1 as possibly proxied, below 0.1 blocked. Those bands are chosen, not calibrated. No accuracy or false-positive rate is published, its README calls it unready for production, and the only field figures are operators grading their own pools, one proxy-company engineer catching his own residential exits about half the time, another roughly 5% against his own tunnel. The mechanism is better measured than the tool: the one published experiment, from 2021 over 23 visits, had assumed-clean sessions agreeing within 0.7% to 13.8% while twelve samples through two commercial providers ran 45% to 136% larger. The consequence is narrow and useful: rotating the pool moves this number not at all, because every exit in it sits equally far from your client, and the fix is a nearer exit, not a faster proxy. And because those counters live in TCP state, an HTTP/3 connection often goes unscored.
Primary signal
IP Reputation & ASN
Anti-bots check your IP against ASN databases. AWS (AS16509), GCP (AS15169), Azure (AS8075) are immediately flagged. DigitalOcean, Linode, Vultr, all known. Even residential proxy networks from DataCenter IPs in the 24.105.x.x range are flagged if the ASN is a known proxy provider. Genuine ISP residential or 4G carrier IPs are the only reliably clean option.
Browser API · Often overlooked
WebRTC IP Leak
JavaScript can query WebRTC ICE candidates which reveal your real local and public IP, even through a proxy. If your browser has a US proxy but WebRTC reveals a Pakistani local address, or the ICE candidate is from a different subnet than the HTTP request IP, that's an immediate flag. Camoufox's geoip=True aligns WebRTC candidates with the proxy exit country.
All five must agree
The Coherence Test
Anti-bots run a coherence check across: IP country, timezone, Accept-Language, WebRTC candidate, DNS resolver location. A US proxy with Accept-Language: ur-PK fails immediately. All five must tell a consistent geographic story. This is why setting geoip=True in Camoufox is critical, it auto-configures all five to match the proxy's exit country.

Layer 3.5, DOM Honeypots: The Trap Doesn't Care About Your Fingerprint

Hidden DOM elements
Honeypot Fields and Links
Invisible form fields and hidden links that humans never see but bots fill in or click. Triggered = bot detected = IP banned. Common patterns: display:none, visibility:hidden, opacity:0zero-dimension elements, off-screen positioning, fields with tabindex="-1"or links placed after the closing </body> tag.
Data poisoning
Fake Data Served to Suspected Bots
More dangerous than blocking, sites detect a scraper and silently serve different prices, fake reviews, wrong stock counts. You think you're scraping successfully but your dataset is corrupted. Defence: compare scrapes from 2+ different IP profiles for the same URL. Mismatched data = poisoning. Always check element visibility (getBoundingClientRect()) before interacting.

Layer 4, Behavioural ML: You Can't Fake Being Human

Physics-based · Gaussian jitter
Mouse Movement Curves
Human mouse movements follow Bezier curves with Gaussian noise applied to velocity. The mouse decelerates as it approaches a target (Fitts's Law), overshoots slightly, then corrects. Scrapers that click elements directly (teleporting the mouse to x,y) create a trajectory signature that's statistically impossible for a human. DataDome's 35-signal behavioural model catches this immediately. The opposite case is visible too. A session that never moves the pointer is not neutral: it reports an empty behaviour record. In a September 2026 capture of DataDome’s tag on one classifieds site, the report from a run with no interaction carried eventCounters as an empty [] beside the fingerprint payload, so the vendor sees zero interaction directly instead of inferring it. If a job loads pages without interacting, expect that state to be scored, and prefer a documented data endpoint over rendering a page whose sensor will report an idle visitor. That is one tag build on one site, and how the server weighs the empty field is not public. Botasaurus generates physically realistic curves with randomised velocity profiles.
Sub-millisecond precision · ML scored
Timing Analysis
Transformer ML models trained on millions of sessions measure: time between page load and first interaction, scroll acceleration curves, inter-keystroke timing variance, navigation dwell time, and micro-timing of JS event handlers at <1ms precision. A scraper that immediately calls document.querySelector() after DOMContentLoaded looks nothing like a human who reads the page for 2.3 seconds first. LLM agents turn this around. The model’s think time can look human, but the framework underneath still emits machine input, so judge an agent by the events it sends, not by how long it waits. Paste-style or programmatic form filling, keystrokes a few milliseconds apart, and a pointer that jumps straight to its target are all readable. Akamai reported that over 98% of requests from Comet, Atlas and the Claude Chrome extension carried little or no mouse data. An academic study separated seven agents from human participants on behavioural features alone at F1 0.9993, though it was closed-world and dropped to as low as 0.63 on unseen task types. Layering synthetic curves on top gives a model more to check. The cheaper fix is to remove UI actions: when you only need the content, fetch and parse the page instead of clicking through it. Every interaction you skip is one less behavioural signal to score. Warm-up navigation (visiting homepage before target) significantly improves behavioural scores.
A sharp edge most stealth setups forget: behaviour is judged server-side too, from request timing alone, with no JavaScript and no fingerprint involved. The behavioural layer is usually discussed as client-side mouse and keyboard telemetry, which is why a real browser feels like a safe haven. But a server watching only the sequence and timing of your requests can still catch automation. A self-hosted open-source detector demonstrated this by flagging a genuine Chrome being driven by an LLM over the Chrome DevTools Protocol, with no client-side fingerprinting and no TLS interception in play. The tell was subtle: the automated session requested resources in an order and rhythm that did not match its own cache state, fetching things as though the cache were cold while it was actually warm. Real browsing has a characteristic shape, conditional requests, parallelism, think-time, asset ordering driven by the renderer, that a request sequencer reproduces imperfectly. That asymmetry is worth stating as a rule, because it decides where your effort keeps its value: a tell living in a patchable artifact carries a version number and eventually dies; one living in timing or handshake shape does not. Watch it happen to the most-cited automation artifact of all. Runtime.enable appears as a CDP tell throughout this guide, and the mechanism behind the name was narrow: a getter on an Error’s stack that fired when an attached inspector previewed the object, letting a page notice it was watched. Two V8 commits in May 2025 closed it, and the shipped branches settle the milestone: the guard is absent from V8 13.7 and present in 13.8 with no merge-back, which puts it in stable Chrome 138 in June 2025, not the 137 that gets repeated. One read survived on a specification technicality: the guard tests the own property descriptor, and name is never an own property of an error, so the lookup falls through to whatever the page installed on Error.prototype. Hold that lightly. It is read out of shipped source rather than run against a build, the single public measurement is one run on one Chrome build that saw no stack lookups and three name lookups, and the obituary was written by an anti-bot vendor about a method it had just lost. Any attached inspector walks the same path, a developer with DevTools open included, so what survives scores as noise. The startup handshake and the request rhythm above it needed no patch and got none. Not every page-side tell is gone, though, and the live ones key on which protocol domain is enabled rather than on CDP as a whole. Treat the Debugger domain as visible to the page: while it is enabled, V8 writes a private debugging symbol onto the reject callback of Promise.all, so a page that passes a Proxy as that callback gets a TypeError where an empty array should simply resolve. Reproduced on V8 14.6 in Node 26: clean with only Runtime.enable, flipped by Debugger.enable, clean again once it was disabled. Stock Playwright and Puppeteer send Debugger.enable only when collecting JS coverage, so this probe mostly separates instrumented sessions, and probably developers with DevTools open, from ordinary automation. It has not yet been tested in a Chrome build.
Why it survives a perfect fingerprint
Timing is orthogonal to identity
TLS, JA4, canvas, and Client Hints describe what you are. Request timing describes how you behave over time, and the two are independent. You can present a flawless real-Chrome fingerprint and still emit a request rhythm no human produces. A patient detector does not even need to be certain on request one: it can lower confidence, watch the next few requests more closely, and converge, which is exactly what a behavioural memory model is built to do.
What it means for your crawler
Make the cache and the cadence honest
Drive real navigations rather than firing a hand-built request list: let the browser fetch sub-resources in its natural order, honour caching so a warm session does not re-request what it already holds, and keep think-time and parallelism in a human range. The same logic that beats client behavioural ML, move like a person, applies one layer down at the request-sequence level. Server-side timing is cheap to run, needs no client cooperation, and is the layer a fingerprint patch never touches.
The 2026 direction of travel: continuous session-long behavioural validation. The behavioural checks above are mostly evaluated in bursts, at a challenge or on a scored request. The newer enterprise approach (Cloudflare shipped a productised version in mid-2026) drops that model and validates behaviour continuously across the whole session. A dynamically injected script quietly collects interaction signals as you use the site, cursor motion, scroll dynamics, typing cadence, clipboard actions, how long the page is actually visible, and streams them back to be scored in real time, with the verdict compounding as the session goes on. The design premise is precise: modern automation can execute JavaScript, run a real browser, and pass a single CAPTCHA without raising a flag, so a point-in-time check catches less and less. What stays hard is producing consistent human behaviour over an entire visit. You can at least see the shape of that instrumentation before it sees you, because most of it runs on page load whether or not you ever interact. A single instrumented pass over the hundred most-visited pages, with no clicking, scrolling or typing at all, counted which fingerprinting API families anything on the page touched. Of the 71 pages that surfaced, seven families were read on every one; then anti-debug APIs on 70, storage on 69, workers on 67, automation artifacts on 60, canvas and WebGL on 56, and WebRTC on 23, second from last of twenty-one families and the one forums argue about hardest. Handle those numbers carefully. It is one beta-stage vendor, one run, no crawl methodology published, and 11 pages came back walled while 18 never finished, so the denominator you choose moves every figure: against the hundred attempted, WebRTC is 23% and anti-debug 70%. It also counts any script, so 70 pages reading anti-debug APIs is not 70 sites running a defence, and a video-conferencing site opening an RTCPeerConnection is using a feature, not checking you. The nearest independent measurement is built differently and lands lower: across the first 10,000 Tranco sites, 3,649 of 7,944 scanned, about 46%, probed a first-tier automation signal, with navigator.webdriver read on 34%. Neither has been replicated. Use the ranking for the one thing it is good for, ordering your effort: the surfaces read before you do anything are introspection, timing and anti-debug, which is precisely the feed a session-long behavioural model is scored from.
Why bursts stop working
You cannot reset a session by refreshing
A per-request or per-challenge check is a door you pass once; a session-long model is a companion that never leaves. Because the score compounds with context across the visit, refreshing the page or navigating away does not wipe the behavioural signature, the session carries forward, so the cheap escape hatches (reload on block, spin a new page) stop helping. For automation this changes the target completely: it is no longer enough to look human for the one interaction being scored, the whole arc of the visit has to hold together, which is far more expensive to simulate and far less reliable to keep up at scale.
Where synthetic behaviour cracks
Human movement is not just "noisy"
The common way to fake human input is to add Gaussian noise or uniform random delays to mouse paths and keystroke timing. That defeats a naive "is this too perfect" check but fails a model that knows the real texture of human motion, which has structure noise does not, the acceleration and correction profile of a real cursor, the rhythm of real typing. The other half is cross-signal coherence: does pointer activity line up with when the page was actually visible, is a text field genuinely focused during the typing events it claims. Random jitter satisfies none of those joint constraints. The defensive lesson mirrors the offensive one from the session-stickiness section, coherence across signals over time is the wall, and a bolt-on layer of randomness is exactly what it is built to catch.
Where this is heading, because AI is quietly reshaping both sides. Two shifts are worth internalising before the vendor section. First, the barrier to writing a bot has collapsed, and not mainly through vision or computer-use agents (those are roughly fifty times more expensive to run, so most abuse avoids them), but through AI-assisted coding: every frontier model was trained on the public corpus of anti-bot bypass code, so producing a one-shot automation script and then iterating "now make it evade detection" is trivial for anyone. The effect is a flood of low-effort automation and a higher floor on how polished the average bot looks. Second, the defensive response is to abandon the signals AI makes cheap to fake. Micro-behavioural checks like mouse-movement and typing-cadence analysis are being retired as primary signals precisely because they are noisy and now trivially synthesised, in favour of harder-to-phrase signals and two things a spoofed browser cannot easily supply. One is cross-signal deception detection: change your user agent to Safari while everything else says Chrome-on-Apple and the isolated manipulation is itself the flag, the same coherence principle this section is built on, turned into a primary defence. The other is context a fingerprint has no access to, application and business logic (the best bots run indistinguishable browsers but still beeline for the valuable flow in a way no human browses) and consortium or network reputation (a fingerprint seen misbehaving across many sites gets banned everywhere at once). The strategic reading for a scraper is that out-resembling the browser matters less every year, while behaving like a plausible participant in the specific application, and not reusing an identity already burned across a network, matters more.
The story so far: You now understand the full detection stack, TLS fingerprints at the network layer, JS interrogation in the browser, IP reputation checks per request, and ML behavioural analysis across the session, including the server-side request-timing variant that needs no client signal at all. The next sections show you exactly which anti-bot vendors use which combination of these layers, and the specific bypass strategies for each.
20 field notes on detection Reported from practice. Click any card to open it.
+
Aug 2026 / Browser internals
Why Chrome 136 Broke Attaching to Your Logged-In Profile
If --remote-debugging-port silently stopped working against your everyday Chrome profile, this is why. From Chrome 136, both --remote-debugging-port and --remote-debugging-pipe are ignored unless you also pass --user-data-dir pointing at a non-standard directory. It is not a bug and there is no flag to restore the old behaviour. A non-default data directory gets a different encryption key, so the profile's cookies and saved passwords stay protected from anything that talks to the debug port.
💡 The automation substrate you rely on became an exfiltration path, and it got closed
Why Google did it. App-Bound Encryption on Windows made reading the cookie database off disk significantly harder, so the technique migrated: rather than decrypt the file, ask the browser. Google noted in March 2025 that since App-Bound Encryption shipped they had seen an increase in attackers using Chrome Remote Debugging to extract cookies. CDP running against an authenticated profile hands over live session state through ordinary API calls — Storage.getCookies returns the cookie jar without ever touching the encrypted store. The 136 change closes the convenient version of that: you can still enable CDP, but not against the profile that holds the sessions.

What it means for your setup. The attach-to-my-real-logged-in-browser pattern is over on default profiles. Pair the flag with a throwaway --user-data-dir and you get a clean profile that you must authenticate into — which is the point. Plan for that in any workflow that assumed an existing session, and note it applies to Playwright and Puppeteer identically, because the restriction lives in Chrome, not the driver.

If you run the endpoints being scraped. Researchers have since shown the restriction can be sidestepped by enabling CDP from inside an already-running process rather than via command line, which no browser flag can prevent. The published detection is host-level, not browser-level: Sysmon Event 8 (CreateRemoteThread) and Event 10 (ProcessAccess) targeting chrome.exe or msedge.exe, with access mask 0x143a the notable one. Worth handing to whoever owns endpoint detection, because a stolen live session walks straight past multi-factor authentication — the authentication already happened.
+
Aug 2026 / Measured
Ten Bots Per Shopper, One Hit Every 6.5 Seconds: What a Scalper Feed Actually Looks Like
On one retailer's DDR5 memory pages, 91% of traffic is now automated — roughly ten bot requests for every human visit, per DataDome's threat research (Jerome Segura), picked up by Tom's Hardware. In March, across a broader set of e-commerce sites, the ratio was nearer 6:1. But the headline number is the least interesting part.
💡 The request pattern, not the percentage, is what marks it as a real-time inventory feed rather than scraping
The pattern is the tell. In a one-hour sample, 91 listings were each polled about 551 times — one hit every 6.5 seconds per SKU — each request carrying cache-busting query parameters. That last detail is the whole story: a unique query string forces a miss at the CDN edge and pushes every single stock check through to origin. It is deliberate. This is not catalogue scraping that runs once and moves on; it is a real-time inventory monitor running on infrastructure the retailer pays for, watching for the instant a 32GB kit comes back in stock — kits whose price surged from $72 to $392 over the window, which is the economic engine underneath the traffic.

Why it belongs in this guide, from both sides. If you build collection, this is the anti-pattern that gets a whole technique class blocked for everyone: cache-busting every request is maximally hostile to the target's economics, so it draws the hardest possible response and trains defenders to watch for per-SKU polling cadence. If you defend, the lesson is that a bot share alone tells you little — 551 polls at a fixed 6.5-second interval with cache-busters is a signature you can write a rule against, where a raw percentage is not. It pairs with the guide's cost material from the defender's chair: this traffic is expensive precisely because it is engineered to bypass the cache and hit origin every time.

Source: Tom's Hardware, on DataDome research by Jerome Segura.
+
Aug 2026 / Teardown
Obscura Against Akamai, and the Sentence That Settles the Fingerprint Argument
The first real test of an agent-first engine against a hardened commercial target, run against a hotel chain's availability API behind Akamai Bot Manager. The engine performed well. The section heading that matters reads: the wall is the IP, not the fingerprint.
💡 Requests that cleared Akamai returned 500 from the backend — a bug, not a block, which is how you know the fingerprint passed
What it is. A headless engine written from scratch in Rust that embeds V8, ships no Chromium and no Node, and claims to run in about 30 MB of RAM. It exposes a CDP server so Playwright drives it unchanged, and carries a --stealth flag. On a local fingerprint probe it scored 94/100, against 70 for standard Chromium CDP tooling and 35 for vanilla Playwright and Selenium: navigator.webdriver false, a complete window.chrome with runtime and loadTimes, a spoofed NVIDIA GPU over WebGL.

The architecture is the hybrid this guide keeps recommending. Use the browser only to mint the session, then replay with a fast HTTP client. Obscura produced valid Akamai cookies — _abck, bm_sv, bm_mi — and curl_cffi carried them to the availability endpoint for a 200. The session module retries up to six times with an eight-second settle between reloads, validating by calling the rooms endpoint and checking the cookies are actually present rather than merely set.

Where the lightness costs you. Heavy fingerprinting suites never finish inside a minimal engine — boolean checks pass, but CreepJS and Pixelscan stall rather than completing. That has a real consequence: the Akamai sensor script sometimes ran incompletely, leaving _abck in its unsolved state, marked by a -1 in the second segment, which returns 403. Camoufox, a real Firefox, did not wobble. So the trade is explicit: a tenth of the memory, most of the fingerprint, and a sensor that occasionally does not finish.

And then the finding that outranks all of it. From clean residential IPs the first load sometimes worked and sometimes needed a reload or two; the determinant was address reputation rather than fingerprint quality. The proof is the detail worth stealing: requests issued from inside the page context came back HTTP 500 — a backend error, not a bot block. A 500 means your traffic reached the application. That is a cleaner pass signal than any 200, because nobody serves you an accidental stack trace to trick you.

Source: The Web Scraping Club, THE LAB #113.
+
Aug 2026 / Field note
Every Header Correct, Blocked Six Times Out of Six
Short and exact. A practitioner copied every header from the browser, precisely, and the request was refused six times running. The headers were never the problem. The refusal happened in the TLS handshake, before a single header was read.
💡 Matching the connection, then sending the identical headers, was accepted immediately
This is the shortest possible demonstration of the principle the guide keeps returning to, and it is worth keeping precisely because it is small. Copying headers is the most natural thing to try — it is visible in DevTools, it is easy to verify, and it feels like the thing that identifies you. But JA4 and the ClientHello are settled while the connection is still being negotiated, which is before the server has read one byte of what you so carefully copied. A perfect header set carried over a Python-shaped handshake is a perfect answer to a question nobody asked.

The diagnostic that saves the week is to notice which layer refused you before choosing what to fix. If the same headers succeed once the transport changes, the headers were never the variable, and every hour spent tuning them was an hour spent proving something that was already true. The tell is exactly what happened here: identical request content, different transport, opposite outcome.

Read this next to the earlier card on the block page naming a different vendor from the header. Both are the same failure of attribution — a confident fix applied to the wrong layer, or the wrong vendor — and between them they probably account for more wasted engineering hours than any technique in this guide accounts for saved ones.

Oct 2026 update. A bot-management vendor’s staff made the defender’s version of this point: a curl call carrying Chrome 154’s full header set should be caught by any protection worth paying for. Checked here with that exact command: macOS curl 8.7.1 presented JA4 t13d497h2 (49 ciphers, 7 extensions) against Chrome 154’s t13d1517h2, and HTTP/2 pseudo-headers as m,s,a,p where Chrome sends m,a,s,p. One curl build; vendor-authored post.
+
Aug 2026 / Field note
A Referer Is Required On One Endpoint and Fatal On the Next
A worked Google Trends teardown with a detail worth more than the target it came from. Route requests through a proxy and you get USER_TYPE_EMBED_OVER_QUOTA almost immediately — but the interesting part is what happened after the TLS problem was solved.
💡 Header rules are set per endpoint, by different people, and they contradict each other
The first wall is below the headers. The load balancers inspect the TLS handshake before anything reads an HTTP header, so requests and httpx are dropped on their fingerprints regardless of how carefully the headers were assembled. The fix is the one this guide keeps arriving at: curl_cffi with impersonate="chrome", which is a transport change rather than a header change.

The second wall is the useful one. Google runs different internal widget endpoints for timeline, heatmap and related-queries data, and their header expectations disagree: the timeline endpoint requires a referer, while sending any referer at all to the regional endpoints triggers a block. Stripping the header completely is what gets the regional data out. Reported result was near-total success with no headless browser anywhere in the path.

Why this generalises past Google Trends. A per-site header profile is the wrong unit. Anti-bot configuration is applied per route by different teams at different times, so two endpoints on one host can hold opposite rules and a profile that satisfies both cannot exist. If a request works on one path and fails on the next with the same client, stop looking for a better global profile and start diffing what you sent to each. The same reasoning is why enumerating routes beats accepting the hardest one.
+
Aug 2026 / Reverse engineering
Google Ships a Virtual Machine To Fingerprint Your First Visit
Research into a strict first visit — no cookies, nothing in local storage, no history — to Google search. The answer lives in Search Guard, which serves an interstitial instead of results and settles into the SG_SS cookie. It is a virtual machine shipped with an encrypted program for it, and the fingerprint is assembled inside that machine.
💡 The script reads deliberately absent names too — what you lack identifies you like what you have
The methodology is the contribution, more than the finding. The stated approach is to work in layers from the cheapest thing you can try down to the most laborious, and the explicit criticism is that most write-ups jump straight to the bottom layer and make the work look like magic. The top layer answers a practical question without weeks of work: which properties and methods does this script actually read and call? That produces a full map of Browser API access — and it is enough to act on, long before anyone touches dispatch, opcodes, registers or the cipher.

The detail worth carrying into your own work is that the challenge reads “every property, method and deliberately absent name” it cares about. Absence is a signal. A patched environment that adds a property real Chrome lacks, or leaves present something a real build removed, is identified by the gap rather than by the value — which is the coherence principle in its purest form and the reason adding realism keeps making things worse.

On the ethics of the write-up, worth noting because it is a good pattern: the analysis runs offline against a capture you make yourself, so no vendor code is redistributed. That is a reasonable model for publishing this kind of work, and a better one than shipping somebody else's obfuscated bundle in a repository.
+
Aug 2026 / Below the sandbox
ShaderGhost: A Supercookie That Lives in Your GPU Shader Cache
Announced by Joe Rutkowski, who builds overpoweredJS, and billed as the tracking ID you cannot delete. The proof of concept writes a 32-bit random number into the shader cache and reads it back — from any site, across profile wipes. He puts the reach at 99% of browsers by market share: Safari, Chrome, Edge, Brave, on all platforms.
💡 Storage you did not know was storage, with no clear button pointed at it
The shader cache exists for a good reason. Compiling GPU shaders is expensive, so browsers keep compiled results on disk and reuse them, which is why a game or a WebGL-heavy page is slower the first time you load it. That cache is keyed on the shader source. Write a shader whose source encodes a value, and the presence or absence of a cached compilation for it becomes a readable bit. Repeat, and you have a number that persists in a place cookie policy was never written to cover.

Why this belongs next to Frost and the WASM SIMD probes rather than with cookies. Every mitigation you have for tracking assumes the state lives somewhere the browser namespaces per origin and clears on request — cookies, localStorage, IndexedDB, the HTTP cache. This is a shared compilation artefact of the graphics stack. It is not partitioned by origin in the way storage is, which is what "accessible from literally every site" means, and there is no user-facing control pointed at it. The fix has to come from browser vendors partitioning or keying the cache differently, and that carries a real performance cost they will have to weigh, exactly as with disk-timing mitigation.

For a scraper, invert it before you dismiss it as a privacy story. A persistent, cross-site, hard-to-clear identifier attached to a machine is precisely what a bot-management vendor wants most, because it survives the thing you rely on: a fresh profile. Everything in the fingerprint-replay economy assumes that a clean browser is a clean slate. If a hardware-adjacent cache carries an ID across your profile resets, then "new session" stops being true at the layer you cannot patch, and the answer is the same one that defeats SIMD probes and math fingerprints — separate real machines, not separate profiles on one.

Worth pairing with a gap another practitioner named the same week: we have plenty of tools that patch a browser, and almost none that let you watch what an anti-bot script is actually reading from your system. Techniques like this arrive faster than the instrumentation to detect them being used. shaderghost.gg
+
Aug 2026 / Epistemics
Does Fingerprint Randomisation Actually Work? Nobody Has Shown That It Does
A question that quietly undermines an entire product category. Every anti-detect browser demonstrates the same thing: randomise a set of characteristics, watch the fingerprint hash change, conclude it works. The author's objection is that this proves almost nothing.
💡 The question is not whether values change. It is whether the values that matter to the tracker change.
Two problems, and the first is a missing baseline. Before you can evaluate randomisation you have to establish that an identical browser environment produces an identical observable fingerprint across different physical devices, verified against multiple independent fingerprinting tests. Without that you do not know which characteristics were actually distinguishing you, so obscuring a chosen subset is a guess dressed as a defence. Their experiments did show that identical virtual environments can produce identical fingerprints across different machines, and that selected characteristics can be randomised — and then showed the weakness plainly.

The weakness is that a tracker gets to choose what it looks at. It can simply ignore the fields it knows are randomised and identify you on whatever stayed stable. Randomising the canvas hash is worthless if the identifying signal was the math library, the compilation cache, the TLS stack or the timing profile, none of which your anti-detect layer touched. Which is the same conclusion the guide reaches from the other direction: the coherent-story problem is not solved by making more fields noisy, because noise in a field the detector has written off is free for them and expensive for you.

The sharpest line is methodological, and it generalises well beyond fingerprinting. Preventing identification and proving you have prevented identification are different problems, and the second has received remarkably little attention. Applied to your own stack, that is a request for a specific kind of evidence: not "my fingerprint changed between runs", which any tool can show you, but "sessions that should be unlinkable were not linked by the target", which almost nobody measures. If your only evidence that rotation works is that a test page shows different values, you have measured your tool rather than the detector. The fingerprint dilemma
+
Aug 2026 / Measured, uncomfortably
Fifteen Stealth Engines, and the Detector That Said They All Worked
A team running 15 stealth browser engines in production finally tested whether choosing the best one matters as much as the marketing implies. They put three head to head: ChromiumFish, a native fork that spoofs at the C++ engine layer rather than patching JavaScript, against patchright (JS-patched Chromium) and camoufox (patched Firefox).
💡 All three cleared the demo detector. All three hard-403'd on the real deployment, at the same rate.
On a free detector's core suite, all three passed nearly everything, with one real gap: both Chromium-based engines fail the WebGL check, because headless Chrome has no GPU context to report and native spoofing does not conjure one. Firefox-based camoufox does not have that problem. Otherwise a tie.

Then the same three went against a live Cloudflare Bot Management deployment rather than a JS-challenge demo. All three had cleared the demo cleanly, rendering real content with no challenge page. All three hard-403'd on the real target, at an identical rate, regardless of which had the cleaner fingerprint an hour earlier. Attempting to fix it with a rotating proxy pool produced six attempts, four hard blocks, one dropped connection, and one that served real page content under a 403 status anyway. The sampled exit IPs were still datacenter and cloud infrastructure rather than residential, which is the actual reason nothing changed.

Three things this pins down that the guide has argued and can now point at. First, a public detector measures whether you pass a public detector; it is not a proxy for a tuned commercial deployment, and the gap between the two is where most stealth-tool comparisons quietly live. Second, once the network layer is wrong, fingerprint quality stops being the variable, which is why the engine choice made no difference at all here. Third, and the part they said they did not expect to relearn so cleanly: maintaining fifteen separate fingerprint-spoofing engines is real, recurring engineering cost buying a difference that a datacenter IP erases completely. Fix the layer the block is happening on before you invest in the layer you enjoy working on.
+
Aug 2026 / Protocol layer
The First Fingerprint Where Using a Proxy Is What Creates the Signal
HTTP/3 runs over QUIC, which runs over UDP. Every proxy you own speaks TCP. The consequence is not a performance footnote: the moment you configure a proxy, Chrome negotiates HTTP/2 regardless of what the server supports, and drops into the protocol tier where the bots live.
💡 Everywhere else a proxy hides you. Here it is the tell.
The mechanics are dull and total. An HTTP proxy tunnels TCP only. SOCKS5 does technically define a UDP ASSOCIATE command, but browsers do not expose it, so Chrome is TCP-only over SOCKS5 in practice. There is no combination of a stock browser and a conventional proxy that produces an HTTP/3 request. Your fingerprint work at layers 1 and 2 can be flawless and you will still be announcing something before a single byte of TLS is compared.

What makes it worth acting on is the trend, not the level. Cloudflare Radar's likely-bot share by protocol version: HTTP/1.1 at 73.75%, down from 82.07% a year earlier. HTTP/2 at 26.76%, up from around 21%. HTTP/3 at 3.33%, essentially unchanged. Read those three numbers together and the migration is visible: automated traffic is moving off HTTP/1.1 into HTTP/2, and almost none of it is arriving over HTTP/3. If that continues, protocol version stops being a weak prior and becomes a strong one, and the proxied browser is structurally parked on the wrong side of it.

The honest options, none of them free. RFC 9298 defines UDP proxying over HTTP (CONNECT-UDP), which is the standards-track answer and needs your provider to implement it. A network-layer tunnel (a VPN rather than an HTTP proxy) carries UDP and therefore carries QUIC, at the cost of the per-request IP control that made proxies useful. Or wait for browsers to expose UDP proxying, which nobody should plan around. A small number of providers have started advertising HTTP/3 support; that claim is now worth testing rather than assuming, and it is easy to test, because the negotiated protocol is visible in your own response. Full writeup by Web Scraper
+
Aug 2026 / Debugging war story
They Blamed the Proxies. It Was the Cookie They Added to Look Legitimate.
An API started returning 429. The team did the obvious things: switched provider, switched pool, slowed the request rate. Still 429. Then someone tried the one thing nobody had considered — sending the request with no cookies at all. Consistent 200s.
💡 Every piece of state you attach is also something that can be judged
They had been attaching a session cookie so the request would look like a normal returning user. That was the mistake. A request with no cookies is anonymous — there is no history to hold against it. A request with a session cookie has an identity, and identities accumulate a reputation: request counts, past behaviour, a trust score. Theirs had already spent its budget. They thought they were adding credibility; they were adding a record.

The inversion is worth stating plainly, because it is genuinely counter-intuitive against everything else in this guide: the authenticated-looking session was rate-limited while the anonymous one was not. Both behaviours are rational from the defender's side. Anonymous traffic gets a generic bucket. Identified traffic gets a per-identity budget, and a per-identity budget is a thing you can exhaust.

How to use it without over-learning it. The lesson is not "never send cookies" — on a site that scores session continuity, dropping cookies is exactly how you get flagged. The lesson is that headers, cookies, tokens and fingerprints are all claims, and each claim is something that can be held against you as well as for you. So before scaling infrastructure to get around a limit, run the cheapest possible experiment: the same request with the state removed. If the anonymous request succeeds where the identified one fails, you were not being rate-limited by IP, you were telling the server exactly who to rate-limit. Add it to the block-triage checklist alongside "try a different TLS profile before you escalate the tool" — both are one-request experiments that save a week of the wrong work.
+
Aug 2026 / Read from the other side
The Attacks That Are Hard to Stop Are the Ones That Look Like You
A defender-side breakdown worth reading as a scraper, because it explains the shape of every rate limit you will ever hit. In 2018 GitHub absorbed a 1.35 Tbps memcached flood and was back in minutes — a colossal number that was easy, because nothing a real user does looks like that. The hard ones are small.
💡 Defender cost is set by resemblance, not volume — and so is yours
Four that sit on the difficult side of that line. Slowloris (2009): one machine, almost no bandwidth, opens many connections, sends partial headers and drips just enough to avoid the timeout until the connection pool is gone. Each one reads as a slow client on bad Wi-Fi. Mitigated with header/connection timeouts, per-IP connection caps, and a reverse proxy that buffers incomplete requests. Mēris (2021): roughly 250,000 compromised MikroTik routers, peaking at 21.8 million requests per second, every request syntactically valid, every source an ordinary network. Mitigation is behavioural baselines and IP reputation, not signatures. HTTP/2 Rapid Reset (CVE-2023-44487): open a stream, cancel it immediately with RST_STREAM, repeat — 398 million requests per second from about 20,000 machines, and every frame was valid HTTP/2 using a feature real browsers use. Mitigated by counting resets per connection and capping concurrent streams. CONTINUATION Flood (2024): unbounded HTTP/2 CONTINUATION frames without END_HEADERS, so the server buffers until it dies — and because the request never completes, it is never written to the access log. The server falls over with no evidence.

Why a scraper should care. Every one of these forced defenders to move from cheap packet-level filters to expensive behavioural comparison against a baseline — CPU the attacker never had to spend. That is the same machinery that decides whether your crawler gets through, and it explains something that confuses people: why a modest, well-behaved scraper sometimes gets throttled harder than a loud one. Loud is cheap to classify. Ambiguous is expensive, and expensive traffic gets treated conservatively. It also explains protocol-level limits that look arbitrary — concurrent-stream caps, reset accounting, header-size ceilings. If your HTTP/2 client aggressively cancels requests, reuses connections unusually, or sends unusual header volumes, you are wearing a costume with a known bad reputation, entirely by accident. bunny.net's full breakdown
+
2026 shift / Browser vendors
The Vendors That Wrote the Detection Rules Are Quietly Breaking Them
Microsoft Edge now returns navigator.webdriver = false when an AI agent drives Playwright. Google patched out CDP detection in V8. Neither change was announced. The signals every anti-bot tool relied on to flag automation just became officially unreliable, because AI agents browsing on behalf of real users broke the human-vs-automation binary.
💡 Detection has to move up the stack to behaviour, intent, and network identity
For years the W3C spec required automated browsers to flag themselves via navigator.webdriver = true. That was the easiest detection signal in the industry, and most public anti-bot stacks were built around it (along with CDP-detection tricks for Chrome's DevTools Protocol). Two undocumented changes in 2026 have just made those signals soft: (1) Microsoft Edge returns navigator.webdriver = false when Playwright is driven by an AI agent on behalf of a user; (2) Google patched out the most common CDP-detection technique in V8. No release notes, no announcements. The reason is rational from the browser vendors' side: agentic browsing is now a legitimate use case (Anthropic Computer Use, OpenAI Operator, Browser Use, etc.) and the old binary doesn't apply. The implication for the guide and for production scrapers: any detection or bypass strategy that pivots on these flags needs to assume they are no longer reliable as either signal or counter-signal. Detection has to move up the stack, to behavioural ML, intent patterns, and network-identity layers (TLS JA4, IP reputation, WebRTC, DNS coherence), all of which are far harder to remove from the inside. DataDome's threat-research team published a longer breakdown if you want the technical specifics.
+
Detection vector / Hardware side-channel
"Frost": Tracking Users via SSD Timing, Zero Permissions Required
A new side-channel technique called Frost measures microscopic latencies in the SSD subsystem to fingerprint users and infer activity in other tabs. It requires no permissions, no APIs that prompt the user, and ad-blockers + incognito cannot mitigate it. Standard JavaScript on any page can read these timing variations.
💡 The fingerprinting frontier is now below the JS sandbox layer
Every active tab and every site generates a unique load pattern on the local disk subsystem. By timing common disk-touching operations through standard browser APIs, a script can probabilistically infer what other tabs the user has open and what they are doing in them, without ever requesting filesystem access. Not seen in the wild yet, but the technique class is what matters here: it joins WASM SIMD CPU probes and hyphenation-dictionary checks in a growing family of hardware-level fingerprinting that the browser sandbox does not stop. There is no direct fix until browser vendors add fuzzing or rounding to disk-operation timing, which has a real performance cost they have to weigh. For scraping: any stealth strategy that depends on JS-level patches stops working against this class of probe by design, because the signal is below the JS layer. The medium-term answer is the same one that defeats WASM SIMD probes: real hardware via real devices, not patched browsers in datacenters.
+
Detection vector / Timing attack
Incognito Detection by Timing a Single Byte Write
detectIncognito v1.7 (Nov 2026) integrates a side-channel that detects Chromium incognito mode by writing one byte to navigator.storage and timing the flush. Incognito routes storage to RAM, normal mode hits disk. RAM is faster. That is the entire vulnerability.
💡 <0.1ms flush time = RAM = incognito, no API prompts needed
The technique writes one byte three times to navigator.storage and measures the flush time. Under 0.1ms indicates RAM (incognito), above indicates disk (normal mode). No permission prompts, no API quirks the user can disable, runs in standard JavaScript on any page. Known caveats: RAM disks (used by some privacy-conscious users) trigger false positives unless the threshold is tuned (~0.01ms separates RAM-disk from incognito on test hardware); slow HDDs do not produce false positives because the technique detects suspiciously fast writes, not slow ones. For anti-bot: detecting incognito is a useful behavioural signal (legitimate buyers rarely shop in incognito; scrapers and abuse traffic over-index on it). For scraping: if your stealth stack uses incognito or per-session ephemeral storage to keep contexts clean, you are leaking a signal that is now trivially detectable. The fix is the same as for the broader timing-attack class: use persistent profile directories that hit real disk, accept the cookie/storage management overhead.
+
Detection / OS fingerprinting
The Detection Vector Nobody Patches: Hyphenation Dictionaries
Chromium on Windows and Linux requires a hyphenation dictionary to be bundled at build time. Most custom Chromium forks ship without one. The result: many stealth browsers literally cannot hyphenate words — a signal anti-bots can probe via hyphens: auto CSS and measure rendered output.
💡 Camoufox, PatchRight, undetected forks — check yours with the PoC
When CSS hyphens: auto is set and text overflows a container, the browser inserts soft hyphens at language-specific break points (so "hyphenation" becomes "hy-phen-ation"). The dictionary that drives this is OS-level on Android and macOS, but Chromium on Windows and Linux must bundle it at build time. Most people forking Chromium don't know this — the build artifact is large and the feature is invisible until you specifically test it. Joe (joe12387) demonstrated this is a detection vector: anti-bot scripts can render a known word in a known-width container with hyphens: auto, screenshot via Canvas, and compare the hyphenation positions against expected values for the claimed OS. A custom Chromium fork that fails to hyphenate at all (or hyphenates wrong) reveals itself instantly. Mitigation: ensure your build includes the hyphenation dictionary for the languages you claim to support, or run real Chromium binaries (not forks) under XVFB instead. Live PoC · github.com/joe12387
+
Debugging
Your Scraper Is Blocked Because of Behaviour, Not Code
Identical headers. Machine-speed intervals. No session state. Datacenter IPs. Fix: rotate headers, random.uniform(1.8, 4.3) delays, requests.Session(), residential proxies.
💡 sleep(random.uniform(1.8, 4.3)) beats sleep(2) every time
The signals that get you blocked, in order of detection speed: TLS fingerprint (detected before first HTTP byte), HTTP/2 SETTINGS frames (detected at connection), Request headers (User-Agent, Accept-Language, Sec-CH-UA, checked immediately), Request timing (identical intervals are machine-like), Session patterns (no cookies accumulated, no referrer chain), IP reputation (ASN, datacenter range). Fix each layer: curl_cffi for TLS, full Chrome headers via httpx or curl_cffi, random.uniform(1.8, 4.3) delays, requests.Session() for cookie accumulation, residential/mobile proxies for IP. Check your current fingerprint at tls.browserleaks.com/json.
+
Detection / WASM
The Detection Layer Your Stealth Browser Cannot Patch
WebAssembly SIMD probes the actual CPU. WebAssembly shared memory gives anti-bots a 17× higher-resolution timer than performance.now(). Both run below the JS hooks Camoufox, CloakBrowser, and PatchRight patch.
💡 The arms race of better JS patches has a ceiling. WASM fingerprinting is past it.
The DataDome engineering team published in May 2026 a method for fingerprinting CPUs from the browser using WebAssembly SIMD. Vector operations on 128-bit registers map directly to CPU instructions (NEON on ARM, SSE/AVX on x86), and their timing reveals the actual silicon underneath, not whatever the browser claims.

The enabling primitive arrived in 2024 from Manuel at brokenbrowser.com: a one-liner that gets you a real SharedArrayBuffer on any page, no special headers, by calling new WebAssembly.Memory({shared:true}).buffer. Drive a MessageChannel ping-pong with Atomics.add() inside it and you have a counter ticking at 100,000 Hz, micro-timing precision around 6 microseconds. Chrome marked it Won't Fix.

What this defeats:
× Camoufox (Firefox C++ patches at the browser layer)
× CloakBrowser (49 Chromium binary patches)
× PatchRight, undetected-chromedriver, Nodriver, Pydoll
× Every JS prototype patch (Function.toString detection is irrelevant when nothing JS is touched)

What still works: real hardware diversity. The future of stealth scraping is real consumer machines on real ISP IPs, which is essentially what high-quality residential proxy networks like Massive, Bright Data, and Oxylabs already provide. As detection moves into the CPU layer, the value of actually being real compounds.

This is also why akamai-v3-sensor works on Akamai v3: it never executes the WASM at all because it never reaches sensor.js. By bypassing at the TLS layer, you skip every detection layer above it.

Sources: Anthony Manikhouth (DataDome) and Manuel (brokenbrowser.com).
+
Persistence / Evercookie tradition
The Cookie That Refuses to Die
Anti-bots increasingly persist tracking state across cookie clears using a chain of fallback storage: localStorage, IndexedDB, Service Workers, Cache API, FileSystem. Clear one, the others restore it. The 2010 Evercookie idea is back, in 2026 form.
💡 If clearing cookies does not reset your session, the detection layer is not in the cookie
The unclearable-cookie repo demonstrates the modern version of Samy Kamkar's 2010 Evercookie technique: when a user clears cookies in DevTools, the cookie immediately respawns from a copy held in localStorage, IndexedDB, or a Service Worker cache. The visual is hypnotic, you delete it, refresh, it is back.

Why this matters for scrapers: if you are rotating cookies between requests to look like a fresh visitor, but the target site is reading your localStorage entry from the previous session, your rotation does nothing. Anti-bot vendors like Forter and Riskified have shipped variants of this for years. Cloudflare's cf_clearance cookie now has localStorage backup in some configurations.

Storage layers a real reset has to clear:
✓ Cookies (HTTP and JS)
✓ localStorage and sessionStorage
✓ IndexedDB (every database)
✓ Service Worker registrations and Cache API entries
✓ FileSystem API (legacy but still works)
✓ Web SQL (deprecated but persists on older Chromium)
✓ ETag / If-Modified-Since headers cached at HTTP layer
✓ HSTS pin database (yes, browsing data can be encoded in HSTS pins, this is real)

Practical implication for scrapers: when you rotate sessions, do not just clear cookies. Either spin up a fresh browser profile each session (Playwright context.close() + new context, or a fresh Camoufox BrowserContext), or run in an entirely isolated container. Half-measures leak state.

For the curious: the original Evercookie by Samy Kamkar in 2010 used 13 storage mechanisms. Modern browsers have removed several (Flash LSO, Silverlight, Java applets), but added more (Service Workers, BroadcastChannel, OPFS). The trick is alive and well, just modernised.
+
Research · Castle Intelligence · April 2026
Your Anti-Bot Fingerprint Is Probably for Sale Right Now
Castle analysed 811 bot-adjacent sites and found browser fingerprints being collected, packaged, and sold as operational assets. 12.5% deployed fingerprinting scripts consistent with harvesting. Services openly advertise "comprehensive TLS, HTTP/2, and JavaScript fingerprint collection."
💡 Detection systems must assume replay. Client-side signals are untrusted input, not proof of identity.
Castle Research (Antoine Vastel), April 2026 analysed 811 bot-adjacent websites including proxy providers, CAPTCHA farms, engagement manipulation services, and sneaker bots. The findings document a structured, commercialised layer of the bot ecosystem that most scraping engineers have not thought about.

What they found:

12.5% of analysed sites deployed fingerprinting-related scripts consistent with harvesting. A subset replicated vendor-specific telemetry from PerimeterX, Incapsula, Akamai, Adyen, and hCaptcha, not to defend themselves, but to collect and replay the same signals against those vendors.

The mechanics:

Services like impersonate[.]pro openly advertise "comprehensive TLS, HTTP/2, HTTP/3, and JavaScript fingerprint collection." In Discord and Telegram communities, bot developers discuss embedding custom JavaScript on real websites specifically to harvest fingerprints from genuine visitors. The goal: build inventories of authentic device profiles that can be injected into automated sessions.

The PerfectCanvas mechanism from Bablosoft is the clearest example. Their documentation describes exactly the pattern:
• Render canvas on a real remote machine with a real GPU
• Send the canvas output to the automation server
• Inject it into the headless browser's response to the canvas probe

This is the harvesting-and-replay model made explicit. Instead of spoofing canvas values (detectable via inconsistency), you replay values from a real Mac. The fingerprint is genuine. It just came from a different device.

Genesis Marketplace established the precedent: ~323,000 compromised browser environments for sale, each bundled with a real device fingerprint and a custom Chromium extension that injected the victim's browser profile into attacker sessions. F5 Labs and Europol both documented this. Castle's report shows the same approach is now commercialised at scale for bot traffic, not just account takeover.

What this means for scrapers:

The arms race has a new dimension. Anti-bots are scoring fingerprints. Bot services are buying real fingerprints to replay. Defenders are now building for replay conditions, not just spoofing conditions. This is why:

• Canvas/WebGL probes are increasingly paired with behavioural and timing signals (harder to replay than static values)
• WASM SIMD CPU probes (above) are valuable precisely because they are harder to harvest and replay than JS-layer fingerprints
• Anti-bots are introducing controlled variability in their own client-side scripts so that even valid payloads can't be reverse-engineered and replayed reliably

The implication for this guide: when a stealth browser passes the canvas probe, it may not be because it spoofed the hash well. It may be because it replayed a real hash that was never flagged. The distinction matters because vendors will move toward replay-resistant probes, making the harvest-and-replay model progressively harder. WASM SIMD (which requires real hardware timing) is an early example of a replay-resistant signal.

Source: Fingerprint Harvesting in the Bot Ecosystem, Castle Research, Antoine Vastel, April 2026.

2026 update, the session becomes the unit of judgement. In July 2026 Cloudflare shipped Precursor, which productises exactly the principle this layer describes. Rather than scoring a request, it injects JavaScript dynamically and collects behavioural signals continuously as a visitor moves through an application, feeding them into bot scoring in real time across the whole session.

The reasoning Cloudflare gives for building it is worth reading carefully, because it is a concession as much as a capability. Modern automation can execute JavaScript, drive a genuine browser environment, and pass an individual challenge. What it has not learned to do is stay convincing over time. So the defence stops asking whether this request looks legitimate and starts asking whether this journey does, which is the same move Akamai made with multi-request trust accumulation and the same reason a warm-up navigation improves your score.

The practical consequence for anyone building automation is that per-request thinking is now the wrong altitude. A stateless design that treats every fetch as independent has no way to be coherent over a session, because there is no session to be coherent within. This is also why a stateless per-request browser engine, however efficient, is the wrong tool against a target scoring at this layer.
Layer 5, Fingerprint Replay: The Game Stopped Being About Spoofing

Harvested assets · Not synthetic spoofs
Your Fingerprint Can Be Harvested And Replayed
For years the model was simple: anti-bots read your fingerprint, you spoof the attributes they read. That model is now incomplete. There is a parallel economy that collects real browser fingerprints from genuine user traffic and replays them inside automation. Disposable email sites, CAPTCHA farms, and sneaker bots quietly embed collectors that pull canvas, WebGL, audio, fonts, and device attributes from every real visitor.

Real fingerprints trade as operational assets. I have seen forum threads pricing them around five dollars per thousand, and one ecosystem scan found roughly one in eight bot-adjacent sites running a fingerprinting collector. The implication for you: a perfectly coherent fingerprint is not necessarily a spoofed one. It may be a real environment, lifted from a real person, replayed at scale.
Remote render · Inject real output
Canvas Rendered On Real Hardware
The most interesting harvesting technique splits fingerprint generation from the automation client. Instead of synthetically faking a canvas hash (which leaves detectable artifacts), the canvas is rendered on a real remote machine and the output injected into the automation browser. The hash you present came from genuine hardware, so the usual "this canvas looks computed" tells disappear.

The harvested collectors I have looked at do not stop at raw attributes. They mirror vendor-specific challenge logic: Akamai-style feature bitmasks, PerimeterX canvas markers, payment-provider canvas seeds, hashed feature keys aligned with named CAPTCHA vendors. The dataset is dual layer: raw device signals plus vendor-shaped outputs. That is what makes replay viable against a specific wall rather than just "looking human."
The defensive countermove
Why This Changes Your Strategy
If real fingerprints can be replayed, defenders stop trusting any single client-side signal in isolation. The countermeasures they reach for are exactly the ones that break naive scrapers: rotate the fingerprinting logic itself so harvested payloads go stale, randomise payload schemas so an offline-generated payload no longer matches, bind payloads to per-session nonces so a replayed body is invalid, and cross-check redundant signals across main page, iframe, and worker so a partially patched environment contradicts itself.

The takeaway I keep coming back to: coherence and freshness now matter more than any one spoofed attribute. A clean canvas hash from last week, replayed today against a vendor that rotated its logic, fails harder than no spoofing at all. Treat your fingerprint as a living thing that has to agree with itself across every context, every request.

Now you know the detection layers, four signal families plus the replay economy sitting on top of them. Every vendor below is just a different weighting of those same signals, some prioritise TLS, others behaviour, others network identity. Knowing the layer tells you which tool to pick. Here are the six walls.

03 The vendors

Six companies built the walls.
Here's every key.

Each vendor applies the detection layers differently, different weights, different signals, different architectures. What bypasses Cloudflare has zero effect on Kasada. You need to know exactly which wall you're facing before you choose a tool.

Step 0, Before anything else

Identify which anti-bot you're facing

Wrong strategy on the wrong vendor wastes hours. Before writing a single line of code, spend 30 seconds identifying exactly what's protecting the target.

Check whether anything is protecting it at all. Do not assume a hardened target by default. In a June 2026 outside-in test by DataDome, ten simulated bot profiles sent from residential IPs to 21,491 popular homepages were stopped by none of the defences on 65.3% of sites; only 2.4% stopped or challenged every profile, bots openly impersonating AI services got through on over 70%, and only 5.5% caught forged fingerprints. So send one plain request first and read the response before reaching for a browser, a residential pool or a solver. Two limits apply: the test only touched homepages, and many sites leave the front page open while protecting login, search and checkout, so repeat the check on the page you actually need. And the figures come from a bot-protection vendor describing the market it sells into, with the bot profiles and pass rule not published in detail, so treat them as a rough base rate rather than a census.

A one-line orientation to how the big four fail differently, because the fix for one can hurt you on another. Before diving into any single vendor, it helps to hold a rough map of where each one concentrates its scrutiny, since a change that unblocks one can flag you on the next. In broad, practitioner-reported terms: Cloudflare failures are often an IP-reputation and TLS-fingerprint problem, so the first moves are a cleaner network and a coherent handshake. DataDome is frequently your header set rather than your JavaScript, which is why the lean-header lesson above bites hardest here. Before changing anything after a DataDome refusal, work out which piece answered: the edge in front of the application, the sensor script, its collector, the challenge bootstraps on captcha-delivery.com, or the challenge page itself. Read the 403 body before you touch the fingerprint. On leboncoin.fr in September 2026, four curl requests with and without a Chrome User-Agent all drew a 771-to-774-byte challenge bootstrap, and the User-Agent changed only the bootstrap type (rt=c or rt=i). A different challenge is the same refusal, not progress. The sensor host can also look like the site’s own (dd.leboncoin.fr, served from S3 behind CloudFront), so a vendor check that only looks for third-party script domains can miss it. That is one site and one tag build, and the server-side scoring is not visible from outside. PerimeterX (HUMAN) leans heavily on behavioural signals, so a perfect static fingerprint still fails without human-like interaction over time. And Akamai is deep TLS and HTTP/2 fingerprinting, where pseudo-header order and JA4-level detail decide the outcome. These are tendencies, not laws, and every vendor blends all the layers, but knowing each one's centre of gravity keeps you from spending an afternoon hardening the layer that particular wall was never watching. Hold that map loosely: what you identify is a behaviour on your fetch path, not a table entry, and flatly stated rules soften the moment somebody runs a control. Google’s wrapped result links are widely described as a tax every Chrome pays; one fresh, hand-driven profile on one query returned 213 direct links and zero wrappers, while the same profile with an unattached --remote-debugging-port returned 96 wrapped and none direct. Two trials against one control, no mechanism identified, a link count nearly halved between arms — enough to retire the word always, not to install a replacement. Count wrapped against direct under your own launch config before budgeting a request per result, then read what you were already handed: on one sampled page the destination still sat beside the wrapped token in the page’s own server-rendered state, pairing 8 of 8 web results at no extra cost — one capture, denominators of seven to nine, reported mostly by firms selling SERP APIs.

Then hold something back even when a fetch comes back clean: a 200 is not an identification. Traffic-scoring products sold to advertisers route traffic they distrust to a honeypot, and name antivirus scanners by brand while explaining their own false positives; joining those two facts is an inference, not a marketed feature. The catalogued variant that matters for recon withholds payloads from proxy, VPN and datacentre addresses as likely researchers. A datacentre exit is not refused there, it is made blind. Re-sample from another address class, and at another hour, before closing the case.
1 Wappalyzer Chrome Extension Install free ↗

Visit the target site, click the Wappalyzer icon in your toolbar. It instantly shows all detected technologies, including the anti-bot vendor. Shows Akamai, Cloudflare, DataDome, PerimeterX, Kasada and more with a single click.

2 Check response cookies

Open DevTools → Application → Cookies. Match any cookie name to identify the vendor. Multiple vendors can run on the same site. For CLI scanning at scale: wafw00f https://target.com identifies WAF + anti-bot vendor in one command.

3 Check response headers

DevTools → Network → any request → Response Headers. Look for x-datadome, server: cloudflare, x-akamai-request-idor challenge redirect URLs containing vendor names.

🔍 Wappalyzer

Free Chrome + Firefox extension. One click on any site shows:

  • Anti-bot / security vendor
  • CDN provider
  • CMS, framework, analytics
  • Server technology
Install Wappalyzer Free ↗ Firefox version ↗
01/06 · Airlines · Banks · ~30% Fortune 500
Bot Manager v3+ injects sensor.js (~512KB, fully obfuscated) into every protected page. Unlike Cloudflare which checks at CDN edge, Akamai runs its full fingerprint suite inside your browser via this script. It collects 500+ signals over multiple requests, trust accumulates across the session, not just on the first hit. Expect the payload itself to be tied to the session and to the script build. A 2025 teardown of Akamai v3, written by the founder of a company that sells a bypass API, describes the sensor payload being scrambled with one key derived from the served script and another derived from the bm_sz session cookie, so a payload captured in one session or from one script version neither decodes without that script nor replays anywhere else. That is why a new sensor build can break an emulation pipeline overnight while a real browser keeps passing. If you emulate, budget for rework on every build and treat any captured payload as single-use; if you only need the data, a real browser avoids the problem. The description dates from April 2025, so check whether the scheme has changed before relying on it. Assume the sensor can see the page around you, not only your browser. A real visit to a large commercial site loads dozens of ad, analytics and tag-manager scripts from third-party domains, and a sensor running in the page can record which ones arrived. If you emulate the sensor payload from a plain HTTP client instead of running it, capture the resource list from a real browser session on the same target and compare it with what your pipeline presents. Test with ad and tracker blocking both on and off, because a blocker changes that set too. The evidence here is thin: one reverse engineer reported in September 2026 that the Akamai sensor on one airline site checked a set of about 47 third-party script domains for consistency. That is one reading of one sensor build, with no published code or capture, so treat it as a check to run on your own target, not documented Akamai behaviour. The critical 2026 signal: 60 chrome-extension:// URL probes. Zero passing = instant bot score regardless of all other signals. JA4+ is checked at EdgeWorker before HTML is served.
_abck cookie bm_sz 60 ext probes Battery API Multi-req scoring
Bypass strategy
Step 1: Check for GraphQL/XHR API first, a direct endpoint bypasses HTML anti-bot entirely
curl_cffi impersonate="chrome124" handles TLS + HTTP/2 layer
CloakBrowser with 49 C++ patches handles sensor.js interrogation
Load Bitwarden + 1Password extensions to pass 60 extension probes
ISP/static residential proxy, never rotate mid-session (trust accumulates)
Homepage warm-up → 2–3s human dwell → scroll → navigate to target
Script size
~512KB
Re-obfuscated per rotation
Ext probes
60
Zero passing = instant block
Fortune 500
~30%
Retail, airlines, finance
Scoring
Multi-req
Trust builds across session
02/06 · 20% of all internet traffic · 200+ countries
Cloudflare's uniqueness is infrastructure-level deployment. JA4 is computed in a Rust crate running on every Cloudflare edge node, your request is fingerprinted before it reaches any application server. The ML bot score (1–99) is trained on Cloudflare's view of 20% of all internet traffic, giving it an unmatched baseline for what "real" browsers look like. Do not assume that a setup which passes one Cloudflare site will pass another: on paid plans the baseline is also per site. Cloudflare’s 23 September 2025 post describes per-customer behavioural models, on Bot Management and on Super Bot Fight Mode (Pro, Business and Enterprise), that learn each zone’s normal traffic and train on no other zone’s data. Its examples are a script visiting every product page in alphabetical order and slow, sequential calls walking a whole leaderboard. Cloudflare reported 138 million scraping requests flagged on five beta zones in 24 hours, 34% of them missed by the global bot score. Those are vendor figures with no method published. In practice, randomise crawl order, pace to that site’s visitors rather than your fleet average, and expect a clean fingerprint to start failing once a crawl pattern is learned. Turnstile (their CAPTCHA replacement) submits a 79-parameter POST including Canvas hash, font measurements, SHA-256 proof-of-work, and TEA-encrypted timing data. Its AI-crawler labels are a weaker instrument than that score. Cloudflare’s documentation says AI Crawl Control, on plans without Bot Management, recognises AI crawlers by user-agent string, so a scraper that borrows GPTBot or ChatGPT-User is counted as that crawler and inherits whatever rule the owner set for it, very often a flat block. On one publisher’s site the dashboard attributed about 17,850 requests to OpenAI, about 11,890 of them were answered 403, and only around 22% came from OpenAI’s published ranges (one site, method unpublished). Two consequences for you: a borrowed AI user agent buys no access on a site that blocks that crawler, and it puts you in a bucket the owner can split from the real operator with a single IP lookup. An honest user agent of your own is the cheaper position.
cf_clearance __cf_bm JA4 Rust edge Turnstile 79 params ML score 1–99
Bypass strategy
Origin IP bypass: check SecurityTrails DNS history, many sites had Cloudflare added later, origin IP is in old A records
Camoufox with geoip=True, 100% pass rate Mar 2026 on Instagram, Reddit, X, LinkedIn (FF135 base; stable moved to FF146 on 16 Jul 2026 — re-test)
Scrapling's StealthyFetcher attempts Turnstile natively when you pass solve_cloudflare=True (off by default)
Turnstile HTTP bypass possible: solve the PoW + Canvas hash without a browser in ~0.27s
Camoufox uses Juggler (not CDP), zero CDP timing artifacts that Cloudflare's ML scores heavily
Web coverage
20%
All internet traffic
Turnstile params
79
Canvas + PoW + TEA crypto
Camoufox
100%
Pass rate Mar 2026 · FF135
ML training
Global
20% of all traffic
03/06 · 5 trillion signals/day · 1,200+ clients
DataDome's architecture is fundamentally different from the others: it deploys 85,000 separate ML modelsone per protected site. There is no universal bypass. What works on Grainger.com may not work on Le Monde. It runs at the application server level (not CDN), so origin IP bypass is impossible. The WASM boring_challenge is a Rust-compiled state machine that cannot be emulatedit requires actual browser execution to produce valid tokens. IP reputation alone accounts for 25–30% of the total trust score. Spend realism on rendering, not on navigator values. In six DataDome builds analysed by TakionAPI, a vendor that sells DataDome bypass, the plv3 virtual machine that now runs on the interstitial and the captcha gave 53 strings to a CSS calc() layout readback, plus WebGL readPixels and canvas compositing, and only 7 to navigator. The same author describes the WASM challenge above as a proof-of-work he decompiled and ran outside a browser: a CPU cost the server can recheck, not proof of a browser. Treat “cannot be emulated” as a claim that now belongs to plv3, which reports its own step count and a hash of its interpreter so that rewritten or hooked runs differ, and read both as a vendor’s account rather than an independent test.
datadome cookie WASM boring_challenge Picasso device FP 35+ behavioural 85K per-site models
Confirmed bypass, Grainger.com ✓
Always try first: find __NEXT_DATA__ in HTML source, Grainger had 110KB of product data in it, bypassing DataDome entirely
curl_cffi chrome124 + residential proxy → confirmed 200 OK (Grainger.com)
Mobile carrier IP (T-Mobile, Vodafone 4G), highest trust score, hardest to flag
Camoufox + geoip=Truealigns all 5 identity vectors with proxy exit country
2ms real-time response means every request is independently scored
Grade the request, not the jar: on hermes.com’s listing endpoint the same URL answered 403 as a top-level navigation and 200 as the page’s own same-site fetch (Origin + sec-fetch-mode: cors) — empty cookie jar, no token ever issued. One endpoint on one site, self-reported; rules are per customer and path
Coherence, not honesty: five bracketed cells on a live idealista slider — stock Linux Chrome wearing its own UA solved, the same binary forced to a Windows UA blocked, a bare (Linux) token blocked. Bracket every A/B with a known-good control: a failed burst degrades the exit IP for ~15 minutes and then everything fails
ML models
85,000
One per protected site
Response
2ms
Real-time, app server
IP weight
25–30%
Of total trust score
Universal bypass
None
Per-site models
04/06 · HUMAN Security · 3 billion devices
After merging with HUMAN Security, PerimeterX gained the most powerful network effect in anti-bot. It verifies 15 trillion interactions per week across 3 billion devices. The critical risk: get detected on any one of 29,650+ protected sites and your fingerprint is flagged across the entire network. Nike, Walmart, Zillow, StubHub all share reputation data. Its 5-vector unified score (TLS + IP + HTTP headers + JS fingerprint + Behaviour) requires all five to pass simultaneously, fixing only one vector has zero effect.
_px3 cookie _pxde cookie 5-vector score 29,650 site network Human Challenge
Bypass strategy
All 5 vectors must pass simultaneouslyCamoufox + residential proxy addresses all of them
Generate a fresh fingerprint per session, never reuse fingerprints across different target domains
SeleniumWire can intercept the _px3 token generation flow for token replay
Scrapfly's ASP flag handles all 5 layers automatically at managed API level
Never use burned IPs, the network effect means cross-site reputation
Sites
29,650+
Nike, Walmart, Zillow
Weekly verif.
15T
3B devices/month
Vectors
5/5
All must pass
Network effect
Global
Reputation shared
05/06 · No CAPTCHA · Gatekeeper proxy architecture
Kasada operates as a gatekeeper proxyevery request flows through it before reaching origin. Its JavaScript (ips.jsrenamed polymorphically each deployment) issues proof-of-work challenges that require real CPU cycles and browser APIs to solve. There are no CAPTCHAs, failures are silent 403s or 429s with no explanation. The critical 2026 fact: Kasada specifically fingerprints playwright-stealth by calling Function.prototype.toString() on patched native functions. The patch signatures are catalogued.
x-kpsdk-ct x-kpsdk-cd ips.js PoW polymorphic JS toString() inspection
Bypass strategy
Never use playwright-stealthKasada has its toString() signatures catalogued and blocks it outright
PatchRight patches at Python source level, nothing in the JS runtime to inspect via toString()
SeleniumBase UC mode, removes webdriver flag and auto-handles PoW challenges
Residential proxy essential, datacenter IPs receive near-zero trust regardless of browser
PoW tokens are single-use, never replay, always generate fresh per request
Block style
Silent
403 no explanation
playwright-stealth
Detected
Catalogued signatures
Challenge
JS PoW
Real CPU required
JS file
Polymorphic
Renamed each deploy
06/06 · $1 billion acquisition · Most sophisticated
F5 acquired Shape Security for $1 billion in 2020and the price reflects what they built. Shape runs a custom JavaScript virtual machine. The bytecode that executes in the browser is not standard JavaScript, it's a proprietary instruction set that you cannot reverse-engineer with standard tooling. Session tokens expire in minutes. The challenge payload is re-generated with every rotation. For production scraping at scale, DIY bypass is economically irrational, the engineering cost of maintaining a bypass exceeds the cost of Bright Data's API within weeks.
reese84 cookie TS cookie custom JS VM minute-cadence rotation $rsc= params
Bypass strategy
First: check if mobile app uses a weaker backend, Shape is often only on the web frontend
Only reliable option at scale: Bright Data (98.44%) or Zyte (93.14%) managed APIs
DIY reverse engineering: deobfuscate VM bytecode, takes weeks per rotation
Cost-justify: >2 days/month of maintenance time → managed API is cheaper
The custom VM produces tokens that can be replayed for a few minutes, session pooling can reduce API costs
Acquisition
$1B
F5 Networks 2020
Token expiry
Minutes
Tight rotation cadence
VM type
Custom
Proprietary bytecode
DIY viability
None
Use managed API
Forter
Fraud / Behavioural
Focuses on behavioural analysis and device fingerprinting for fraud prevention. Monitors checkout speed, typing rhythm, and device profile. Common on e-commerce checkouts. Expect form behaviour to be scored as a sequence, not as single events. Open-source fraud tooling now ships rules such as “paste into field X, then submit”, evaluated over the whole session rather than per request, and one console flags more than three copies and three pastes in a session by default as possible credential stuffing or automated form filling. A filler that pastes every value and submits at once matches that shape exactly, however clean its fingerprint, and the same console can also record what was pasted unless the operator switches it off. These are configurable defaults with no published false-positive rate, so the weight any particular site gives them is unknown; what is known is that the check is cheap enough to ship free. Bypass: headless browser with randomised timings, diverse residential proxy pool, replay real user interaction sequences.
BehaviouralDevice FPCheckout fraud
Riskified
Fraud / Behavioural
Monitors shopping and payment behaviour alongside device fingerprinting. Flags anomalies in purchase flow, typing patterns, and system details. Bypass: Playwright Stealth with realistic interaction replay, residential proxies, maintain full session cookies across the purchase flow.
BehaviouralDevice FPPayment flows
Imperva Incapsula
WAF · IP reputation · JS challenges
Enterprise WAF used by Fortune 500 financial and government sites. Focuses on IP reputation databases + JavaScript challenges + behavioural analysis. Less aggressive than DataDome on TLS but harsh on flagged IPs. Check whether the cookie is bound before you pay for the IP. Imperva’s reese84 is earned by a browser sensor, and on at least one deployment it was not tied to the client that earned it. Warm a session once in a real browser, save the whole jar (reese84 plus the incap_* and nlbi cookies), then replay it from a plain TLS-impersonating client on a different network. If the real page comes back, you can split the work: pay for the stealthy warm-up, and spend the token from cheap infrastructure until the server’s renewInSec runs out. In a September 2026 test on one retail site, a jar with renewInSec of 699 seconds (about 11.5 minutes) returned full HTML from a residential IP, a datacenter IP and a home line in another country. A captcha-assisted warm-up cost around a tenth of a cent. That is one site on one date, and binding can differ per deployment, so treat it as a test to run, not a rule to assume. Bypass: residential proxies (datacenter IPs nuked instantly), Camoufox or fortified browser, slow request pacing.
IP reputationJS challengeEnterprise/finance
AWS WAF
Cloud-native · Bot Control · Captcha
Amazon's managed WAF with Bot Control add-on. Three protection levels: Common (signature-based), Targeted (behaviour + JS challenge), Custom rules. Used by AWS-hosted apps. Bypass: rotate residential IPs (Common tier blocks AWS IPs themselves), browser automation for Targeted tier, request rate ≤ 5/sec to avoid trigger thresholds.
AWS-nativeBot ControlCAPTCHA
Anubis
Open-source · PoW · Anti-AI scraper
Self-hosted Web AI Firewall (15k+ stars on GitHub, MIT licensed, written in Go by Xe Iaso/TecharoHQ). Sits as a reverse proxy and issues JavaScript proof-of-work challenges before serving requests. Built specifically against AI scrapers that ignore robots.txt. Used by Codeberg, FFmpeg, the Linux kernel source, Sourcehut, and most non-Cloudflare FOSS projects. Recognisable by its anime "Anubis" mascot illustration during the challenge. Bypass: headless Chromium with JS enabled (it'll solve the PoW naturally, just slower), or persist the verification cookie across requests. Codeberg confirmed in mid-2025 that AI scrapers already learned to solve Anubis challenges, so it slows scraping but doesn't stop a determined operator.
Proof-of-workSelf-hostedAI-targetedFOSS
CDN-native · JS Proof-of-Work · Next-Gen WAF
Fastly runs bot detection at its CDN edge, layered on the Next-Gen WAF (the former Signal Sciences engine). It does the now-standard stack, JA3/JA4 TLS fingerprinting and HTTP header-order analysis before any HTML, IP reputation against datacenter and proxy ranges, and behavioural scoring, but its defining mechanic is the dynamic client challenge. A non-interactive JavaScript Proof-of-Work challenge tests that the client is a real JS-executing browser, escalating to an interactive CAPTCHA only on suspicion, and it can also issue Private Access Tokens. The tell is the cookie pair issued from the customer's own domain: _fs_ch_st_* marks a challenge starting and _fs_ch_cp_* marks it solved, with _fs_cd_cp_* appearing when advanced client-side detection is enabled. A solved challenge yields a token cookie (default one hour) that later requests must carry. Because the challenge is a JS PoW rather than a heavy obfuscated VM, a real JS-executing browser engine clears it where a plain HTTP client cannot, which puts it closer to Anubis in difficulty than to Kasada. The usual caveat holds: identify it from the _fs_ch_* cookies first, then match a real browser stack end to end (TLS, header order, and JS execution) rather than reaching for a heavier tool than the challenge needs.
_fs_ch_st__fs_ch_cp_JS PoWJA3/JA4Header orderPAT

Quick identification reference

What you seeAnti-botKey cookie/headerDetection method
"Pardon Our Interruption" pageAkamai block_abckWappalyzer · response body
CF-Ray header · Turnstile iframeCloudflare challengecf_clearanceResponse header CF-Ray
JSON with datadome keyDataDome blockdatadomeResponse header x-datadome
_px3 or _pxde setPerimeterX block_px3Cookie inspection
Silent 403 · no bodyKasada silentx-kpsdk-ctResponse headers · ips.js in source
reese84 or ___utmvc cookieImperva (Incapsula) blockreese84Cookie names · Incapsula challenge script
TS* or TSPD_101* cookieF5 (BIG-IP / Shape) blockTSPD_101*Cookie names · Shape JS reference
Anime mascot "weighing your soul" pageAnubis challengetecharo.lol-anubis-authJS PoW challenge · Anubis HTML title
302 redirect to a virtual waiting roomQueue-It queueQueue-it token cookieX-Queueit-Connector header · queue-it.net redirect
The through-line: Every anti-bot vendor is defending against the same thing, automated access that looks like a machine. The difference is which layer they weight most heavily. Akamai weights browser execution (sensor.js). Cloudflare weights TLS + global ML. DataDome weights per-site behaviour + IP. PerimeterX weights the network effect. Kasada weights PoW + JS integrity. F5 Shape weights token validity via a proprietary VM. The tools in the next section exist as direct countermeasures to each of these specific approaches.
9 field notes on the vendors Reported from practice. Click any card to open it.
+
Aug 2026 / Anti-bot internals
Kasada Is Not Obfuscated JavaScript Any More. It Is a Virtual Machine.
A researcher pointed an agentic AI at Kasada on nike.com and published the inventory rather than a bypass, because there is no longer any readable logic to pretty-print. Kasada now ships a custom VM plus a bytecode blob: beautify it and you get an interpreter loop, not a program. The measured shape of SDK j-1.2.726: p.js is 163,120 bytes around a 136,736-character string blob, decoded by a variable-radix varint with a 72-character alphabet at radix 48; ips.js is 655,603 bytes around a 603,415-character blob, alphabet 71, radix 46, and wrapped in a wall-clock time lock.
💡 The load chain hides where KPSDK is actually born, and that matters if you are replaying
The chain, in order. The page loads p.js, which fires an mfc request and creates two iframe navigations to an fp endpoint. The fp response is a nine-line HTML bootstrap, and it is that bootstrap, not p.js, that creates window.KPSDK — pinning KPSDK.now to performance.now bound to performance with Date.now as fallback, stamping KPSDK.start immediately, then posting to the parent. Only then does ips.js load, carrying three query parameters: KP_UIDz, x-kpsdk-v (the SDK version, in cleartext) and x-kpsdk-im.

Four layers, and only one is worth your time. p.js is the loader plus integrity checks. ips.js is the fingerprinting program. Then the transport, then the server. The researcher went for ips.js and ignored the rest.

The methodological line worth stealing. He graded every finding by evidence strength, because a fingerprinting inventory built from decoded strings is mostly wrong: “A string sitting in a table hasn't been read by anything.” Finding navigator.hardwareConcurrency in a decoded string table proves the string exists, not that the program reads it. Distinguishing the two is the entire difficulty of the exercise, and it is the same mistake people make when they grep a bundle and announce what a vendor checks.
+
Aug 2026 / Sector data
Fashion Runs the Hardest Doors on the Web, and Almost No CAPTCHAs
Not banking. Not gambling. Fashion is the single hardest industry for a machine to read — averaging 2.86 out of five on access complexity against a research-wide mean of 1.58, and topping all eighteen sub-sectors of retail and e-commerce. The interesting part is which controls it uses, and which it refuses.
💡 The defences moved to where a shopper never looks: the connection, the rate, the pattern
What is on, and what is deliberately off. Rate limiting runs on 54% of fashion sites, the highest of any industry measured. JavaScript is required on 49%. TLS fingerprinting — screening you at the moment you connect, before a byte of HTML — sits at 32%, beaten only by jewellery and luxury. A WAF runs on 91%, though most arrived bundled with a CDN and do little alone. And then the one that explains the strategy: CAPTCHA appears on only 15%, below the cross-industry average. That restraint is commercial, not technical — a CAPTCHA in a checkout flow costs sales. So the industry moved its defences to the places a customer never encounters and a collector always does.

Layering is the norm, not the exception. Most fashion sites run three or more barriers at once, sitting at different levels: the connection, the request rate, the rendering, the behaviour. Any one of them is minor. Together they turn a fetch into sustained engineering, which is exactly what pushes the sector up the scale. Roughly a third of fashion sites still resolve as Easy — but more than half land at Moderate or above and better than one in four reach the tiers that need residential proxies and heavier infrastructure.

The split inside the sector is the useful bit. At the premium and heritage end the defences are quiet: an enterprise bot-management platform in the background, no CAPTCHA to interrupt the shopper, and a block returned on the very first request rather than a polite request to slow down. High-volume fast fashion runs lighter, leaning on basic rate limiting. The rule underneath: the closer a brand sits to scarcity and price protection, the heavier and quieter its controls. Much of the machinery now standard across the sector was proven on limited sneaker drops, where a shoe sells out in seconds and resells for multiples — queues, waiting rooms and behavioural detection were built to hold off automated buyers, then generalised.

The figure that says where the anxiety actually is. Only 4% of fashion sites name an AI crawler to block in robots.txt, a fraction of the rate in news and publishing, and about 69% publish a robots.txt at all — with most of the attention still on Googlebot rather than GPTBot. A newspaper worries about a model trained on its archive. A fashion retailer is largely indifferent to whether a chatbot read its About page, because its concern is commercial data: prices, ranges, stock levels. The industry that lives on reading demand signals understands exactly what its own signals are worth.

And the operational warning, which corrects a reasonable assumption. On these sites full access is required from the very first request, and the rate limiting responds to client identity rather than speed — so slowing down will not help you. That is the opposite of the 429 case elsewhere in this feed, and telling the two apart before you start tuning is the whole game.
+
Aug 2026 / Diagnosis
The Cloudflare Header Does Not Tell You Who Blocked You
A short post carrying a genuinely expensive lesson. A practitioner took a 403, read the header, and started debugging Cloudflare. The block page named a different company entirely — DataDome one time, PerimeterX another. Cloudflare was the front door and never made the decision.
💡 The header names the door. The body names the decision.
This is the most common wasted week in the field, and it is wasted before any technique is chosen. A CDN in front of an origin will stamp its own headers on a response whose refusal was made by a vendor sitting behind it, and modern stacks routinely run two or three defences at once — this guide has documented single properties loading Akamai, PerimeterX and reCAPTCHA Enterprise simultaneously. If you tune your TLS profile against the wrong one, everything you learn is true and useless.

The check costs one request. Read the response body, not just the status and headers: block pages name their vendor, in visible text or in a script path or a support link. Then look at the cookies actually set on the failure — _abck and bm_sz mean Akamai, datadome means DataDome, cf_clearance means Cloudflare made the call, reese84 means Incapsula. The cookie jar is harder to misread than the header, because it is written by whichever system actually ran. Not every gating cookie belongs to a vendor. One historical case (March 2025, reported by the founder of a bypass-API vendor) was a car-rental site whose search API refused requests unless a cookie was present that the page built in JavaScript from the colour attributes of four elements: a home-grown check, not a product. When a request works in the browser and fails in your client, find which single cookie actually gates it by removing them one at a time, then look at what writes it before assuming a commercial anti-bot is involved.

The general form is a principle already in this guide, applied one level up: the block is at a layer you are not working on, and here it is at a vendor you are not working on. Before choosing a tool, establish which system refused you — and treat the CDN name in the header as the least reliable evidence available, because it is the one thing guaranteed to be there whether or not it was involved in the decision.
+
Aug 2026 / Detection archaeology
The 100%-Precise Turnstile Check Was a Chrome Bug, and Google Just Fixed It
Cloudflare had a Turnstile check that caught every mainstream automation framework with no false positives. Puppeteer, Playwright, Selenium, all of them. It was not clever heuristics or behavioural scoring. It was a coordinate bug in Chrome, open since 2023.
💡 The sharpest detections are vendor bugs, which means they expire
The mechanism is beautifully small. Turnstile renders inside an iframe. When a real person clicks, the reported mouse coordinates are relative to the main frame, so inside a widget sitting partway down a page they land in the hundreds. When the click is dispatched through the Chrome DevTools Protocol, the coordinates come out relative to the iframe instead, so they are under 100. One comparison, no machine learning, no false positives, and every CDP-driven browser failed it identically because they all go through the same code path.

Google fixed the underlying bug in September 2025, years after it was first reported. Once the fix is broadly deployed, the check is dead.

Three things worth taking from this. First, the highest-precision detection in a vendor's arsenal is often not a model, it is a discrepancy, and discrepancies get patched by someone with no stake in the detection. Second, this is the same story as browser vendors quietly softening navigator.webdriver and CDP detection: the detection surface is owned by the browser vendor, not the anti-bot vendor, and the browser vendor's incentives now include making agentic browsing work. Third, and most practically, the class of bypass that survived this was input generated outside the browser — xdotool, AutoHotkey, pyautogui — because a click synthesised at the OS level is not a CDP click at all, it is a real one, and it carries real coordinates. That is the general shape of the answer whenever a detection keys on how an action was dispatched rather than what it looked like. The teardown
+
Aug 2026 / Measured, not estimated
Only 18.5% of the Web's Front Doors Are Actually Open
The largest published audit of web access controls to date measured 24,898 of the world's most-visited sites across 230 countries and 110 industries, scoring each against six barrier categories: WAF, anti-bot, CAPTCHA, JavaScript dependence, rate limiting and TLS fingerprinting. It replaces a lot of folklore with a number.
💡 Most sites did not choose their WAF. It arrived with the CDN.
Headline findings, as a mid-2026 snapshot: only 18.5% of landing pages carry no technical barrier at all, meaning 81.5% deploy at least one, and half of all sites run two or more layered across the network and application layers. WAFs appear on more than nine sites in ten, dynamic rate limiting on more than half. Roughly four sites in ten now disallow AI crawlers in robots.txt. Fashion measured harder to access than banking, which surprises people until you remember what price and inventory data is worth.

The finding that matters most is the one that reads as good news. Despite all of that, 88% of landing pages still sit in the two lowest accessibility tiers. Both halves of that are true at once, and holding them together is the whole point of the difficulty ladder above: a WAF being present is not the same as the target being hard, because most WAFs ship switched on by default with a CDN and were never tuned by anyone. The practical reading is that the barrier count tells you almost nothing on its own, and the only honest way to place a target is still to probe the endpoint you actually need. State of Web Access 2026 (Zyte), full report
+
2026 reality / Stack composition
Defense in Depth Is Table Stakes Now (26-Site Scan)
A scan of 26 e-commerce and ticketing sites found the same pattern everywhere: the defense is no longer a vendor, it is a stack. Home Depot's sign-in loads Akamai Bot Manager, PerimeterX/HUMAN, Forter, AND reCAPTCHA Enterprise simultaneously. Beating one is tractable. Beating the composition is not.
💡 If you are still planning around a single vendor, you are two layers behind
Five patterns kept appearing across all 26 sites. (1) Stack not vendor: multi-vendor compositions are now the norm, not the exception. (2) Brand names lie: Ticketmaster calls their system "EPS", but open the bundle and you find iamNotaRobot.js, abuse-component.js, aps.js — it is PerimeterX rebranded and served from their own domain. Trust the code, not the label. (3) First-party cloaking: Home Depot serves PX-shaped scripts under random first-party filenames. You cannot identify defenders by checking hostnames anymore, you have to watch how the script behaves at runtime. (4) Lazy-loaded defenses: Ticketmaster ships a reCAPTCHA site key in the homepage JSON but the SDK only loads on login or checkout. Probing only the homepage misses everything. Multi-hop traversal (homepage → login → cart) is the minimum recon bar now. (5) The fingerprint dictates the budget: PerimeterX + behavioral biometrics + reCAPTCHA Enterprise on one page tells you exactly what tier of browser, what kind of proxy, and how slow your automation has to be. Recon is upstream of every other decision. The takeaway: stop asking "which vendor does this site use" and start asking "which stack does this site compose, and where on each layer do I look for cracks."
+
FOSS Defense / Anti-AI
Anubis: The Anime-Mascot Firewall Protecting FOSS
Self-hosted Web AI Firewall (15k+ stars) built by Xe Iaso. Used by Codeberg, FFmpeg, the Linux kernel docs, Sourcehut. Issues a JS proof-of-work challenge with a furry-eared mascot. Codeberg admitted in mid-2025 that AI bots already learned to solve it.
💡 PoW slows scrapers but headless Chromium solves it naturally
Anubis is the new category of anti-scraper protection most guides miss: self-hosted, FOSS-targeted, AI-scraper-focused. Unlike Cloudflare and Akamai (enterprise SaaS), Anubis runs as a reverse proxy on the same server as the protected site. The challenge is pure client-side JavaScript proof-of-work — the browser hashes until it finds a nonce matching a difficulty target. For real users this is invisible (~1-3 seconds on modern hardware, painful on old phones). For scrapers using plain requests or curl_cffi, the challenge is unsolvable without JS execution. The bypass is mundane: any headless browser (Playwright, Camoufox, Patchright) with JS enabled will solve it automatically. Persist the auth cookie (techaro.lol-anubis-auth) and reuse it across requests. The political angle: Anubis exists because AI scrapers (OpenAI, Anthropic, Common Crawl, ByteDance) were DDoSing small FOSS projects by ignoring robots.txt. It's a community response, not a commercial product. github.com/TecharoHQ/anubis
+
TLS / Anti-bot
Cloudflare Turnstile Solved Without a Browser
Solvable with pure HTTP, no browser needed. Reverse-engineer the POST payload: 79 parameters covering Canvas, WebGL, Timing and crypto hashes. Status 200 in 0.27s.
💡 Turnstile PoW is solvable in under 1s via plain HTTP
The Turnstile POST payload contains 79 parameters. Key groups: Fingerprint (Canvas hash via OffscreenCanvas, WebGL renderer, AudioContext output), Browser Environment (navigator properties, screen dimensions, timezone), Interaction sequence (mouse path, click timing), and Crypto (custom SHA-256 + TEA encryption of the challenge nonce). The Sitekey is extracted automatically from the page source. Algorithms used: Custom SHA-256, TEA block cipher. Token format: Encrypted_Data-Timestamp-Version-Checksum. Full flow: extract Sitekey → initiate challenge → construct responses → generate 95-char token. Result: cf_clearance accepted in 0.27s. No browser process needed.
+
Defence experiment · Behavioural CAPTCHA · 2026
A CAPTCHA That Watches How You Tell a Story
A researcher built a deliberately awkward CAPTCHA as a behavioural-biometrics experiment: it shows a random prompt, asks you to type a short story about it, then rate the experience from 1 to 10. The trick is hidden in the interaction order. If you rate the CAPTCHA before you hit submit, you are probably a bot, because a human reads the story task first and rates last. The signal is not the answer, it is the sequence and rhythm of how you produced it.
💡 The frontier of human-checking is behaviour over time, not a single correct response.
This is worth studying for what it tells you about where bot detection is heading, on both sides.

Why the idea is clever. A traditional CAPTCHA asks for an answer a script can compute or outsource to a solving farm. A behavioural CAPTCHA scores how the answer was produced: typing cadence, edit and pause patterns, the order in which UI elements were touched, time spent reading versus writing. Those are expensive to fake convincingly because they are emergent properties of a real person working through a task, not a field you can fill in. The out-of-order tell (rating before submitting) is a neat tripwire: it catches automation that fills every field it sees without modelling the human workflow the form implies.

Why it still falls. Within a day of the public demo, another engineer bypassed it consistently with an LLM plus Playwright. The sequence is familiar from the rest of this guide: first attempt scored too low and was rejected, the approach was tuned, the second and third attempts passed, including a live run. Behavioural scoring raises the cost of automation, it does not create a wall, because a scripted agent can be taught to produce human-shaped timings and to touch the form in the order a person would. The lesson cuts both ways: if you defend, behavioural signals are a strong layer but not a final one, and you must assume they will be modelled; if you scrape, the modern bar is not "submit the right value" but "reproduce the human process that produced it," which is exactly the territory automation-protocol and interaction-timing detection already live in.

The healthy norm on display. Both the defence and the bypass were published openly, demo and method in the open, framed as understanding security rather than breaking it. That is the same posture this guide takes: the techniques are dual-use, and studying them in public is how both sides get sharper.

StoryCaptcha by Tyler Richards (stackedqueries), a stated proof-of-concept and not production-ready by the author's own note; public AI-plus-Playwright bypass write-up, 2026.
One thing worth holding in mind before the tools: "99% of bots blocked" is the most comforting line in any security stack, and it is the one to be most suspicious of. Real bot protection is not a checkbox bundled next to caching and DDoS. It is a research field. When you rebuild a protection’s bytecode interpreter outside the page, test it against the original read by read, not only on where it finishes. JavaScript coercion can be part of the instruction set: one account of Google’s BotGuard describes a function stored in the instruction-pointer register, which a shift turns into 0 and += 8 turns into a string, so the real interpreter rereads byte zero while a version that “tidies” the function to 0 reads the next byte, and both reportedly end at the same position. The coercion itself reproduces in Node 26: bytes AA BB come back AA AA from the function-valued register. That account is a single practitioner’s, with no published build, so treat the specific trick as illustrative; the testing rule holds for any VM-protected sensor. And before spending weeks on it, check whether the data has an official API, which for most Google properties will be the cheaper route. When a VM ships its instruction set inside each build, anchor any analysis on structure rather than values, and compare sets of builds, never two files. In one vendor teardown of DataDome’s plv3 VM, opcode numbers, register offsets and tag bytes changed every build while the handler count stayed at 103, and a one-against-one comparison of captcha and interstitial surfaced dozens of apparent differences that collapsed to a single API call once three builds per family were intersected. Expect instrumentation to be visible as well: the same VM sends its dispatch step count and a hash of its interpreter’s source with the token, and a browser run and an offline run differed by 0.8% on that count. The account comes from TakionAPI, which sells DataDome bypass, and has not been replicated. Application-layer detection alone means shipping obfuscated challenge code inside a custom VM and staying ahead of the people who devirtualise it for a living, knowing which canvas and timing artifacts separate real Chrome from automation and which an attacker can cheaply fake. Below it sit entirely separate specialisms: TLS fingerprinting, HTTP/2 frame ordering, reading the OS and tunnelling from raw TCP. Real detection holds all of them current at once against an adversary who ships patches in hours, and the moment a vendor leans on a single layer, that is the layer that breaks. The practical consequence for a scraper is the inverse of the marketing: a great deal of deployed protection was built once and quietly stopped being touched. A toggle implies the problem is solved and static; it is neither. When a defence feels immovable, the useful question is how recently anyone actually worked on it.

Six walls. Now the tools. Every library below exists as a direct response to one of those six systems, curl_cffi was built because JA4 broke Python's TLS. Camoufox because CDP leaks signal automation. PatchRight because Kasada fingerprints JS patches. The arms race made this arsenal.

04 Field notes

How I approached real-world bypasses

The theory above tells you what anti-bots do. These notes tell you what I did when I hit them on a production job. Each is a full day or two of work distilled to: what I tried, why it failed, what finally worked, and the decision tree I'd use next time.

Read these as snapshots, not recipes. Each case is dated and reflects what worked on a specific target at a specific time. Anti-bot systems are probabilistic and adapt continuously, so the exact stack that worked for me may behave differently for you, on a different target, today. The durable value is the reasoning, not the recipe. See Legal & Reality for the full caveat.
Production bypass · ~1 day No browser · 0 blocks in 500+ requests · 24 req/min sustained

Akamai v3 in 2026: cracking it without a browser

Field notes from a production scraping job. The story of what I tried, why each thing failed, and the exact approach that finally got clean 200 responses with zero browser overhead.

The core problem · _abck ~-1~ won't flip

Akamai's _abck cookie has two states. ~-1~ means unvalidated, full bot score, blocked. ~0~ means validated, trust granted. The cookie is set immediately on any page load, but only flips to ~0~ after sensor.js (a 512KB obfuscated fingerprinting script) executes, collects signals, and POSTs them to /_bm/data.

Signals that matter most: canvas fingerprint (pixel-level hash of GPU-rendered shapes and text), WebGL renderer (exact GPU model via WEBGL_debug_renderer_info), AudioContext (floating-point sine wave through a compressor node), Chrome extension probes (60 chrome-extension:// URLs fetched via fetch(), zero passing = instant bot score), mouse/scroll trajectory physics, and navigator properties cross-checked against the fingerprint.

The kicker: validation is multi-request. Trust accumulates across the session, not just on the first hit.

What failed (and why)
× Attempt 1 — Headless Chrome variants (undetected-chromedriver, Pydoll)
Standard starting point. Tried Chrome headless with undetected-chromedriver (uc) routed through a Comcast ISP proxy. Then switched to Pydoll — CDP automation without the usual webdriver flags. Both behaved identically. _abck set immediately as ~-1~, never flips. Waited 60 seconds, scrolled, dispatched JS mouse events. Nothing.
Why: headless Chrome has no GPU. gl.getContext('webgl') returns null. Sensor.js sees zero WebGL context and assigns maximum bot score before the session even starts.
× Attempt 2 — SwiftShader software GPU
Tried --use-angle=swiftshader --use-gl=angle. WebGL works. Canvas renders. AudioContext works. Renderer: ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE))).
Why: 0x0000C0DE is SwiftShader's device ID, in public lists of virtual GPU IDs. Akamai checks the unmasked renderer against a blocklist. SwiftShader is on it. The canvas hash it produces is also deterministic and known.
× Attempt 3 — Camoufox (Firefox with C++ patches)
Camoufox is excellent for Cloudflare. Real canvas hash, coherent device profile, no CDP artefacts because it uses Mozilla's Juggler protocol. geoip=True aligns WebRTC, DNS, and timezone with the proxy exit country. Set up a session, pointed it at the target, ran a few warm-up requests.
Why: Camoufox patches the browser, not the network. The TLS fingerprint it produces is Firefox, but on this particular Akamai deployment, even Firefox's real JA4 wasn't enough — the IP reputation scoring on my exit IP and the multi-request trust accumulation Akamai uses meant validation never completed. Camoufox does great work where Cloudflare scoring is the gate; here the gate was earlier and harder.
× Attempt 4 — JS prototype patching via CDP
Inject before page load via Page.addScriptToEvaluateOnNewDocument: patch WebGLRenderingContext.prototype.getParameter to return "Intel Iris OpenGL Engine". Patch navigator.platform to "MacIntel", deviceMemory to 8, battery API, chrome.runtime. Result: 2327-byte error page before sensor.js runs.
Why: Akamai's EdgeWorker fires at the TLS layer before HTML is served. JS injection patches don't affect the TLS handshake. The site also detects the timing signature left by Page.addScriptToEvaluateOnNewDocument. The prototype tampering itself is detectable via Function.prototype.toString().
× Attempt 5 — CDP UA override only
Use Network.setUserAgentOverride with full userAgentMetadata to spoof macOS Chrome 148. No JS injection. Same error page.
Why: the UA override changes HTTP headers and what navigator.userAgent returns, but not the TLS ClientHello fingerprint. Akamai's EdgeWorker sees the JA4 hash (still Linux Python automation) and blocks at the network layer before the page loads.
× Attempt 6 — Xvfb virtual display + non-headless Chrome
The hypothesis: headless detection is at the GPU level. Give Chrome a virtual display, it thinks it has a real screen. xvfb-run -a, Chrome launches in headless=False. Pages load fully (1.1MB real HTML, all images, category navigation).
Why: Xvfb has no GPU either. glxinfo shows Mesa software rasterizer. Canvas hash from Mesa llvmpipe is different from SwiftShader but still a known server software renderer, also flagged. _abck stays ~-1~ for 60+ seconds regardless of scrolling.
× Attempt 7 — Inject real Mac canvas hashes via CDP
If the server's canvas hash is flagged, why not inject a real Mac hash? Requires Page.addScriptToEvaluateOnNewDocument again. Same problem as Attempt 3.
Why: the injection itself is the signal, regardless of what values we inject. Akamai detects the patch through CDP timing artefacts and Function.toString() inspection.
The breakthrough · I was fixing the wrong layer

Every failed attempt above tried to fix the browser layer. The fundamental insight: most Akamai-protected sites never reach the deep sensor.js evaluation if the request looks like real Chrome at the network layer first.

Akamai scores in five layers:

Layer 1 · TLS JA4fires before HTML is served
Layer 2 · HTTP/2 SETTINGSHEADER_TABLE_SIZE, WINDOW_SIZE
Layer 3 · ALPN + header orderh2 vs h3, Chrome order
Layer 4 · sensor.jscanvas, WebGL, audio, extensions
Layer 5 · Behaviouralmouse Bezier, scroll timing

Python's requests, httpx, even curl_cffi with a wrong impersonation profile all fail at Layer 1. The JA4 hash doesn't match Chrome 148's actual ClientHello. Check your impersonation profile against the current Chrome release, not against the version it was named after, because one missing extension is enough to move your JA4. In an independent twice-weekly measurement on 26 Sep 2026, Chrome 154 through 156 all sent 17 ClientHello extensions, including the new trust_anchors (0xca34). tls-client with its chrome_152 profile matched on JA4, groups, GREASE and HTTP/2; curl_cffi 0.16.3 with chrome150 sent 16, lacked GREASE in signature algorithms, and so failed JA4 while still passing groups and HTTP/2; uTLS’s auto Chrome profile also failed HTTP/2. Treat these as one young tracker’s macOS results covering only the handshake layer, so a pass is necessary rather than sufficient, and re-run the same comparison yourself whenever a new Chrome stable ships. Fix Layers 1-3 correctly and you often never reach Layer 4.

✓ What worked · a Go library reimplementing Chrome's TLS stack

A Go library, akamai-v3-sensor, reimplements Chrome's exact TLS stack at the C level: cipher suite order, GREASE values, extension ordering, ALPN, HTTP/2 SETTINGS frames, HTTP/3 QUIC parameters. The JA4 fingerprint it produces is indistinguishable from real Chrome 148 because it is Chrome 148's cipher suite, implemented in Go.

// One session, one proxy, one request
s := sensor.NewSession("chrome-148",
    sensor.WithSessionProxy("http://user:pass@comcast-ip:port"),
    sensor.WithSessionTimeout(30*time.Second),
)
resp, _ := s.Get(context.Background(), "https://target-site.com/")
// Status: 200, Protocol: h2, _abck: ~0~ (validated)

// Then GraphQL directly on the same session
gql, _ := s.DoWithBody(ctx, req, bytes.NewReader(payload))
// Status: 200, 30KB product data, zero blocks

No browser process. No GPU. No canvas hash. No sensor.js execution. Just a TLS handshake that matches Chrome 148 exactly because it uses Chrome 148's cipher suites.

Production architecture
Scrapy spider
  → GoProxyMiddleware (urllib, ~35ms round trip)
      → Go HTTP server :8765 (4-session pool)
          → Go TLS library sessions
              → ISP proxy (Comcast AS7015, static residential)
                  → Target site

Session rotation logic: 206 or GenericError triggers the next session in the pool. Three errors on one session triggers a background re-warm (new TLS handshake, new session state). All 4 sessions blocked returns 503; Python middleware waits 5s and retries up to 3× before falling back to curl_cffi.

24 req/min sustained
0 blocks in 500+ requests
0 browser processes
4 session pool
Key takeaways
Canvas fingerprinting cannot be fixed at the JS layer. Patching toDataURL() or getParameter() in JavaScript is detectable via Function.prototype.toString(). The only real fix is at the C++ level, either a real GPU or a library that bypasses the browser entirely.
SwiftShader's 0x0000C0DE device ID is permanently flagged. Don't bother. It's in Akamai's blocklist and the deterministic canvas hash is also known. Same for Mesa llvmpipe.
Page.addScriptToEvaluateOnNewDocument is itself a signal. Akamai's EdgeWorker detects the timing gap left by CDP's Runtime.enable command. The injection runs, but the metadata around it is visible.
The TLS layer is the one that matters first. Fix JA4, HTTP/2, ALPN, and header order before worrying about canvas or WebGL. Most deployments never even reach sensor.js if the TLS fingerprint doesn't match.
A clean ISP proxy IP matters. Comcast AS7015 static residential is what worked. Datacenter IPs fail at the IP reputation layer regardless of TLS quality. Rotating residential proxies break session trust accumulation, Akamai scores per-session not per-request.
Practical decision tree · Akamai in 2026
01 Find the mobile / GraphQL API first. Often zero anti-bot. Same data, no sensor.js. Look for /graphql, /api/v1/, mobile traffic intercepted via HTTP Toolkit.
02 curl_cffi chrome131 + ISP residential proxy. Works on ~60% of Akamai targets where sensor.js scoring is light.
03 Go TLS library (akamai-v3-sensor) + ISP proxy. For targets with heavier sensor.js where curl_cffi's impersonation doesn't pass JA4 at the EdgeWorker.
04 CloakBrowser (49 C++ patches, loads real extensions). For targets requiring a real canvas hash and passing the 60-extension probe. The kill-switch for sites where TLS spoofing alone is not enough.
05 Managed API (Scrapfly, Bright Data). For Bot Manager Premier targets running pixel challenges. Engineering cost exceeds managed API cost.
2 field notes on real targets Reported from practice. Click any card to open it.
+
Aug 2026 / Case study
The Entire US Hermès Catalogue, Behind DataDome, With Zero Browsers
A team from the sneakerbotting world pulled 7,690 products off hermes.com's own JSON API — through DataDome, no browser at all — in about twenty minutes on eight sessions, for a few euros. It's open-sourced, every number reproducible, and it's the cleanest worked example of this guide's whole thesis: the page is a wrapper, the data is an API, and a browser is the expensive way to reach it.
💡 A request-based bot against a modern anti-bot lives or dies on exactly three signals
The three signals, and nothing else matters. Strip the folklore and a browserless bot against DataDome wins on: (1) a TLS fingerprint that looks like Chrome — JA3/JA4 and all, via bogdanfinn/tls-client with a Chrome profile and randomised extension order; (2) header order, not just which headers but the exact sequence including the HTTP/2 pseudo-header order; and (3) a valid DataDome token minted from real sensor data, which is the one part you cannot fake by copying headers. Get the first two wrong and you're flagged before the server reads a single value.

The detail that proves they actually did the work: the bck.hermes.com API sends every header lowercase over HTTP/2, while DataDome's own geo.captcha-delivery.com endpoints run HTTP/1.1 and want Chrome's Title-Case names. They matched every request against a real browser capture to get that right. Header order was declared explicitly in code, never left to a map's iteration order.

Why no browser, in one number. The product page is just a wrapper around bck.hermes.com/product?productsku=..., which returns the structured object directly — price, every colour and finish variant, breadcrumbs, stock, dimensions — at about 37 KB per product. The equivalent full page render is roughly a hundred times heavier, and you pay residential proxy rates on every megabyte of it. This is the cost section's argument made concrete: the bill scales with kilobytes, not with a browser farm.

The flow worth stealing. Warm one session through DataDome once, reuse it for dozens of calls: load the homepage, solve the challenge if it fires, post the client tags (ch then le) to raise the trust score, call /sync-form to mint the x-xsrf-token, then hit the API — rotating the session after a set number of calls so no single datadome cookie is stretched too far. And you don't need a sitemap: bck.hermes.com/menu exposes the full category tree, and a top-level category already aggregates its whole sub-tree (pulling WOMEN returned all 2,785 women's products at pagesize=144).

The honest caveat is that the metered part — the DataDome solves — runs through a paid API (Hyper Solutions), so this isn't free; it's cheap because the sensor calls amortise across every product a warm session pulls, and everything else is plain HTTP. That's the real lesson, and it's the opposite of the managed-challenge dead end elsewhere in this feed: where the token is the product and the session is reusable, browserless collection is dramatically cheaper. Where the challenge grades the browser every time, it isn't. Knowing which case you're in is the whole skill.

Source: Hyper Solutions guest post on The Web Scraping Club; repo open-sourced.
+
Case study / CDN routing gap
Bypassing a Queue-It Virtual Waiting Room via a CDN Gap
FlySafair put its whole site behind a Queue-It virtual waiting room (a Cloudflare Worker checking for a queue token) during a flash sale. But two paths, /check-in and /manage, were served from a separate AWS CloudFront origin that was never routed through the Worker. And that origin served the entire SPA bundle.
💡 If any path serves the SPA bundle, it must enforce the same queue
The bypass chain is a masterclass in CDN-gap exploitation. (1) Recon: probe the public sitemap, log response headers per path. Most paths returned Cloudflare headers and redirected to the queue. Two paths (/check-in, /manage) returned AWS CloudFront headers, a completely separate origin. (2) That CloudFront origin didn't serve a lightweight stub, it served the full single-page-app bundle, the same JavaScript that powers the booking flow. (3) The bundle still runs Queue-It's client-side connector on load, which calls assets.queue-it.net and redirects if no token. But that check fires AFTER the browser has the full bundle. Block assets.queue-it.net in DevTools and the check never fires. (4) Because it's an SPA, once the bundle is running every navigation is client-side. Two console lines, history.pushState({}, '', '/') then dispatch a popstate event, render the home page with zero new server requests. Queue entirely bypassed. The root cause isn't a Queue-It bug, it's a CDN routing gap: most of the site went through Cloudflare with the Worker active, but two paths on a separate CloudFront origin served the same SPA without enforcement. Full writeup on GitHub
05 The arsenal

Every tool built to fight
every wall we just described.

Now that you understand the detection stack and the six anti-bot vendors, every library below makes sense in context. curl_cffi exists because of JA4. Camoufox exists because of CDP leaks. PatchRight exists because of Kasada's toString() inspection. The arsenal wasn't built randomly, each tool is a direct countermeasure to a specific detection innovation. Treat any star count in the table below as a dated snapshot, not a live signal, and check a repo’s last push, licence and archived flag against the GitHub API before you adopt it. A star count can now move faster than anyone could read the code behind it, so weigh it least of all the signals. One reverse-engineering MCP server went from 136,505 stars on 12 October 2026 to 260,901 on 15 October, nearly doubling in three days, while its own commits celebrated each milestone and its stargazer list could not be enumerated to see who starred. The curve may be genuine attention, but nothing public lets you audit it. Before adopting a fast-rising tool, read its releases and security notes, pin an exact version, install with lifecycle scripts disabled, and try it offline on traffic you captured yourself. For a tool that reads apps and pages on your behalf, check what it keeps and what it passes to your model provider; no star count answers either question. A 28 Sep 2026 recheck of seven popular scraping repos found every one alive and none relicensed, but the table’s own figures had drifted badly: Scrapling was listed at 38k against a live 84.2k, ScrapeGraphAI at 18k against 31.4k, Crawlee at 15k against 25.9k and Katana at 8k against 17.6k. One widely recommended research tool, minet, has no row, and it ships under GPL-3.0, the only copyleft licence of the set, which matters if you embed it rather than run it. Stars measure attention, not whether a tool clears a given anti-bot, so use them to spot abandoned projects rather than to rank capable ones. Read the licence file and the commit graph as well as the star count, because a young stealth tool can be very popular and still rest on one maintainer. invisible_playwright had 3,009 stars less than five months after its 13 May 2026 creation, but one account wrote 663 of its 728 commits, PyPI shows 38 releases in the 30 days to 5 October, and the engine ships only for Windows and Linux. GitHub lists its licence as MIT, while its own pyproject.toml declares MIT AND Apache-2.0 because it bundles a fork of Playwright’s Python client, and the patched Firefox binary is MPL-2.0. Pin an exact version, keep the engine build you validated, and check the declared licence in the package metadata rather than the repository badge if you redistribute it.

One distinction decides your tool choice more than any other: which layer does your target actually gate on? There are three separable surfaces, and most stealth tooling only solves the first two. TLS fingerprinting keys on the handshake, a Firefox-shaped TLS via Camoufox or a Chrome-shaped TLS via curl_cffi both answer it. JavaScript-layer detection reads navigator properties after the page loads, where a current unpatched Chromium already passes most panels. The third surface is the one that quietly defeats expensive tooling: automation-protocol fingerprinting, which detects how the browser is being driven rather than what it claims to be.

This is the layer that does not care how good your fingerprint patches are. Anything driving Chrome through Playwright leaves a recognisable shape in the control protocol at startup, the Runtime.enable and Target.setAutoAttach handshake sequence. A fork can rewrite navigator properties all day without touching it. The tools that clear this layer are the ones that remove the standard automation framework from the control plane entirely: nodriver drives system Chrome over a direct CDP connection with no Playwright shim, which is why it walks through Cloudflare Turnstile gates that every patched Playwright fork fails. The practical lesson from running these side by side: identify the gate's layer first, then pick the cheapest tool that covers it. A twenty-line curl_cffi wrapper can match a 130MB patched Chromium fork on a TLS-and-JS target, and lose entirely on an automation-protocol target where only a non-Playwright control plane gets through. Patches are not the lever you think they are, the control plane is.
Before the table · pick the right language first

Scraping is no longer Python-only.

Python still dominates the open-source ecosystem (Scrapy, curl_cffi, Camoufox), but the hardest 10% of targets in 2026 reach for Go, TypeScript, or Rust. Here's when each language earns its place, and why mixing them in one pipeline is the production-grade move.

Python Default
Largest ecosystem. Scrapy + curl_cffi + Camoufox cover 80% of targets. Best for data engineers already running pandas, Airflow, dbt downstream.
Use when: default choice, fast prototyping, ML pipelines, when team is Python-native
Key libs: Scrapy, curl_cffi, Camoufox, PatchRight, Crawlee-python, scrapy-stealth
Go TLS & concurrency
Closest language to OS-level TLS control. Akamai-grade JA4 spoofing is a Go specialty. Native goroutines beat asyncio at 10,000+ concurrent connections. Single binary deploys.
Use when: Akamai v3, F5 Shape, TLS fingerprint precision, 10K+ concurrent crawls, edge deployments
Key libs: akamai-v3-sensor, tls-client, colly, surf, rod, cycletls
TypeScript / Node.js
First-class Playwright and Puppeteer. Better browser automation primitives than Python ports. Strongest ecosystem for AI agents and Chrome extension work. Apify's Crawlee was Node-first for a reason.
Use when: browser automation is the bottleneck, AI agents, Computer Use Agents, working with Chrome DevTools deeply, full-stack JS team
Key libs: Crawlee, Playwright (TS), Puppeteer, Stagehand, Browser Use, ScrapingBee SDK, got-scraping
Rust Emerging
Lower-level than Go, even more control over TLS internals and memory. Used where you need both performance and Chrome-level fingerprint precision. 67% fewer tokens than equivalent Python in some benchmarks. Steeper learning curve.
Use when: webclaw, custom TLS work, MCP servers, performance is critical, you already have Rust on the team
Key libs: webclaw, rquest, rnet, scraper, fantoccini
The pragmatic move in 2026:
Run Python for orchestration (Scrapy is still the best framework for crawl logic, queues, deduplication, and ML downstream). Drop down to Go via a sidecar HTTP service only for the requests that need true Chrome JA4 (Akamai v3, F5 Shape). Use Node + Crawlee or Playwright (TS) when the work is genuinely browser-automation-heavy, especially for AI agents. The Akamai case study above shows this exact pattern: Scrapy spider in Python, calling a Go server via urllib for the protected requests only.

Master comparison table, all 91 libraries & tools

Library (click to expand)TypeLangJS renderTLS spoofTLS detailAnti-bot targetMCPStars
curl_cffi ⚡HTTPPython Chrome JA4+Akamai, DataDome–
⚡ HTTP
Under the hood: libcurl C library with custom TLS patches. Emits exact Chrome/Safari/Firefox TLS ClientHello at the C level, cipher suites, extensions, ALPN, GREASE all match real browsers.
✓ Pros
  • Fastest HTTP option. Pure HTTP speed, no browser overhead
  • Confirmed DataDome + Akamai bypass in 2026
  • Asyncio support via AsyncSession
  • Simple requests-compatible API
✗ Cons
  • No JavaScript execution, useless for JS-rendered pages
  • Cannot solve CAPTCHA or Turnstile challenges
  • TLS fingerprint only, no behaviour or canvas spoofing
Scrapling ⚡HTTPPython Chrome TLSCloudflare Turnstile88.6k
⚡ HTTP
Under the hood: Wraps curl_cffi for stealth HTTP; since v0.5 (17 Oct 2026) every browser fetch goes through StealthyFetcher on Patchright-driven Chromium (DynamicFetcher was removed). No Camoufox or Firefox dependency.
✓ Pros
  • StealthyFetcher solves Cloudflare Turnstile natively
  • Async spider v0.4, pause/resume, per-domain throttling
  • Dual-mode: HTTP for speed, browser for hard targets
  • Active development, 88.6K stars (Oct 2026)
✗ Cons
  • Higher complexity than plain curl_cffi
  • Browser mode is full Chromium (about 1.1 GB peak RSS in one test) and ignores HTTPS certificate errors by default
webclaw ⚡HTTPRust Chrome TLSMedium targets–
⚡ HTTP
Under the hood: Rust HTTP client with TLS fingerprint spoofing. Emits browser TLS signatures from Rust, fast and low-memory.
✓ Pros
  • Rust speed, very low CPU/memory overhead
  • TLS fingerprinting at Rust level
  • Good for high-volume HTTP scraping
✗ Cons
  • Rust, no Python API
  • Less widespread adoption
  • No JS rendering
httpx ⚡HTTPPython NoneUnprotected only–
⚡ HTTP
Under the hood: Modern Python HTTP library with async support and HTTP/2.
✓ Pros
  • Async + sync in one library
  • HTTP/2 support unlike requests
  • Type hints, modern API
✗ Cons
  • TLS fingerprint still Python-default, detectable
  • Not as stealthy as curl_cffi without patching
requests ⚡HTTPPython NoneUnprotected only52k
⚡ HTTP
Under the hood: Pure Python HTTP library. Sends HTTP/1.1 requests with standard Python TLS.
✓ Pros
  • Simple API, universally known
  • Synchronous, easy to debug
✗ Cons
  • TLS fingerprint is instantly detectable (Python urllib3)
  • No async, slow for concurrent scraping
  • No anti-bot capability
tls-client ⚡HTTPGo/Py Chrome/Firefox TLSAkamai, DataDome–
⚡ HTTP
Under the hood: Go/Python wrapper around a Go TLS client that mimics browser fingerprints. Predecessor to cycle-tls.
✓ Pros
  • Python bindings available
  • Bypasses JA3/JA4 fingerprinting
  • Lighter than curl_cffi
✗ Cons
  • Less actively maintained than curl_cffi
  • No JS rendering
  • Binary dependency
Playwright 🌐BrowserPy/JS CDP (detectable)Medium (CDP leaks)68k
🌐 Browser
Under the hood: Chromium DevTools Protocol (CDP). Microsoft-maintained. Drives real Chromium, Firefox, or WebKit browsers over CDP socket.
✓ Pros
  • Best JS execution support, renders any SPA
  • 68K stars, massive ecosystem and docs
  • Cross-browser: Chrome, Firefox, Safari (WebKit)
  • Screenshot, PDF, network intercept built-in
✗ Cons
  • CDP is detectable, needs C++ wrapper (PatchRight/Camoufox)
  • Heavy: launches a full browser process per session
  • Slow vs HTTP, ~10× more memory per concurrent task
Camoufox 🌐BrowserPython C++ Firefox JugglerCloudflare 100%, Akamai–
🌐 Browser
Under the hood: Forked Firefox with C++ binary patches to Juggler protocol (below CDP). Patches navigator, canvas, WebGL, fonts, window.chrome at binary level.
✓ Pros
  • 100% Cloudflare pass rate as of March 2026
  • geoip=True aligns all 5 identity vectors automatically
  • Below-CDP, invisible to JS-level detection
  • Async context manager, drop-in playwright replacement
✗ Cons
  • Firefox only, no Chrome/Safari
  • Heavier than curl_cffi
  • Occasional site-specific quirks with Firefox fingerprint
⚠ CVE-2026-6770 — Process-Level Fingerprint Leak (patched Firefox 150) Firefox below v150 returned IndexedDB database names in hash table iteration order rather than sorted order. Because the hash table is shared across all origins within the same browser process, the ordering became a stable, high-entropy process-lifetime fingerprint — consistent across tabs, sites, private windows, and even Tor Browser's "New Identity" resets. Anti-bot systems could use this to correlate multiple scraping identities running in the same browser process, regardless of proxy rotation or profile switching.

Fix: Use Camoufox built on Firefox 150+ (patched April 2026). Verify your version.
Scraper lesson: Always isolate identities at the process level, not just the profile level. Multiple sessions in one browser process can be correlated through memory-state artifacts like this even when fingerprint patching is otherwise perfect.
Ref: CVE-2026-6770 · mfsa2026-30 · Fixed Firefox 150 / Tor Browser 15.0.10
CloakBrowser 🌐BrowserPython 49 C++ patchesAkamai, reCAPTCHA v3 0.9–
🌐 Browser
Under the hood: 49+ C++ binary patches to Chromium itself. Patches webdriver, chrome object, plugins, permissions, WebGL, Canvas, AudioContext, and extension probe responses at the C++ level, not JavaScript. Repo: github.com/CloakHQ/CloakBrowser.
✓ Pros
  • reCAPTCHA v3 score 0.9, the highest of any tool I have tested
  • Passes Akamai's 60-URL extension probe (loads real 1Password / Bitwarden / LastPass profiles)
  • Real extension fingerprint database built-in, no manual setup
  • C++ level patches survive Function.prototype.toString() inspection (the kill-switch for JS stealth tools like puppeteer-stealth and patchright)
  • Solves the hard problems Camoufox and PatchRight cannot: canvas hash at the C++ rendering layer, AudioContext at the audio pipeline layer, real extension probe responses
  • Now open source on GitHub, active development
✗ Cons
  • Higher resource cost than HTTP-only tools (200MB+ per instance)
  • Chromium only, no Firefox variant
  • Newer than Camoufox, smaller community
  • Overkill for sites that curl_cffi + ISP proxy already handle
PatchRight 🌐BrowserPython Py source patchesKasada, Cloudflare–
🌐 Browser
Under the hood: Patches Playwright Python source files at install time. Removes CDP signatures, webdriver property, and stealth tells from the JS layer.
✓ Pros
  • Open source, free, Kasada bypass confirmed
  • Drop-in Playwright replacement, zero API changes
  • Patches JS layer without C++ recompilation
✗ Cons
  • JS-level patches only, determined adversary can detect at binary level
  • Less robust than Camoufox on Cloudflare 5-second challenge
  • Requires Playwright to be installed first
Puppeteer 🌐BrowserNode CDP (detectable)Medium targets89k
🌐 Browser
Under the hood: Node.js CDP driver for Chromium. Google-maintained. The original headless browser automation library.
✓ Pros
  • 89K stars, largest ecosystem
  • Native Google product, Chromium compatibility guaranteed
  • Good for CI/CD screenshot and PDF generation
✗ Cons
  • CDP is easily detectable (webdriver=true, window.chrome absent)
  • Node.js only, no Python
  • No anti-bot stealth built-in
Selenium 🌐BrowserMulti webdriver=trueWeak (legacy)29k
🌐 Browser
Under the hood: WebDriver protocol (W3C standard). Drives any browser via standardised JSON protocol. The original browser automation framework.
✓ Pros
  • Multi-language: Python, Java, C#, Ruby, JS
  • Supports all browsers including IE and Safari
  • Huge ecosystem, well-documented
✗ Cons
  • navigator.webdriver=true is trivially detectable
  • Slowest option, WebDriver adds round-trip latency
  • Requires ChromeDriver binary management
SeleniumBase UC 🌐BrowserPython UC removes WD flagKasada, general stealth10k
🌐 Browser
Under the hood: SeleniumBase with undetected-chromedriver mode. Patches Chrome binary to remove webdriver flag and CDP signatures.
✓ Pros
  • UC mode removes webdriver=true flag
  • Passes basic Cloudflare and PerimeterX
  • Built-in test framework, good for QA teams
✗ Cons
  • Not as strong as Camoufox/PatchRight on hard targets
  • Chrome binary patches can break on updates
  • Slower than Playwright equivalent
Selenium-Driverless 🌐BrowserPython CDP no WebDriverMedium targets–
🌐 Browser
Under the hood: Direct CDP connection without ChromeDriver binary, no webdriver flag set. Async Python API.
✓ Pros
  • No ChromeDriver binary needed
  • No webdriver=true flag
  • Async Python native
✗ Cons
  • Newer, less battle-tested than nodriver
  • Chrome only
  • Some CDP signatures still detectable
nodriver 🌐BrowserPython Raw CDP asyncMedium targets–
🌐 Browser
Under the hood: Controls Chrome via its internal DevTools socket without using CDP's standard automation flag. Chrome doesn't know it's being driven.
✓ Pros
  • Chrome does not set automation flags
  • Passes many sites that detect standard CDP
  • Lightweight, lower overhead than full Playwright
✗ Cons
  • Relatively new, less battle-tested
  • Python only
  • Some sites still detect via other JS signals
pydoll 🌐BrowserPython Async CDPMedium targets–
🌐 Browser
Under the hood: Pure Python browser automation using Chrome DevTools Protocol directly. No external driver.
✓ Pros
  • No ChromeDriver dependency
  • Fast startup, no driver process
  • Pure Python, easy to install
✗ Cons
  • CDP still potentially detectable
  • Less mature than Playwright
  • Smaller community
Botright 🌐BrowserPython CAPTCHA solvingCAPTCHA targets–
🌐 Browser
Under the hood: Playwright wrapper focused on CAPTCHA solving and stealth. Uses AI to solve CAPTCHAs during automation.
✓ Pros
  • Auto-solves reCAPTCHA and hCAPTCHA inline
  • Stealth patches on top of Playwright
  • Good for CAPTCHA-heavy targets
✗ Cons
  • Heavier than raw Playwright
  • CAPTCHA AI may be rate-limited
  • Less control over fingerprinting
Botasaurus 🌐BrowserPython Gaussian mouseDataDome behaviour–
🌐 Browser
Under the hood: Playwright wrapper that adds Gaussian mouse movement, realistic typing, scroll physics, and session management.
✓ Pros
  • Gaussian mouse curves, passes behavioural ML checks
  • Handles DataDome behavioural scoring
  • Session persistence and rotating profiles built-in
✗ Cons
  • Browser-based overhead
  • Overkill for targets without behavioural analysis
  • Less control than raw Playwright
rayobrowse 🌐BrowserPy/Docker Real device FP DBHard targets–
🌐 Browser
Under the hood: Docker-based stealth Chromium browser from Rayobyte. C++ level patches (not JS-level), exposed via CDP so Playwright/Puppeteer/Selenium can connect natively. Self-hosted = free and unlimited; managed Cloud version available.
✓ Pros
  • Free and unlimited self-hosted (Docker), Cloud version managed
  • C++ level patches survive Function.toString() inspection
  • Coherent device profile: UA, WebGL, Canvas, AudioContext, fonts all match
  • Native CDP, drop-in for Playwright/Puppeteer/Selenium
  • Used by Rayobyte to scrape millions of pages/day in production
✗ Cons
  • Still in beta, results vary by target site
  • Windows + Android profiles strongest, macOS/Linux less mature
  • Closed source (license restricts certain organizations)
  • Canvas/WebGL FP coverage still evolving
undetected-chromedriver 🌐BrowserPython Removes WD flagMedium targets5k
🌐 Browser
Under the hood: Patches ChromeDriver binary to remove webdriver=true and CDP automation flags at binary level.
✓ Pros
  • Removes most obvious webdriver signals
  • Simple: just replace webdriver.Chrome with uc.Chrome
✗ Cons
  • Chrome binary patches break on updates frequently
  • Not as robust as Camoufox on modern Cloudflare
  • Maintenance has slowed
⭐ Scrapy ⚡FrameworkPython Via curl_cffi mwMedium (with middleware)52k
⚡ HTTP
Under the hood: Twisted-based async Python framework. Pure HTTP, sends requests, receives responses, parses with XPath/CSS. No browser.
✓ Pros
  • 52K stars, production standard for HTTP scraping
  • Massive ecosystem: scrapy-redis, scrapy-playwright, scrapyd
  • Async by default, hundreds of concurrent requests
  • Mature: pipelines, middlewares, extensions all built-in
✗ Cons
  • No JS rendering by default (need playwright middleware)
  • Pure HTTP, detectable by TLS fingerprint without curl_cffi middleware
  • Steeper learning curve than requests
Crawlee 🌐FrameworkNode/Py Playwright-basedMedium targets26k
🌐 Browser
Under the hood: Apify's unified Node.js framework. Wraps both HTTP (got-scraping) and Playwright/Puppeteer. Handles retries, deduplication, storage.
✓ Pros
  • Dual HTTP+browser mode in one framework
  • 15K stars, actively maintained by Apify
  • Built-in dataset storage, request queue, proxy rotation
✗ Cons
  • Node.js primary (Python port is newer, less mature)
  • More opinionated than Scrapy, harder to customise
  • Heavier dependency footprint
scrapy-camoufox ⚡FrameworkPython Camoufox integrationHard targets–
⚡ HTTP
Under the hood: Scrapy middleware that routes requests through Camoufox browser for stealth. Best of Scrapy + Camoufox.
✓ Pros
  • Scrapy pipeline management + Camoufox stealth
  • Per-request browser decision (HTTP vs browser)
  • Good for mixed protection targets
✗ Cons
  • Camoufox overhead on browser requests
  • Requires both Scrapy and Camoufox installed
scrapy-nodriver ⚡FrameworkPython nodriver integrationMedium targets–
⚡ HTTP
Under the hood: Scrapy middleware using nodriver for browser requests, Chrome without CDP flags.
✓ Pros
  • Scrapy framework + Chrome without automation flags
  • Good for Cloudflare-protected targets
  • Use Scrapy architecture you know
✗ Cons
  • nodriver overhead per browser request
  • Less control than raw nodriver
scrapy-stealth ⚡FrameworkPython Browser TLS + HTTP/2Cloudflare, Akamaiv0.4 (2026)
⚡ HTTP
Under the hood: Pluggable Scrapy DOWNLOADER_MIDDLEWARE with three drivers: basic + turbo (TLS fingerprint + HTTP/2 impersonation, no browser), and browser (real Chrome via CDP for JS-heavy targets). Per-request engine switching via request.meta["stealth"]. Repo: github.com/fawadss1/scrapy-stealth. Author Fawad ships frequent updates.
✓ Pros
  • Built-in TLS fingerprint spoofing, scrapy-playwright/scrapy-splash/scrapy-selenium do not have this
  • Per-request engine switching: keep light HTTP for easy URLs, browser only for protected ones
  • Built-in proxy + fingerprint rotation (no separate middleware needed)
  • Native Cloudflare and Akamai detection via status + body keyword checks
  • Browser profiles like chrome_147, safari_ios_18_1_1 kept current
  • MIT license, active development (v0.4 May 2026)
✗ Cons
  • Project is new with limited GitHub adoption (low star count)
  • Less battle-tested than scrapy-playwright in production at scale
  • Browser driver 5-15s per page, use selectively for JS-protected URLs only
  • Requires Python 3.11+ and Scrapy 2.15+
Firecrawl ⚡AIAPI FIRE-1 engineHard via managed111k
⚡ HTTP
Under the hood: API service that converts any URL to clean Markdown or structured JSON for LLM consumption. FIRE-1 agent for multi-page crawls.
✓ Pros
  • 111K stars, most popular LLM scraping tool
  • Outputs clean Markdown, 67% fewer tokens for LLMs
  • MCP server for Claude/Cursor/LangChain
  • Handles JS rendering and auth flows
✗ Cons
  • API cost at scale
  • Less control over request details vs raw scraping
  • Data goes through third-party servers
Crawl4AI 🌐AIPython Playwright-basedMedium targets60k
🌐 Browser
Under the hood: Local Playwright wrapper optimised for LLM output. Runs locally, converts pages to clean Markdown with BM25 relevance filtering.
✓ Pros
  • Fully local, no API cost
  • BM25 filter reduces LLM context bloat
  • LLM extraction schema definition
  • MIT license, commercial friendly
✗ Cons
  • Playwright overhead per page
  • Less anti-bot bypass than Camoufox
  • No managed infrastructure
ScrapeGraphAI ⚡AIPython NL graph pipelineLight protection31k
⚡ HTTP
Under the hood: LLM-powered extraction that builds a graph pipeline from a natural language prompt. Local or API.
✓ Pros
  • Natural language extraction definition
  • Open source, self-hostable
  • Graph pipeline handles multi-step extractions
✗ Cons
  • LLM inference cost/latency per extraction
  • Less deterministic than CSS/XPath selectors
  • Newer, less battle-tested at scale
Jina Reader API ⚡AIAPI Built-in renderingMedium targets–
⚡ HTTP
Under the hood: REST API: prefix r.jina.ai/ to any URL to get clean Markdown back. Zero setup.
✓ Pros
  • Simplest possible API, one URL prefix
  • Good JS rendering
  • Free tier available
✗ Cons
  • Data goes through Jina servers
  • Less control than local scraping
  • Rate limited on free tier
Steel 🌐AIAPI Docker browserMedium targets–
🌐 Browser
Under the hood: Self-hosted browser API with MCP server. AI agents call it as a tool to browse the web.
✓ Pros
  • Self-hosted, data stays local
  • MCP server for AI agent integration
  • Docker deployment
✗ Cons
  • Newer product, smaller community
  • Setup overhead vs managed services
Bright Data ⚡ManagedAPI Full enterprise stackAll incl. F5 Shape–
⚡ HTTP
Under the hood: 72M+ IP network + scraping API. Managed infrastructure handles anti-bot, JS rendering, proxy rotation.
✓ Pros
  • 98.44% success rate, highest benchmark
  • Covers F5 Shape (only managed service that does)
  • Residential + ISP + datacenter + mobile IPs
  • Dataset marketplace for pre-scraped data
✗ Cons
  • Most expensive option
  • Data goes through third-party
  • Overkill for simple targets
Zyte ⚡ManagedAPI Full stackAll targets–
⚡ HTTP
Under the hood: Scrapy company's managed scraping platform. Zyte API + AutoExtract for structured data.
✓ Pros
  • #1 Proxyway benchmark 2025
  • AutoExtract returns structured product/article data
  • Built by the Scrapy maintainers
  • Smart proxy rotation built-in
✗ Cons
  • Expensive at scale
  • AutoExtract less flexible than custom extraction
Apify ⚡ManagedAPI 10K+ ActorsMedium-hard–
⚡ HTTP
Under the hood: 10,000+ pre-built Actors on serverless cloud. Crawlee at core. MCP server for AI agents.
✓ Pros
  • Biggest marketplace of pre-built scrapers
  • MCP server: AI agents call Actors as tools
  • Free $5/mo credit for casual use
  • Crawlee open source available locally
✗ Cons
  • CU pricing can escalate
  • Data goes through Apify cloud
  • Less control over anti-bot approach in Actors
ScrapingBee ⚡ManagedAPI Managed renderingMedium targets–
⚡ HTTP
Under the hood: Managed scraping API. Handles JS rendering, CAPTCHA, proxies via simple REST call.
✓ Pros
  • Dead simple: one API call, get HTML back
  • Free tier available
  • Handles most modern JS rendering
✗ Cons
  • Less anti-bot strength than Zyte or Bright Data
  • Per-call pricing
  • Less control over request details
SerpAPI ⚡ManagedAPI SERP JSON APISearch engine data–
⚡ HTTP
Under the hood: Managed API that abstracts Google, Bing, Baidu, Yandex, Yahoo, DuckDuckGo and 80+ other engines behind a single REST endpoint. Returns fully parsed, normalised JSON — organic results, ads, featured snippets, knowledge graphs, local packs, shopping, images, news — without you touching a proxy or a headless browser.
✓ Pros
  • 80+ search engines including Google, Bing, Baidu, Yandex, Yahoo, DuckDuckGo, Google Maps, Google Shopping, Google Scholar
  • Richest parsed output — 15+ SERP element types including AI Overviews, PAA, video carousels, local pack
  • Advanced geo-targeting and device simulation per request
  • Free tier: 100 searches/month, no credit card required
  • Well-documented, widely adopted — largest mindshare in SERP API category
✗ Cons
  • Premium pricing: $75/mo for 5K, $150/mo for 15K searches — expensive vs alternatives like Scrape.do (21x cheaper per request)
  • SERP-specific only, not a general-purpose scraping API
  • Data routed through SerpAPI servers
ScrapeBadger ⚡ManagedAPI Smart billing + AI extractCloudflare, DataDome, hard targets–
⚡ HTTP
Under the hood: Newer managed scraping API built natively for modern anti-bot stacks. Key differentiator: smart billing — if you enable JS rendering and anti-bot bypass but the target doesn't need them, ScrapeBadger auto-downgrades the request and charges you less. Also ships an MCP server for Twitter/X scraping (profiles, tweets, trends) for AI agent workflows.
✓ Pros
  • Pay-only-for-success model — failed requests don't cost you
  • Smart auto-downgrade: enables anti-bot features only when needed, reducing cost automatically
  • Native Cloudflare Turnstile and DataDome bypass — 99%+ reported success on Zillow, Amazon, LinkedIn
  • AI extraction mode: pass a plain-English prompt, get structured JSON back without writing selectors
  • MCP server for Twitter/X — profiles, tweets, trends for AI agent pipelines
✗ Cons
  • Newer entrant — less battle-tested at scale than Bright Data or Zyte
  • Smaller community and ecosystem vs established providers
  • Data routed through third-party servers
Oxylabs ⚡ManagedAPI OxyCopilot AIHard targets–
⚡ HTTP
Under the hood: 102M+ IP network with OxyCopilot AI extraction and scraper APIs.
✓ Pros
  • Largest IP pool (102M+)
  • OxyCopilot: AI-powered extraction
  • Strong residential + datacenter options
✗ Cons
  • Enterprise pricing
  • Data through third-party
Browserbase 🌐ManagedAPI Managed browserHard targets–
🌐 Browser
Under the hood: Managed Playwright cloud. Run Playwright scripts remotely without managing browser infrastructure.
✓ Pros
  • No browser infra to manage
  • Scales automatically
  • Playwright API unchanged, zero code changes
✗ Cons
  • 42% success rate on anti-bot benchmark (vs 81% Browser Use)
  • Per-session pricing
  • Less stealth than self-hosted Camoufox
chompjs ⚡ParserPython N/AParser only–
⚡ HTTP
Under the hood: Python library to parse JavaScript objects embedded in HTML pages. Converts JS literals to Python dicts.
✓ Pros
  • Handles malformed JSON that json.loads rejects
  • Extracts __NEXT_DATA__ and embedded JS objects
  • Zero dependencies
✗ Cons
  • Parsing only, not a scraping framework
  • Narrow use case
Parsel ⚡ParserPython N/AParser only–
⚡ HTTP
Under the hood: Scrapy's HTML/XML parser library. XPath and CSS selectors with a clean Python API.
✓ Pros
  • XPath + CSS in one library
  • Used inside Scrapy, familiar API
  • Faster than BeautifulSoup for selection
✗ Cons
  • Parsing only, no HTTP requests
  • Less beginner-friendly than BS4
BeautifulSoup4 ⚡ParserPython N/AParser only10k
⚡ HTTP
Under the hood: Python HTML/XML parser. Wraps lxml or html.parser. Builds a parse tree from raw HTML strings.
✓ Pros
  • Simple, readable API, beginner-friendly
  • Works on any HTML string regardless of source
  • No network requests, pure parsing
✗ Cons
  • Not a scraping framework, needs requests/httpx separately
  • Slow on large documents vs selectolax/lxml
  • No anti-bot capability whatsoever
mitmproxy ⚡RE ToolPython N/ARE / intercept37k
⚡ HTTP
Under the hood: Python-based HTTPS proxy. Intercepts, inspects, and modifies HTTP/HTTPS traffic between client and server.
✓ Pros
  • Full request/response visibility and modification
  • Script intercepted traffic with Python
  • Good for understanding anti-bot request patterns
✗ Cons
  • Requires certificate trust on device
  • SSL pinning blocks it on hardened apps
  • For analysis/RE, not production scraping
HTTPToolkit ⚡RE ToolAny N/AMobile API intercept–
⚡ HTTP
Under the hood: HTTPS intercepting proxy for development and mobile API discovery. Open source.
✓ Pros
  • Intercepts HTTPS without SSL pinning (with rooted device)
  • Beautiful UI for inspecting requests
  • Works with Android emulators via ADB
✗ Cons
  • For analysis only, not for production scraping
  • Requires rooted device for mobile apps
Frida ⚡RE ToolPy/JS N/ASSL hooks–
⚡ HTTP
Under the hood: Dynamic instrumentation toolkit. Injects JavaScript into running processes. Used to hook native functions and bypass SSL pinning.
✓ Pros
  • Bypass SSL pinning in any Android/iOS app
  • Hook any native function at runtime
  • Essential for mobile app API extraction
✗ Cons
  • Requires rooted/jailbroken device
  • Complex setup, not for beginners
  • App-specific scripts needed per target
rebrowser-patches 🌐BrowserPython Chrome source patchesMedium targets–
🌐 Browser
Under the hood: JavaScript patches injected into Playwright/Puppeteer pages to mask automation signals.
✓ Pros
  • Removes navigator.webdriver and CDP signals at JS level
  • Works with any Playwright version
  • Easy to integrate
✗ Cons
  • JS-level only, binary signals still present
  • Less robust than C++ patches
cycle-tls ⚡HTTPGo/JS Chrome/Firefox TLSAkamai, DataDome–
⚡ HTTP
Under the hood: Node.js/Go TLS client that cycles through browser fingerprints. Sends real JA3 hashes per request.
✓ Pros
  • Node.js TLS fingerprint spoofing
  • Per-request fingerprint rotation
  • Good for JS pipeline scraping
✗ Cons
  • Node.js only, no Python
  • Less robust than curl_cffi on hard targets
GoLogin 🌐BrowserCloud Antidetect profilesHard multi-account–
🌐 Browser
Under the hood: Cloud anti-detect browser. Manages browser profiles with unique fingerprints stored in cloud. Multi-account management.
✓ Pros
  • Profile fingerprint management at scale
  • Good for multi-account scraping operations
  • Team sharing of browser profiles
✗ Cons
  • Paid product, cloud-dependent
  • Not suitable for automated pipeline scraping
  • Designed for manual browsing, not scripted crawling
Multilogin 🌐BrowserCloud Antidetect profilesHard multi-account–
🌐 Browser
Under the hood: Commercial anti-detect browser with managed profile fingerprints. Team collaboration on browser profiles.
✓ Pros
  • Professional multi-account management
  • Managed fingerprint database
  • Team profile sharing
✗ Cons
  • Very expensive
  • Designed for manual use, not automated crawling
  • Data in cloud
ScraperAPI ⚡ManagedAPI Full stackAll incl. Walmart–
⚡ HTTP
Under the hood: Simple proxy rotation + JS rendering API. Handles geo-targeting and header rotation.
✓ Pros
  • Simple integration, just prepend URL
  • Free tier with 1000 calls/mo
  • Geo-targeting built in
✗ Cons
  • Weaker on hard anti-bot targets
  • Basic anti-bot handling vs Zyte/Bright Data
Decodo ⚡ManagedAPI Full stackAll targets–
⚡ HTTP
Under the hood: Smartproxy's new brand. Residential, datacenter, and mobile proxy network.
✓ Pros
  • Affordable residential proxies
  • Pay-as-you-go pricing
  • Good for mid-scale scraping
✗ Cons
  • Less powerful than Bright Data on hard targets
  • Smaller IP pool
CapSolver ⚡CAPTCHAAPI N/AreCAPTCHA/hCaptcha–
⚡ HTTP
Under the hood: AI-powered CAPTCHA solving service. Uses computer vision to solve reCAPTCHA v2/v3, hCAPTCHA, Cloudflare Turnstile.
✓ Pros
  • Solves reCAPTCHA v3, hCAPTCHA, Turnstile, ImageCAPTCHA
  • Fast: under 10 seconds for most CAPTCHA types
  • API-based, works with any language
✗ Cons
  • Cost per solve (~$0.001–0.002)
  • reCAPTCHA v3 score may be low vs C++ browser
  • Solving is symptomatic, better to avoid triggering CAPTCHA
2captcha ⚡CAPTCHAAPI N/AAll CAPTCHA types–
⚡ HTTP
Under the hood: Human + AI hybrid CAPTCHA solving service. One of the oldest in the market.
✓ Pros
  • Solves almost any CAPTCHA type including custom ones
  • Human fallback for unusual CAPTCHAs
  • Large API ecosystem
✗ Cons
  • Slowest option, human solving adds latency
  • Cost per solve
  • Less automated than CapSolver
Anti-Captcha ⚡CAPTCHAAPI N/AreCAPTCHA/image–
⚡ HTTP
Under the hood: Human + AI CAPTCHA solving service. Competitor to 2captcha.
✓ Pros
  • Solves all major CAPTCHA types
  • Competitive pricing
  • API compatible with 2captcha
✗ Cons
  • Human solving latency
  • Cost per solve
  • Better to avoid triggering CAPTCHA in the first place
CapMonster Cloud ⚡CAPTCHAAPI ~$0.60/1k v2reCAPTCHA, Turnstile, image–
⚡ HTTP
Under the hood: AI solver. Hosted sibling of the downloadable CapMonster app; a model answers, no humans in the loop.
✓ Pros
  • Among the cheapest AI solvers (verified ~$0.60 per 1,000 reCAPTCHA v2 tokens Sep 2026)
  • Fast, fully automated
  • Text-CAPTCHA and image tiers priced lower still
✗ Cons
  • Struggles on the hardest challenge families where human-backed services still win
  • Token price, not a flat licence
  • Quality varies by challenge type
NopeCHA ⚡CAPTCHAAPI sub, daily capsreCAPTCHA, hCaptcha, Turnstile, text–
⚡ HTTP
Under the hood: AI solver sold as a SUBSCRIPTION with daily solve caps, not per-solve — so effective cost is a utilisation question.
✓ Pros
  • Predictable monthly bill (~$4.99 for 2,000/day up to ~$99.99 for 200,000/day, Sep 2026)
  • Browser-extension and API modes
  • Cheap per-solve if you saturate a tier (~3 cents/1,000 at the Basic cap)
✗ Cons
  • Pay far above the market rate if you run it lightly
  • Daily cap can throttle a burst
  • Not a per-1,000 product, so it does not slot cleanly into a cost-per-solve comparison
DeathByCaptcha ⚡CAPTCHAAPI discount tierreCAPTCHA, image, text–
⚡ HTTP
Under the hood: Hybrid: a model tries first and a human worker picks up the miss. One of the older discount solvers.
✓ Pros
  • Cheap, long-established
  • Human fallback for unusual CAPTCHAs
  • Simple API compatible with common client libraries
✗ Cons
  • Human step adds latency on the hard ones
  • Quality/uptime less consistent than the newer AI leaders
  • Cost per solve
SolveCaptcha ⚡CAPTCHAAPI per-solvereCAPTCHA, hCaptcha, image–
⚡ HTTP
Under the hood: Automatic recognition service, API-compatible with the 2captcha family. Marketed as automatic; no explicit human-vs-AI split stated by the vendor.
✓ Pros
  • Drop-in for code already written against the 2captcha API
  • Covers the mainstream challenge types
  • Per-solve pricing in the mainstream range
✗ Cons
  • Vendor discloses less about how it solves than the AI-first services do
  • Smaller/less-documented than 2captcha or CapSolver
  • Cost per solve
CapSkip ⚡CAPTCHALocal API flat licencereCAPTCHA, Turnstile (local)–
⚡ HTTP
Under the hood: LOCAL AI solver: the model runs on YOUR machine and exposes a local API that emulates the 2captcha / RuCaptcha / SolveCaptcha endpoints. Flat licence, no per-solve fee.
✓ Pros
  • No per-solve cost — flat ~$10–$20/mo (plus an optional lifetime fee)
  • Data and tokens never leave your machine
  • Drop-in for the 2captcha API surface
✗ Cons
  • Needs real local hardware (GPU/RAM) to run the model
  • You maintain the runtime, not a vendor
  • Newest and least battle-tested of the eight; local inference quality is on you
Scrapyd ⚡FrameworkPython Via middlewareScrapy deploy tool–
⚡ HTTP
Under the hood: Daemon that deploys and runs Scrapy spiders via JSON API. Port 6800. Process-based job queue.
✓ Pros
  • Zero cloud cost, runs on any server
  • ScrapydWeb provides visual dashboard
  • Simple deploy: scrapyd-deploy -p project
✗ Cons
  • Single node by default, no horizontal scaling
  • No built-in monitoring or alerting
  • Job isolation is process-level only
scrapy-redis ⚡FrameworkPython N/ADistributed Scrapy–
⚡ HTTP
Under the hood: Scrapy extension connecting spiders to a Redis shared URL queue. Enables distributed crawling.
✓ Pros
  • Horizontal scale: add workers without code change
  • Redis deduplicates URLs across all workers
  • One codebase, N machines
✗ Cons
  • Redis is a new SPOF
  • No built-in job scheduling
  • Requires Redis infrastructure
scrapy-cluster ⚡FrameworkPython N/AEnterprise Scrapy–
⚡ HTTP
Under the hood: Distributed Scrapy cluster using Redis + Kafka + Zookeeper. Enterprise-scale distributed crawling.
✓ Pros
  • True enterprise-scale distributed crawling
  • Kafka for message durability
  • Multi-project support
✗ Cons
  • Complex infra: Redis + Kafka + Zookeeper
  • Overkill for most use cases
  • High ops overhead
scrapy-poet ⚡FrameworkPython N/APage Object pattern–
⚡ HTTP
Under the hood: Dependency injection framework for Scrapy spiders. Cleaner spider code with page objects.
✓ Pros
  • Cleaner code via page objects pattern
  • Works with zyte-spider and AutoExtract
  • Testable spider logic
✗ Cons
  • Adds abstraction overhead
  • Learning curve for Scrapy veterans
Splash 🌐BrowserDocker Lua scriptingLight protection–
🌐 Browser
Under the hood: Lua-scriptable browser for JS rendering, runs in Docker. Integrates with Scrapy via scrapy-splash.
✓ Pros
  • Docker-based, easy to deploy
  • Lua scripting for complex interactions
  • Good for Scrapy integration on JS sites
✗ Cons
  • Outdated, Playwright has superseded it
  • Lua scripting adds complexity
  • Less stealth than Camoufox
selectolax ⚡ParserPython N/AFast HTML parser–
⚡ HTTP
Under the hood: C-based HTML parser (lexbor engine). 10–100× faster than BeautifulSoup for pure parsing tasks.
✓ Pros
  • Extremely fast, C engine vs Python in BS4
  • CSS selectors with clean Python API
  • Low memory footprint
✗ Cons
  • CSS selectors only, no XPath
  • Less forgiving on malformed HTML than BS4
  • Smaller community/docs
lxml ⚡ParserPython N/AXPath + CSS parser–
⚡ HTTP
Under the hood: C-based XML/HTML parser. Fastest Python HTML parsing option.
✓ Pros
  • Fastest Python HTML parser by far
  • Full XPath 1.0 support
  • Handles massive documents efficiently
✗ Cons
  • Stricter on malformed HTML than BS4
  • C dependency, occasional install issues
  • Verbose API vs BS4
w3lib ⚡ParserPython N/AURL/text utils–
⚡ HTTP
Under the hood: Web-related utility functions. URL normalisation, encoding handling. Used internally by Scrapy.
✓ Pros
  • URL cleaning and normalisation
  • Encoding detection and conversion
  • Scrapy internals, very stable
✗ Cons
  • Utility library only, not a scraper
  • Most devs use it via Scrapy, not directly
SwiftShadow ⚡ProxyPython N/AProxy pool manager–
⚡ HTTP
Under the hood: Free proxy pool manager. Fetches, validates and rotates free proxies automatically.
✓ Pros
  • Free, zero proxy cost
  • Auto-validates and rotates on failure
  • 2 lines of code integration
✗ Cons
  • Free proxies are low quality, high failure rate
  • Not for hard anti-bot targets
  • IP reputation usually poor
requests-ip-rotator ⚡ProxyPython N/AAWS API Gateway IPs–
⚡ HTTP
Under the hood: Rotates requests through AWS API Gateway endpoints to get rotating IPs.
✓ Pros
  • Free if AWS free tier available
  • AWS IPs have good reputation
  • Works with requests library
✗ Cons
  • AWS API Gateway has rate limits
  • Setup requires AWS account
  • Limited rotation speed
Colly ⚡FrameworkGo Go TLSMedium targets15k
⚡ HTTP
Under the hood: Go HTTP scraping framework. Fast, concurrent, clean API.
✓ Pros
  • Very fast, Go concurrency model
  • Low memory vs Python
  • Good for high-throughput HTTP scraping
✗ Cons
  • Go only, no Python
  • Smaller ecosystem than Scrapy
  • No browser support
Katana ⚡FrameworkGo Go TLS + ChromiumMedium targets18k
⚡ HTTP
Under the hood: Go-based web crawler by ProjectDiscovery. Designed for security research and recon.
✓ Pros
  • Extremely fast Go crawler
  • Headless mode with Playwright integration
  • Built for large-scale URL discovery
✗ Cons
  • Security/recon focus, not a data scraping framework
  • Go only
  • Less data extraction tooling than Scrapy
playwright-go 🌐BrowserGo CDP (detectable)Medium targets–
🌐 Browser
Under the hood: Go bindings for Playwright. Same Playwright API in Go.
✓ Pros
  • Go concurrency for browser scraping
  • Same Playwright API and capabilities
  • Lower memory than Python for concurrent sessions
✗ Cons
  • Less mature than Python Playwright
  • Smaller community
  • No stealth patches yet
Charles Proxy ⚡RE ToolAny N/AMobile API intercept–
⚡ HTTP
Under the hood: Commercial HTTPS proxy for request inspection and debugging. GUI-based.
✓ Pros
  • GUI-based, easy to use for non-developers
  • SSL proxying with certificate install
  • Session recording and replay
✗ Cons
  • Paid product
  • For debugging only, not automated scraping
  • Less powerful than mitmproxy for scripting
Selenoid ⚡HTTPGo (Docker) Browser-as-a-serviceMedium targets2.6k
⚡ HTTP
Under the hood: Docker containers running headless Chrome/Firefox in parallel, Aerokube's Go-based Selenium grid replacement.
✓ Pros
  • Run dozens of browsers in parallel from one host
  • Lower memory than Selenium Grid
  • Built-in video recording per session
  • Drop-in replacement for Selenium Grid
✗ Cons
  • Browsers still detectable as headless without stealth patches
  • Older project, slower release cadence
  • Requires Docker infrastructure
noble-tls ⚡HTTPPython Chrome JA3/JA4Cloudflare, DataDome–
⚡ HTTP
Under the hood: Python port of uTLS via custom TLS handshake stack, emits browser-matching ClientHello.
✓ Pros
  • Bypasses JA3/JA4 fingerprinting
  • Pure Python, no C compilation
  • Lighter than curl_cffi for simple cases
  • Easy install via pip
✗ Cons
  • Smaller community than curl_cffi
  • Fewer browser impersonation profiles
  • Less battle-tested in production
hrequests ⚡HTTPPython Browser-grade TLSDataDome, Cloudflare900
⚡ HTTP
Under the hood: Drop-in requests replacement with TLS impersonation, header order matching, and optional Playwright browser mode.
✓ Pros
  • requests-compatible API with stealth built in
  • Header order mimics real Chrome
  • Optional browser mode for JS rendering
  • Built-in async support
✗ Cons
  • Smaller ecosystem than curl_cffi
  • Fewer impersonate profiles
  • Newer project, some edge cases
crawlee-python 🌐BrowserPython Via curl_cffi backendMost targets6.2k
🌐 Browser
Under the hood: Python port of Apify Crawlee, wraps curl_cffi for HTTP and Playwright for browser modes in a unified framework.
✓ Pros
  • Mix HTTP and browser workers in one crawler
  • Auto-scaling and proxy rotation built in
  • Storage abstraction for results
  • Strong production patterns from Apify
✗ Cons
  • Larger than plain Scrapy
  • Newer than Node.js Crawlee, some features lag
  • Opinionated framework
estela ⚡FrameworkPython (K8s) Spider-dependentDistributed Scrapy90
⚡ HTTP
Under the hood: Kubernetes orchestrator for Scrapy, schedules and runs spiders as K8s jobs with auto-scaling.
✓ Pros
  • Open source alternative to Zyte Cloud
  • Elastic scaling on Kubernetes
  • Built-in monitoring and stats UI
  • Multi-tenant by design
✗ Cons
  • Requires Kubernetes infrastructure
  • Heavyweight for small projects
  • Smaller community than Scrapyd
fake-useragent ⚡HTTPPython UA strings onlyLightweight only3.8k
⚡ HTTP
Under the hood: Curated database of real-world User-Agent strings, sampled from browser telemetry sources.
✓ Pros
  • Realistic UA strings ready out of the box
  • Filter by browser family or OS
  • Updated database
  • Tiny dependency
✗ Cons
  • UA alone is trivially detectable in 2026
  • Not enough for any modern anti-bot
  • Database can become stale
grequests ⚡HTTPPython requests + geventUnprotected APIs4.4k
⚡ HTTP
Under the hood: gevent-monkey-patched requests, fires hundreds of HTTP calls in parallel via greenlets.
✓ Pros
  • Drop-in async for requests users
  • Simpler than asyncio for bulk fetches
  • Battle-tested gevent under the hood
✗ Cons
  • Monkey-patching can conflict with other libs
  • No HTTP/2 support
  • Newer code should use httpx instead
Scrapoxy DISCONTINUEDFrameworkNode.js Proxy managerDiscontinued Feb 20262.4k
⚡ HTTP
Under the hood: Discontinued 6 February 2026 after eleven years — kept here because it is still widely recommended. Was a self-hosted proxy pool manager that provisioned proxies on AWS, Azure and GCP and rotated IPs automatically. Docker images have been pulled from public registries, the documentation is offline, and shared infrastructure is shut down for non-paying users.
✓ Pros
  • Was the reference implementation of the provider-agnostic proxy pool
  • Auto-provisioned and tore down cloud IPs
  • Ban detection and auto-rotation built in
  • Multi-tenant
✗ Cons
  • Dead, and not forkable. The code was closed before the shutdown and stays the author’s property under a licence prohibiting forking, redistribution and derivative works — there is no community takeover coming
  • Not an “open-source alternative” any more, whatever the blog posts still say
  • Cloud provider costs add up at scale
  • Cloud IPs flagged faster than residential
primp ⚡HTTPPython (Rust) Chrome JA3/JA4/HTTP2Akamai, DataDome–
⚡ HTTP
Under the hood: Python Requests IMPersonate. Rust binding over a patched reqwest stack, emitting browser-matching TLS and HTTP/2 fingerprints.
✓ Pros
  • Rust core, tuned for throughput at volume
  • Sets impersonate_os separately from the browser profile
  • Precompiled wheels for Linux, Windows, macOS
  • Sync and async clients
✗ Cons
  • Smaller community and docs than curl_cffi
  • API is requests-flavoured but not a true drop-in
  • No HTTP/3 fingerprints yet
impit ⚡HTTPRust / Py / JS Browser TLS + HTTP/3Cloudflare, DataDome–
⚡ HTTP
Under the hood: Rust library built on rustls and reqwest, patched to reproduce browser TLS fingerprints without touching system libraries. Successor to got-scraping.
✓ Pros
  • Now the default HTTP client in Crawlee for Python
  • Handles HTTP/2 and HTTP/3 browser behaviour natively
  • Prebuilt binaries incl. Windows and macOS ARM
  • Avoids the curl-impersonate build pain
✗ Cons
  • Younger than curl_cffi, fewer profiles documented
  • Best experience is inside the Crawlee ecosystem
  • Native binary adds a build dependency
rnet ⚡HTTPPython (Rust) Browser JA3/JA4Cloudflare, Akamai–
⚡ HTTP
Under the hood: Async Python HTTP client over a Rust TLS stack that emits browser-shaped ClientHello and HTTP/2 settings.
✓ Pros
  • Fast async client for high-volume fetching
  • Common partner to a browser in hybrid architectures
  • Actively maintained impersonation profiles
✗ Cons
  • Documentation is thin compared to curl_cffi
  • Smaller ecosystem and fewer integrations
  • Network layer only, no JS execution
zendriver 🌐BrowserPython Real Chrome stackCloudflare, DataDome–
🌐 Browser
Under the hood: Maintained CDP-driven wrapper around real Chrome with the undetected configuration handled for you, in the nodriver lineage. No Selenium or WebDriver binary.
✓ Pros
  • Drives real Chrome, so the TLS stack is genuine
  • No ChromeDriver binary, so no $cdc_ markers
  • Actively maintained fork lineage
  • Common browser half of a hybrid pipeline
✗ Cons
  • Browser cost per page, slow next to HTTP clients
  • Still needs proxy and behaviour work at scale
  • Chromium-only
scrapy-impersonate ⚡FrameworkPython Chrome JA3/JA4Akamai, Cloudflare–
⚡ HTTP
Under the hood: Scrapy download handler backed by curl_cffi, so spiders get browser TLS and header order without leaving the Scrapy request cycle.
✓ Pros
  • Drop-in handler, spider code stays unchanged
  • Platform spoofing across macOS, Windows, iOS, Android
  • Keeps Scrapy middleware, retries and stats intact
  • Far cheaper than bolting a browser into the spider
✗ Cons
  • Tracks whatever profiles curl_cffi ships
  • No JavaScript rendering
  • Another moving part to keep in step with Scrapy versions
Trafilatura ⚡ParserPythonContent extraction–
⚡ HTTP
Under the hood: Heuristic main-content extractor: strips boilerplate and returns article text plus metadata (title, author, date, language, site name) as text, markdown, or XML.
✓ Pros
  • The de-facto standard for corpus-scale text extraction
  • Used to build RefinedWeb and FineWeb
  • Metadata extraction alongside the body text
  • Python library and CLI, no model required
✗ Cons
  • Heuristic, so structured elements can be mangled
  • Tables, formulas and code are the weak spots
  • Not a fetcher, you bring the HTML
Resiliparse ⚡ParserPython / C++Content extraction–
⚡ HTTP
Under the hood: High-performance HTML parsing and main-content extraction from the ChatNoir stack, built for web-scale corpora.
✓ Pros
  • Very fast, designed for billions of pages
  • Used to build the DCLM and Dolma corpora
  • Robust encoding detection and DOM handling
  • Pairs well with Common Crawl pipelines
✗ Cons
  • Heuristic like Trafilatura, same structural blind spots
  • Heavier install than a pure-Python parser
  • Aimed at corpus work, not per-page precision
spider ⚡FrameworkRustHigh-volume crawling–
⚡ HTTP
Under the hood: Async Rust crawler that discovers, fetches, and queues at very high concurrency, with a subscribe API for streaming pages as they land.
✓ Pros
  • Among the fastest open-source crawlers available
  • Streaming subscription model suits pipelined extraction
  • Low memory footprint per page
  • Composes with Rust extractors like rs-trafilatura
✗ Cons
  • Rust, so a steeper on-ramp for Python teams
  • Thinner anti-bot story than the Python stealth stack
  • Smaller ecosystem of ready-made middleware
browser-use 🌐AIPython Real browser stackInteraction-heavy flows100k
🌐 Browser
Under the hood: Hands a real browser to an LLM agent that clicks, scrolls, types and reads screenshots with vision when the DOM is unusable.
✓ Pros
  • Handles logins, modals and multi-step flows natively
  • Model-agnostic across the major LLMs
  • Vision fallback when the DOM is a mess
  • No selectors to maintain
✗ Cons
  • Slow and token-expensive per run
  • Wrong tool for bulk fetching
  • Non-deterministic, needs verification on output
FlareSolverr 🌐ManagedTypeScript Real browser stackCloudflare IUAM–
🌐 Browser
Under the hood: Runs a browser behind a simple HTTP API, solves the challenge, and returns the rendered response, so the browser lives outside your crawler.
✓ Pros
  • Keeps browser lifecycle out of the spider process
  • One service can serve many crawlers
  • Scales and fails independently of the scraper
  • Easy to route only the requests that need it
✗ Cons
  • Another service to run and monitor
  • Challenge coverage drifts as vendors update
  • Not a general anti-bot solution
DeepScrape 🌐AITypeScript Fingerprint hygieneContent extraction272
🌐 Browser
Under the hood: Self-hosted scraping API: markdown and structured extraction, self-healing CSS specs, an autonomous agent, and an MCP server exposing each saved site spec as its own tool.
✓ Pros
  • Grounding-based confidence flags hallucinated fields
  • Self-healing specs re-derive selectors on drift
  • Per-site MCP tools instead of one generic verb
  • Apache-2.0, self-hosted, runs on internal sites
✗ Cons
  • Needs an LLM key for the derivation steps
  • Explicitly not a CAPTCHA or anti-bot bypass
  • Sessions are in-memory, so long auth flows need tuning
Yes Partial No HTTP/Parser/RE Browser Framework AI Managed CAPTCHA Proxy

Browser engines, deep dive

Critical 2026 fact: CDP (Chrome DevTools Protocol) is itself detectable. Runtime.enable timing, execution context leaks, and binding exposure all signal automation. Camoufox uses Mozilla's Juggler protocol below CDP, no CDP leaks. playwright-stealth patches JS at runtime but Function.toString() exposes the patch. When you judge a stealth tool, ask what it changes in the engine’s own bookkeeping, not only which JavaScript values it rewrites. Patching navigator properties or console serialisation leaves untouched the code paths V8 alters while a debugger is attached, and those are readable from ordinary page code: with the Debugger domain enabled, an empty Promise.all given a Proxy reject callback throws a TypeError instead of resolving (reproduced on V8 14.6). A fingerprint benchmark that scores JavaScript values will not show that difference. It is one vector, shown by one researcher and reproduced outside Chrome, so read it as a reason to audit which protocol domains your stack enables rather than as proof that vendors check it.
That is the argument when detection is what you are paying for. Reverse the pressure — cost rather than concealment, a lighter engine rather than a patched one — and the failure mode inverts: a stripped-down engine buys its memory and start-up wins by implementing less of the DOM, and the parts it omits never announce themselves. Feature detection is built to accept a negative answer, so a probe for an API the engine never shipped returns false, the page takes its legacy path, and the HTML you asked for still arrives. The bill lands as latency and request volume instead of an exception, which is how a light engine passes your smoke test and still runs slowest in the fleet. Two absent methods — HTMLScriptElement.supports and DOMTokenList.supports — were enough to drop one Shopify theme’s loader onto fetch()-based chunk loading and re-pull the same Vue bundle nine times a page against Chrome’s once: 676 requests to headless Chrome’s 432 over two pages, warm runs about 46% slower than the browser being replaced. Read that as mechanism, not magnitude — it is Lightpanda’s own harness grading Lightpanda, one storefront, reported medians, no independent replication. Both gaps are patched; the next will be a different API on a different theme. So qualify a light engine the way you qualify a proxy pool: diff request counts against a real Chrome on your own targets, and read the defaults while you are there. Diff the output as well as the traffic. On a demo page that builds its list with document.write(), jsdom returned all 10 items in exactly the reverse of Chromium’s order, with no error and nothing missing, which no memory or success-rate benchmark would catch and which corrupts any record where position is data, such as rankings and search results. Compare word counts and the order of a few known items against a real browser for each target before you switch, and repeat the check after an engine upgrade. That is one page and one engine version, so read it as a class of failure to test for rather than a measured rate. That same engine installed no HTTP cache unless you passed a cache directory, and the flag alone bought more than the API fix. Decide which pages fall back to a full browser by checking the content, not the engine’s error log. In one two-run retest of Lightpanda on 28 JavaScript-heavy homepages, eight pages that built correctly also logged JavaScript errors, so routing on errors would have sent good pages to Chromium, while a rule sending pages with under 100 words or a challenge-page title to Chromium caught every failure, though it was tuned on those same sites. Word counts still miss partial pages: one auction table came back with all 50 rows but every domain name replaced by an icon and the time left missing. Add a field check per site, such as every row having a name and an end time, and remember the engine announces itself as Lightpanda/1.0, so a bot check may treat it differently from Chrome.
Microsoft 2020
Playwright ★ 68k
Chromium + Firefox + WebKit
The 2026 standard framework. Powers Firecrawl, Crawl4AI, Browserbase. CDP is detectableuse C++ wrappers above Playwright. Auto-wait, network interception, multi-browser.

pip install playwright && playwright install
C++ Firefox · Juggler
Camoufox ★ 100%
Zero CDP exposure · geoip alignment
Mozilla Juggler below CDP levelzero CDP leaks. Near-zero fingerprint surface. 100% pass rate Mar 2026 on Cloudflare, Instagram, Reddit, X. (FF135 base; stable moved to FF146 on 16 Jul 2026 — re-test). Note: Firefox ~3% market share.

from camoufox.sync_api import Firefox
Stealth Chromium
CloakBrowser
49+ C++ binary patches
Binary patches: Canvas, WebGL, Battery API, AudioContext, CDP input. reCAPTCHA v3 score 0.9. Passes Akamai's 60 extension probes with real extension loading. Best for Akamai-targeted Chromium sites.
Playwright source fork
PatchRight
No JS signatures anywhere
Patches Playwright Python source, not JS injection. Kasada fingerprints playwright-stealth via toString(). PatchRight leaves nothing in the runtime to inspect.

pip install patchright
Google · Node.js
Puppeteer ★ 89k
Chrome DevTools Protocol
Google's original CDP automation. puppeteer-stealth plugin patches common detection points. CDP signature still visible at protocol level. Better for rendering tasks than hard anti-bot targets.
Multi-language · WebDriver
Selenium ★ 29k
Legacy, navigator.webdriver=true
navigator.webdriver=true detectable in 2 JS lines. Use SeleniumBase UC mode to remove. Stock Selenium is dead against Akamai in 2026. Still valid for non-protected targets.
Python · UC Mode
SeleniumBase ★ 10k
Undetected Chrome Mode
UC mode removes navigator.webdriver. Auto-solves many CAPTCHAs. Good for Kasada, medium targets. Not production-safe against Akamai at scale.

from seleniumbase import Driver
Raw CDP · Async Python
nodriver / pydoll
Direct Chrome DevTools Protocol
Direct CDP without WebDriver overhead. Used with Botright for CAPTCHA solving. scrapy-nodriver integrates with Scrapy directly. Lighter than full Playwright for medium targets.
Human behaviour simulation
Botasaurus
Gaussian mouse physics
Physically realistic mouse curves via Gaussian jitter. Combines with Patchright for protocol-level evasion + human behaviour. Effective against DataDome's 35-signal behavioural analysis.

pip install botasaurus
One check belongs beside the detection surface when you pick a driver from this class, and almost nobody runs it: read the licence. Everything above ships BSD, MIT or Apache and says nothing at all about what you point it at, so the habit of skipping the file is well earned — which is precisely why the exception reaches production unread. DrissionPage, a CDP-native Python driver at 12,468 stars, carries no OSI licence: a bespoke grant limiting use to personal learning and legal non-profit activity, barring commercial use without the author’s authorisation, and expiring automatically if any of its four terms is breached — the fourth of which is to follow the Robots protocol. Robots.txt is not binding on its own, as the legal section sets out; what is unusual here is a library making it a condition of the grant. Your scanner will not raise it either, because the gap is the identifier and not the text: GitHub reports NOASSERTION, the PyPI record carries a null licence field and no License :: classifier, while the wheel still ships the file and repeats the terms in the installed package’s own docstring. An SPDX-keyed gate sees a blank and waves it through, so grep your lockfiles for the name instead. None of this is legal advice — “commercial purposes” is undefined, automatic expiry is a unilateral term of untested strength, and no enforcement is on record — and note that the library’s speed advantage over Selenium is its author’s claim about his own project, with no published benchmark behind it.
python
from camoufox.sync_api import Firefox

# geoip=True: auto-aligns IP, timezone, locale, WebRTC simultaneously
with Firefox(
    geoip=True–        # align all 5 identity vectors to proxy exit country
    humanize=True–    # Gaussian mouse jitter
    proxy={"server": "http://proxy.provider.com:8011"–
           "username": "user"– "password": "pass"},
    screen={"width": 1920– "height": 1080}
) as browser:
    page = browser.new_page()
    # Warm up, never go directly to target URL
    page.goto("https://www.google.com")
    page.wait_for_timeout(2000)
    page.goto("https://cloudflare-protected.com")
    page.wait_for_load_state("networkidle")
    print(page.content()[:500])
10 field notes on the arsenal Reported from practice. Click any card to open it.
+
Aug 2026 / Tooling
An Escalation Ladder, Shipped as a Library Default
The newest scrapy-stealth release adds driver="auto", and it is this guide's cheapest-first principle turned into a runtime decision. The engine attempts fast HTTP impersonation first and escalates to a real Chrome browser only when it meets a JavaScript challenge or a ban — so you pay browser prices on the pages that need a browser and request prices on the ones that do not.
💡 The escalation is per-request, which is where the savings actually live
Why this is the right shape. Most estates pick one tier for the whole crawl and pay its cost everywhere, because choosing per-target is manual work nobody schedules. Deciding per request, at the moment of refusal, means the expensive path is reserved for the pages that actually earned it — and on a typical mixed estate that is a small minority.

The rest of the release, briefly: proxy health scoring with automatic cooldown on dead exits; behavioural mouse and scroll replay with no configuration; browser cookie handoff, so you log in once in a browser and reuse that session on the faster request path; custom DNS overrides to pin hosts to fixed IPs; and static-asset blocking to skip images, fonts and CSS on browser fetches.

The one to actually look at is the cookie handoff, because it attacks the usual reason people run browsers at all. Very often the browser is not needed to render anything — it is needed once, to acquire a session. Separate those two jobs and the browser becomes a login step rather than a per-page cost.
+
Aug 2026 / Tooling
Scrapling Has 77,000 Stars and Its “AI” Parser Is difflib
Scrapling is the library people reach for when selectors break, and it genuinely earns its reputation — 76,946 GitHub stars, verified against the API rather than a screenshot. But the adaptive matcher marketed with language about intelligent similarity algorithms contains no machine learning of any kind. It is difflib.SequenceMatcher from the Python standard library: Ratcliff/Obershelp gestalt pattern matching, imported on line 4 of parser.py.
💡 The default acceptance threshold for relocating your element is 40%
How it actually works. On save, Scrapling serialises the element's tag, cleaned attributes, stripped text, tree path, parent tag/attributes/text, and sibling and child tag-name tuples. On relocation it scores every candidate node against that stored dictionary across roughly a dozen checks — tag exact match as 1 or 0, text similarity ratio, and attribute dictionaries scored as 0.5 × key-sequence ratio + 0.5 × value-sequence ratio — then normalises to round((score / checks) * 100, 2).

Three consequences worth knowing before you trust it in production. First, the default threshold is percentage=40, so an element scoring 40% similarity is accepted as a match. Second, relocate() deliberately never stops early even on a perfect score, so it is O(n) across every node on the page, every time. Third, it returns a list, not a single element, so tied candidates all come back.

This is not a criticism. String diffing over a rich property dictionary is a sensible, debuggable, dependency-free design, and it is why the library is fast and installs cleanly. It is a criticism of reading “intelligent” in a README and assuming a model is involved. Know the threshold, and set it yourself.
+
Aug 2026 / Tooling
Markdown Is the Interchange Format, and Microsoft Ships the Converter
The argument for converting before ingestion is a cost argument, not an aesthetic one. Markdown preserves headings, lists, tables, links and code blocks without HTML's structural noise, so a model processes it on materially fewer tokens — which is lower inference cost and more context window left for content that matters. MarkItDown, Microsoft's open-source Python library, is the multi-format converter: Office documents, PDFs, images, audio, EPUB, ZIP archives, HTML, YouTube URLs and structured data, through one API, with a CLI, a plugin architecture and an MCP server.
💡 It is a converter, not a fetcher — and that distinction will bite you
The gotcha the feature list hides. MarkItDown will happily take a URL: md.convert("https://example.com"). Under the hood that is a plain HTTP GET through Requests — no browser, no fingerprint management, no retries. Against anything with anti-bot protection it simply fails. Scrape with your own stack and hand MarkItDown the saved HTML.

Two more. There is no proxy option at all; because it uses Requests, you configure HTTP_PROXY and HTTPS_PROXY as environment variables before instantiating. And YouTube conversion typically returns 429 Too Many Requests even from a fresh IP, so that feature is unusable at scale without paid residential infrastructure — a cost the docs do not mention.

Practical setup. Python 3.10 or higher. pip install 'markitdown[all]' pulls heavy dependencies, so prefer selective extras — there are eleven, including [pdf], [docx], [audio-transcription] and Azure Document Intelligence — and keep the scraper container light. Plugins are disabled by default and must be enabled explicitly.
+
Aug 2026 / Lighter browsers
Moli: Run the JavaScript, Skip the Pixels
A headless Chrome instance sits around 773 MiB. Fifty in parallel is roughly 40 GB, and most teams simply accept that as the price of automation at scale. Moli, a new open-source Rust engine, reported 73 MiB on the same work. Ten times less.
💡 Most agent and scraping work needs the DOM, not the paint
The idea is on-demand rendering. By default Moli runs real JavaScript through V8, builds the DOM, and computes CSS via Servo's Stylo, then it stops. Layout and paint only happen if you ask for them with a flag, and screenshots, PDFs and media are separate toggles. Chrome pays for full layout on every page whether you needed it or not, and for a scraper or an agent consuming structure rather than pixels, that is the entire waste.

On a 192-URL crawl it reported a success rate level with Chrome, 53.6% against 52.6%, at a tenth of the memory. It speaks CDP from a single binary, so existing Playwright and Puppeteer scripts connect directly.

Read the caveats as stated, because they matter. The benchmarks are self-reported, there is no Chrome pixel parity, and the project is young. Note also what that success rate is telling you: roughly half of a 192-URL crawl failed for both engines, which is a reminder that the engine was never the thing deciding whether you get in.

This lands squarely in the lighter-browser thread alongside Kitesurf, Obscura and Lightpanda, and the convergence is now hard to miss. Kitesurf is Blitz rendering plus Firefox's Stylo plus the Boa JS engine, built in twelve weeks on Workers, and Cloudflare credits Obscura as the inspiration. Moli uses V8 and Stylo. Everyone building for machine consumers is arriving at the same conclusion independently: keep the JavaScript engine and the DOM, throw away the rendering pipeline, and keep CDP so nobody has to rewrite their scripts.
+
Aug 2026 / Tool selection
Two Stealth Libraries That Cover the Same Layer Are One Stealth Library
The commonest wasted afternoon in this work: you install a second stealth library, nothing changes, and you conclude the target is unbeatable. Both libraries were patching the same layer, and the block was somewhere else. The fix is not a better library, it is knowing which rung you are standing on.
💡 The order is IP and ASN, then TLS, then HTTP/2 — canvas and WebGL only matter once a script runs
Stated for public pages, not accounts, which is the distinction that makes the ordering true. A site sees your IP and ASN first, then your TLS handshake, then your HTTP/2 settings. Every JavaScript-layer signal — canvas, WebGL, audio, the navigator object — is downstream of a script actually running, so a JS-layer patch cannot help with a block that happened before the HTML was served. That is the same conclusion the Akamai case study in this guide reaches the expensive way.

Five tools, placed by layer rather than by popularity. curl_cffi impersonates browser TLS and HTTP/2 fingerprints from Python and runs no JavaScript, so JS challenges are exactly where it stops — and it is the cheapest thing to try, so try it first when you do not need page JavaScript. fingerprint-suite is Apify's Node toolkit: one call gives you a Playwright context where headers and JavaScript APIs are generated from a single coherent fingerprint, which is the whole game (see the coherence material above — the failure mode is spoofing one axis and leaving the others pinned). Obscura is an independent Rust and V8 engine with no Chromium underneath, currently v0.2.0, and its own notes warn that rendering and Web API behaviour can diverge from Chromium — the same structural trade as Kitesurf. TRAWL is the escalation ladder as a product: plain HTTP first, then a cached session, then a fresh browser solve, then a residential proxy, so a target that yields at rung one never costs you rung four — roughly sub-100ms, ~500ms, seconds, then tens of seconds, and it speaks FlareSolverr v2 so it drops into existing stacks. stealth-browser-mcp exposes a stealth browser over MCP, pinned to nodriver 0.47.0 — useful, and a good illustration of the last point.

The closing advice is the part people skip: check the recent commits before you install any of them. In a field where the detectors ship weekly, a stealth tool's commit history is a freshness indicator, and a pinned dependency means you inherit that driver's fixes only when someone remembers to bump the pin. Read the pin as a date, not a version.
+
Trending / Browser stealth
CloakBrowser Just Crossed +9.1K Stars in One Week
The fastest-growing GitHub repo this week is a scraping tool. CloakBrowser, the stealth Chromium with source-level fingerprint patches, hit +9.1K stars (Nov 2026), passing 30/30 anti-bot tests. Confirms what production scrapers already knew: real browsers with patched binaries beat stealth plugins.
💡 Drop-in Playwright replacement, no JS patches to detect
Stealth plugins (puppeteer-extra-stealth, playwright-stealth) overwrite JS properties at runtime — which is itself a detection signal. Anti-bot scripts can see the override happen. CloakBrowser patches Chromium's C++ source directly so the fingerprint is real at the binary level. There's no override to detect. The recent velocity (+9.1K stars in one week) was driven by it passing every public anti-bot test suite including Sannysoft, Antoinevastel, Creepjs, and Bot.sannysoft. Its closest analogue is Camoufox (Firefox-based, same approach with Juggler) but CloakBrowser uses Chromium so it works with existing Playwright pipelines. The tradeoff: maintenance burden. When Chrome ships a new version, CloakBrowser has to patch and rebuild. The team has been keeping up so far. github.com/CloakHQ/CloakBrowser
+
Benchmarks · 2026
The 30-Point Gap: Browser Scraping Success Rates Are Not Equal
71 protected sites tested: Browser Use Cloud hit 81%Browserbase hit 42%. That gap is no longer marginal, it is the difference between a working pipeline and a broken one.
💡 A 30-point gap in success rate = the difference between a working pipeline and a broken one
The benchmark tested 71 sites protected by Cloudflare, Akamai, PerimeterX, and DataDome. Methodology: each provider was given identical target lists and measured on first-request success (no retries). Browser Use Cloud succeeded on 81%, achieved via custom Chrome patches at the C++ binary level plus coordinated fingerprint management. Browserbase succeeded on 42%, detected primarily via CDP timing signatures and canvas hash consistency. The gap exists because basic scraping (fetch URL, parse HTML) is commoditised. The data worth having in 2026 sits behind login walls, search interfaces, and multi-step authenticated flows requiring actual browser interaction. Cheap providers are adequate for unprotected targets; they fail silently on protected ones.
+
Python Framework
Scrapling v0.4: The Biggest Python Scraping Update Yet
New async spider: concurrent crawling, mix HTTP and stealth sessions, pause/resume from checkpoint, stream items live. Thread-safe ProxyRotator built in. Handles Turnstile natively.
💡 pip install scrapling --upgrade
The async spider framework uses a Scrapy-like API: define a Spider class, set start_urls, implement parse(). Key differentiators from Scrapy: mixed session types in one spider (HTTP fetchers, headless Camoufox, stealth browser), checkpoint/resumeCtrl+C saves state, restart continues from last position, per-domain throttlingset different rates per target. ProxyRotator: thread-safe, works across all fetcher types, supports custom rotation strategies, per-request override. Parser improvements: blocked_domains list to block tracking/CDN requests in headless mode, automatic proxy-aware retry on network errors, Response.follow() for easy link chaining. Install: pip install scrapling --upgrade.

Oct 2026 update. v0.5 (17 Oct 2026) breaks parts of this. DynamicFetcher and DynamicSession are removed, so every browser session is Patchright Chromium via StealthySession (no Camoufox). Response.follow() no longer inherits POST bodies. Changed request fingerprints can orphan old checkpoints. Read the official migration guide before running pip install --upgrade on a live spider. See the v0.5 hands-on card.
+
Library Analysis
Scrapling Hit 200K Views, Invest in Your Network Layer
Detection vendors update, bypass libraries break, new ones ship. This cycle repeats every few months. What never depreciates: your proxy infrastructure. Invest there first.
💡 Invest more in your network layer, it depreciates slower than your library
The library lifecycle in scraping works like this: a new bypass technique is discovered, someone publishes a library implementing it, the library becomes popular, detection vendors add the library's fingerprints to their models, the library gets blocked, repeat. This cycle runs on a 2-4 month cadence for fast-moving targets. Your proxy setup operates on a different timeline: a well-configured residential proxy pool with good IP diversity and correct session management continues to work across multiple library generations. The specific libraries come and go but the network signals (IP reputation, ASN, session behaviour, timing patterns) remain consistent requirements. Conclusion: spend more engineering time on proxy quality, session management, and IP pool diversity than on tracking the latest bypass library.
+
Pattern · Tooling ergonomics · 2026
The curl Command as a Front-End to a Real Browser
A recurring 2026 pattern wraps a real browser behind the interface developers already know: you write an ordinary curl command and a tool parses it, replays the same method, headers, body, cookies, and auth inside an actual Chromium driven over the automation protocol, clears the browser-side friction (Cloudflare's managed challenge, a Turnstile widget), and hands back the final response. The point is not a new bypass, it is that the browser-grade request is expressed as the one-liner you would have written anyway.
💡 The ergonomic win is meeting people at the curl command, not asking them to rewrite their workflow as browser automation.
This is worth noting as a shape, not a single product. The reference implementation in this lineage is CurlWright (open source, github.com/seifreed/Curlwright): it parses a curl invocation, supports the common flags (-X, -H, -d/--data-*, -b cookies, -u auth, -x proxy, -L, -k, --max-time), and runs it through a genuine Chrome rather than a spoofed fingerprint. Two design choices generalise beyond it.

Stealth below the automation layer. Rather than faking a browser with injected JavaScript, the current generation of these wrappers drive the real Chrome binary through a patched automation stack (Patchright-style) so the automation tells (the Runtime.enable CDP leak, navigator.webdriver, headless markers) are neutralised at the protocol level, and falls back to a CDP-free driver (nodriver-style) for the hardened "Just a moment" managed challenge. This is the same lesson the detection and library sections of this guide keep arriving at: on protocol-fingerprinting targets, driving a real browser without the standard automation surface beats patching a headless one. Persisting and re-importing the solved-cookie session (so a warmed-up cf_clearance can be reused across calls) is what makes the curl ergonomic actually practical at more than one request.

Machine-readable output for pipelines. The other generalisable idea is emitting structured JSON and even SARIF alongside the response, so a browser-grade fetch can drop straight into CI and security tooling that already understand those formats. It is a small thing that quietly moves browser-based fetching from an interactive task to a scriptable pipeline step. The honest constraint of the whole pattern is the one its own authors flag: because it drives a real Chrome, it needs that browser present on the host and carries the cost and weight of a full browser per protected request, so it earns its place on the hard targets, not as a default replacement for a plain HTTP client on everything.

Pattern reference: CurlWright (seifreed, open source), 2026, plus the Patchright and nodriver engines it builds on. Described here as a tooling-ergonomics pattern, not an endorsement of any one tool.

The tools above solve the access problem. But once you have the raw HTML or JSON, you still need to extract meaning from it. That is where AI-native scraping changes everything. In 2026 the bottleneck is not access. It is the extraction layer. If you are building a corpus rather than a price feed, do not spend long choosing between heuristic extractors. A September 2026 CMU preprint ran four of them (resiliparse, trafilatura, jusText and the model-based Dripper), each followed by the same RefinedWeb rules, and at 1.4B parameters all four landed within 0.003 DCLM Core of each other. All four trailed a single 0.6B model (ReScraper, Apache-2.0) that reads the whole rendered page and decides what to cut. The input mattered more than the extractor: heuristic extraction fused table cells and dropped bulleted lists before any cleaner saw the text, and no later stage could put them back. So keep the raw HTML, treat the extractor as replaceable, and test extraction on your own tables and lists, not only on article prose. This is one preprint on one Common Crawl pool, scored by downstream pretraining rather than extraction accuracy, and nobody has reproduced it yet.

06 AI & LLM Scraping

Describe, don't
select

AI-native scraping replaces CSS selectors with natural language. A 2025 NEXT-EVAL benchmark showed an LLM hit F1 0.96 on record extraction when the page was fed as flattened JSON, and 0.10 when fed as slimmed HTML: the input format, not the model, decided the result. The 0.96 (exactly 0.9567) is Gemini 2.5 Pro on 164 real pages after each page was flattened into a map from XPath to text. Given the same pages as slimmed HTML it scored an F1 of 0.10 with a hallucination rate of 0.91, and on nested JSON 0.40. So convert the DOM into a flat, addressed representation before you ask a model for records, and have the model return paths that code then applies. Do not quote the 0.95 as what an LLM does on HTML. It is one model on one benchmark from 2025, and the authors designed both the benchmark and the input formats.

2026 Market Shift
Why AI scraping matters now
Firecrawl's markdown output uses 67% fewer tokens than raw HTML, compounds significantly at thousands of pages for RAG pipelines. AI web scraping market: $7.5B → $38B by 2034 (CAGR 19.93%). LangChain, LlamaIndex, and CrewAI all have native integrations. Claude and Cursor can scrape the web via MCP tools with zero code. A server like the Decodo MCP is the worked example: it gives the model a scraping tool that returns Markdown, JSON, or screenshots for JS-heavy pages with no proxy rotation or anti-bot handling on your side, which is exactly the shape a RAG or research pipeline wants.
Firecrawl ★ 111k
Managed · Self-hostable · FIRE-1 · MCP
Send URL → clean Markdown/JSON. No selectors. MCP serverClaude scrapes via natural language. FIRE-1 agent autonomously navigates. /interact endpoint clicks, fills forms, extracts behind dynamic content. SAP, Zapier, Deloitte.

app.scrape(url) | app.crawl(site) | app.search("query")
✓ LangChain + LlamaIndex native · 500 free/mo
Crawl4AI ★ 60k
Open-source · Local LLM · Full control · MIT
"Scrapy for the LLM era." Runs on your infrastructuredata never leaves your servers. Adaptive crawling learns selectors over time. BM25 content filter. Plug in Ollama for local models or OpenAI/Deepseek.

result = await crawler.arun(url)
✓ Full data sovereignty, free, MIT license
ScrapeGraphAI ★ 18k
NL prompts · Graph pipeline · Self-healing
Describe what you want, LLM builds and executes a graph-based extraction pipeline. Self-healing: site structure changes, re-describe and it adapts. No selectors ever written. Supports OpenAI, Claude, local.

SmartScraperGraph(prompt="...", source=url)
✓ Best for: schema-free exploration, prototyping
Rust · Chrome TLS · 10 MCP tools · 95.1% accuracy
Rust-native scraper built for AI agent integration. 10 MCP toolsClaude and Cursor can call it directly via natural language. The 95.1% the vendor publishes is boilerplate-removal accuracy on the author’s own 50-page test, not a bot-protection success rate. Zero Python overhead, runs as subprocess or HTTP service. Chrome-level TLS fingerprinting baked in.

npx create-webclaw
✓ Best for: AI agents needing high-performance scraping + MCP integration
Jina Reader API
URL → clean text · Zero code
Simplest LLM scraping tool. r.jina.ai/{url} is the entire API. Returns clean Markdown. Dynamic content handled via built-in rendering. Free tier available, paid ~$0.002–$0.01/page.
✓ Best for: text extraction, no-code integration
Steel
Open-source · Docker · MCP · AI agents
Self-hostable headless browser API for AI agents. MCP serverClaude controls browsers directly. Session persistence + CAPTCHA auto-solve. <1s session start. LangChain/CrewAI integration.
✓ Best for: AI agents needing browser control
Browserbase
Managed cloud · $300M valuation
50M sessions in 2025. Playwright/Puppeteer drop-in, one endpoint swap. Session recordings + CAPTCHA auto-solve. Used by AI agent frameworks as the browser layer. From $50/mo.
✓ Best for: AI agent infrastructure at scale
python
import asyncio
from crawl4ai import AsyncWebCrawler
from crawl4ai.extraction_strategy import LLMExtractionStrategy
from pydantic import BaseModel

# Define exactly what you want, LLM extracts it, no selectors needed
class Product(BaseModel):
    name: str
    price: float
    model_number: str
    brand: str

async def extract(url):
    strategy = LLMExtractionStrategy(
        provider="openai/gpt-4o-mini"–
        schema=Product.model_json_schema(),
        extraction_type="schema"–
        instruction="Extract all products with prices and model numbers"
    )
    async with AsyncWebCrawler() as crawler:
        result = await crawler.arun(url=url– extraction_strategy=strategy)
        import json
        return json.loads(result.extracted_content)
# NEXT-EVAL 2025: F1 0.96 on flat-JSON input, 0.10 on slimmed HTML
MCP Server · 55 production tools · MIT
Crawilfy github.com/razavioo/crawilfy-mcp-server
The full scraping stack as an MCP server — gives Claude Code, Cursor, and Codex 55 production tools for the complete crawling pipeline.

What it ships:
• Stealth via curl_cffi TLS impersonation + rotating proxies
• Auto-discover REST + GraphQL endpoints on any site
• Record a flow once, export it as a runnable Python crawler
• Smart extraction with any OpenAI-compatible LLM (free tiers + local Ollama work)
• MIT licensed

One command: uvx crawilfy-mcp-server

Why this matters: at $0.002–0.01 per request, commercial scraping APIs compound fast on any non-trivial AI agent. Crawilfy brings the full stack in-process: TLS impersonation, proxy rotation, LLM extraction, all from within your IDE. The alternative is paying per-request at scale.
GitHub ↗ PyPI ↗

From extraction to production-grade data

Crawl4AI and Firecrawl get you semantic understanding out of an LLM. But ask an LLM for a price across 10,000 articles and you will get $40, 40 dollars, 40 USD, "forty dollars", and occasionally null. Production pipelines cannot ingest that. The fix is to separate the two concerns LLMs conflate: semantic understanding and structural guarantees.

The pattern · Pydantic + Instructor + LLM
Schema-validated LLM extraction
Define a Pydantic schema for what you want. Pass it to the LLM via Instructor (which patches OpenAI, Anthropic, Mistral clients to return validated schema objects, not free text). Instructor handles retries when the LLM returns malformed output, and rejects responses that fail validation before they enter your pipeline.

# pip install instructor pydantic anthropic
from pydantic import BaseModel, Field
import instructor, anthropic

class JobPosting(BaseModel):
    title: str
    company: str
    salary_min_usd: int | None = Field(description="Floor of salary range in USD")
    salary_max_usd: int | None
    years_experience_min: int
    location: str
    remote: bool

client = instructor.from_anthropic(anthropic.Anthropic())
result = client.messages.create(
    model="claude-sonnet-4",
    response_model=JobPosting,
    messages=[{"role": "user", "content": scraped_html}],
    max_retries=3,
)
# result is a validated JobPosting object, not a string
# If LLM hallucinates "competitive" for salary, Instructor retries
Why this beats raw LLM calls: normalises currencies, units, and phrasings ("just under two percent" → 1.8); rejects hallucinated dates that don't fit the schema; retries automatically on type errors; gives you a real Python object downstream.
When classical NLP still wins
spaCy, NLTK, Stanford NLP at scale
LLMs cost $0.001–0.01 per article on Claude or GPT-5. Classical NLP costs effectively zero after model load. For 10M+ document extraction at scale where the schema is narrow and the domain is stable, spaCy NER + dependency parsing still wins on cost.

Use classical NLP when: scraping millions of consistent documents (e-commerce, classifieds), schema is fixed, domain doesn't shift, latency budget is <5ms per doc.

Use LLM + Instructor when: messy heterogeneous sources (news, newsletters, job boards), context disambiguation matters ("Apple" the company vs the fruit), schema may evolve, semantic equivalences need resolving ("FTE" = "full-time" = "permanent" = "direct hire").

Hybrid in production: classical NLP pre-filters and tags. LLM resolves only the ambiguous cases. This is what Bloomberg, Reuters Refinitiv, and FactSet actually do, not pure LLM pipelines.

Sources and further reading: Federico Trotta, The Web Scraping Club, May 2026; Instructor library.
The production failure mode nobody warns you about: LLMs hallucinate plausibly. If your scraped article doesn't mention a publication date, an LLM will sometimes invent one that fits the article's tone. A Pydantic date | None field with Instructor's retry logic catches this, the LLM has to either find a real date or return None. Without schema validation, fabricated dates pass into your database as facts.
The framing to keep your head straight: AI agents did not replace scraping, they became its biggest new client. When half the industry is writing the eulogy for web scraping, it is worth noticing that an agent which browses the web is doing exactly what a scraper does. It makes HTTP requests, it hits anti-bot walls, it gets rate limited, it needs proxies and a believable fingerprint. The only thing that changed is who writes the prompt. The tell is in the spend: teams shipping agents bought more proxy and unblocking infrastructure through 2025, not less, because every agent loop ends in the same place a scraper's does, at a server that would rather not serve it. Everything in this guide applies to your agent the moment it leaves the sandbox.

Making LLM extraction production-reliable: the parts that are engineering, not prompting

Asking a model to read a page and return the fields is the easy 20 percent. A team running AI-generated scrapers across hundreds of partner sites reported the number that matters: first-attempt LLM selectors fail to yield any data roughly 30 to 40 percent of the time. The model is strong at pattern recognition and has no way to check its own guess against the real DOM, so the reliability lives entirely in the scaffolding around the model, validation, diagnosis, cleaning, and a bounded retry loop. Three pieces of that scaffolding are worth stealing outright.
1 · Steer the model toward durable selectors
The selector-stability hierarchy
Left alone a model reaches for whatever selector matches, which is often a hashed layout class (div.qElViY from a Wix or Chakra build) that changes on the next redeploy. Instruct it to prefer selectors in order of durability: (1) JSON-LD structured data, a declared schema contract, (2) data-testid attributes, added for automation and rarely changed, (3) id attributes, (4) semantic elements (h1, time, address, article), (5) itemprop / schema.org attributes that are part of a public SEO contract, (6) named platform class prefixes (tn-, ot_), and only last (7) visual styling classes, the first thing to break on a redesign. The higher the selector sits, the longer it survives, so a self-healing loop that prefers the top of this list heals far less often.
2 · Do not pour the whole DOM into context
A cleaned page, then a DOM-exploration agent
A single listing page is often 200 to 400 thousand characters, and sending that on every retry is slow, expensive, and sometimes larger than the context window. The same rule applies to tool results inside an agent: keep the page out of the context and hand the model a path or an extract. Playwright MCP now saves its page snapshot to a file and returns a short reply with a link, which measured 78 to 177 tokens on three pages, while the snapshots themselves ran from 10,824 tokens for a product page to 31,565 for a long Wikipedia article. A fetch tool that truncates at 5,000 characters by default returned roughly 1,000 to 1,850 tokens on the same pages. Have the agent open the file only when a step needs it, and extract fields in code before anything reaches the model. That is three pages, one run and one tokenizer, so size the budget on your own targets. First pre-clean: strip <style>, <svg>, <noscript>, and inline scripts (but keep <script type="application/ld+json">, that is data, not code), which typically shrinks the HTML three to five times. When even the clean page will not fit, do not truncate blindly, give the model targeted tools over the DOM instead, downloading the HTML to a file and exposing helpers like count_selector, dom_excerpt, and find_repeating_blocks so it can answer questions about structure without loading the whole document. And separate the two jobs: the model produces the selectors, your deterministic code does the actual field extraction, so the expensive model never has to see every row it is pulling. Keep the JSON-LD, but never hand it to a model as trusted text. Structured data is where scrapers go for clean fields, and it is also a delivery channel for instructions aimed at them: a 2026 scan of 1.2 billion URLs found 1,996 validated prompt injections in structured data, 26% of those in page bodies and mostly JSON-LD, plus 7,887 in custom response headers such as X-AI. Parse those fields with code, screen headers and JSON-LD for instruction-like text before any model call, and do not make plain text the default model input. In the same study’s 5,200 trials, a page flattened to plain text was obeyed 3.9% of the time against 1.1% for HTML, because stripping the markup also strips the evidence that the text was hidden. Read that gap as indicative rather than exact, since HTML inputs also overflowed smaller models’ context windows in about a fifth of runs.
3 · Diagnose the failure before you retry. The worst response to a selector that returned nothing is to resend the same prompt and hope. Most extraction failures fall into a few recognisable classes, and naming the class turns a blind retry into a targeted fix: extracted too little (selector too specific, matched a fragment), extracted too much (matched a container with surrounding noise), wrong DOM region (matched a real element, but the venue name in the footer instead of the event header), attribute-versus-text confusion (pulled an href when you needed the link text), and template mismatch (the sample pages the model saw do not represent every template on the site). Feeding back "the selector is too narrow, find a parent element" produces a far better second attempt than "try again", and it is the diagnostic layer that the circuit-breaker and correction-loop from the architecture section depend on. The same source also flags two JSON-LD traps worth pre-empting: server-side frameworks sometimes inject HTML comment markers (<!---->) inside the script tag that make json.loads fail silently, so strip comments before parsing, and JSON-LD can be present on some pages of a site and absent on others, so a CSS fallback is mandatory rather than optional. A diagnosis only runs on rows you decided to look at, and that decision is usually delegated to the model’s own confidence score — the one number in the pipeline produced by the thing under audit. Treat self-graded confidence as routing metadata, not as evidence, and the case for sampling thins the moment you price a separate checker. A purpose-built, non-generative extraction judge such as TypeSafe’s jev line lists at $0.042 per million input tokens against $0.75 for the mini-tier extractor it audits, output unmetered — about 18× cheaper per input token on published September 2026 rate cards. That is arithmetic on list prices, not a measured per-row saving; one gateway is reported to be serving the judge free on promotion, so a $0 metering line is a campaign, not a price; and the accuracy chart beside it is a vendor grading its own product against LLM-written ground truth. What the ratio changes is the default: you check every field rather than the one row in eight a confidence gate forwards, and your escalation rate sets the bill, because the rung you escalate to meters reasoning as output at $30 per million. Two design points outlive the pricing. Score each field on several independent binary failure modes — fabricated, off-target, wrong type, malformed, incomplete — and aggregate them with max, never a mean, so one confident flag escalates instead of averaging into silence. And keep the scope honest: a same-bytes check proves fidelity to the page you fetched, never that the page was fresh, unblocked, or truthful. That still costs a re-fetch.

Serving scraped data to an agent: the naming problem replaces the scraping problem

There is a structural shift worth noting for anyone wiring web data into an agent rather than a pipeline. The old pattern was to write a tool wrapper around each scraper, hand-write its schema, teach the model when to call it, and keep fixing the wrapper every time a site changed. Exposing the scraping capability as an MCP server collapses that layer: the agent connects once and asks in plain language, and the model selects the right endpoint out of a large catalogue and gets structured JSON back, with no bespoke wrapper to break. The non-obvious consequence, reported by people who have built these, is that the hard problem moves. It stops being "how do I scrape this" and becomes "which of these hundreds of endpoints does the model reach for", which means clear, disambiguating tool names and descriptions start mattering more than the scraping code underneath. It is the same lesson as the selector hierarchy one level up the stack: when a model is choosing, the quality of what it chooses from, and how legibly it is labelled, is the thing you actually engineer.
One concrete answer to that naming problem, plus three extraction patterns worth copying. An open-source (Apache-2.0) implementation, DeepScrape, ships a pattern that resolves the "which of hundreds of endpoints" question rather neatly: instead of exposing generic verbs, you save a named site spec (the fields you want plus the derived selectors for one site), and each saved spec is published as its own MCP tool, so the agent discovers a specific, typed tool for the job rather than a generic scrape verb it has to aim. Because the spec is deterministic once derived and re-derives itself when the site drifts, it is the self-healing pattern and the naming fix in one object. It also runs on your own infrastructure, which is what makes it usable against internal or authenticated targets by binding a spec to an already-logged-in session, with the credentials staying yours.
Stop hallucination being silent
Grounding-based confidence signals
The most valuable idea in that codebase for anyone doing LLM extraction: after the model returns fields, check each value back against the source text deterministically and report what you found. A field that is present in the output but cannot be located in the page is flagged suspect (a probable hallucination); a field that is absent or empty is flagged missing (an omission). That single distinction converts the two failure modes that usually pass silently into machine-readable signals you can gate a pipeline on, and it costs nothing but a string search. Apply the same check to citations, not only to fields: when a model says a source supports a claim, require a verbatim quote and confirm in code that it occurs in the page you fetched, after normalising quote marks and whitespace. One vendor tutorial builds exactly this, re-fetching each cited URL at check time and letting a single contradicted citation override any number of supporting ones. Know the limit before you rely on it: a substring match proves the quote exists, not that it supports the verdict, so a real sentence saying 100°C can still be labelled as support for 50°C. Add a second deterministic test wherever the claim carries a number or a date (does the quote contain it?), and sample the rest by hand. Screen out challenge pages and soft 404s first, because a verifier fed a block page will call a true claim unsupported. It pairs directly with the promotion-gauntlet idea from the self-healing section, the model proposes, but something deterministic decides whether to trust it.
Three smaller patterns
Pruning, discovery, and drift
Link-density pruning: when converting a page to clean markdown for a model, score each DOM node and use link density as the key signal to strip nav, footers, and boilerplate while preserving headings, tables, and code, which cuts tokens without the blunt damage of a readability heuristic. If you let a model do the stripping, make it emit deletions, not text. CMU’s ReScraper renders the page one block per line with numbered ids. The model returns only commands such as rm 1-18 or an exact substring to delete, and plain code applies them, so every kept word is copied from the page rather than retyped. On its held-out pages, 96% lost a leading block and 98% a trailing one, and those edge blocks held 89% of the removed words: boilerplate sits at the edges. The output grows with the number of removals, not with page length, which is what keeps a 0.6B model cheap. Fidelity drops on the one operation that writes new text, rewrite (mean BERTScore 0.89 against the source), so switch it off for data that must stay verbatim. This is a research preprint aimed at pretraining corpora, not a validated tool for record extraction. If your corpus starts from Common Crawl, do not take its ready-made WET text as it is: extract from the WARC HTML instead. On 664 English pages from one 2026 WARC file, FineWeb’s own quality filter passed 10% of WET pages against 51% for trafilatura and 34% for Resiliparse, and WET kept about half as many usable words, losing mostly to menus and link lists. Between the two extractors, the DataComp-LM paper found similar downstream model quality and an 8× speed gap, and the retest measured about 17× on one machine, so Resiliparse suits bulk work and trafilatura suits smaller jobs where each page counts. That filter was tuned on trafilatura output, which may favour it, so keep the raw HTML and treat the extractor as a choice you can rerun. Automated hidden-API discovery: rather than hunting XHR calls by hand in DevTools, load the page once in a real browser, record every fetch it makes, and return the JSON-ish endpoints, turning the guide's find-the-backend-API advice into a repeatable step. Change tracking: diff a page's main content against its previous scrape and return a status of new, same, or changed with the added and removed lines, which is the cheap way to watch for the site drift that breaks extractors before it silently corrupts your data.
Worth noting the posture as much as the features, because it is a good model for how to talk about this work. That project states its limits explicitly in its own documentation: the fingerprint work is described as hygiene rather than warfare (consistent UA, platform, locale, timezone, and WebGL, plus patching the obvious automation leaks) and it says plainly that it does not solve CAPTCHAs, does not defeat the major anti-bot vendors, is not a residential unblocker, and honours robots.txt, pointing you at a purpose-built anti-detect browser if that is what you actually need. It also documents where its own session model is weak rather than hiding it. Tools that tell you where they stop are more useful than tools that imply they never do, and the same standard is worth applying to anything you evaluate, including the vendor benchmarks discussed in the cost section.
Q2 2026 Landscape · mapped by Massive

The agentic browser stack is 8 layers, not one product

"Browser agent" sounds like a single tool. It's a stack. Most teams building AI agents account for one or two layers; the reliable ones map all eight. When an agent fails a task, the cause is usually not the framework everyone debates, it's one of the other seven layers nobody mapped. The proxy layer sits at the bottom and every layer above it still has to reach the live site.

Layer 1 · Cloud Browser Platforms
Hosted headless browsers at scale
Managed Chrome/Chromium in the cloud, no infra to run. Browserbase, Kernel, Notte, Anchor Browser, Browserless, Hyperbrowser.
Layer 2 · Agent Frameworks & SDKs
Natural-language task → browser actions
Browser Use, Stagehand, Skyvern, AgentQL, Dendrite, Nanobrowser. These translate "log in and download my invoices" into clicks and form fills.
Layer 3 · Browser Automation
The execution primitives
Playwright, Puppeteer, Selenium, Crawlee (by Apify), BrowserMCP. Every layer above eventually calls down to one of these.
Layer 4 · Computer Use Agents
Models that see the screen and act
Anthropic Computer Use, OpenAI Operator, Fellou, Twin, MultiOn. Vision-driven, they screenshot the page and decide the next click rather than reading the DOM.
Layer 5 · Stealth & Anti-Detection
Where most agent tasks silently fail
CloakBrowser, Steel, Lightpanda, Pydoll, Camoufox. A flawless agent framework with a detected browser fingerprint still gets blocked.
Layer 6 · Data Extraction & Enrichment
Page → structured records
Diffbot, ScrapingBee, ScraperAPI, Zyte, ScrapeGraphAI. Turn the rendered page into clean JSON.
Layer 7 · LLM-Optimized Crawling
Crawl output formatted for models
Crawl4AI, Firecrawl, Jina AI, Apify, LLMScraper, Scrapy. Markdown/clean-text output that costs fewer tokens downstream.
Layer 8 · Network & Proxy Layer
The foundation everything depends on
Massive (affiliate), Bright Data, Oxylabs, Smartproxy, NetNut, IPRoyal. A perfect stack with a flagged datacenter IP fails at the first request.
How to use this map when debugging: When an agent fails, don't start with the framework (Layer 2), that's where everyone wastes time. Check Layer 5 (is the browser fingerprint detected?) and Layer 8 (is the IP flagged?) first. The boring layers fail more often than the clever ones. Credit: landscape mapped by Massive, Q2 2026.
5c New in Aug 2026

When an agent writes your scraper ten failure modes, from reading one end to end

The toolchains that let an LLM build a production scraper for you stopped being demos this year. The most complete public example is Zyte's open-source claude-skills plugin (v0.2.3, fifteen SKILL.md files): a staged pipeline that explores a site, negotiates a schema with you, generates a scrapy-poet project with real page objects, and deploys it to Scrapy Cloud. I read the whole repository, ran it, and wrote up thirty-four findings. The bugs themselves will be fixed and are not the interesting part. The shape of the failures is, because it is not specific to one vendor. If you let an agent build scrapers, in any framework, these ten are the ones to check for.

Credit where it is due, because it frames the rest. The page-object architecture underneath is the right call and it is why the output is repairable at all. Extraction lives in a class decorated with @handle_urls(domain) and typed Returns[ItemClass], separate from the crawl loop; the generated project ships an executable web-poet fixture suite; the schema is negotiated with a human before any code is written. A pipeline that gets the architecture right and the instrumentation wrong is a far better starting point than the reverse. Everything below is about the instrumentation.
1 · Recon reads the DOM, not the traffic
The cheap path is never even looked for
A real Chromium launches and no page.on("response") listener is registered anywhere in the repository. The entire harvest is one line: html = await page.content(). Every JSON, XHR and GraphQL response the page fetched to build itself is discarded the moment it is used. The HTML cleaner then strips <script> before the model sees anything, which deletes __NEXT_DATA__, __NUXT__ and self.__next_f — the exact places modern sites park their data.

Why it generalises: the stage that chooses between a private JSON endpoint and a headless browser is the stage that has been made blind to the endpoint. The browser decision is committed before anything could have argued against it.

Rule: recon captures the network, not the document. If your agent cannot show you the response bodies it saw, it did not choose between HTTP and browser — it defaulted.
2 · Neither browser rung scrolls
The spec is built from the first viewport
Viewport is pinned at 1280×720. The local rung waits for networkidle and snapshots; the only scroll-related call sizes the screenshot afterwards. The hosted rung sends {"browserHtml": true, "screenshot": true} with no actions array, so the provider's own scrollBottom and waitForSelector are never requested. Grep the repo for mouse.wheel, scroll_into_view or wait_for_selector and you get nothing.

Why it generalises: on any infinite-scroll listing the agent negotiates a schema against the twelve items it happened to see, and the spec looks perfectly reasonable.

Rule: the recon fetch must be at least as capable as the production fetch. Prove it on a lazy-loaded page before you trust a generated spec.
3 · Two robots.txt policies, never reconciled
Exploration ignores it; the deliverable obeys it
Grep the whole repository for robots and you get exactly two lines: the downloader sets "ROBOTSTXT_OBEY": False, and the generated project template sets ROBOTSTXT_OBEY = True. Nothing fetches robots.txt while the spec is being built, nothing checks the captured URLs against it, and nothing warns about the mismatch.

Why it generalises: the spec is built from pages the shipped spider may be forbidden to fetch, and you learn that in production, on someone else's schedule.

Rule: pin one policy across recon and production and assert it at spec time. A legal posture that changes between the prototype and the deliverable is not a posture.
4 · The recon identity outguns production
You tested with a browser and shipped a library banner
Recon drives real Chromium. The generated spider announces Scrapy/<version> (+https://scrapy.org), because nothing sets Scrapy's USER_AGENT or DEFAULT_REQUEST_HEADERS on the default path — the one user-agent constant in the settings file is provider attribution, not target-facing identity — and transparent mode is off by default. No Accept-Language, no browser Accept. Combined with ROBOTSTXT_OBEY = True, the very first request to every new domain is a robots.txt fetch from that identity.

Why it generalises: every agentic toolchain uses a heavy client to look and a light client to run. The gap between them is where the block lives.

Rule: whatever built the spec must not be more capable than what runs it. Test the fingerprint the deliverable actually ships with.
5 · Truncation becomes ground truth
A context-window trim silently redefines the site
The link extractor keeps the true total, then ships group_links[:max_links]. The next stage iterates the truncated list and writes only {url, text} — the count is discarded entirely, so nothing downstream can detect that truncation happened even in principle. That shortened list is copied into the values file, then into the spec, then verbatim into the test fixtures.

Why it generalises: every agent pipeline trims lists to fit a context window. The bug is not the trim, it is that the trim leaves no scar.

Rule: carry the true count next to every sample an LLM is shown, and fail loudly when the sample and the count disagree.
6 · The test oracle is model-authored
Green means the extractor agrees with itself
The one human-verified artefact in the whole pipeline is the corrected example values you approve during schema negotiation — the moment you say "price should be without the currency symbol". That correction is persisted correctly as schema examples and routed into the extractor as context. Then nothing ever validates against it: the value extractor matches on field name only and never reads examples, and the fixture converter stamps the model's own output as the assertion target. It also writes the whole values dict, including fields the page object was never asked to produce, because it is never told which fields the page object emits.

Why it generalises: a suite where the model writes both the code and the expected output is a regression detector, not a correctness test. Useful, but it will happily lock in a wrong answer. When no ground truth exists, compare runs rather than trusting a single model verdict, and still expect some wrong picks. Harness², an October 2026 preprint from Google Cloud AI Research, improves an agent without labels by running each proposed change on the same task as a reference run and keeping only what the behavioural difference supports. On the authors’ own benchmarks one such step raised a GLM-5 agent from 64.2 to 75.7 on WorkBuddy-Bench, three runs each, scored by LLM judges. Its own failure analysis lists nine regressions, including the selector choosing an output that scored lower than one it rejected, and an unchecked premise copied into every candidate. Read that as the ceiling of judging without truth: contrast tells you a change did something, not that the result is right. Keep the human-corrected value as the assertion target wherever you have one.

Rule: the human-corrected value is the only ground truth an agentic pipeline has. Wire it into the assertion, or admit you have no test.
7 · Coverage has no baseline
Only zero percent has a mechanical verdict
The single mechanical rule in the trust layer is "flag any expected field at 0% coverage". Everything between 0% and 100% is left to judgement: download a few items where the field is missing and decide whether it is legitimately absent. Search the repository for threshold, baseline, previous job or expected items and you get nothing.

Why it generalises: a run that falls from 40,000 items to 4,000 with every field populated passes every check. That is the most common real failure in production scraping and it is invisible to a level-based test.

Rule: coverage is a delta, not a level. Compare against the last good run and alert on the derivative.
8 · Items carry no provenance
Careful during recon, dropped on the way out
The spec side is meticulous: the metadata file records URL, captured_at, http_status, full headers, final_url on redirect, and the backlinks a page was discovered from. None of it survives into the output. The item class is built purely from the schema, the spider yields await page.to_item() and nothing more, and there is no item pipeline hook that could stamp anything afterwards.

Why it generalises: three months later the price field goes null and the only question that matters is "which URL, fetched when, with what status". If the row does not carry it, the answer is a re-crawl.

Rule: every row ships source URL, fetch timestamp and HTTP status. It costs three columns and it is the difference between a diagnosis and a shrug.
9 · The unattended watcher lies when it fails
"Cannot reach the API" reported as "still running"
The background job poller only assigns its last-seen job on a successful poll. If every poll fails — wrong endpoint, mistyped job key, a purged job returning an empty list — it prints an empty timeout after the full twenty minutes, and the skill then instructs the agent, unconditionally, to tell the user the job is still running and offer to keep waiting. Three exception classes are also outside the retry handler and kill the poller outright.

Why it generalises: this is the classic monitoring inversion, and agentic pipelines make it worse because the agent narrates the result to a human in confident prose.

Rule: "no news" and "could not ask" must be different states with different messages. If they collapse, your monitoring is a comfort blanket.
10 · The toolchain is a credential surface
The part people forget to threat-model
The cloud API helper takes the API URL verbatim from the command line and attaches Authorization: Basic <b64(apikey)> to whatever host it names — no scheme check, no host check, no allowlist; grep for urlparse, netloc, hostname or allowlist across the repo returns zero validation hits. Separately, the human-approval channel is a local HTTP server whose do_POST reads the body and writes it straight to the output path: no Origin check, no Host check, no shared secret, no size cap, no content-type check, and its logger is stubbed out. The only gate is an ephemeral port written in plaintext into a JavaScript file.

Why it generalises: an agent that reads web pages can be argued with by a web page. If it can also be handed a URL that its credentials follow, the injection has somewhere to go.

Rule: allowlist the hosts your credentials are permitted to touch, and treat any local approval channel as an unauthenticated endpoint on the user's machine, because that is what it is.
One more that is worth naming separately, because it is the most expensive kind. A shortcut in the spec stage promotes a list-page-only spec when every requested field appears somewhere in a list-page sample — and in the default invocation that gate is vacuously true, because the flag it depends on is only set for fields matching explicit hints nobody passed. The result is a spec that quietly drops detail-page extraction from the run. Elsewhere, the code generator is documented to read four files, and the output module is not one of them: it overwrites a module it never read, including the @handle_urls(domain) decorator and Returns[ItemClass] base that a previous step had carefully constructed with libcst. Both are the same class of defect — a later stage that does not read what an earlier stage produced — and it is the defect agentic pipelines are structurally most prone to, because each stage is a fresh context that only knows what it was handed.
Strategy · what to do instead
Make the network log the recon artefact. Not the HTML. A recon run that ends with a HAR, or at minimum a list of every JSON response over a few hundred bytes, lets the strategy decision be argued rather than defaulted. This single change flips findings 1 and 2 at once.

Test for properties, not for examples. The sharpest statement of the model-authored-oracle problem I have seen came from someone whose agent shipped 127 passing tests and a clean linter, including a test named after the exact promise it was violating: two distinct items never collide. The function was not injective, and the suite never looked, because it exercised the hostile inputs somebody thought of rather than searching for a counterexample. Never let the agent that writes a scraper fix also be what decides the fix is right, and never let it see the answers. In one open-source self-healing Scrapy prototype shown at Extract Summit 2026, the repair agent, working in the live repository, found the test site’s templates, a README naming the expected outcome and an API listing the ground-truth endpoints, so its workspace had to be cut down to a one-commit copy of its spider. A variant it wrote for pages it had never been shown took a Prev link for Next and delivered 445 of 566 items with no error, until the gate required held examples and page numbers that go up by one. Zyte’s CEO described the same pattern in production: an agent that added “green tea” to a list of non-colours to satisfy one failing test. Curate what the agent can read, validate on held pages it did not choose, and treat a special case added per failing test as a smell. These are one prototype’s logs and one talk, not rates. A green suite is evidence that the cases you encoded ran, and nothing more. Where a field carries a real property — uniqueness, monotonicity, a total ordering, a stable key — property-based testing searches the space instead of sampling your imagination, and it is the one addition that makes a machine-authored suite mean something.

Make the human correction executable. Wherever a person corrects a value, that corrected value must become an assertion, not a comment and not context. If your pipeline has exactly one human-verified fact in it, spend it on the test.

Emit provenance by default, strip it later if you must. Source URL, fetch time, HTTP status, and the selector or page-object version that produced the row. Four fields. They are the entire difference between debugging and guessing.

Alert on the derivative, not the level. Item count versus the last good run. Field fill-rate versus the last good run. Response-code mix versus the last good run. A threshold against zero catches the failure that was never going to be subtle.

Keep one identity policy end to end. The same user agent, the same header set, the same robots posture in recon and in production. Where they must differ, make the difference explicit and asserted, so nobody discovers it from a 403.

Treat every stage boundary as a lossy channel. The characteristic agentic bug is a later stage reading a truncated, reformatted or regenerated version of what an earlier stage knew. Carry counts alongside samples, carry file hashes alongside paths, and have the last stage assert that what it received still matches what was produced.

Threat-model the agent's own tools. Host allowlists on anything that carries a credential; authentication on any local approval server; a size cap on anything that writes to disk. Host allowlists on anything that carries a credential; authentication on any local approval server; a size cap on anything that writes to disk. Apply the same rule to scraping middleware that runs a local proxy relay. Any helper that adds your upstream Proxy-Authorization to forwarded traffic has to listen on loopback or check who connects. Otherwise it is an open proxy billed to your account. In scrapy-stealth 1.0.0, turning on the new STEALTH_CDP_URL mode moves that relay from 127.0.0.1 to 0.0.0.0, still with no client authentication. On a cloud VM with a permissive security group, that can put your residential credentials in reach of anyone who finds the ephemeral port. This comes from reading the source, not from a demonstrated exploit, but the check is cheap. Before running any stealth layer, grep it for start_server, 0.0.0.0 and what it writes into site-packages or sends home at startup. Then run ss -ltnp during a crawl to see what is actually listening. If you need a remote browser, put it behind a firewall rule or an SSH tunnel. The agent reads untrusted text for a living.

And keep the good part. Page objects, a negotiated schema, an executable fixture suite and a surgical repair path are genuinely the right architecture for machine-authored scrapers. The lesson from reading this end to end is not that agents cannot build scrapers. It is that they build the code well and the instrumentation badly, and the instrumentation is what you will be living with.
6 field notes on AI and LLM extraction Reported from practice. Click any card to open it.
+
Aug 2026 / Worked build
A Costed RAG Ingestion Pipeline, With the Refresh Problem Actually Solved
This guide has argued for a while that a RAG system is bounded by its acquisition layer — a vector store indexed six months ago is a model with a stale cutoff. Here is a full worked pipeline for the collection half, including the two parts most write-ups skip: how it stays current, and what it costs.
💡 Incremental sync by content hash, and a live fetch when the index is known stale
The gap it starts from is that a cloud provider's native web crawler for its own knowledge-base product handles static sites only — it breaks on JavaScript-rendered pages, on bot protection, and on geo-restricted content. Which is a neat statement of why this guide exists: the managed ingestion tool assumes the easy half of the web.

The shape. An unblocking API fetches clean Markdown from pages that would otherwise be unreachable, lands it in object storage with metadata sidecars, embeds it, and stores vectors. Then the parts worth stealing: incremental syncs driven by content hashing so unchanged pages are not re-embedded, cited answers via retrieve-and-generate so a claim can be traced to a source, a golden-set evaluation script so a change to the pipeline can be scored rather than eyeballed, and hybrid routing that falls back to a live fetch when the best retrieved chunk falls below a relevance floor.

That last one is the design idea, and the trigger matters. It is not a staleness clock — it is the index admitting, per query, that it does not hold a good enough answer. Most RAG builds treat the index as the truth and quietly serve the closest thing they have, however far away that is. Scoring the best chunk and routing past the index when it is too weak makes freshness a runtime decision rather than a cron job's problem, and it degrades to a live fetch instead of to a confident wrong answer.

Reading the repository rather than the announcement adds the parts worth stealing. Change detection hashes the raw scrape, not the stripped body, so cosmetic boilerplate churn does not force a re-embed, and last-seen is tracked in a separate ledger from content age. A render check exists as its own step because it has to: on a JavaScript-only page a plain GET returned 96 characters and rendering returned the whole thing. Boilerplate stripping removed 21% of characters batch-wide. Near-duplicates go through MinHash/LSH. A reconcile step retires URLs that have dropped out of the source list, which is the unglamorous half of freshness nobody builds. The golden set scores hit@k and MRR rather than vibes — the six-page pilot reported hit@5 of 100% and MRR 0.88.

And it is costed, which is rarer than it should be: a six-page pilot with weekly refreshes runs about four cents a month. Small enough to be a toy, but the unit is right — cost per page per refresh is the number that decides whether an index of ten thousand pages is a line item or a project. Vendor-authored, so read the tooling choice accordingly; the architecture is independent of it.
+
Aug 2026 / RAG and retrieval
Your RAG Pipeline Is Only as Good as the Layer Nobody Budgeted For
The argument, stated plainly: the quality of a RAG system is bound entirely by the quality of its retrieval layer. Everything downstream — the embedding model, the chunking strategy, the reranker, the prompt — is working on whatever the acquisition layer handed it, and cannot recover what was never collected.
💡 A vector store indexed six months ago is just an LLM with a stale cutoff
Four failure modes, and the third is the one that quietly ruins corpora.

Freshness. A static knowledge base decays. Index six months ago and you have rebuilt the exact knowledge-cutoff problem RAG existed to solve, only now you own the infrastructure too.

Coverage. Search engines index a fraction of the public web. Recently published pages, low-authority domains, anything dynamically rendered, and anything behind an anti-bot are invisible to search-backed retrieval. If your pipeline gets its documents through a search API, your corpus has the shape of someone else's index, including its blind spots.

Extraction fidelity. Feeding raw HTML in means navigation, footers and cookie banners enter the corpus as content, and in the author's phrasing that is noise no chunking strategy can fully recover from. Boilerplate does not just waste tokens, it competes for similarity with the text you wanted.

Chunking is downstream of all of it. Poor source text produces semantically incoherent chunks, which lowers retrieval precision, which looks like a model problem and is not.

The practices that follow. Tiered crawl schedules matched to how fast each source actually changes, rather than one global interval, so breaking news is hourly and reference pages are weekly. Version-controlled vector stores, so a retrieval result is reproducible and auditable months later. Extract to structured JSON or Markdown, never raw HTML. Treat freshness as an SLA you can be held to rather than a vague intention. And use purpose-built scrapers for the sources that matter to you instead of outsourcing coverage to a search integration.

This is the same economics as the cost-per-usable-document material in the cost section, arriving from the AI side: the expensive failure is never the fetch, it is discovering months later that what you collected was incomplete, stale or full of furniture. Full piece

Oct 2026 update. A follow-up Web Scraping Club piece (Federico Trotta, 13 Sep) shows the search-integration risk in chat. Claude, ChatGPT and Perplexity ran different queries and cited different sources for the same prompt, and changing “tell me” to “find” changed Claude’s second query. Given a paywalled URL, Claude said it could not open it and answered from “other coverage”; ChatGPT and Perplexity did not flag the failure. Screenshots only, no counts or repeats.
+
Aug 2026 / Agent economics
The Accessibility Tree Turns Out to Be the Cheapest Selector Nobody Uses
Most browser agents already read the accessibility tree, and it buys them almost nothing, because they still run the same loop: look at the page, ask a model what to do, click, look again. A team at Evinced tried using the semantics to make the behaviour deterministic instead, and measured it across 300 tasks on 136 live sites.
💡 Roles and accessible names survive redesigns that kill CSS and XPath
Two mechanisms, both boring in the good way. Pre-defined skills: accessibility standards already specify how a dialog, a menu or a combobox is supposed to behave, so an agent that recognises the pattern can operate it as one atomic action with no model round trip. Picking an option from a custom dropdown goes from screenshot → DOM → LLM call → click → screenshot → DOM → LLM call → click, six steps and two round trips, down to: read role=combobox with accessible name "Fare", then selectOption("Economy"). One step, zero round trips. Learned skills: roles and accessible names stay stable across sessions in a way generated selectors do not, so a successful run can be cached against them. A ten-step flight search on united.com collapses into find_flights(origin, destination, dates) and later runs just fill in the parameters.

Against Browser-Use on the Online-Mind2Web benchmark: 5.4× faster and 3.2× cheaper in tokens on average. The combined price-performance number is the one worth sitting with — roughly 32×, not 17×, because the two gains are positively correlated: the flows where you cut the most tokens are the flows where you cut the most time, since both come from deleting the same round trips. The distribution is heavily right-skewed, with about 11% of flows over 100×. Sites that score well on accessibility checks gave much bigger gains.

Why this belongs in a scraping guide rather than an accessibility one. The thing that makes agent automation expensive is not the browser, it is the model call between every action, and the accessibility layer is a pre-existing, standardised, semantically-labelled index of the page that most scrapers ignore in favour of selectors they have to repair. If you run agent-driven extraction at any volume, the role/accessible-name pair is a selector strategy with a maintenance profile better than CSS and a cost profile better than vision. The honest caveat, from the authors: what an agent needs from a page is a subset of what a person needs, so agent-readiness does not mean a site is actually accessible. Full writeup in The AI Journal
+
Concept / Shift in thinking
The Web Is Becoming a Real-Time Database for AI Crawlers
Classic scraping: crawl on a schedule, store in a database, query the database. With cheap residential proxies, a new pattern emerged: don't store anything. The target website is your database. Scrape on demand, treat every page load as a query. For AI crawlers especially, why save data when you can fetch it live whenever you want?
💡 Resproxies made scraping cheap enough to skip storage entirely
This is a genuine shift in how scraping infrastructure gets designed. The old model assumed scraping was expensive and risky, so you scraped once, stored aggressively, and served queries from your own database. That meant stale data, storage costs, and sync logic. Residential proxies changed the economics: scraping became cheap and reliable enough that for many use cases you can just hit the live site every time a user (or an AI agent) asks. The website hosts the data, you treat it as a remote database, and the latency tradeoff is acceptable because the customer is willing to wait a few seconds for fresh answers instead of getting instant but stale results. For AI crawlers and agentic workflows this is even more pronounced, there's no point caching a price or a flight time when the agent can re-fetch it at query time. The implication for anti-bot teams: scraping volume is no longer bounded by "how often do they refresh their DB", it's bounded by "how often does a user ask", which can be far higher and far spikier.
+
RAG / LLM Pipelines
Keep Your LLM Context Fresh: Incremental Indexing
Scraped data goes stale fast. CocoIndex builds a continuously updated vector indexonly changed rows re-run. Pgvector, LanceDB, Neo4j targets. #1 GitHub Trending on launch.
💡 github.com/cocoindex-io/cocoindex, incremental RAG for LLM agents
The core problem: you scrape a site, embed it into a vector store, and 48 hours later 30% of the content has changed. Traditional batch re-indexing re-processes everything. CocoIndex solves this with a Rust-based delta engine: it tracks byte-level lineage per document, and when you re-run it only processes changed chunks. Target vector stores: Pgvector (PostgreSQL), LanceDB (local), Neo4j (graph). Python with Rust core means the delta calculation is very fast even on large corpora. The LLM integration: your agent always queries a fresh index, so answers reflect current scraped data. Setup: pip install cocoindexconfigure sources (files, URLs, S3), define your chunker and embedding model, run cocoindex.build()done in under 10 minutes.
+
Pattern · Self-healing scrapers · June 2026
The LLM as Compiler and Oracle, Not Runtime
A self-healing scraper design where the LLM compiles a cheap deterministic crawler once, then your pages run at zero model cost. When a redesign breaks the crawler, the model serves that one request live to avoid an outage while it regenerates a new crawler in the background. The hard part is not the regenerating, any model can rewrite a broken scraper. The hard part is trusting the rewrite before it ships.
💡 The moat is not the heal. It is the proof that the heal is right.
CRAWLOOP: SELF-HEALING VIA COMPILE, NOT RUNTIME the LLM compiles a cheap crawler and judges its replacements; it is not in the hot path LLM: COMPILER + ORACLE writes the crawler · is the source of truth DETERMINISTIC CRAWLER $0 / instant · no model calls EXTRACTION PIPELINE free, reproducible data WEBSITE LAYOUT CHANGE break detected compiles once breaks it break detected LLM SERVES THE REQUEST LIVE 0 downtime while it regenerates PROMOTION GAUNTLET a regenerated crawler is trusted only if it passes all of these regenerate in bg ORACLE agrees EVERY item ≥0.98 RECORD count must match exactly SAMPLES holds across ≥3 runs never a mean no missed rows no lucky page TIERED MODEL ESCALATION cheap model default → strong only if stuck TRUST score FAIL regenerate, try again PROMOTE → DEPLOY new crawler runs at $0 again promoted crawler replaces the broken one, back to $0 deterministic extraction Proven live against a real public site over real HTTP: cheap model failed the gauntlet, auto-escalated, promoted a crawler that then extracted a fresh page (title, price, stock, URL, image) for $0 with zero model calls. The architecture treats the LLM as two roles at once. As a compiler it turns a target into cheap deterministic extraction code that runs for free. As an oracle it is the source of truth a regenerated crawler must agree with before it is trusted. Between them sits the piece that makes the whole thing safe to run unattended: a promotion gauntlet.

How a regenerated crawler earns promotion:
• It must agree with the LLM oracle on every item, not on average. A high mean is not good enough; one disagreement fails the batch.
• It must return the exact same record count as the oracle pass.
• It must hold across at least three independent samples, so a lucky single page cannot promote a broken crawler.

Tiered model escalation keeps the cost sane. A cheap model runs the regeneration by default; the loop only escalates to a stronger model when nothing clears the gauntlet. Whatever gets promoted is still free deterministic code, so the one-time model cost amortises across every later run.

This matters because "self-healing scraper" demos are easy and trustworthy self-healing is hard. Any model can produce a plausible rewrite. The engineering is in the verification that decides whether the rewrite is correct before it touches production data, which is the same adversarial-verification idea this guide's AI Workflow is built around. It pairs naturally with the agentic reverse-engineering shift in the next card: the model drives the toolchain, but a gauntlet, not the model's confidence, decides what is true.

The proof that separates it from a demo: the author ran the whole loop live against a real public site over real HTTP, not a saved fixture. The cheap model could not clear the gauntlet, so the loop auto-escalated to a stronger model, promoted a crawler, and that promoted crawler then extracted a fresh page (title, price, stock, URL, cover image) for zero cost with zero further model calls. That is the claim that matters: not "an LLM fixed my scraper" but "the system decided, on its own, that the fix was trustworthy enough to run unattended, and it was right." The honest weak spot is in the open too: holding a greedy oracle to exact agreement across a twenty-item listing is hard, and that is where it currently strains.

Pattern and live proof of concept shared publicly by the author of Crawloop (Apache-2.0 alpha POC, github.com/Jimmynycu/Crawloop), June 2026. Honest status from the README: a working POC without the managed proxies, scale, or dashboards the funded self-healing tools (Kadoa, ScrapeGraphAI, and others) ship.

When DIY cost exceeds platform cost, these services handle the heavy lifting. Each solves a specific problem, choosing the right one depends on which wall you are facing and at what scale.

A shift worth watching: browser agents are becoming coding agents. The first generation of browser agents worked by translating the page into something a model could digest, flattening the DOM, the accessibility tree, a screenshot and browser state into a simplified representation and asking the model to choose an action against it. That representation is lossy by construction, and every extraction is bounded by what the harness decided to include.

The newer approach inverts it. Rather than handing the model a summary of the page, hand it the Chrome DevTools Protocol directly and let it write its own low-level browser code: explore, form a hypothesis about where the data lives, write the query, extract exactly what it needs. The team behind one of the most widely used agent frameworks describes this as their current direction, and reports two related observations, that stronger models now rarely reach for screenshots, and that they prefer working against structure over pixels.

The reason this belongs in a scraping guide rather than an agent guide is the economics it implies. An agent that writes code once and runs it is doing the compile-and-run pattern described elsewhere in this section, arriving at it from the other direction. An agent that reasons over a screenshot on every page is paying model inference for every row it extracts. The first scales, the second demos.
The other way to slice it: the four layers of the runtime loop

The eight-layer map above is a catalogue of tool categories, useful for asking "which product do I need for this job." There is a second, orthogonal way to read a browser agent that is more useful for reasoning about latency and token cost: the four layers its runtime loop actually passes through on every single action. A model decides, something executes that decision against a browser, the result comes back, repeat. Those are four distinct layers, and many products blur several at once, which is exactly why they are hard to compare.
The four layers
Model, harness, driver, engine
The model reads context and picks the next action; it cannot fetch a page or click on its own. The harness is the agent program wrapped around it (Claude Code, Codex, and other generalists) that holds the conversation, exposes tools, and feeds results back, with the browser as one tool among many. The browser driver gives the harness hands on the browser (agent-browser, browser-use, Stagehand, and Playwright or Puppeteer as libraries your code imports), translating an intent like "click" into the protocol the engine speaks. The engine is the browser itself, loading pages and running their JavaScript to expose a DOM. Naming a product's layer tells you what it does for you and what you still have to supply around it.
Why the shape matters
Every boundary is a translation tax
Each layer boundary is a place where a call is serialised, crossed between processes, and deserialised on the way down, with the result climbing back up the same way. A typical Chromium setup runs the automation layer in its own Node or Python process and the browser in another beside it, so every action pays that translation tax in latency, CPU, and memory, and everything that reaches the model costs tokens. Two design moves attack this. Collapsing layers into one process (an engine that speaks its driver and loop natively, with no CDP hop) removes the cross-process cost. And compacting what the model sees: returning a compact accessibility-tree snapshot with short element refs runs a few hundred tokens against the several thousand you would spend dumping raw DOM, the same "don't pour the DOM into context" lesson from the extraction section, applied to the agent loop.
One consequence is worth its own line because it changes the cost model. If the loop is deterministic once the model has solved a task, you can run the reasoning at build time, once, record the resolved sequence of actions as a plain script, and replay that script afterwards with no model in the loop at all. The reasoning is paid for a single time; every replay after that costs zero tokens.

If you let a coding agent drive a browser through a thin CDP harness that writes its own helpers, budget for the harness to grow, and keep the fetch-first rule. Browser Use’s Browser Harness was launched as roughly 600 lines across run.py, helpers.py and daemon.py. Five months later the same three files total 1,975 lines, and the repo also has auth, MCP and recording modules and about 97 domain-skill folders. So the claim that you get a small core the agent extends holds only partly: the vendor’s core keeps growing as well. Treat agent-written helpers as code you review before reuse. The published cases, such as an upload helper that moved to chunking after a 10 MB websocket limit, come from the vendor and give no success rates. A single local daemon has one current tab, so run parallel agents through separate browsers or queue them one at a time. The harness’s own skill file gives the same rule as this guide: use curl for a public page, and open the browser only when that fails or returns an empty shell.

For a recurring browsing job (a scheduled price check, a nightly login-and-export) this is the difference between paying an LLM on every run and paying it once. The record-and-replay pattern only holds while the target's flow is stable, a layout change invalidates the script and sends you back through the model once, which is simply the self-healing trigger from the architecture section wearing a different hat.

The injection surface that never crosses the network. Anyone pointing an agent at the open web should understand one delivery mechanism in particular, because it defeats the obvious defences. Everything after the hash in a URL is the fragment, and the fragment is never sent to the server. It stays in the browser. That means no network appliance, no WAF, no proxy log and no server-side filter can inspect it, because none of them ever receive it.

The attack writes instructions into that fragment, appends it to a link to a genuinely trustworthy site, and distributes the link normally. The page loads clean because the page is clean. When the reader asks an assistant built into the browser to summarise what they are looking at, the assistant reads the full URL, fragment included, and treats the text it finds there as part of what it is working on.

Two rules follow, and they apply whether you are building an agent or using one. Content retrieved from the web is data, never instruction, and that includes the URL itself rather than only the page body. Keep the scope of an agent’s browser out of the model’s reach. If the agent can pass its own allowed-domains list or browser-session name as tool arguments, a page that injects instructions can widen both. One open-source research workbench that drives the user’s real, logged-in Chrome strips those arguments from every model-issued browser call and sets them from the application’s settings and the conversation’s identity instead, so a prompt cannot reach another conversation’s browser. That is the shape to copy: the model chooses what to click, and the application decides where it may go and whose session it acts in. Check that such a guard is actually loaded, though: the same project’s source records an earlier version in which the guard module failed to register and was silently never applied. And an agent with both browsing and any acting capability needs those separated by something more deliberate than the model's own judgement, because the model is being handed the attacker's text through a channel your infrastructure cannot see.
The CAPTCHA reality check: the LLM in your agent is almost never the thing solving it

Every agentic browser ships a "solves CAPTCHAs" bullet point. Open the code of the open-source ones and a different story appears: almost none of them use the LLM to solve a visible challenge. They either decline to try or try and fail. One popular open agent's entire CAPTCHA file just waits for a solver running in the vendor's cloud and blocks the loop until it answers; run it locally with your own model and there is nothing to wait for. Another, asked directly how its open release solves CAPTCHAs, ships a handler that logs "please solve the captcha, you have 30 seconds", sleeps for thirty seconds, and returns success no matter what happened. The honest maintainers say so outright: the anti-bot pieces are kept in the closed cloud product on purpose. Treat a vendor’s closed solver as something to test, not assume, and on an invisible score-based CAPTCHA try the run with it switched off. In one October 2026 review of a hosted browser’s built-in solver, it cleared a checkbox CAPTCHA in 5 of 7 runs (10 to 35 seconds each; the other two hung for 75 seconds). On a login guarded by invisible reCAPTCHA Enterprise it failed on every try, while the same stealth session with the solver disabled let the page mint its own token and logged in 2 of 2. A score-based check grades the whole session, so an injected token can do worse than the page’s own. The runs are small, on two sites in one country, and the vendor says a fix is in progress, so rerun before relying on either result.
So when a product says its agent "solves hCaptcha", it is doing one of four things, and a model reading the page is not on the list. Either it stays stealthy enough that the visible challenge never fires (clean residential proxies plus fingerprint shaping, so the score stays low and no puzzle appears), or it declares itself an allowlisted bot and is waved through by a business arrangement with the CAPTCHA vendor (the verified-bot path, the same idea as Web Bot Auth), or it ships its own solver for some challenge types and documents a third-party solver service for the rest, or it runs an in-house solver (vision model or classical computer vision) that owns the whole stack. The marketing word "AI" sits on top of all four, but the actual work is proxies, partnerships, or a dedicated solver, not the agent's language model puzzling out the image.
The detail that decides what is even possible
The same-origin boundary around the widget
hCaptcha renders inside an iframe whose origin is hcaptcha.com, cross-origin to the host page. The solve happens entirely inside that frame, and when it finishes the widget writes a token into a hidden textarea[name="h-captcha-response"] the host form submits. An agent driving the outer page over Playwright or CDP has full control of the host but only limited reach inside the widget frame, because the same-origin policy blocks it. A browser extension, which runs with cross-origin privileges, can observe and click inside the widget frame. That asymmetry, not model intelligence, is why extension-based solvers work where a page-driving agent stalls.
What this means for your pipeline
Plan around it, do not buy the bullet point
The practical reading: invisible challenges (reCAPTCHA v3, Turnstile) are a fingerprint and proxy problem, solve those upstream and the puzzle never shows. Visible image challenges (hCaptcha, reCAPTCHA v2) still demand an answer, and in 2026 that answer comes from a dedicated solver service or an in-house vision model wired in through an extension or API, not from your agent's LLM reading the screen. Budget and architect for that explicitly rather than assuming the model handles it, because the open-source evidence says it does not. Treat any in-house image solver as an upkeep commitment whose pace the defender sets. Practitioners describe the standard production setup as one dedicated classifier per challenge type, which means a new labelling and retraining round every time a variant appears, plus downtime while it trains. Cloud vision models are reported as too slow and costly for that traffic. A small self-hosted model tuned once might shorten the loop, but the only public figure, 90%+ on unseen classes, is one person’s unpublished claim with no split, latency or pass-rate definition. The same practitioner credits the challenge vendor with an obfuscated WASM VM and fingerprint probes that flag anti-detect browsers, so solving the image does not settle the request. The cheaper path remains the one above: keep the challenge from appearing at all.
6b The new client

Agentic browsers
the visitor that reads, decides, then clicks

A category that did not exist in a serious way two years ago and now sits on both sides of this guide: it is the thing you might build with, and it is the thing arriving at your target. Build on it knowing the money behind it. Verified rounds from May to September 2026 alone include Exa ($250M at $2.2B), Oxylabs ($130M at about $3.6B, its first outside capital), Parallel Web Systems ($100M at $2B) and Firecrawl ($75M), and lifetime totals across 13 agent web-access companies reach roughly $1.3B. That capital buys below-cost pricing today and makes consolidation or price rises likely later. It also pushes infrastructure vendors up-stack: a proxy or scraping supplier with fresh capital may launch the same agent search API you are building on its network. Keep your fetch and search providers behind an interface you can swap, and do not size a business case on today’s per-call prices. When you compare search-and-extract providers for an agent, split the score in two: what each search surfaced, and how each extractor did on the URLs both returned. In one vendor-run comparison of Apify’s RAG Web Browser and Jina AI Search on a single query, the headline was 8 usable results against 6 and one access failure against three, yet the six sources both tools returned were usable on both, and all three of Jina’s failures (403s and a 401 from Forbes, Reddit and Reuters) were pages the other tool never returned. A blocked publisher in the result list is a discovery outcome, not an extraction defect, and a 47% larger text haul is also a larger token bill for the model that reads it. Run several of your own queries on the topics your agent actually serves, and define “usable” before you look at the results. When a search API can answer for you, switch that off and ask for the page text instead: request raw content, keep the URL beside it, and run your own model over it, so the corpus you audit is the one your agent actually read. Check the vendor’s defaults before trusting a wrapper’s promise. On Tavily, the generated answer is already off by default and raw content is off too, so raw content is the one flag that changes what you receive; the snippet field is still generated server-side according to search depth, and the Research endpoint returns a report written by Tavily’s own model. A client that advertises “no AI post-processing” can only vouch for its own side of the wire, never for the vendor’s. Widely shared “$1B in 12 months” tallies mix older rounds with lifetime totals, so check dates before quoting them. Worth its own section because the economics, the failure modes and the threat model are all different from headless automation, and most of what is written about it is either marketing or panic.

The one-line definition, because it gets muddled with headless browsing. A headless browser runs a script you wrote; when the UI changes, the script breaks. An agentic browser puts an LLM in the decision layer and runs a loop of perception, planning and action, so it adapts when the UI changes instead of breaking. That is the whole trade: you swap a deterministic thing that fails loudly for a probabilistic thing that fails quietly, and you pay per decision.

Four categories, and they are not competing with each other

People argue about these as if one wins. They serve genuinely different jobs, and the distinction that matters is who the browser is for and whose session it uses.

1 · AI-native consumer browsers
A person is still in the loop
ChatGPT Atlas, Perplexity Comet, Opera's AI browser, The Browser Company's Dia, and Chrome itself now that Auto Browse handles multi-step tasks from natural language, with Gemini Nano on-device for low-latency work and a persistent preference layer across the user's Google account. These are full browsers built around a model as the primary interface. They drive the lower layers on behalf of a real user, which is a large part of why browser vendors quietly stopped enforcing automation-transparency flags.
2 · Agent-first engines
No tabs, no address bar, no human
Cloudflare Kitesurf, Moli, Obscura, Lightpanda. Built on the premise that when the consumer is a model, most of the rendering stack is dead weight: it wants structured content, a small token count and a cheap session it can throw away. Kitesurf is Blitz rendering plus Firefox's Stylo plus the Boa JS engine, shipped in twelve weeks on Workers. Moli is a Rust kernel over libcurl, html5ever, V8, Stylo, Taffy and Parley. Both keep CDP so existing Playwright and Puppeteer code connects unchanged. Know what the light engines buy you on a defended page before you route to them. In one open benchmark run in September 2026, against 100 bot-protected URLs with four attempts each, Moli passed 44 direct, Lightpanda 38 direct and 44 through a filtered residential pool, the Vercel agent browser 30 and Obscura 27, while the best anti-detect browser in the same run reached 66. Footprint is where the light engines earn their place: Lightpanda averaged about a quarter of a core and peaked under 1 GB, where Camoufox averaged 1.3 cores and peaked near 4.7 GB. Lightness is not automatic, though; Obscura in that run peaked above 8 GB. Route by door: send open and lightly protected pages to the light engine and escalate to a full anti-detect browser only where it fails. Single run, one US region; expect the numbers to move day to day.
3 · Local, on your own session
The blocking problem solved by not leaving home
BrowserOS neo and its category. Runs on the user's machine, imports their real Chrome sessions, and shows the work: a live cockpit, session replay as scrubbable video, parallel agents in isolated tabs. No proxy, no reused-IP reputation, no credential handed to a third party. Correct architecture for authorised, logged-in, user-owned work, and the wrong one for public collection at volume, because it is one machine on one residential IP.
4 · Frameworks that drive a browser
Where most engineering actually happens
Browser Use, Hermes, Vercel's agent-browser, Libretto's browser tools, stealth-browser-mcp. These are not browsers, they are the tool surface between a model and one. That makes them the least glamorous category and the one where the cost is decided, as the numbers below show. The trend across all of them is identical: away from one tool per action, toward the agent writing a script.

The cost is the tool surface, not the model

This is now the best-evidenced claim in agentic browsing, with five independent measurements from five teams using five methods. It is also the one most likely to save you money this week, because on most frameworks it is a configuration change rather than a rewrite. When you choose a scraping MCP server for an agent, count the tools it registers before you compare anything else, and load only a subset where the server allows it. One vendor comparison of nine scraping MCP servers, checked against documentation rather than run, lists about 7 tools for Jina, 9 for Crawlbase, 26 for Firecrawl’s full profile and 28 for ScraperAPI. That is a fourfold spread in what rides along on every turn before a single page is fetched. Bright Data exposes GROUPS and TOOLS settings so you load only what you call. The counts come from vendor documentation and shift between versions, and nobody has published a token cost per server, so measure your own system prompt with each candidate installed. Measured counts for a few keyless servers now exist, so use them as a floor and then check your own build. Counted with one OpenAI tokenizer on 12 October 2026, Playwright MCP 0.0.83 declared 25 tools in 4,450 tokens and Chrome DevTools MCP 1.10.1 declared 30 in 6,527, well below the older figures quoted below for the same servers, and six servers together came to 15,950. The same run found that one Playwright snapshot of a single Wikipedia article cost 31,565 tokens, so for a scraping agent the pages it reads outweigh the menu within a call or two. Treat these as one run on one tokenizer without any client wrapper, and re-measure after every server upgrade, because tool menus change between releases. A crawler that only ever calls one fetch tool gains nothing from a 26-tool menu, and it pays for that menu on every step. Count the menu for every server you add, including the open-source ones. Scrapling’s v0.5 MCP server, counted the same way with tiktoken’s o200k_base over each tool’s name, description and input schema, registers 16 tools in 10,645 tokens. Its chained browser_actions tool alone is 2,301. That is more than twice the 4,450 measured for Playwright MCP 0.0.83’s 25 tools, even though the release notes describe leaner tool descriptions. Also check what the server does for you before you pay for it. The headline claim that it “can solve almost any CAPTCHA” is demonstrated by an AI agent driving its browser tools, while the documentation names only Cloudflare Turnstile and Interstitial handling. These counts come from one run and one tokenizer and will move between releases, so re-measure.

Where the tokens actually go. An MCP tool surface ships its JSON Schema definitions into context on every session, whether you call the tools or not: Playwright MCP costs roughly 13,700 tokens before you have done anything, Chrome DevTools MCP roughly 17,000. (A published October 2026 measurement put the declarations at about 4,450 and 6,527 tokens respectively: the count depends heavily on server version and which tools are enabled, so measure the surface you actually load.) A CLI-shaped surface costs zero, because there is nothing to declare. Then it compounds per step. A single page snapshot: ~1,000 tokens against ~15,000. A ten-step automation flow: ~7,000 tokens against ~50,000 for Chrome DevTools MCP and ~114,000 for Playwright MCP. That is a sixteen-fold spread on the same work, decided entirely by the shape of the interface. The corollary is a reading rule for every headline percentage a vendor prints: ask which of those two streams it counted. A cut in the definitions you carry and a cut in the tokens a finished task actually burned are different measurements, and the largest published numbers are all the first kind. Cloudflare’s Code Mode reaches 2,500-plus API endpoints through two tools in roughly 1,000 tokens and calls that a 99.9% reduction in input tokens — against a 1.17-million-token baseline it calculated for a one-tool-per-endpoint server nobody ever built or metered. Anthropic’s equivalent, 150,000 tokens down to 2,000, is an illustrative calculation over a single snippet by the party promoting the approach. Neither publishes what a task on that surface costs to finish, and the only task-level figures in circulation — 32% on a simple job, 81% on a thirty-one-event one — come from a single unrepeated demo of a much smaller calendar agent, where most of the 81% looks like batching rather than schemas. Nobody in that set publishes an error or retry rate for model-written code, so none of the savings is netted against the script the model got wrong. Count the tokens in your system prompt before the first user message, then size the prize honestly: the win scales with how many definitions you carry, so a crawler holding one scrape(url) → markdown tool has nothing there to cut. Its bill is the payload it drags back. Trim that first, because it is free, and the menu second, because it costs you a sandbox.
Vercel · agent-browser
17 tools → 2
3.5× faster, 37% fewer tokens — and the number people skip: success rate went from 80% to 100%. Fewer tools did not merely cost less, it made the agent more accurate. Rust CLI for sub-millisecond parsing, a Node daemon holding a warm browser, CDP underneath, and compact accessibility-tree snapshots at 200 to 400 tokens.
Hermes · browser-use CLI 3.0
Twelve tools → one script
Measured by Nous Research at 48 to 66% fewer tokens with no drop in accuracy. The diagnosis was that a dozen tool schemas rode along in every request whether used or not, and opening a page to read one headline cost four or five round trips.
Libretto · browser tools
~70% fewer tokens
Across 26 tasks on a local desktop Chrome, ~61% cheaper on one agent and 54%/38% on another. Same model, same browser, only the tool surface changed. Stock tools were chatty: large page payloads dumped into context, a wide tool menu, the model re-reading what it already had.
Evinced · accessibility tree
~32× price-performance
5.4× faster and 3.2× cheaper across 300 tasks on 136 live sites, combining to roughly 32× because the gains correlate. Operates known UI patterns as atomic actions with no model round trip at all, and caches learned flows against roles and accessible names rather than selectors.
Browser Use · BrowserCode
Screenshots are out of favour
Strong models rarely want screenshots and prefer inspecting the page through raw CDP, writing their own low-level code, finding internal APIs and taking JavaScript shortcuts. The exception is website QA, where an agent constrained to behave like a human is still the better instrument. Remember that inspection is itself observable. Agent tools that attach over CDP to read scripts or measure coverage typically enable the Debugger domain, and at least one popular reverse-engineering server does so during page capture; while that domain is on, V8 changes how ordinary page code runs, and a page can test for it with an empty Promise.all (reproduced on V8 14.6). The same server’s documentation warns that precise coverage changes timing and JIT behaviour. Run reconnaissance in a separate profile and session from the crawler that follows, so whatever the target learns about the inspection is not attached to the identity you later scrape with.
Moli · engine layer
73 MiB vs 773 MiB
A tenth of headless Chrome's memory at level success on a 192-URL crawl (53.6% against 52.6%), with CDP ready in 34.85ms against 169.37ms. Layout and paint are off by default and only happen behind a flag. Note that half that crawl failed for both engines: the engine was never what decided whether you get in.
The single sentence to take from all of it. The expensive thing in agentic browsing is how often the model has to look and how many schemas it carries while looking — not how well it thinks. Every team that fixed it made the same move, from one tool per action to one script per task. And the Vercel accuracy result suggests the reason is not just economy: a smaller tool surface leaves the model less to be wrong about.

The security problem is architectural, not a bug queue

At Black Hat USA 2026, Brave's Artem Chaikin presented "Attacking and Defending AI Browsers" and tested Comet, ChatGPT Atlas and Opera's AI browser against indirect prompt injection. None of them held. No malware, no exploit chain, just words on a page the model decided to trust. The same class has separately been reproduced against MultiOn, Browser Use and OpenAI Operator, so this is not three unlucky products. The cause is that these agents feed raw page content into the model with no separation between the user's instructions and the page's content, which is not a defect you can patch out of an architecture built to follow instructions from whatever it reads. There is now prevalence data, and it names scrapers as the main audience. A 2026 measurement study scanned 1.2 billion URLs and validated 15,387 prompt injections on 11,722 pages. Crawlers and data scrapers were the dominant target class. About 70% of the injections sat in HTML that never renders, such as headers, comments and metadata, and JSON-LD carried 26% of those in page bodies. So treat structured data as untrusted text before any model reads it, and prefer inputs that keep structure: across the study’s 5,200 controlled trials, compliance reached 8% for smaller models on plain text and fell to 0.2–1.1% when structural cues were kept. The authors call in-page injection not yet a dominant threat, but real and widespread enough to deserve attention. We spent a decade teaching browsers to distrust the page. Agentic browsers handed that trust back by default. And nobody upstream is going to hand it back for you, which is why the confirmation gate has to be code you own rather than a box ticked during procurement. Score the market on its own documentation and the safety column is close to empty: across 45 browser agents rated 0–2 on each of ten capabilities, one of which is confirmation before an irreversible action, transactional ability averaged 0.43 of 2, the lowest of the ten, and exactly two — Amazon Nova Act and ChatGPT Desktop — document both completing a purchase and pausing to ask first. Of the thirty-five that fully document finishing a multi-step task, eighteen document no confirmation step at all, and seventeen document running with no active user session behind them. Read those scores as a ceiling rather than a floor: the rubric only subtracts when testing contradicts a claim and never adds, so no tool can score above what it wrote down, and a careful product with thin documentation scores zero on a gate it genuinely ships. Discount further for the publisher — a firm selling access infrastructure to agents, running each task twice with its own team, naming no versions, dates or dataset. What survives the discount is the shape rather than the decimal. Defending a site, assume the agent arriving at your checkout has nothing human behind it. Building one, assume nothing upstream will stop the call, and put the stop in your own path.
The WebMCP chain
Injection becomes authenticated action
A page can declare named tools with typed inputs that an in-browser agent invokes inside the user's authenticated session. So the attack stops being informational and becomes transactional: plant hidden instructions in content the agent will read, the agent treats them as a user directive, and it calls your tools with attacker-controlled parameters carrying the user's cookies, CSRF token and permissions.

The worked example is a line of hidden text in a product review saying to call updateEmail with the attacker's address. And it chains: read cart, apply coupon, change address, check out. Named vectors to worry about are anything the agent ingests without you writing it — reviews, emails, fetched pages, PDFs, support tickets.
HTML-in-Canvas
Why text sanitisation is not enough
The vector that defeats the obvious mitigation. Fraudulent instructions are rendered inside a canvas element, so they never exist as text in the DOM at all — and a vision-language agent reads them as page content anyway.

That matters because the first thing most teams reach for is a filter that strips known injection phrases from the text before it reaches the model. Against a canvas payload there is no text to strip. Any defence that operates on the DOM string is structurally blind to it, which is the clearest argument going for putting the control on the action rather than on the input.
Mitigation, in the order worth doing it
Assume the bad text got through. This is the premise, not a step. Every control below is designed to hold when the injection succeeded, because prompt filtering is a speed bump and the canvas vector shows why.

1. Register fewer tools. Expose only what an agent genuinely needs and delete the discretionary ones. This is also the change that makes the agent cheaper and, per Vercel's numbers, more accurate. Rare alignment between security and performance, so take it.

2. Put consequential actions out of band. The good formulation: make it something the agent narrates but cannot click. A human confirmation the model has no tool to satisfy is the only control an injection cannot argue its way past.

3. Re-validate server-side on every call. Permissions and ownership, every time, treating each request as potentially hostile. The agent's session being valid tells you nothing about whether this particular call was the user's idea.

4. Scope the session. Narrow the agent's access to what the task needs, with short-lived tokens rather than the user's full standing authority.

5. Rate-limit and log per tool. This one is for afterwards. It will not stop the first incident, it is how you find out it happened and what it touched.

And the question to sit with if you run agents: would you let one browse arbitrary pages using the same account that holds your production access? Most teams answer no immediately, and then discover their agent does exactly that.

The operational tax nobody scopes

Agentic browsing demos beautifully and degrades quietly, which is the worst combination for a project plan. Three failure modes that show up after the pilot.

The one that gets projects cancelled
Silent task degradation
Task completion rates decline within 60 to 90 days of deployment where teams skipped the audit and observability work. Nothing throws. The agent keeps reporting success on a task it is now doing worse, which is the same shape as the coverage-without-a-baseline problem in the post-extraction section: you cannot notice a decline you never measured a starting point for.
Perception mode has consequences
DOM-only blindness
DOM parsing is cheaper than a vision model and brittle on dynamic pages. Modern portals using declarative partial updates change visible content with no full page reload, so a DOM-only agent goes blind exactly where the content is freshest. Teams without a vision fallback end up maintaining scripts alongside the agent, which defeats the reason they bought it.
Budget reality
Three times the scope
The estimate worth quoting at a planning meeting: observability, session management and retry logic cost around three times what teams initially scope on open-source implementations. That gap is the whole distance between a working demo and something you can leave running, and it is the same lesson as the agent-built-scrapers section — the code is the easy part, the instrumentation is what you live with.
What to build, concretely. Structured action logs with task_id, step, timestamp, action, outcome and a confidence score, so a decline is visible as a trend rather than an anecdote. Screenshot traces for debugging, because reconstructing an agent's reasoning from text alone is miserable. Outcome validation before anything progresses down the pipeline. Credentials in a vault or environment, never in the agent's context. And treat the agent as an untrusted endpoint on your own network: scope its file access to the task, and monitor its session the way you would a third party's.

What this means if you scrape rather than build agents

Agent traffic is a client class now
Sites have started treating declared agents as a distinct audience with its own commercial treatment, not merely allowing or blocking them. A publisher serving assistant crawlers a different, smaller, separately-monetised edition of the same URL is the clearest example, and your 200 will look perfectly healthy while you collect it. If you identify as an agent, check what you are being served against what a browser is served, at least once. That commercial treatment now has a protocol. An agent may meet 402 Payment Required where it used to meet a block. Cloudflare’s Monetization Gateway, announced in July 2026 and so far only a waitlist, would let a site charge any caller for a page, an API route or an MCP tool call over x402, settling in stablecoins with no account: the server states the price, the client pays and resends with proof, and a facilitator verifies. Cloudflare’s examples run from $0.01 per call on a premium route to variable prices up to $2, and include turning an origin’s 401 into a 402. If you give an agent a wallet, give it a per-task budget, an allowlist of payees and a log of every payment it makes, because a page can now ask for money as easily as it asks for a click, and content the agent reads may try to steer that decision.
Agent-first engines cannot do the hard part
The trade is structural rather than a missing feature. Cloudflare states plainly that Kitesurf is not for negotiating a bot-challenge handshake with real TLS fingerprints or holding long authenticated sessions, and points you back to Chromium. A browser that is not Chromium cannot produce a Chromium ClientHello, and a stateless engine spun up per request has nowhere to accumulate session trust. Cheap and fast for open pages; wrong tool for a defended one.
Agent-readiness is becoming a site property
Two surfaces are appearing next to the HTML: a semantic layer through roles and accessible names, and a declared tool layer through WebMCP. Both are more stable than the markup around them, because both have somebody else's compliance or contract riding on them. Check for either before you write a selector — and diff what they return against the rendered page at least once, because they are maintained by different code and can disagree.
The binary you are detected by is dissolving
Automated traffic is now the majority, and a growing share of it is an agent acting for a real person who is genuinely the audience. That is why navigator.webdriver and CDP detection went soft, and why detection is moving to behaviour, intent and network identity. For a scraper the practical read is that "look human" is a less useful goal than "make coherent sense as some legitimate client", which may not be a human at all. Agents got the harder default, not the softer one. Cloudflare’s 15 September presets for a new ad-supported domain allow Search, set Training to Disallow AI Training (a robots.txt preference that Accountable crawlers honour), and set Agent to Block on pages with ads, a network block decided page by page. Cloudflare says it offers no Disallow setting for agents yet, because no established directive exists to express one. So an agent acting for a person cannot earn access to those pages by reading and obeying robots.txt; it is refused at the edge on exactly the pages that carry ads, while the same site’s ad-free pages stay open. If your agent’s success rate splits along that line, suspect the ads detector before your fingerprint. This is Cloudflare’s description of its own presets, and owners can change them at onboarding or any time afterwards.
27 field notes on agentic browsers Reported from practice. Click any card to open it.
+
Aug 2026 / Agents
Computer-Use Agents Take 2.7 to 4.3× More Steps Than Necessary
OSWorld-Human (arXiv:2506.16042, UC San Diego, accepted to MLSys 2026) re-annotated all 369 OSWorld tasks by hand with two graduate annotators and cross-validation, then measured how efficiently agents actually work. The widely-quoted figure of 1.4–2.7× more steps than necessary is from the June 2025 v1 abstract and has been superseded: the May 2026 revision re-ran the analysis and moved it up to 2.7–4.3×, across the same 16 agents. If you see 1.4–2.7 quoted in 2026, it is stale.
💡 Twelve minutes to double-space two paragraphs
Use the paper's own example, not the one going round LinkedIn. The much-repeated “two minutes for a human, twenty for an agent” line appears only in the authors' lab blog, not in either version of the paper. What is in the paper is sharper anyway: changing the line spacing of two paragraphs to double-spaced takes a computer-use agent 12 minutes, against under 30 seconds for a typical user.

The metric worth borrowing. They introduce a Weighted Efficiency Score that penalises both inefficiency and the wasted steps a failure drags behind it. The gap it exposes is the point: the best agent with public trajectories scores 41.4% on success but 15.6% on WES. Success rate flatters agents enormously, because it counts the finish and ignores the wandering.

What this means for a scraping budget. Per-call browser-infrastructure pricing is the number people compare, but steps-per-task is the multiplier nobody prices in. An agent that needs three to four times the necessary actions pays that multiple on every one of them. It is the strongest available argument for spending the model once at build time to generate a deterministic extractor, and running the extractor per page.
+
Aug 2026 / Infrastructure
Cloudflare's Bet: Fewer Than 10% of Agent Tasks Need a Container
@cloudflare/computer, announced 3 Aug 2026, is a virtual filesystem living inside a Durable Object with authoritative state in SQLite, exposing one pluggable execution surface via workspace.runtime.exec(source, { backend }). The premise: giving every agent its own container will not scale, so route the roughly 90% of agent work that is plain file manipulation into cheap isolates, and reserve containers for native binaries and package managers.
💡 Three corrections to how this is being described
It does not ship a browser. The three backends are a FUSE-mounted Linux container, a bash-only isolate shell, and a JavaScript isolate runtime. Browser work is a separate Cloudflare product (Browser Run, renamed from Browser Rendering in April 2026); the announcement names a browser only as future intent. It is not a hosted product with a price — it is an MIT-licensed npm library, explicitly marked preview-only with unstable APIs, suitable for prototypes; you pay for the underlying Workers, Durable Objects and Containers it orchestrates. And it is not a quiet release: 8,805 GitHub stars, not the four-figure number doing the rounds.

The benchmark is more interesting than the pitch. On their own numbers, the FUSE mount is roughly 2× slower than ext4 and 3.6× slower than tmpfs on a full npm install, and up to 41× slower on bulk I/O. But it beats real disk on metadata-heavy work: removing 1,000 files at 0.66×, walking a tree at 0.72×, git init plus a 100-file commit faster than local.

Read that as a shape, not a score. Agent work is overwhelmingly metadata-heavy — listing, stating, moving, small edits — which is exactly where this design wins and exactly why the 10% claim is plausible.
+
Aug 2026 / The other side
What a Site Actually Changes When It Decides to Be Agent-Readable
A consultancy published the build log rather than the opinion piece, and it is the most concrete answer yet to what does agent-ready actually mean. Three tiers — Discovery, Understanding, Action — of which they implemented the first two and deliberately skipped the third. Every change is verifiable live, which is the reason to cite this one over the hundred think-pieces published the same month.
💡 llms-full.txt is 26,839 bytes of the whole site as clean markdown
Discovery tier. An XML sitemap generated automatically by the build. A custom build integration emitting llms.txt (1,699 bytes, the index) and llms-full.txt (26,839 bytes, about 4,054 words) on every deploy rather than maintained by hand — each page's HTML parsed, stripped of navigation, headers, footers and scripts, then converted to clean markdown. And a robots.txt carrying a Content-Signal directive, live and exact: Content-Signal: search=yes, ai-input=yes, ai-train=no. Read that as: index me, ground an answer in me, do not train on me.

The deadline behind it. They cite Cloudflare's July announcement that from 15 September 2026 the defaults would block AI agents on pages displaying ads for new and existing free customers. Correction, 30 Sep 2026: the change Cloudflare actually shipped that day applies to new domains only, defaults ad-supported sites to a robots.txt “Disallow AI Training” signal rather than a block, and migrates existing Block settings to that signal. Sites that want agent traffic still do best to say so explicitly.

Honest note on the framing. They did not skip the Action tier to protect human visitors, which is how this gets retold. Their stated reason was simpler and more useful: the tier did not yet earn its complexity for a site of that size.
+
Aug 2026 / Threat model
Five Tricks in One Email: The Anatomy of an Agentic-Browser Injection
The guide has covered that agentic browsers fall to indirect prompt injection. A security lab published the construction of one such payload against Claude in Chrome, and the value is in seeing that a working injection is not one clever sentence — it is five deliberate techniques layered into a normal-looking email.
💡 The payload does not look like an attack; it looks like structure the model already trusts
The five layers, because the composition is the lesson. A fake structural boundary convinces the model the email has “ended,” so what follows reads as a new context rather than quoted content. A hidden image steers the agent toward the tool that will read the rest of the payload. A spoofed assistant response is planted to build false trust, so the model treats the next line as its own prior reasoning. A fake user command, disguised as an innocent debugging request, issues the actual instruction. And padding blends the whole thing into the page's normal structure so nothing stands out. The end state: a normal-looking email that, to the model, was a complete instruction set ending in arbitrary JavaScript execution from an attacker-controlled CDN.

Why a scraper-builder should read an attacker's write-up. This is the exact inverse of the collection problem, and it is the same mechanism the guide keeps returning to: an agent cannot separate instruction from content, so any page or document it ingests is a potential instruction set. If you point an agentic browser at pages you do not control — which is the whole job — you are the target this payload was built for. The defensive takeaway from the authors is the honest one: understanding how the payload is assembled is the first step to defending against it, because there is no single string to filter — each of the five parts is innocuous alone. Control the actions the agent can take, not the text it reads.

Source: Zenity Labs.
+
Aug 2026 / New pattern
Agents Rediscover the Web Every Session. Somebody Started Keeping the Notes.
An agent can spend fifteen tool calls working out a bad date picker, finding a hidden JSON endpoint, or learning the shortest route through a site. Then the session ends and every bit of that is thrown away. The next agent arrives and pays for the same discovery again.
💡 Once the hard thinking is done, you no longer need the model that did it
What a “skill” actually is here: a markdown file that is “not merely a replay of clicks, but a distillation of the durable knowledge about a site” — the URL scheme that skips the whole interface, the hidden JSON blob the page inlines, the anti-bot quirk, a runnable extractor. It is written automatically from instrumented sessions: the commands issued, the URLs visited, the task given, how long it took, with credentials and one-off parameters stripped before anything is saved.

The lifecycle is the part worth copying, because a memory that only grows is a liability. Skills are widened when a later run discovers more (a country filter the first agent never tried), gated by safety and topicality checks, provenance on whoever produced the session, and human review before anything reaches a public catalogue. Each carries success and failure counts and is automatically demoted when it drops below threshold. Knowledge that stops being true gets retired rather than quietly poisoning the next run — which is the same discipline the self-healing card in this feed argues for, applied to memory instead of code.

Why it changes the bill and not just the latency. The claim is that an open-weight model can then handle site tasks that would otherwise need a frontier one, because following a proven recipe is instruction-following rather than exploration. That is this guide’s own principle from a new direction: spending the model once at build time is cheaper than spending it on every request — and here the “build” is done by whichever agent happened to go first, on behalf of everyone after it.

The caveat to hold. A shared recipe pool across tenants is a shared blast radius: a skill that encodes a site quirk is fine, a skill that quietly encodes an assumption that has since changed is a wrong answer delivered fast and cheap. The failure counters are the control, so the number that matters when evaluating any system like this is how quickly a stale skill is demoted, not how many skills it holds.
+
Aug 2026 / Observation
Sites Now Serve Two Versions of Themselves, and the Clean One Is For Machines
The shortest post in this batch and among the sharpest. The human version has cookie walls, popups and fake countdown timers. The machine version is clean, structured, no theatre. People are upset about the split and have decided the bot web is the fake one.
💡 Look at what each version was built for before deciding which is the honest one
The inversion is uncomfortable and correct. The version shown to people is optimised against them — consent friction, interstitials, manufactured scarcity, layout that fights the reader for attention. The version shown to machines carries the same underlying facts with none of it, because none of that machinery works on a parser and all of it costs tokens. Whatever else it is, the machine version is the one where the publisher stopped trying to manipulate the reader, for the simple reason that the reader could not be manipulated.

Two numbers to sit alongside it. Cloudflare's public bot-versus-human tracker puts bots at roughly 57 to 58% of HTTP requests for HTML content against 42 to 43% human. Imperva's report on 2025 data puts bots at about 53% of measured traffic for the second year running. Different methods, different denominators, same conclusion: the majority audience for HTML is already not people.

Where it stops being comfortable. A two-version web is a cloaking arrangement, and cloaking has always been the thing search engines punish and researchers distrust — you cannot verify a claim about a page if the page you are shown differs from the page others get. It also sits uneasily beside the poisoned-font card above, which is the same capability turned hostile: once serving different bytes to different clients is normal infrastructure, serving deliberately wrong bytes is a configuration change rather than an attack. The split is not itself dishonest. It removes the guarantee that used to make dishonesty detectable.
+
Aug 2026 / Shipped
WebMCP Ships At the Edge, and Is a Bridge To Something You Have Not Built
The callable half of the agentic-web stack landed as a product feature: toggle a setting, a script is injected into your HTML, and browser agents can discover tools on your page. Then you read the mechanics.
💡 The pack that takes real action is a proxy to an MCP server you are already hosting
Two gaps, both structural rather than teething. First, the component that lets an agent do something — call a search, place an order — forwards to an MCP server you built and host yourself at your own endpoint. If that does not exist, the feature does nothing. The bridge shipped; the far bank is still your problem, which is a very different proposition from “toggle a setting”.

Second, and more interesting for anyone deciding where to spend: these tools only activate for an agent that is actually driving a browser. A large share of agent traffic that matters commercially never opens one — desktop assistants and coding agents call MCP endpoints directly. For those clients, a script injected into your HTML is invisible by construction. So the feature addresses the browser-agent case precisely, and the non-browser case not at all.

Read the source carefully: this critique comes from a company selling the alternative it names, which is a reason to check the mechanics rather than to dismiss them — and the mechanics check out, because “it proxies to your own endpoint” is a claim about documented behaviour rather than a matter of opinion. Set against the adoption numbers elsewhere in this feed, where MCP server cards appeared on fewer than fifteen sites in an entire scan, the honest summary is that the plumbing is arriving faster than anything to plumb.
+
Aug 2026 / Measured
The Big Assistants Do Not Run Your JavaScript. The Others Do.
A follow-up to the traffic-measurement card, and sharper than it. When you ask an assistant to read a URL it fetches the HTML and stops. A structured test found none of ChatGPT, Claude or Gemini rendered JavaScript — while several non-US assistants including DeepSeek and Mistral did, and a re-check this month found it still true.
💡 If your content needs JS to appear, the largest assistants cannot see it at all
Two consequences, and the second is the one nobody has priced in. First, the obvious one: a page whose content is assembled client-side is, to those assistants, an empty shell. Not ranked badly, not summarised poorly — absent. Every argument about being discoverable to AI is downstream of whether the content exists in the HTML at all, and this is a single-request check anyone can run against their own site today.

Second: they are invisible in your analytics. Client-side analytics fire from JavaScript. No JavaScript, no beacon, no row. So the assistant traffic arriving at your site does not appear in the dashboard most teams use to decide what to build, which is precisely why the measured 2:1 software-to-human ratio in the earlier card surprised the people who measured it. They had to read raw server logs to find it. If your only instrument is client-side, your instrument is blind to the fastest-growing segment of your traffic by construction.

The split is the interesting part. That the US assistants skip rendering and several others do not is a cost decision, not a capability gap — rendering is expensive at fetch volume. It also means “do AI assistants execute JavaScript” has no single answer, and any advice built on one is describing whichever assistant the author happened to test. Segment by client, as ever.
+
Aug 2026 / Measured
268,000 Agent Requests, Two Months, and Almost Nothing Read the llms.txt
A team instrumented their own site for two months and counted what actually arrived. Software traffic beat human traffic by more than two to one — roughly 268,000 agent-tracked requests against about 107,000 human pageviews. The numbers underneath that are the interesting part, because several of them contradict what the ecosystem has spent a year recommending.
💡 “AI traffic” is not one population — segment by user agent before you believe any conclusion
What arrived, by name. ChatGPT-User was 196,973 requests, about 73% of all agent traffic, and it pulled markdown 272 times — call it 0.1%. Claude Code was 23,300 requests and asked for markdown 17,814 times, 76%. Perplexity was 7,728 and took markdown essentially never. OAI-SearchBot 7,255 at 26%; GPTBot 3,579 at 31%. Across everything markdown was about 15% of reads, roughly 40,000 against 227,000 HTML.

The lesson is not “ship markdown” or “do not”. It is that the answer differs by nearly three orders of magnitude between clients, so advice treating agent traffic as one population is advice about an average nobody experiences. A site serving mostly ChatGPT users and a site serving mostly coding agents should do different things.

The uncomfortable finding. /llms.txt was fetched about 660 times and /llms-full.txt about 110 — but only 37 of those came from a named AI assistant. The rest were search crawlers and unidentified bots. And a hidden <link> element pointing at the markdown version, the trick that circulated widely as a cheap win, recorded zero measurable hits across all 268,000 requests.

Two operational notes that generalise. AI crawlers do not execute JavaScript, so client-side analytics cannot see any of this — the measurement has to happen in middleware reading raw headers, which is also why most teams have no idea what their agent traffic looks like. And the referrer data was contaminated: 106 of 117 referred hits carried a single frozen Chrome/111.0 string, a reminder that a user-agent is a claim rather than a fact.

Read it against the adoption numbers. Cloudflare's Agent Readiness work, published the same month, scores sites on discoverability, content, bot access control and capabilities, and found 78% of sites have a robots.txt, 4% have declared AI usage preferences in it, and markdown content negotiation passes on 3.9%. MCP Server Cards and API catalogues together appeared on fewer than fifteen sites in the entire dataset. Markdown is worth up to 80% fewer tokens, and Cloudflare's own docs measured 31% fewer tokens and 66% faster to a correct answer than an unoptimised competitor's.

And the dissent worth keeping. One practitioner's answer to the whole agent-readiness push: crawlable is not the same as recommended. A model can index you perfectly and still never say your name, because it also read what the rest of the internet says about you, and that is decided outside your domain. Access is necessary and nowhere near sufficient.

Sources: evilmartians.com chronicles (Rita Klubochkina); blog.cloudflare.com/agent-readiness; isitagentready.com.
+
Aug 2026 / New category
HTTP 402 Sat Reserved For Thirty Years. Agents Are What Finally Uses It.
This guide has covered Web Bot Auth at length: the automated client cryptographically signs its request and the site verifies the signature, so identity stops being a user-agent claim. The half that was missing is what happens once you know who it is. A cluster of work this month answers that with a status code that has sat marked for future use since HTTP/1.1.
💡 Identity was the hard part; billing is the part that changes who gets served
The problem is an accounting one. The open web runs on an attention model — the page is served free and value settles downstream through a human seeing an ad or paying a subscription. An agent consumes the compute and skips the settlement entirely. Worse, much of the traffic is waste on both ends: Cloudflare's data shows a large share of well-behaved bot traffic is re-fetching pages that have not changed.

The handshake. The edge intercepts the request and returns 402 Payment Required with its terms, the client pays, and the proxy verifies proof of payment before the origin ever executes. No pre-shared API key, no account, no checkout redirect. It is framed as four primitives that only work together — readable (markdown for agents on the server side, Kitesurf as the client-side half), discoverable (agent engine optimisation rather than keyword SEO), callable (WebMCP, where a site exposes its actions as explicit tool contracts with JSON schemas instead of making an agent guess which button to click, plus Code Mode where the agent calls endpoints in code rather than prose), and payable (x402 and PACT tokens, with wallets on the agent side).

Why a collector should care, in both directions. If you gather data, this is the first credible pricing mechanism aimed squarely at you, and it prices per request rather than per contract — a very different negotiation from a rate card. If you run a site, it turns the bot question from block-or-allow into a third option, and the argument for it is commercial rather than moral: behind almost every agent is a paying customer, so treating agents like legacy scrapers means losing the customer.

The honest caveat. This is largely one vendor's stack described in that vendor's own posts, and the adoption numbers in the card above — fewer than fifteen sites in the dataset carrying an MCP Server Card — say the callable and payable layers are proposals rather than conditions. Worth understanding now, worth building against later, worth not rearchitecting around yet.
+
Aug 2026 / New pattern
The Agent Hits a 403 and Solves It Itself, Because the Solver Is an MCP Tool
A pattern this guide has watched arrive from several directions landed concretely this month. Instead of a human wiring up a bypass for a known site in advance, the solver is published to the model as tools, and the agent reaches for one when it meets a challenge nobody anticipated.
💡 Solve server-side and you submit the container's TLS fingerprint, not a browser's
What is exposed. An MCP server over stdio publishing akamai_solve (for _abck), datadome_solve (from an HTML response), plus health_check, solver_stats and akamai_queue_metrics for backpressure. The usage pattern in the demo is a prompt rather than a script: fetch this URL, and if you get a 403 or an access-denied page, use the solver, then retry the same request with the cookies it hands back.

The technical point worth extracting is why the Akamai path drives a real Chrome relaying each sensor request over the session socket instead of computing the sensor server-side. Solving server-side transmits the container's TLS fingerprint rather than a browser's, and any challenge that verifies the client actually submitting it will reject the answer: correct sensor payload, wrong envelope. A run against a live commercial target completed in 20.8 seconds.

The maintenance lesson underneath it applies whether or not you ever touch this. The browser identity had been fragmented across several files, so the Akamai scripts had quietly drifted two versions behind the DataDome clients. The fix was a single profile module everything derives from — DataDome clients, Akamai telemetry, CDP overrides, MITM headers, the MCP tools — with values like sec-ch-ua computed rather than hardcoded. A fingerprint assembled in more than one place will eventually disagree with itself, and that disagreement is what gets caught.

One operational constraint stated plainly and worth repeating: the clearance cookies are bound to the exit IP, so the retry has to leave through the same proxy. An agent whose built-in fetch tool egresses somewhere else will earn a fresh 403 while holding a perfectly valid cookie.

The deployment model is the actual differentiator, and it is worth separating from the MCP novelty. This ships as a Docker image you run on your own hosts: your worker calls the container on localhost, the solver generates the payload, and a valid token comes back without a single external API call. That is the opposite shape to every managed unblocker in §07 — those are per-request calls to somebody else’s endpoint, which means your target list, your URLs and your timing all leave your network. Here nothing does, which makes air-gapped deployment possible. Billing follows the architecture: a flat fee for unlimited solves rather than per-request or per-GB, which inverts the crossover maths in §13 — the cost stops scaling with volume and starts being a fixed line.

One claim worth reading against the artifact. The product page describes solving at the payload level, “milliseconds, not seconds”, with no Playwright instances and no per-challenge browser overhead. The pull request above, which is the changeset that adds the Akamai path, drives a real Chrome and reports 20.8 seconds. Both can be true at once, and the reconciliation is the lesson: the payload-level story holds for DataDome and does not survive contact with a vendor that verifies the client actually submitting the answer. Read any blanket “no browsers needed” claim per vendor, not per product — and note that the honest version of the architecture is visible in the repository while the simpler version is on the homepage. The commit is the better source, which is generally true and is why this guide keeps linking them.

Source: xhrdev/examples PR #40.
+
Aug 2026 / Design for machines
Send the Same POST Twice. An Agent Will Never Tell You It Made Two Rows.
Not a scraping post, but it belongs here because it names this guide's recurring failure mode from the other end. An engineer sat down to write about how their API handles retries safely, sent the identical POST twice, and got back two rows with two different ids.
💡 The properties that matter most to a machine caller are the ones no human tester will ever report
The diagnosis is the good part. Three of their four design decisions for a non-human caller held up: UUID primary keys so an id survives across sessions, an OpenAPI schema generated from the serializers so the contract cannot drift from the code, and server-side filtering so a caller does not pull the whole set to find one row — turning a forty-request pagination loop into one indexed query. The fourth did not exist at all. No Idempotency-Key header, no unique constraint, and an ingest endpoint calling create() keyed on nothing: same URL five times, five items.

Their own framing of the mistake is the line worth keeping. They had built the identifier story and mistaken it for the write story. A UUID makes a row addressable once it exists. It does nothing about the request that created it arriving twice.

Why it lands harder with an agent on the other end. A human sees the duplicate and files a bug. An agent times out, cannot determine whether the write landed, retries, and says nothing — so the duplicate arrives silently and surfaces much later as a data-quality problem with no obvious cause. That is the same shape as the silent-200 this guide keeps returning to: invisible where it happens, expensive where it shows up.

The two fixes named. Either a client-supplied Idempotency-Key stored behind a database constraint, replaying the stored response on a repeat; or, for a narrow ingest endpoint, get_or_create on the natural key such as (owner, url). If you are building the API your own collection pipeline writes into, this is a ten-minute check: send the same request twice and look at what you get.

Source: bessavagner.com, an API-first vault my agents can call.
+
Aug 2026 / The argument
Three Answers to "Agents Need Browsers", and One Person Saying They Do Not
This guide has been tracking the lighter-browser thread with some enthusiasm. It deserves its counter-argument, and this week produced a genuine three-way disagreement worth holding all at once.
💡 Shrink the browser, multiply it cheaply, or move the tooling inside it — or skip it entirely
Answer one, shrink the browser. Kitesurf, Moli, Obscura, Lightpanda. When the consumer is a model, the rendering pipeline is dead weight, so throw it away and keep the DOM and CDP. Covered at length above.

Answer two, keep the real browser and make it cheap to run thousands of. Unikraft's founder puts the objection to answer one bluntly: you cannot run JavaScript, render a page, take a screenshot or click a button from a comfortable CLI, and agents touching real sites need a genuine engine underneath rather than a lightweight stand-in. His framing is the useful part: the problem was never whether agents need real browsers, it is what happens once you try to run thousands of them. That reframes it from a rendering problem to a cold-start and isolation problem, which is unikernel territory rather than browser-engine territory.

Answer three, put the operating system in the browser instead. BrowserPod 3.0 goes the other way: rather than shrinking the browser, it implements Linux syscalls in WebAssembly — a custom wasm32-browserpod-linux-musl target rather than WASI, giving a persistent virtual filesystem, inbound and outbound networking, threads on separate Web Workers, and real subprocesses. They compiled and ran ripgrep, Starship, jj and OpenAI's Codex unmodified, the last at around 1.25 million lines. The limitation they state plainly is that Rust programs must be compiled offline, and the target declares arch="wasm64" while excluding the wasm family tag to dodge ecosystem assumptions about reduced-capability builds.

And the dissent, which cuts all three. A sceptic's post on running full browsers in V8 isolates called it a leaky abstraction and asked the question the whole category should answer: for production agents you usually need specific data points, not a simulated DOM and rendering engine, so a purpose-built HTTP client with proper error handling and retry logic will beat a simulated browser most of the time. He is right about the median case, and this guide's own escalation ladder agrees with him — plain HTTP first, browser only when proven necessary. Where he is too strong is the tail: login flows, canvas-rendered content and JavaScript-constructed data are not reachable from a CLI at any level of retry sophistication. The honest position is that all three architectures are answers to the minority of traffic that genuinely needs a browser, and the argument is only about how expensive that minority has to be.
+
Aug 2026 / New pattern
Repos That Install Themselves, Because the Reader Is an Agent
A small open-source release with a design choice worth more than the tool: a stealth-browser MCP server whose repository is written to be read by an AI assistant rather than a person. Hand your agent the GitHub link and it reads hidden <ai_context> instructions in the repo, installs the Playwright binaries, and configures the tooling for itself.
💡 A README for humans and an install script for agents are becoming different documents
The author calls it an agent-first architecture, and once you see it you notice the same instinct forming elsewhere: llms.txt, WebMCP tool declarations, accessibility-first markup, and now repositories carrying embedded setup instructions addressed to whatever model is reading them. The through-line is that the machine consumer has stopped being an afterthought parsing documentation written for people, and is being handed its own channel.

Which is genuinely useful and also a small, clear security lesson. A repository that instructs your agent is, definitionally, untrusted text that your agent will act on. It is the supply-chain version of the prompt-injection problem in the agentic section above: nobody had to compromise a package registry, because the instructions are the feature. An <ai_context> block that installs binaries and rewrites your tool configuration is doing precisely what an injection payload would do, with your consent, and the only difference between the helpful case and the hostile one is the author's intent, which you cannot read from the file.

So treat it as you would any install script. Read the embedded instructions yourself before pointing an agent at a repository, particularly one that installs browser binaries or edits configuration. And note the asymmetry that makes this worth flagging rather than shrugging at: a human skimming a README will not see an <ai_context> block at all, while the agent reads it as the primary content. That is the same invisible-to-humans, authoritative-to-machines shape as the canvas injection vector and the markdown edition a publisher serves only to bots.
+
Aug 2026 / Agent security
Every Agentic Browser Tested at Black Hat Lost to a Web Page
Brave's Artem Chaikin presented "Attacking and Defending AI Browsers" at Black Hat USA 2026, testing Comet, ChatGPT Atlas and Opera's AI browser against indirect prompt injection. None of them held. No malware, no exploit chain, just words on a page the model decided to trust.
💡 We spent a decade teaching browsers to distrust the page. Agentic browsers handed that trust back by default.
The mechanism is architectural rather than a bug, which is why it is not a patch away. These agents feed raw page content straight into the model with no separation between the user's instructions and the page's content, so a hostile page can issue commands the agent treats as trusted. What that unlocks: exfiltration from the active browsing session, and account takeover through hijacked trusted UI actions, using plain web content with no payload to detect. Researchers have separately reproduced the same class against MultiOn, Browser Use and OpenAI Operator, so this is not three unlucky products.

WebMCP widens the same wound, and this is the part teams miss when they ship it. Every tool you register is effectively a public API that a stranger can invoke with words you never wrote. An attacker needs no login and no stolen token; they plant text where the agent will read it, and the agent calls your tools using its own trusted session — the user's cookies, the user's CSRF token, the user's permissions. A registered updateEmail or applyCoupon executes as the logged-in user. Chrome's own guidance is explicit that registered tools are an attack surface and prompt injection is the delivery mechanism.

What follows for both sides. If you publish tools, decide before launch which actions an agent may take unsupervised and which require a human in the loop, because that is a design decision and not something you bolt on afterwards. If you run agents against the open web, the question to sit with is the one Chaikin's work implies: would you let an agent browse arbitrary pages using the same account it holds production access with? The guide's own prompt-injection material covers the URL-fragment surface that never crosses the network; this is the same threat with a session and a tool list attached.
+
Aug 2026 / Agent economics
Two Thirds of an Agent's Token Bill Is the Browser Tool, Not the Thinking
Someone swapped the built-in browser tools out of two popular coding agents and measured it across 26 tasks on a local desktop Chrome. One agent used around 70% fewer tokens and cost about 61% less. The other, 54% fewer tokens and 38% cheaper. Same model, same tasks, same browser. Only the tool surface changed.
💡 The waste is not the reasoning. It is the page text you keep re-reading.
The diagnosis is worth more than the numbers. Stock agent browser tools are chatty: they dump large page payloads into context and expose a wide menu of tools. The model then spends tokens re-reading content it already has and calling tools it did not need, and every one of those is a round trip. The replacement is two ideas: a deliberately tiny snapshot tool, so the page arrives as a lean representation rather than a wall of DOM, and an exec tool that accepts Playwright code, so a whole batch of interactions happens in a single model turn instead of one turn per click.

This is now the fifth independent measurement pointing the same way, and at this point it is a settled finding rather than a claim. The Browser Use team found strong models rarely want screenshots and prefer to inspect the page through code they write themselves. The accessibility-tree work found the win comes from operating known UI patterns as atomic actions with no model round trip at all. Hermes swapped twelve per-action browser tools for a single script-driven engine on browser-use's CLI 3.0, and Nous Research measured 48 to 66% fewer tokens with no drop in accuracy — because a dozen tool schemas were riding along in every request whether used or not, and opening a page to read one headline cost four or five round trips. Vercel's agent-browser made the same move from MCP tool schemas to a lightweight CLI and reported 3.5× faster execution and 37% fewer tokens. And this one finds most of the bill is payload and tool sprawl rather than reasoning. Five teams, five methods, one conclusion: the expensive thing in agentic browsing is the number of times the model has to look and the number of schemas it carries while looking, and the cheapest optimisation is to make it look less often rather than to make it think better. The shape of the fix is identical everywhere too — stop exposing one tool per action, and let the agent express a whole task as one script.

If you are building on an agent framework, this is a config-level change rather than a rewrite, which makes it one of the few genuinely cheap wins in this part of the stack. Measure your own tasks though: these gains come from removing waste specific to a tool surface, so how much you recover depends on how chatty yours is.
+
Aug 2026 / AI visibility
Claude Cannot Follow a Link It Has Not Been Given, and That Is Deliberate
Fingerprint's team tested how Claude actually reaches pages on the web and found a constraint that quietly rewrites "optimise for AI discovery": a relative link sitting in HTML the model has already fetched is not enough to unlock that page. Not across multiple pages, not via an llms.txt, not via a .md version.
💡 It is a security boundary, not a crawler limitation — so it will not be "fixed"
The mechanism is documented but rarely read: the fetch tool is provenance-gated. It can only fetch URLs that have already appeared in the conversation context — user messages, prior search results, prior fetch results. It cannot follow a URL it constructed itself. The stated reason is exfiltration defence: if a model could fetch any URL it composed, then any attacker-controlled text on any page it read could become a channel for sending your context somewhere. Read that alongside the prompt-injection material in this guide and the design is obviously correct, which is also why nobody should plan around it changing.

The consequence for anyone who wants to be read by an assistant. The search index becomes the gatekeeper rather than your link graph. For Claude that means Brave Search is the practical entry point — a page that is not indexed there may simply never be reachable, no matter how clean your internal linking is. Absolute URLs help, because an absolute URL that appears in fetched content has provenance where a relative one does not. Practitioners in the thread noted the same asymmetry applies to every assistant with its own index, and that there is no Search Console equivalent for most of them, so the only honest method is to probe and measure rather than assume.

The mirror image, for scrapers: this is a reminder that agent traffic is shaped by tool policy, not just by crawl budget. If you are modelling which of your pages assistants can see, the question is not "did I allow the bot" but "did the URL ever have provenance". Anthropic's web-fetch tool documentation
+
Aug 2026 / The machine-facing web
WebMCP Shipped, and Most of What Shipped Is a Bridge to Something You Have Not Built
Cloudflare turned on WebMCP as a developer preview: flip a switch in the dashboard, pick your packs, and the next HTML your site serves carries the bridge — nothing deployed, nothing changed at your origin. It rides document.modelContext, the browser API shipping experimentally in Chrome 146, so browser agents can discover tools on your page. The plumbing is real. The part people assumed was included is not — the pack that lets an agent take a real action is a proxy that forwards to an MCP server you are already hosting at your own endpoint.
💡 A site can now have two front doors, and they can disagree
Two gaps got named quickly. First, the action layer is a bridge with nothing on the far side unless you built it: if you do not have an MCP server, the pack does nothing. Second, WebMCP tools only activate for an agent that is actually driving a browser, and a large share of the agent traffic that matters — desktop assistants, IDE agents — never opens Chrome to read your page; it calls an MCP endpoint directly.

The subtler critique came from the Chrome side, and it is the one worth keeping. Treating WebMCP purely as a tunnel to an existing MCP server throws away the thing that makes it different: WebMCP tools are part of your frontend code, with access to client-side state — cookies, local storage, the DOM — and can write to it as well as read it. A set_filters tool can actually change what the user sees. And registering the same tools twice, once via an MCP server and once via WebMCP, duplicates them for the agent, inflating tool count and creating genuine ambiguity about which to call.

What this means if you scrape. A growing number of sites will expose a declared, structured, versioned tool surface next to their HTML — and the two can disagree, because they are maintained by different code. That is the same lesson as the publisher-serving-bots-a-different-site card, arriving from the opposite direction: the page a machine is offered is increasingly not the page a person is offered. Check for a tool surface before you write a selector, and if you find one, diff what it returns against what the rendered page shows at least once, because you are now choosing between two sources of truth rather than parsing the only one. Cloudflare's WebMCP announcement
+
Aug 2026 / Agent-native browsers
The Other Answer to Agent Blocking: Stop Using Someone Else's IP
Cloud browsers get IP-blocked on the sites agents most need — the ones you are logged into. Headless browsers run blind, so you never see what the agent did or why it failed. BrowserOS neo takes the opposite bet from the whole hosted-browser category: run on the user's own machine, with the user's own Chrome sessions, and show the work.
💡 For logged-in work the residential-proxy problem disappears if you never leave the residence
The design choices are the interesting part, and several are directly transferable whatever you build on. Local execution with imported Chrome sessions, so the agent operates inside apps the user is already authenticated to — no proxy, no reused-IP reputation, no login flow to automate, and no credential ever handed to a third party. A live cockpit showing every agent's activity as it happens, instead of reconstructing failure from logs. Session replay as scrubbable video, which is the single most underrated capability in agent automation: when a run fails you scrub back to what the agent actually saw. Parallel agents in isolated tabs so concurrent runs do not tread on each other's state. And simplified web snapshots instead of screenshots — a lean text representation of the page rather than an image, which cuts token use substantially and lands in the same place as the accessibility-tree result above. Free, open source, macOS and Windows, past 13,000 GitHub stars.

The trade to be honest about. This solves the blocking problem for authorised, logged-in, user-owned work, and it is the correct architecture for that case — it is the same argument the Computer Use Agents section makes about data behind a login the user owns. It does not solve public-web collection at volume: one machine, one residential IP, one browser profile, and the moment you need geographic distribution or concurrency beyond a laptop you are back in proxy territory. Read it as a category boundary rather than a replacement. browseros-ai/BrowserOS on GitHub

Oct 2026 update. Open Science Desktop (MIT, 1,788 stars on 12 Oct 2026), a research workbench, uses the same pattern: its agent drives the user’s own Chrome, logins intact, to harvest preprints, and it strips model-supplied allowedDomains and session arguments so the app sets scope. Its “#1 on ResearchClawBench” is 22.8 averaged over 36 of 40 tasks (20.5 over all 40); the 17 Sep board ranks it seventh.
+
Aug 2026 / Bot-majority web
Half the Web Is Automated, and the Publisher's Best Reader Might Be a Bot
Automated traffic is now around 53% of the web, and US traffic has crossed the 50% line on Cloudflare's own radar. The reflex is to block. A publisher-side story from August makes the case that the reflex is expensive, and it is more interesting than the usual bot-panic piece.
💡 The bot/human binary is a measurement artefact, not a fact about the reader
A team found an AI agent crawling one of their publishers' sites — methodically working through specific topic categories, textbook bot behaviour, exactly the traffic the industry says to block. When they traced it, the agent belonged to one of that publisher's own advertisers: a real professional had dispatched it to research content in their category, and the content was being consumed by a real person, just indirectly. Block that traffic and your numbers get cleaner and smaller, and you have cut off the reader the publication exists to reach.

Their argument is that the answer is not a better filter but a better unit of value — a known audience, because a bot can fake an impression but cannot validate a work email, complete a profile and come back three more times. Whether or not you buy the framing, the underlying observation is the one that matters for anyone building collection infrastructure: the identity of the requester and the identity of the beneficiary have come apart, and every detection system still assumes they are the same.

The legislative counter-current is moving the other way. "Stealth crawler" has gone from slang to a term in draft law: a bill passed in New York last month and legislation introduced in the US House in July both aim to prohibit bots from masking their identity. That is a live risk to a large amount of current practice, and the honest read is that the disclosure question — who you say you are, in your user agent and in your headers — is drifting from an engineering choice toward a compliance one. Track it in the legal section of this guide, and note the direction of travel: identification is becoming cheaper to comply with and more expensive to avoid.
+
Aug 2026 / The machine-facing web
A Publisher Now Serves Bots a Different Website, With Ads Built In
The machine-majority web stopped being a statistic and became a product decision. TIME.com now answers the same article URL with two entirely different documents depending on who asks, and the version written for assistant crawlers carries its own advertising. This is not a block and it is not poisoning. It is a second edition of the site, authored for machines, that no human reader will ever see.
💡 The User-Agent header is quietly becoming a billing identity
The test is one any reader can repeat: fetch the same article, change only the User-Agent, and compare. As Chrome or Safari the response is text/html, 303,235 bytes, and Google's search crawler receives the same page a person does. As ClaudeBot, PerplexityBot or OAI-SearchBot the response is text/markdown, 13,409 bytes, roughly one twenty-third the size, byte for byte identical between those three. GPTBot and ChatGPT-User are refused outright with a 406.

Inside the markdown, where no person would ever encounter it, sits sponsored content: adtech vendor Mobian serving FAQ blocks for an online bank and a professional body, with tracking links carrying campaign identifiers, and response headers x-mobian-impression (a fresh UUID per request) and x-mobian-tokens counting what was served. Three separate audiences, three commercial treatments, decided at the header.

Why this matters to a scraper, and it is not the obvious reason. The guide has covered poisoned data, where a site detects you and quietly serves wrong values. This is the opposite in intent and identical in consequence: the document you receive is legitimately, deliberately not the document a human receives, and nothing about it looks like a block. Your status code is 200, your parse succeeds, your record count is plausible, and you have collected a different edition of the page. Two habits follow. Diff your extraction against a browser-rendered fetch of the same URL, not just against yesterday's run, because a content-type shift from html to markdown is invisible to a selector-based validator that only counts fields. And treat the identity you declare as a variable that changes what you are served, not merely whether you are served. Vincent Schmalbach's original teardown
+
Aug 2026 / Agent-native browsers
Cloudflare Shipped a Browser for Agents That Cannot Pass Its Own Challenges
Kitesurf, announced 6 August 2026 during Cloudflare's Agents Week, is a browser engine written from scratch in Rust, compiled to WebAssembly, running inside V8 isolates on Workers. There is no Chromium underneath. It implements the Chrome DevTools Protocol, so existing Puppeteer, Playwright and chrome-remote-interface clients connect unchanged. Free while in beta.
💡 It speaks CDP, so your code works. It has no real TLS fingerprint, so hard targets will not.
The numbers, from Cloudflare's own benchmarking, are a clean trade rather than a win: 3.1 to 3.8 times less CPU and 4.7 to 7.0 times less memory than Chromium on common agent tasks, while Chromium remains 1.7 to 1.8 times faster on wall time. Kitesurf wins on cost per session, not on speed.

The limitation is the interesting part, and Cloudflare states it plainly. Kitesurf does not support bot-challenge handshakes that require real TLS fingerprints, and it has no WebGL, no video, and no long authenticated sessions that need persistent state. For those cases the documentation points you back to Chromium. Read that against Layer 1 of the detection section and the reason is structural rather than a missing feature: a browser that is not Chromium cannot produce a Chromium ClientHello, and a stateless engine spun up per request has nowhere to accumulate the session trust that vendors like Akamai score across multiple requests.

This lands squarely in the lighter-browser thread alongside Obscura and Lightpanda: when the consumer is a model rather than a person, most of the rendering stack is dead weight, so the sensible move is to drop it. Kitesurf is the largest infrastructure provider on the web making that bet in public. It also puts Cloudflare on both sides of the same street, selling the bot management that decides which automated clients get through and the browser those clients drive. Cloudflare Browser Run docs
+
2026 landscape / AI browser stack
The AI Browser Stack: 3 New Layers Nobody Talks About Yet
The AI browser race has the headlines (Atlas, Comet, Dia, new contender every month). But the winner gets decided by what sits underneath. A fresh map of the stack the AI browsers actually run on shows three categories that did not exist a year ago: AI-native browsers, anti-detect platforms, and publisher licensing/tolls.
💡 Publisher licensing is the economic answer to "AI crawlers are scrapers too"
Three additions worth tracking, beyond the agentic browser map already in this guide. (1) AI-native browsers as a top layer: ChatGPT Atlas, Perplexity Comet, The Browser Company's Dia. Not wrappers, full browsers built around an LLM as the primary user interface. They drive the lower layers as agents on behalf of real users, which is part of why Microsoft Edge and V8 quietly stopped enforcing automation-transparency flags (see the "Vendors That Wrote the Detection Rules" card). (2) Anti-detect & fingerprinting platforms as a distinct category: Multilogin, AdsPower, Kameleo. These are not scraping libraries, they are productised browser-profile managers (each profile = a coherent fingerprint with its own canvas/WebGL/timezone/IP), originally for multi-account ecommerce and affiliate work but increasingly used by serious scrapers. Worth knowing about because the techniques inside them are the same fingerprint-coherence rules this guide describes, just packaged with a UI. Some are repositioning around scraping outright: Kameleo 5.0 (2026) dropped the signup wall so you can run a few lines against its local API and test its two in-house stealth browsers on your target before creating an account, an acknowledgement that the centre of gravity for these tools has moved from multi-account management to undetected automation for scraping teams. (3) Publisher licensing & tolls, the genuinely new layer: TollBit, Cloudflare Pay-Per-Crawl, ProRata.ai. The premise: instead of fighting AI crawlers with detection, charge them per access. Publishers expose a paid API for LLMs and agents, with rate-limited free tiers and metered paid ones. This is the economic acknowledgment that AI crawlers are scrapers and that some categories of access are worth paying for. If you build agentic browser systems, this is the layer that may eventually replace bot-detection-as-defense for content-heavy sites. The detection layer is designed to tell bots from people, but the AI browsers on top and the residential networks on the bottom both look the part, so this line is blurring fast. Credit: landscape framing by Massive (Q4 2026 update).
+
Landscape / Agentic browsers
"Browser Agent" Is Not One Product — It's 8 Layers
Massive mapped the agentic browser infrastructure landscape for Q2 2026. The insight: most teams building AI agents think about one or two layers. The reliable ones account for all eight. When an agent fails a task, the cause is usually not the framework everyone debates — it's one of the other seven layers nobody mapped.
💡 Every layer above the network still has to reach the live site
The eight layers of the 2026 agentic browser stack: (1) Cloud Browser Platforms — Browserbase, Kernel, Notte, Anchor Browser, Browserless, Hyperbrowser. (2) Agent Frameworks & SDKs — Browser Use, Stagehand, Skyvern, AgentQL, Dendrite, Nanobrowser. (3) Browser Automation — Playwright, Puppeteer, Selenium, Crawlee, BrowserMCP. (4) Computer Use Agents — Anthropic CUA, OpenAI Operator, Fellou, Twin, MultiOn. (5) Stealth & Anti-Detection — CloakBrowser, Steel, Lightpanda, Pydoll, Camoufox. (6) Data Extraction & Enrichment — Diffbot, ScrapingBee, ScraperAPI, Zyte, ScrapeGraphAI. (7) LLM-Optimized Crawling — Crawl4AI, Firecrawl, Jina AI, Apify, LLMScraper, Scrapy. (8) Network & Proxy Layer — Massive, Bright Data, Oxylabs, Smartproxy, NetNut, IPRoyal. The proxy layer sits at the bottom and everything above it depends on it: a perfect agent framework with a flagged datacenter IP still fails. Most agent debugging focuses on layer 2 (the framework) when the actual failure is layer 5 (detection) or layer 8 (the IP). Map all eight before you debug one.
+
Workflow / AI-assisted recon
Burp Suite MCP + Claude Code = Anti-Bot Recon in Minutes
PortSwigger shipped an MCP server for Burp Suite. Point it at Claude Code and the hours-long ritual of tracing which cookie unlocks which route, when the sensor payload fires, what gets re-validated on POST — collapses into a single prompt. The bar for what counts as anti-bot recon just moved.
💡 Build a burp-antibot-recon SKILL.md and replay it across targets
The classic anti-bot recon workflow: capture a session through Burp, scroll the HTTP history one request at a time, manually trace cookie flows (_abck, datadome, cf_clearance, reese84), identify sensor.js challenge endpoints, figure out which requests trigger re-validation. For a moderately complex target like nike.com, this takes hours per session. With Burp's MCP server pointed at Claude Code, you capture the same session and prompt: "trace the _abck cookie lifecycle from home page through add-to-cart, identify all sensor payload endpoints, and explain the validation flow." Claude reads Burp's full history directly and produces the analysis in seconds. The pattern scales: build a reusable burp-antibot-recon Skill once, replay it across Akamai/DataDome/Cloudflare targets. If you work in this space and haven't wired it up, this is the unlock. github.com/PortSwigger/mcp-server
+
Shift · Agentic reverse engineering · 2026
Agents Now Drive the Disassembler, Not Just Read It
The interesting change in LLM-assisted reverse engineering is not that models suddenly read obfuscated code perfectly. It is that, wired to real tooling, they coordinate the whole pipeline: query disassemblers and decompilers, inspect cross-references, generate scripts, patch binaries, rerun analysis, and refine hypotheses in a loop. The shift is from a smart helper sitting next to the analyst to an agent driving the toolchain.
💡 The lever is orchestration of the workflow, not raw comprehension of the code.
For scraping this lands squarely on the hardest target type: native mobile request-signing. The manual version of that work (open the .so in Ghidra, trace the call chain, confirm with Frida, rebuild in Python) is exactly the kind of multi-tool loop an agent can now coordinate. You point a coding agent at a target, it spins up specialist sub-agents (engines, impersonation, detection, architecture, fingerprints), runs them in parallel, then synthesises and stress-tests the result.

The honest caveat is the same one the reverse-engineering community draws: agentic workflows compress the process, they do not dissolve every defence. Some obfuscation classes (heavy virtualisation, bytecode VMs) stay resilient, which is precisely when you fall back to the oracle approach from the mobile section rather than a clean offline rebuild. Treat the agent as a force multiplier on a method you already understand, not a replacement for understanding it.

And the obfuscation side is adapting to the agents specifically. The same practitioners who teach automated deobfuscation (SMT solving, symbolic execution, MBA simplification, program synthesis to recover VM handlers and bytecode) now report protections deliberately engineered to break those pipelines: anti-agentic patterns. Analysis traps that detect a symbolic-execution or instrumentation harness and change behaviour under it, and runtime-bound semantics where a function's meaning depends on live state an offline solver cannot supply, are built to make an automated loop stall or draw a confidently wrong conclusion. The arms race did not end when agents could drive the disassembler; it moved up a level, and the counter to a trap you cannot automate around is still a human who understands what the loop was trying to do.

The defensive counterpart: adversarial, evolutionary obfuscation. The same shift explains why classic JS obfuscation is effectively dead against a capable agent. Packing a script, virtualising it, or flattening its control flow only bloats the code; a frontier model with a sandbox takes those apart in minutes, because the transformation is mechanical and therefore reversible by a mechanical process. The technique defenders are moving to instead is generative and uses the attacker's own tool as the fitness function. Rather than hiding a detection probe by wrapping it, you take a method that is API-adjacent and plausible (a dead-end bot inquiry that looks like ordinary telemetry) and have a model morph it together with the real probe you want to conceal, then point a council of analysis agents at the result and ask them to state its purpose. Whatever they correctly identify, you feed back as the next generation's target to disguise, and you repeat. After only a few rounds the reported case is that the agent council could no longer determine the exact mechanism by which detection was happening, not because the code was denser but because its observable behaviour had been evolved to read as something benign. The lesson cuts both ways for a scraper: the obfuscation you meet on a serious target is no longer a puzzle with a fixed solution your agent will grind out, it is a moving artifact shaped specifically to survive being read by an agent like yours, and the tell you are looking for may have been deliberately grown to look like innocent instrumentation.

A concrete cold-start case. A researcher pointed an agent that pairs a model with a sandboxed VM (Manus AI) at a live Akamai deployment on a real luxury store, with no prior notes and no internal wiki, and asked only that it study how the protection works, deobfuscate the client sensor, and enumerate which parameters feed the score. The agent fetched and instrumented the actual live script in its VM, then returned a structured map of the anti-hook layer, the challenge flow, and the TLS gate. The point is not a finished bypass (none was shipped) but that the expensive, human-gated part, reading minified obfuscated telemetry code and rebuilding its logic, was done by the agent running and checking its own work rather than a person spending days renaming variables.

The guardrail gap is the real story. Ask a guarded chat assistant to deobfuscate a production anti-bot sensor and enumerate its scoring signals and you hit a refusal, because that is squarely inside cybersecurity guardrails. An agent product wired to a VM took the same task and ran it. The economic consequence is what matters for the arms race: the cost that kept most protections standing was the human reverse-engineering hours, and an agent that verifies its own deobfuscation moves that gate. Defenders should now assume sensor logic is cheaper to map than it used to be, and lean harder on the layers that do not live in the client script.

Framing from "Deobfuscation in the Age of Agentic Reverse Engineering" (REcon 2026), practitioner demos of multi-agent RE pipelines, and a documented cold-start agentic mapping of a live Akamai sensor (The Web Scraping Club, Lab #108, June 2026). The agent's specific findings are reportage, not independently re-verified, and operational specifics were redacted at the source.
+
Tooling · Agent-native fetching · 2026
The Browser Is Getting Lighter Because Agents Do Not Need Pixels
A human needs a rendered page. An agent needs the meaning of the page. That gap is producing a new class of fetchers that throw away the visual rendering stack agents never use: headless engines measured in tens of megabytes instead of two hundred, and tools that hand back clean Markdown instead of a DOM you have to parse. When the consumer is an LLM, the heavy browser is mostly overhead.
💡 Match the fetcher to the consumer. An LLM wants structure and speed, not a painted viewport.
Three tools sketch the direction, and the pattern matters more than any one of them:

• Obscura is a headless browser written in Rust that ships its own V8, speaks the Chrome DevTools Protocol, and runs as a single binary with no Chrome and no Node.js. Roughly 30MB resident against 200MB-plus for headless Chrome, with per-session fingerprint randomisation and a DOM-to-Markdown mode for feeding pages straight to a model. Be honest about maturity: it is an early v0.1.0 with self-reported numbers, so star it and benchmark it yourself rather than betting production on it today.

• Crawl4AI turns selected pages into clean Markdown or structured data built for agents, RAG, and pipelines. It is the extract-and-shape half of the stack.

• SearXNG is a self-hosted search layer that finds the candidate URLs in the first place. It is the discover half.

Together they describe a small, controllable agent web-context loop: discover, fetch, extract, cache, cite, with each stage owned by a light tool you can host yourself rather than a heavy browser doing all four jobs badly. The takeaway is not "switch to these tools." It is that when an agent is the consumer, the cost of rendering pixels nobody looks at is pure waste, and the tooling is starting to reflect that.

Tools surfaced publicly by practitioners in 2026: Obscura (Rust headless, Apache-2.0/MIT, early), Crawl4AI (Apache-2.0, 68k+ stars), SearXNG (self-hosted metasearch).
07 Managed platforms

When DIY cost
exceeds platform cost

If spending more than 2 engineer-days/month on anti-bot maintenance, a managed platform is cheaper. Separate two products that are both sold as managed. An unblocker API fetches the page for you and bills per successful response. A token or bypass API returns only the cleared cookie and the headers it was signed against, and you send the real request yourself. With the second kind, the request still leaves your own client, so its TLS handshake, HTTP/2 settings, header order and exit IP must all match the session the token was minted for; vendors in this category state that the token is bound to the proxy IP it was solved under, and a wall that rescores on consistency treats a token replayed from another address as hijacked. Test that on a small sample before committing volume: a valid cookie carried by an incoherent client is still a block, and the bill for the solve arrives either way.

When LLM-written code makes a scraper cheap to build, work out how much of the bill was writing code in the first place. Its share decides how far your break-even moves. One 2026 rerun of a 2023 build-or-buy model found break-even volume fell about two-thirds on light HTML sites but only about a tenth on sites that need a full browser render. The inputs show why. A hard-tier build is about $112.50 in tokens, but at 1.7 MB per rendered page and $8/GB, bandwidth is about $0.0136 per request. At 100,000 requests that is about $1,360, twelve times the token cost, and it comes back every month. Maintenance for simple fixes dropped from about 2 hours to about 30 minutes a month per scraper, but there was no gain when the anti-bot changed. So write the code yourself, and buy proxies, unblocking and browsers unless scraping is your business. If you reach for a visual workflow builder instead of writing the spider, check first what it leaves in your hands: exported code you could maintain without the tool, or a graph file that only that tool’s engine can run. Check the licence as well. Crawlflow, a node-graph editor on top of Scrapy, interprets its saved JSON at run time and exports no spider. At a 5 October 2026 check its public repo was two days old, with zero stars and no licence file, which by default grants no right to reuse the code. That makes it fine to try on a sandbox site, but not yet something to build a pipeline on. A builder that exports a plain spider keeps the write-it-yourself option open. One that only interprets its own format hands your maintenance to its maintainer, and you inherit that project’s pace and its survival. Treat these as one analyst’s model: the per-tier request counts are paywalled, and the per-request figures are derived from the published prices.

Crossover typically hits when facing F5 Shape or Kasada at scale.

Bright Data 98.44%
Enterprise · 72M+ IPs · Scrape.do #1 2025
Highest success rate in Scrape.do 2025. 100% on Indeed, Zillow, Capterra. 72M+ residential IPs. GDPR, ISO 27001. Scraping Browser for JS-heavy targets. $1.50/1K requests.
✓ Best for: F5 Shape, hard targets at scale brightdata.com ↗
Zyte 93.14%
#1 Proxyway 2025 · Fastest API · Scrapy
#1 Proxyway 2025, 93.14% success rate. Fastest API response. Smart Proxy auto-selects type. GPTE AI generates parsers from natural language. scrapy-zyte-smartproxy integration.
✓ Best for: Scrapy pipelines, speed zyte.com ↗
Firecrawl ★ 111k
AI scraping · Self-hostable · MCP
URL → Markdown/JSON. No selectors. MCP server, Claude scrapes via natural language. LangChain + LlamaIndex native. Used by SAP, Zapier, Deloitte. 500 free/mo.
✓ Best for: RAG pipelines, AI agents firecrawl.dev ↗
Crawl4AI ★ 60k
Open-source · Local LLM · Free
89.7% OOTB success rate. Runs on your infrastructure. Local LLM support (Ollama). MIT license. Adaptive crawling. Full data sovereignty, data never leaves your servers.
✓ Best for: privacy, open-source, cost $0 crawl4ai.com ↗
Oxylabs
Enterprise · 100M+ IPs · OxyCopilot AI
100M+ IPs, 195 countries. OxyCopilot AI generates parser code from natural language. Owns ScrapingBee (acquired 2025). ISO 27001 + GDPR. From $49/mo.
✓ Best for: enterprise scale, AI-generated parsers oxylabs.io ↗
ScrapingBee
Headless · Managed rendering
Handles JS rendering, CAPTCHAs and proxies. Simple REST API, pass a URL, get back HTML or screenshots. Good for teams that want managed scraping without infrastructure. Free tier available.
scrapingbee.com ↗
Scrapfly
Anti-bot · AI extraction · Monitoring
Premium scraping API with built-in anti-bot bypass, JS rendering, and AI-powered data extraction. Strong on hard targets. Includes scraping monitoring and scheduling out of the box.
scrapfly.io ↗
Diffbot
AI extraction · Knowledge graph
Uses computer vision and AI to automatically extract structured data from any webpage, no CSS selectors, no XPath. Builds a knowledge graph from scraped content. Best for unstructured web data that needs AI parsing.
diffbot.com ↗
WebScraper.io
No-code · Chrome extension
Point-and-click scraping via a Chrome extension, select elements visually, define pagination, export to CSV. No coding required. Cloud version runs scrapers on schedule. Best for non-technical users.
webscraper.io ↗
Browse.ai
No-code · Monitor · Robots
Train a robot to scrape any website in 2 minutes by clicking on the data you want. Monitors for changes, sends alerts. Handles login flows, pagination, and dynamic sites. No code needed at all.
browse.ai ↗
Browser Use
AI agent · LLM-controlled browser
Open-source library that lets LLMs control a real browser. The AI agent navigates, clicks, fills forms and extracts data from instructions in natural language. 81% success rate on anti-bot benchmarks. GitHub ↗
browser-use.com ↗
Stagehand v3 · OCT 2025
AI Browser SDK · Browserbase · Open source
Browserbase's AI browser automation framework. Four primitives: act(), extract(), observe(), agent(). Write browser flows in plain English ("click submit button") that survive page redesigns via runtime LLM resolution. Built on CDP, supports OpenAI/Anthropic/Gemini. 65% Mind2Web benchmark. Self-healing + auto-caching. TypeScript and Python.
browserbase.com/stagehand ↗
Kadoa
AI · Zero-config · Auto-adapt
AI-powered scraping that requires zero configuration, no selectors, no rules. Understands page structure automatically and adapts when sites change. Ideal for scraping at scale without maintaining spider code.
kadoa.com ↗
ScrapeGraphAI
LLM · Graph pipeline · Open source
Builds a graph-based extraction pipeline from a natural language prompt. Describe what data you want, it generates the scraping logic. Open source and self-hostable. Good for rapid prototyping of complex extractions.
GitHub ↗
TinyFish
AI · Structured extraction · Fast
AI-native scraping API focused on speed and structured data output. Pass a URL and a schema, get back clean typed JSON. Handles JS rendering and basic anti-bot. Good fit for feeding structured data into AI pipelines.
tinyfish.io ↗
Nimble
AI · Structured · E-commerce
AI-powered web data platform with pre-built pipelines for e-commerce, SERP, and social. Returns structured data with no parsing needed. Built-in proxy network. Strong for retail intelligence and price monitoring.
nimbleway.com ↗
NetNut
ISP · Residential · Scraping API
ISP-level residential proxy network with a built-in scraping API. Direct carrier connections for lower detection risk. Strong for e-commerce and SERP scraping where IP freshness and session stability matter.
netnut.io ↗
Infatica
Scraper API · SERP · P2B sourcing
Singapore-based provider with both a P2B residential proxy network and a managed Scraper API (POST URL → HTML/JSON, auto-retry, optional render mode for JS-heavy pages, dedicated SERP endpoint with Google AI Overview support). The differentiator vs Zyte/Bright Data/ScraperAPI: explicit peer-to-business IP sourcing, mandatory KYC on buyers, ISO 27001/27701/22301/20000-1 certified. Smaller pool than the giants but priced below them, with a 5,000-request trial. Worth a look when ethics and certification matter as much as raw scale.
infatica.io ↗
Scraping Robot BY RAYOBYTE
Scraping API · 5,000 free/month · JSON output
Plug-and-play scraping API from Rayobyte. Returns clean JSON, handles cookies + headers + browser attributes automatically. 5,000 free scrapes/month on signup, paid tiers from $5/GB. Built on Rayobyte's proxy network and rayobrowse stealth browser. Lower entry barrier than Bright Data or Zyte for teams wanting "scraping as a service" without infrastructure.
scrapingrobot.com ↗

The next CAPTCHA frontier is liveness, and it is being defeated the same week it ships

The reason the visible puzzle is fading is that behavioural scoring has run into a wall. When most of your traffic is automated and the best of it imitates a person convincingly, a test built on watching for human signals stops separating anyone from anyone. So the frontier is moving to liveness: proof that a real, live body is present at the moment of the check. In June 2026 Google began testing a hand-gesture reCAPTCHA inside Google Cloud Fraud Defense. It asks for camera permission, records a short clip of you waving or holding up an open palm, and uses a hand-tracking model to extract 21 knuckle-landmark coordinates, deleting the video afterward. On paper it is a much harder challenge for a script than reading warped text. Read this as the image puzzle losing its edge, not only as a new gadget. Image challenges have long survived by rotation: new variants arrive faster than a solver can collect, label and retrain a classifier for each. Small open-weight vision models erode that lever. One practitioner reports that fine-tuning a 2B-active model on about 200k hCaptcha images reached 90%+ on challenge types it had not trained on, a figure that is unpublished, undefined and n=1. If even part of that holds, a correct answer says little about who gave it, and the defence moves to what surrounds the image: obfuscated probes, fingerprint checks, motion and now the camera. So expect harder friction where a site escalates, and assume the answer is the least of what it scores. If you run a challenge yourself, the image matters less than the server rules around it. Bind every site key to an origin allowlist, cap attempts per challenge, rate-limit the verify endpoint, and do not serve puzzles from a stock open-source generator. One 2026 teardown of a new waiting-room CAPTCHA reported that the first three were all missing, which let a single researcher resubmit the same image until it was accepted and train a model to a reported 99.8% without a single proxy; a second found that a ticketing site’s captcha came from an unmodified public generator, so labelled training data was free and unlimited. Both are single reports by a bypass vendor and may since be fixed, but the checklist costs nothing to apply, and without it the verify endpoint does the attacker’s labelling.
In practice it was passed with a still image within days of surfacing. A journalist and independent testers fed a plain stock photo of a hand through a virtual camera (OBS Virtual Camera presents any video file or image to the browser as if it were a real webcam) and the check accepted it. No deepfake, no generated animation, no AI bot reading the page, just a static picture routed through the camera input, and the whole thing automatable in minutes with a small script. The lesson is the one this section keeps returning to: the challenge is only as strong as the integrity of the channel it arrives on, and a browser cannot vouch that the pixels on its camera input came from a physical lens rather than a file.
Why a static image gets through
The attack is injection, not forgery
Liveness systems fail at the point where the media enters the pipeline, not at the recognition step. A virtual-camera driver sits between any media file and the browser's getUserMedia stream, so the model never sees a lens, it sees whatever frames you hand it. This is a presentation or injection attack, and it is the same class of bypass that has dogged face liveness for years (masks, screen replays, injected deepfake video). A hand is, if anything, easier to synthesise than a face, with fewer micro-expressions to get right. Reported virtual-camera and deepfake liveness-bypass attempts rose sharply through 2025-2026 as the tooling commoditised, with some injection kits priced around the cost of a coffee.
What it means for a scraper
The friction grows faster than the gap closes
For most scraping you will never trip a camera challenge, because the way to beat the visible test is to never summon it: keep the trust score high with clean sessions and a coherent fingerprint so the hard challenge stays dormant. Liveness raises the ceiling of pain for the cases that do escalate, but it does not close the underlying gap, and it adds real friction and privacy cost for genuine humans. The structural pressure points the other way entirely: bots increasingly skip the rendered interface and hit the JSON API directly (a measurable and rising share of automated traffic in 2025-2026), where there is no camera prompt to inject into at all. The verification ritual gets longer at the front door while the data keeps leaving through the side.
One caveat worth stating plainly: the vendor here is uniquely placed to harden this over time, owning the dominant browser, the hand-tracking models, the reCAPTCHA scoring layer, and a mobile OS, which is exactly the stack you would need to detect virtual-camera injection through capture-integrity and device-attestation signals. The current stock-photo bypass is an early-rollout gap, not proof the approach is permanently hopeless. The durable point is the principle: a liveness check is only as trustworthy as its ability to prove the media came from a real sensor, and that proof is the hard, unfinished part.
5b Adjacent category

Computer Use Agents when scraping isn't enough

A new category emerged in 2025: AI agents that don't just scrape, they log in as the user, navigate any UI (web apps, legacy portals, desktop software), handle MFA and CAPTCHAs, and return structured JSON. Different from scrapers because the user grants permission, "Plaid for any website." If your problem is utility bills, payroll exports, e-commerce backends, or any portal without a public API, this is the category.

Deck FEATURED · $25M RAISED
Computer Use Agents · Credential Vault · SOC 2
Plaid-ifies any website. Provisions isolated desktop VMs, encrypts credentials in Deck Vault, runs AI agents that log in, navigate, and return schema-validated JSON. Founded by the team behind Flinks (Canadian open-banking, acquired for $150M by National Bank). Connects to 100,000+ utility providers across 40+ countries. Handles MFA, CAPTCHA, device fingerprinting, audit-logged sessions. Strong on regulated portals with no public API.
deck.co ↗
Skyvern
Open source · LLM + Computer Vision · 85.8% WebVoyager
YC-backed open-source agent that uses LLMs and computer vision (no XPath or CSS selectors) to operate any browser workflow. State-of-the-art 85.8% on WebVoyager benchmark. Used for invoice retrieval, job applications, government forms, insurance quotes. Both cloud-hosted and self-hostable SDK with Playwright integration.
skyvern.com ↗
Bytebot
SDK · AI browser automation
SDK-first computer use agent platform. Lighter footprint than full VM solutions, integrates into existing apps. Targets developer workflows where you want agentic browser actions without managing browser pools yourself.
bytebot.ai ↗
CloudCruise
Browser automation · Web agents
Developer platform for creating and managing web agents. Focuses on production-grade browser automation infrastructure. Competes with Deck and Browserbase on the infra layer.
cloudcruise.ai ↗
Autotab
Enterprise AI agent · Data + form automation
General-purpose AI agent for enterprise, data collection, form filling, executing actions across business apps. Pitched at operations teams rather than developers.
autotab.ai ↗
Browserless
Managed headless Chrome · CDP-as-a-service
Chrome-as-a-service over WebSocket and REST. Foundation layer that other agent platforms build on. Strong for teams that want managed browser pools without the agent reasoning layer on top.
browserless.io ↗
When to pick this category over scraping: if the data lives behind a login the user owns (their utility bill, their bank statement, their payroll), Computer Use Agents are the right answer: the user’s permission gives you a stronger legal posture and access to data scraping cannot reach. Stronger is not clean. In August 2026 the Ninth Circuit held in Amazon v. Perplexity that the user, not the agent vendor, is the one who accesses the site under the CFAA, but it called the holding narrow; who answers for an agent’s conduct under contract and other law is still open, and the site can always terminate the user’s account. If the data is public-facing (e-commerce listings, SERPs, social), traditional scraping is faster and cheaper. Social platforms are the exception to take carefully. Their terms typically prohibit automated access outright, and LinkedIn’s robots.txt opens by saying automated access without express permission is strictly prohibited; the official APIs that are permitted tend to be expensive, rate-limited or closed to general use. That leaves three routes with different exposure: an official API where one fits, a provider or pre-collected dataset, or your own collector, which carries the most legal and maintenance weight. Do not count a scraping API as a separate legal route from collecting the data yourself; buying a finished dataset is the option that can genuinely move some exposure. In Reddit’s 2025 suit over scraped search results, the AI company that consumed a scraping API was named a co-defendant beside the API vendor. A cease-and-desist sent to a data vendor, by contrast, does not necessarily restrict how its customers use data already bought. A dataset purchase still leaves copyright claims over any protected material in it, staler data, the vendor’s margin, and the risk that the vendor gets blocked and the feed stops. Do not rely on any vendor’s representation that it is legally in the clear. This is one US law professor’s reading of unsettled law, not legal advice. If you build your own, stay logged out, since the guide’s cases put public, logged-out collection on the firmest ground, and expect no generic extractor: X serves feeds through internal GraphQL, LinkedIn through obfuscated internal JSON, and others through server-rendered HTML, each changing without notice. X also shows why logged-out is the realistic boundary: its sign-in is defended like an anti-bot wall. Teardowns published in January and February 2026 by the founder of a bypass-API vendor describe three layers in the web login: a per-request x-client-transaction-id header, a second encrypted header produced by a WebAssembly module that reads automation signals such as navigator.webdriver and whether the user has interacted with the page, and an obfuscated JavaScript challenge (ui_metrics) returned partway through the flow. A plain HTTP client that replays the form therefore fails before credentials are even checked. Rebuilding those values is login automation against the platform’s terms, so this guide does not cover it; if you need data that sits behind X’s login, the official API is the route that holds up. Judge the route before the code, not after.
3 field notes on platforms Reported from practice. Click any card to open it.
+
Aug 2026 / Measured
Two Posts, One Day Apart, and a Working Method For Reading Any Success Rate
One practitioner published four reasons not to trust a quoted success rate. The next day a vendor published a benchmark putting itself first on success rate by fifteen points. Read together they are the best worked example this guide has of how to judge a number somebody else produced.
💡 Four questions: who defined success, who picked the targets, at what request rate, and does the ranking survive per site
The four choices, none of which you made. First, the vendor defines success, and usually it means the request came back — but a challenge page returns 200, a login wall returns 200, and a page whose price sat in a JavaScript variable nobody parsed also returns 200. “98% success” can mean 98% of requests returned something; you hear 98% of the prices you need. Second, somebody chose the target list: in one 2026 benchmark of eleven APIs against fifteen protected sites, Shein averaged 21.88% across every provider tested and G2 36.63%. Blend those with Amazon and Google and the headline describes neither. Third, test conditions move it — in that same report one API fell from 84.47% to 72.98% purely by raising the request rate. Same tool, same targets, twelve points. Fourth, rankings invert per site: on G2, ScraperAPI succeeded where Oxylabs, Decodo and ScrapingBee failed, despite carrying one of the lower blended scores in the test. The “worse” tool was the only one that worked there.

Now score the vendor benchmark against those four. It ran 15 providers over 99 targets, 5 attempts each, 7,425 billable requests, reporting a failure-aware per-target p75 latency. Results: String 97.0% at 9.98s, Scrapfly 82.0% at 18.35s, Context.dev 79.2%, Firecrawl 78.6% at 9.21s, Bright Data 78.0% at 26.14s. And to its real credit, the harness, the 99 target URLs, the pass criteria and every provider adapter are published, so anyone can re-run it. That is a higher bar than almost any benchmark in circulation and it deserves saying plainly.

What still needs holding in mind. The vendor authored the benchmark it comes first in, by fifteen points. The pass criterion — question one, the one that decides everything — lives in the repository rather than in the write-up, so the headline travels without it. The post claims 2 to 10× better cost effectiveness while the benchmark contains no cost analysis at all, which is a separate claim wearing the benchmark's authority. And the comment thread under it is three people from the same company. None of that makes the numbers wrong. It means reproducible and disinterested are different properties, and this is the first without being the second.

The rule that falls out. A published success rate is a claim about somebody else's target list at somebody else's request rate under somebody else's definition of success. The only number that describes you is the one you produce on your own sites at your own volume — which is exactly what an open harness makes possible, and the best reason to want one.
+
Aug 2026 / Buy versus build
The Vendor Question Was Never “Can It Run JavaScript”
Firecrawl in 2026 is no longer a page-to-markdown API: it carries crawl, search, structured extraction, browser interaction, monitoring, an agent endpoint and proxy escalation. That capability growth moves the buy-versus-build line rather than settling it, and the post names where the line actually sits.
💡 Compare on accepted records and completed refreshes, never on raw requests
The dividing question. Not whether the API can render JavaScript — it can — but who owns the gap between a fetched page and an accepted business record. A managed service fits when its standard workflows pass on representative sources and your team can own validation, orchestration and downstream retries. A custom engine fits when each source needs different behaviour under one shared business schema, with monitoring, repair ownership, and an SLA defined around accepted output rather than successful fetches.

The denominator is the part to steal. Cost compared on accepted records and completed refreshes rather than raw requests, because — as the post puts it — that denominator changes many vendor comparisons. It is the same unit this guide's cost section argues for and the same one on the API-versus-scrape scorecard in §01: a request you paid for that produced a record you cannot trust is worse than no request, because somebody downstream acted on it.

Why the reframing matters more than the vendor. Feature lists converge — every managed platform will eventually claim crawl, extract, monitor and an agent endpoint. What does not converge is who is accountable when a source silently changes shape at 3am. Choose on where that accountability sits, then check the price per accepted record, and the feature matrix stops mattering almost entirely.

Sep 2026 update. One more data point for convergence. Spidra (spidra.io, public beta; by its own description Playwright plus Gemini extraction) teased a “major announcement”, and on 29 Sep 2026 it shipped search with optional scraping of the top results in the same call, alongside crawl, batch, schema output and an MCP server. It publishes plan prices ($19–$249 a month; 2 credits per URL, 10 per CAPTCHA) but no success rate, benchmark or cost per accepted record. That the search launch was the teased announcement is our inference.
+
AI Visibility · 2026
Top 10 Scraping APIs per ChatGPT + Perplexity + Gemini + Google
All four AI models queried simultaneously. Consensus: 1. Bright Data · 2. Zyte · 3. ScrapingBee · 4. Firecrawl · 5. Scrape.do. Half of B2B buyers now start research in AI chatbots.
💡 AI search is a real channel, Bright Data #1 across all four models
The research methodology: a scraping API was used to query four AI systems simultaneously from a San Francisco IP address (to simulate a US-based B2B buyer). Prompt: "best web scraping API 2026". Results aggregated by occurrence and ranking position. Full ranking: 1. Bright Data (98.44% success, 72M+ IPs), 2. Zyte (93.14%, #1 Proxyway benchmark), 3. ScrapingBee, 4. Firecrawl (111K GitHub stars, LLM-optimised), 5. Scrape.do, 6. ScraperAPI, 7. Apify, 8. Scrapingdog, 9. Oxylabs, 10. Scrapfly. The AI search SEO implication: if you are building a scraping product, being in AI training data and AI search indexes is now a primary distribution channel. The buyers searching "best scraping API" increasingly ask an AI chatbot, not Google.

Oct 2026 update. More of the content those answers draw on: on 30 September 2026 Lara Translate, a translation company, published a “best no-code web scraping and monitoring tools” list. It has 13 numbered but unranked entries, prices taken from vendor pages on 4 September, and no hands-on test. By its own table, only Octoparse, ParseHub, Browse AI and HasData are no-code or visual. ScrapeGraphAI, which publicised its inclusion, is labelled a developer API. Read it as content marketing, not as an evaluation.

Platforms sort out the browser and the fingerprint. But every request still needs an IP address, and the type of IP matters as much as any other signal in your stack.

08 Proxy strategy

IP type matters
more than provider

Rotating proxies is table stakes. The real variable is IP type, datacenter IPs score near-zero on DataDome and PerimeterX regardless of fingerprint quality. Treat that as the rule for the hardest vendors, not a law for every protected page, and test it on your own targets. In one open benchmark run in September 2026 (100 bot-protected URLs, four attempts, a language model judging success), browsers running straight from CI datacenter runners were far from zero: the best direct run passed 66 of 100, and a Firefox-based stealth browser passed 5 of 16 DataDome-stratum pages direct against 7 of 16 through a filtered residential pool. Several browsers scored higher direct than through either residential provider. So always run a no-proxy control arm beside each proxy arm, because a table that shows only the best proxy row credits the proxy for work the browser did. One run, small per-vendor strata, and both providers were sponsors of the benchmark site, so take the direction, not the decimals.

Datacenter
Trust: Very Low
AWS/GCP/Azure ranges. Instantly flagged by DataDome and PerimeterX. Cheapest (~$0.01/GB). Use only on non-protected public data. Never for Akamai or PerimeterX targets.
Residential
Trust: High
Real home ISP addresses. Passes most trust checks. Confirmed: curl_cffi + residential bypasses DataDome on Grainger.com. Rotate per session, not per request, mid-session rotation = Akamai block.
Mobile / 4G
Trust: Highest
T-Mobile, Vodafone, O2 carrier IPs. Highest trust score on DataDome and PerimeterX. Shared tower IPs, hard to flag. Mobile IPs get DataDome 200 OK where residential fails. ~$10–15/GB.
ISP / Static
Trust: High
Static residential range. Akamai multi-request scoring rewards consistent IPs, trust accumulates from same ISP IP. Best for long sessions on Akamai sites. Never rotate mid-session.
NetNut
ISP Direct
ISP-based infrastructure with direct carrier connections. Lower detection risk than pooled residential. Fast and stable, good for e-commerce targets that check IP freshness and session age.
IPRoyal
Residential
Ethically sourced residential + datacenter. Pay-as-you-go pricing, no long-term commitment. Good entry point before scaling to enterprise contracts with Oxylabs or Bright Data.
Massive I use this Ethical · Founded 2018
Residential · ISP · Web Access API · Web Render API · MCP Server
Try Massive with my referral ↗ (affiliate)
"I've tested a lot of proxy providers across my 7 years in scraping. Massive stands out for two reasons: the ethics are real (not marketing), and the performance numbers hold up under actual load. 99.87% US success rate and 0.52s response time aren't made up, my production runs match that. If you care about running a clean, compliant operation, this is where I'd start." Asad Ikram, Data Engineer
Residential Proxies
1.6M+ IPs, 195+ countries. 99.87% US success rate. 0.52s response time. GDPR + CCPA compliant, AppEsteem certified. From $4.9/GB.
ISP Proxies
Static residential IPs for sticky, session-bound workflows. 100% success rate, 0.09s response (US). From $1.8/IP. Best for continuous monitoring.
Web Render API
Full JavaScript rendering with anti-bot bypass at scale. From $8/mo. Handles Cloudflare-protected pages without you managing browsers.
MCP Server ✦ new
Official MCP server. Use Massive directly from Claude, Cursor, or any MCP client. Geo-targeted search, bulk extraction, SERP analysis without leaving your AI workflow.
99.87%
US success rate
0.52s
response time
195+
countries
99.9%
uptime
100%
ethically sourced
<20%
fraud score (US)
Verified target success rates
Instagram 96% Amazon 94% Google 88% ISP 100% Trusted by Snowflake · Shopee · Tavily
Startups get 1TB free for 3 months, no equity required. 24/7 live support. GDPR + CCPA compliant. AppEsteem certified.
Join with my referral ↗ (affiliate)
Rayobyte
Ethical · Multi-type
"America's #1 proxy provider" (formerly Blazing SEO, est. 2014). 40M+ residential IPs across 100+ countries, plus ISP, datacenter, and mobile. Non-expiring bandwidth sets them apart, $3.50/GB residential dropping to $0.50/GB at 5TB+. Ethically sourced via Cash Raven consent-based proxyware. Ships rayobrowse stealth browser too. Hands-on technical CEO, partners with EWDCI for ethics standards.
Scrapoxy
Open Source Manager
Self-hosted proxy manager that pools and rotates proxies across AWS, Azure, GCP. Routes requests through different IPs automatically. Free alternative to commercial proxy managers. github.com/fabienvauchelles/scrapoxy
Byteful
Residential · ISP
Residential and ISP proxies pitched at scraping engineers who understand the full detection stack. Honest positioning: "proxies solve IP reputation, but TLS fingerprint, header order, browser behaviour, and request cadence all have to line up independently." Good entry point for teams that already have their TLS and browser layers handled.
Infatica
Residential · Mobile · P2B network
Singapore-based provider that runs an explicit peer-to-business (P2B) sourcing model: they pay app developers to embed their SDK on idle user devices instead of buying SDK installs through opaque intermediary chains. Smaller pool than the giants (~15M IPs vs Bright Data's ~150M, Oxylabs' ~175M) but with city, ZIP, and ASN filtering you can combine, and they claim to not resell the pool to other providers. Distinctive for the market: mandatory KYC on buyers, which is unusual and reduces abuse on the network. Pricing sits below the premium tier (subscriptions from $0.30/GB, pay-as-you-go from $8/GB). Worth a look when you want ethical sourcing and stricter buyer vetting without paying Bright Data prices, accepting that filter coverage by country is thinner than the larger networks.
WebRTC coherence rule: Proxy IP country, WebRTC ICE candidate, DNS resolver, timezone, and Accept-Language must all agree. Agreement with the proxy is not the whole check. At least one large marketplace’s anti-bot script compares the addresses reported by ICE candidate events, the SDP local description and getStats() against each other, and gives its STUN query about 300 ms; tunnelling STUN through a SOCKS5 proxy took over 2 s in one operator’s test, so a correct exit address that arrives late can still fail. In the same report, exposing plain private addresses where Chrome normally shows mDNS .local names sharply cut successful tasks per session, and restoring the masking recovered them, ending about 27% above the unchanged servers. That is one operator on one target, but the rule generalises: make every API tell the same story, keep it fast, and keep the browser’s privacy defaults unless you know the device you claim to be would differ. US residential proxy + Pakistani DNS = flagged by every major anti-bot. Use geoip=True in Camoufox to align all five vectors automatically.
Crawlera/Zyte proxy bug: Port 8011 speaks plain HTTP. Both http:// and https:// keys must use http:// scheme. Using https:// causes BoringSSL WRONG_VERSION_NUMBER (TLS-over-TLS failure). Fix: "https": "http://key:@proxy.crawlera.com:8011/"
python
from curl_cffi import requests
import time– random

session = requests.Session(impersonate="chrome124")

# Crawlera/Zyte: BOTH keys use http://, never https://
PROXIES = {
    "http":  "http://apikey:@proxy.crawlera.com:8011"–
    "https": "http://apikey:@proxy.crawlera.com:8011"–  # http:// not https://
}

def fetch(url– retries=3):
    for i in range(retries):
        try:
            r = session.get(url– proxies=PROXIES–
                             timeout=30– verify=False)  # verify=False: proxy cert
            if r.status_code == 200: return r
            if r.status_code in (403–429):
                time.sleep(2**i + random.uniform(0–1))
        except Exception as e:
            print(f"Error: {e}")
    return None

Rotate sessions, not IP addresses: stickiness is the strategy

The most expensive rotation mistake is the one that feels most thorough: buy the biggest IP pool you can, rotate the address on every request, randomise the User-Agent, and assume you are now invisible. What actually happens is that the blocks arrive around request 150 to 300, because modern anti-bot systems (Cloudflare, Akamai, DataDome, PerimeterX) do not treat the IP as the primary signal. They model the session, and per-request IP rotation produces a session that no human could generate: the same logged-in user apparently teleporting between cities mid-visit. More IPs do not fix that, they amplify it. The unit you rotate should be the session object, not the address, where a session bundles one exit IP with one coherent set of cookies, headers, and geo, held stable for as long as a real visit would last. Holding it that long is not merely camouflage, it is the cheaper arrangement, because what a protected target charges is closer to an admission fee than a per-request toll. The clearest measurement of that fee comes from a warm-up ladder pooled across three separate days against Google search results, one harness, one target: sessions that went straight to the query passed 20.0% of the time (33 of 165 judged attempts), while sessions that first walked seven pages of ordinary navigation passed 84.4% (152 of 180), a separation of z = 12.0 that day-to-day drift does not account for. The half of that result which argues for stickiness rather than merely for warming is what happens afterwards: an admitted session keeps being admitted, 108 of 109 follow-up requests on already-served sessions passing. So the dwell you spend getting in—roughly 65 to 195 seconds per identity, depending on how deep you walk the ladder—amortises across every row that session goes on to fetch, and the arithmetic of rotation inverts: short sessions pay the admission fee over and over, long ones pay it once. Treat the absolute rates with scepticism, since that harness is built and maintained by a company that sells proxies and grades the conditions its own product is good at; the shape replicates, the decimal does not travel. And what the depth is buying—target-side reputation keyed to the exit, or nothing more than the browser’s own cookie jar—is still unmeasured.
Where a mid-session IP change gives you away
Coherence is the layer most scrapers fail
Three flows punish an IP swap in the middle. Authenticated sessions: change the IP between the login request and the next data request and the server sees a user who signed in from Paris asking for data from Seoul, so it re-verifies or invalidates. Stateful pagination: many apps tie server-side page state to your IP, so changing address between page 3 and page 4 loses the cursor and returns a block or, worse, silently wrong data. Multi-step forms and checkouts: the state established at step one is carried forward, and a mid-flow IP change is an instant red flag. The rule that falls out is simple: all requests belonging to one logical session must exit through the same IP, or at minimum the same subnet, ASN, and geo. For an account you keep logged in for days or weeks, widen that rule: bind the exit to the account for its whole life, across worker restarts and snapshot restores, not to a session TTL. The ten-minute refresh below is written for anonymous sessions; applied to a logged-in account it moves the geo underneath a live login on every cycle, and some applications revoke on exactly that, a network or location change outside the state you saved. A restored snapshot that suddenly arrives from a new region is both stale-looking and suspicious-looking at once. Keep a durable account-to-exit mapping in the same shared store as the session pool, and when the provider can no longer give you that exit, treat it as a planned re-login from the new location rather than silently continuing. This comes from practitioner experience rather than a measured revocation rate, so test how tolerant your own target is before you rely on it.
How to build the rotation layer
Sticky assignment, honest expiry, careful retries
Key each session deterministically (job plus target domain, not random) so a crawl reuses one session until it expires, and expire on both a TTL and a request count (refresh around every ten minutes or a couple hundred requests, so a long-lived session does not become its own statistical fingerprint). Keep one header profile per session, no swapping the sec-ch-ua or Accept-Language between requests that claim to be the same browser. Between sessions, rotate within the same geo and ASN cluster, since a jump from a Tokyo residential IP to a Toronto one reads as a VPN switch, not a returning user. And separate your retry cases: a transient timeout retries on the same session after a short jittered backoff, only a clear block (407, 429, a CAPTCHA) flags the session and forces a fresh one. At fifty machines this needs a shared store (a Redis-backed pool) so every worker sticks to the same session instead of each inventing its own. Before blaming a provider for a slow scraper, split one proxied request into phases and set it beside a direct one. The CONNECT wait, from the TCP connection to the gateway until the tunnel is ready, is the provider choosing and dialling an exit; time to first byte, counted from when the request is written, is that exit’s path to the target plus the target itself. Go’s httptrace exposes each phase, and small tools such as http-slow --proxy ... --compare-direct print them side by side. Repeat the measurement, because a rotating gateway hands each attempt a different exit and one sample says little. And read the status code first: a fast challenge page makes a target look quick when it has only refused you.
The division of labour that scales best: let a provider that offers sticky sessions guarantee the exit-IP consistency (a session token with a location baked in, held for up to some minutes or hours), and keep your own layer responsible for the cookies, headers, and navigation coherence on top. Building and maintaining a distributed session pool yourself is real, ongoing work that grows every time a target updates its detection. IP rotation is a tactic; session architecture is the strategy, and it is the difference between firefighting blocks and running a scraper that stays a reliable data source as the detectors evolve. On a target that refuses fresh exits, treat an exit that has been served as the asset and an exit that has been refused as worthless. Warm it once, reuse it for a batch of queries, drop it at the first refusal, and stop the run after a few refused exits in a row instead of buying more of them. One open-source Google collector ships exactly those defaults: up to 10 queries per exit, 3 exits tried per query, and a stop after 6 consecutive refusals. Since mid-October 2026 that collector holds a served exit for up to 50 queries, because each retirement throws away a warm-up worth tens of pages of traffic, and its command line starts new exits from a copy of the first served exit’s cookies instead of warming each one, on the stated but unmeasured observation that the target’s acceptance follows the cookies more than the address. If that holds on your target, the state you pay to build lives in the cookie jar, so treat a served jar as reusable inventory with an age limit (that tool stops after three hours) and end the copying the moment a copy is refused on its first query. The maker, which sells proxies, reports about 2.2 MB per successful search and 99.7% of searches served on one server with two browsers, with no run data published, so measure bytes per success yourself. Its measurements, published by a proxy seller on one target, put the first query at 20.0% (33/165) on a cold exit against 84.4% (152/180) after six pages of warm-up, while 108 of 109 follow-up queries on an exit already served were answered. Classify each page before parsing it, so that a challenge is never counted as an empty result: one rule drops the exit, the other keeps it. On a per-GB plan, a session restart is a bandwidth bill as well as a block. When a target opens every session with an anti-bot challenge, that bootstrap is paid in full each time an exit dies, so measure it separately from the page you want. In one vendor benchmark on a Kasada-protected hotel site, the bootstrap cost 342 KB against 104 KB per results page: about 174 KB per search at five searches per session with no restarts, a modelled 244 KB if half of all attempts had to start over, and 106 KB when sticky sessions ran 100 searches each on a single challenge. Rotate on error rather than on a timer, keep an accepted session for as long as the site allows, and compare providers on bytes per successful result with restarts included, not on pool size. That restart penalty is modelled; no session in that test lost its exit.
The mirror-image mistake is just as fatal: rotating IPs under one fixed fingerprint. Sticky sessions solve the case where the IP changes mid-visit, but the opposite pairing is its own tell. If you rotate through a dozen addresses while the browser fingerprint stays byte-identical across all of them, you have not diversified, you have announced that one client is hiding behind twelve IPs, which is behaviour no real user or device produces. Once the fingerprint is part of the identity, an unchanging fingerprint spread across many networks links those sessions together more tightly than a single sticky IP ever would have, so the rotation you added for cover becomes the thing that convicts you. The rule that resolves both failures is the same: one fingerprint travels with one IP for the life of a session, and when you rotate, you rotate both together, the way a real device and its connection move as a unit.
The scaling tension worth naming: real hardware wins precisely because it is not infrastructure, and scaling drags you back toward infrastructure. A real phone on a real mobile connection passes the hardest checks not by spoofing anything but by being genuine at every layer at once, the network, the TLS stack, the sensors, the timing all agree because one real device produced them. That is the cleanest version of the coherence principle this guide keeps returning to. The catch is economic, not technical: one real device does not scale, and the moment you add machines to go faster you are back on servers with datacenter IPs whose Layer-4 (TCP/IP) fingerprint gives you away before a single byte of HTTP is read. Every fix from there reopens the same wound, so the honest way to hold this is as a permanent trade rather than a solved problem: the closer a setup sits to a real device on a real network, the higher its trust and the lower its throughput, and scaling is the deliberate act of spending some of that trust for volume. Naming the trade is what stops you from expecting a single configuration to be both maximally stealthy and maximally fast. It also forces the question hiding behind your rotation constants: how much volume does one unit of trust actually buy? You do not get to choose that number, the target does, and it varies wildly between identities that look identical on paper. Counted across nine freshly minted sessions against one DataDome-protected site, the requests each survived before the wall came down were 21, 48, 61, 67, 70, 94, 98, 106 and 106—a 5× spread whose median is 70, worth setting against the “roughly 100 requests” the collecting harness assumed in its own code comment, because 100 is the top of that distribution and not its centre. Nothing observable told you in advance which draw you were on: a session holding 21 cookies failed its first request while one holding 8 served 98, so cookie count is not a health signal. Design for that. A fixed request ceiling is a guess wearing the costume of a policy, and the better control is the breaker you already put in front of proxy selection, moved up to the session and taught to requeue pending jobs onto a fresh identity rather than drop them—a spent-session 403 is indistinguishable from a detection 403, so a naive retry spends the rest of the budget on a corpse. Carry the instrument rather than the constants: nine sessions, one deployment of one detector, self-reported without raw logs, and these systems are tuned per customer, so the only spread that binds you is the one you measure yourself.

Know where your IPs come from, and remember you are a guest

The detail that separates providers is not the price per gigabyte, it is sourcing: how those residential and ISP addresses end up in the pool. Residential IPs are typically gathered through proxyware, SDKs paid to sit on idle consumer devices, ideally with informed consent (the ethical end of the market) and not so ideally bundled silently into free apps (the part to avoid). ISP proxies are a different animal: addresses registered to consumer ISPs but hosted in datacentres, which is why they combine residential reputation with datacentre stability, and why the way a provider acquires that ISP space is worth understanding before you trust it. Knowing the sourcing model is not a compliance nicety, it directly predicts pool health: consent-based, well-managed pools get burned less and last longer than pools stitched together through opaque intermediary chains.
Two practical stances follow. Pick providers whose sourcing you can actually explain, the reputable ones gatekeep their networks and let only legitimate users on, which is what keeps the IPs clean for you. And carry the right posture onto both networks you touch: you are a guest on the proxy infrastructure and a guest, often an unwanted one, on the target. Behave politely and leave as few footprints as possible. That is not only an ethics point, it is the same discipline that keeps a pool and a target usable for longer.

What a compromised residential pool actually looks like from the inside

It is worth seeing the ugly end of that sourcing question concretely, because a published 2026 teardown traced one botnet from a single infected handset all the way to a law-enforcement takedown, and the anatomy explains a lot about why network-layer defences behave the way they do. The thread starts with arithmetic anyone can do: every proxy storefront advertises millions of residential IPs, but homes are finite, so where do they come from? Three honest answers. People genuinely opt in through bandwidth-sharing SDKs bundled into free apps. Providers buy and resell each other's pools, so one real home appears under five brand names. Map ownership before you count providers, because sibling brands can share supply. Oxylabs has named Honeygain, a bandwidth-sharing app, its main residential source since a 2021 exclusive contract, and since October 2026 Oxylabs, Decodo, Webshare, ScrapingBee and Honeygain have presented themselves as one ecosystem, Source, which says its compliance and use-case vetting standards apply across all of them. If you buy two of those brands for failover, assume correlated pools, refusal lists and outages until your own trial shows otherwise. Ask each vendor which group it belongs to and which upstream networks supply its residential IPs. Ownership is not a quality verdict; the point is that a count of brands overstates the number of independent suppliers you have. Treat browser-extension SDKs as their own sourcing category and ask vendors directly whether they use one. In the documented Mellowtel case, the SDK sat in 245 Chrome, Edge and Firefox extensions. It removed content-security-policy and x-frame-options from responses so it could load targets in hidden iframes in the user’s own browser. That weakens the host’s security whatever opt-in screen came first. Scale claims deserve scrutiny too. A Northwestern IMC ’26 measurement found only 16 of 116 equipped extensions actively crawling during a session, so extension and install counts are an upper bound on real exit nodes. US nodes got up to 8.7× more tasks than other regions. If you defend a site, IP reputation and fingerprinting won’t help here, because these requests really do come from a residential browser. Watch for iframe-only sessions and the same structured extraction repeated across unrelated home IPs. And some of those addresses belong to people who never agreed to anything.
The delivery vehicle
A cracked game that actually works
The infected device in that case was an Android phone that ran warm overnight and burned gigabytes of cellular data doing nothing. The source was a repackaged "free cracked game" from a Telegram channel that posted titles on a schedule. The reason nobody investigates is that the game works: it installs, it launches, it plays. The malware rides along as a passenger while a functioning app does the driving. Inside the repack, three files had been injected that did not belong: a decoy audio file (padding, so the rebuilt package's size lines up), a small bootstrap that loads native code, and the payload itself, a Go-compiled shared library. The intent was legible from its imports alone, a SOCKS5 server and an HTTP proxy library. It was not after photos or files. It wanted the connection.
The architecture
A directory, then one socket that never closes
The command path is two hops, not one. The implant first asks a config domain (sitting behind a mainstream CDN, over TLS) for a relay address, then opens a persistent TCP connection to that relay on an unusual high port and holds it open indefinitely. The domain is only a directory pointing at the muscle, so taking the domain down just moves the relay list. Over that single socket runs a small custom binary protocol that multiplexes many logical channels, so one phone can carry several customers' traffic at once without opening a socket per job. Authentication is a single frame containing a UUID minted when the malware installed, and in the analysed sample the server never expired it, a months-old identifier still authenticated. A phone holding one long-lived tunnel to a datacenter all night is not resting, it is working for someone.
Two findings from that work matter directly to anyone in this field. First, what the customers were actually doing. Watching traffic at the exit, the dominant pattern was not ad verification or price scraping, it was large-scale mailbox credential brute-forcing, waves of IMAP connections against major mail providers, with a side of old-fashioned IoT scanning. The "residential proxy service" label is the storefront; a substantial part of the product is rate-limit and blocklist evasion for people attacking accounts at scale, which is precisely why a home IP is worth paying for. Second, the shape of the business. The infrastructure was deliberately smeared across jurisdictions (storefront behind a CDN in one country, relays on a hosting ASN in a second, backend in a third) so no single abuse desk ever sees the whole picture, and the same device pool was resold under more than one brand name. The scale numbers that look contradictory are not: a storefront dashboard showed roughly 417,000 exits online at any moment, while the takedown reporting cited about 17 million devices across the operation's lifetime, which is the same pool measured instantaneously versus cumulatively.
Why this changes how you read IP-based defences, in both directions. For a defender, it explains why pure IP or ASN blocking has quietly stopped working: when every request arrives from a different real consumer connection attached to a real person's infected device, blocking the address punishes the victim and costs the attacker nothing, since the next request comes from another home entirely. IP reputation remains a useful filter, but it can no longer be the load-bearing signal, which is exactly why the detection stack moved up into TLS, fingerprinting, and behaviour. For a scraper, it is the strongest possible argument for the sourcing discipline above, some of the cheapest residential bandwidth on the market is cheap because the people supplying it never agreed, and traffic that shares an exit with credential-stuffing waves inherits that address's reputation. And there is a closing thought from the same research worth keeping: a residential proxy hides your IP, but the shape of your traffic, its timing, sizes, and rhythm, survives being wrapped and relayed, and that shape is its own fingerprint. The address was never the only thing worth hiding.

Verify the geography, do not buy it. Where a provider says an IP lives and where it answers from are different claims, and the industry lets the first stand in for the second. Location data for most networks comes from geofeeds the operators publish about themselves, which is a self-declaration with no verification step in front of it. Measure instead. Ping the addresses from a distributed measurement network and compare the latency to what the claimed location would require, and a familiar pattern appears: addresses advertised across dozens of countries resolve to a handful of large datacentre regions, with Northern Virginia, Amsterdam and Singapore absorbing most of it. Asia is the worst offender, where an address sold as Indonesian or Australian is very often answering from Singapore because the bandwidth is cheaper there.

This matters beyond truth in advertising. The five-vector coherence test at the top of the network layer assumes your exit is where you think it is. Set a timezone, an Accept-Language and a DNS resolver to match a country your traffic is not actually leaving from, and you have built a contradiction carefully and deliberately. If your measurement depends on what a user in one region actually sees, consider making the location real rather than buying the claim. A University of Potsdam audit of TikTok before three 2024 German state elections ran its 34 bots through servers physically located in each state, because the feed varies with where the user logs in. Its authors also note that residential and mobile IPs add cost and sourcing questions, and that proxied bandwidth becomes impractical for video-heavy platforms. A small machine in the target region, a rented server or even a single-board computer, can be cheaper and more trustworthy than a pool whose geography you cannot check. Keep the other vectors aligned to it. Before you tune the four vectors you control, measure the one you bought.
The risk that runs the other way: your IP is liable for the whole pool

Sourcing is not only about pool health, it is about what your exit IP is on the hook for. When you route through a residential pool, your address is shared infrastructure: at the same moment your scraper reads a public page, other tenants of that pool are sending traffic that exits through addresses just like yours. An investigation that joined one such network as a node and recorded over ten million transiting requests found the mix went well past data collection, into ad-fraud and affiliate-attribution abuse, mass account registration, ticketing and scalping automation, and email spam. The provider's marketing said web data collection and market research; the wire said otherwise. Do not assume a provider’s terms move legal responsibility off you, or that your own marketing is legally harmless. Courts look at who actually did what: in Ticketmaster v. RMG (C.D. Cal. 2007) the blame was not left with the tool’s customers alone, and copyright inducement was found partly in marketing that promised hidden IP addresses and no blocks. An indemnity between you and a vendor allocates money; it is unlikely to defend either of you against a target. The same logic now reaches the proxy layer. In Reddit v. SerpApi (S.D.N.Y., July 2026) a motion-to-dismiss ruling let anti-circumvention trafficking claims proceed against a tool alleged to use proxies and fake user-agent strings to get past Google’s bot check. The ruling is preliminary and US-only, but rotation that reads as circumvention can now carry legal weight as well as technical weight.
The uncomfortable part for a buyer is that strong vetting does not remove the risk, it only narrows it. A provider can run KYC on every customer, throttle the SDK, and inspect traffic on the network, and still be one bad edge case away from your IP carrying something you would never send. A trusted customer's credentials leak. A sensitive destination (a bank, a government service) is not classified correctly and so is not blocked. The provider never anticipated what a local LAN address looks like from inside a residential node. You are trusting that their vetting and security hold, on an address that resolves to a real person's home, which is also why the supply side is uncomfortable. One 2026 scan of 6,038 LG and Samsung smart-TV apps found proxy SDKs in 2,058 of them, 42.5 percent of LG webOS apps and 26.9 percent on Samsung Tizen, fish-tank screensavers and solitaire clones quietly turning the television into an always-on exit node, often with consent reduced to a single setup prompt navigated by remote. Cheap streaming boxes have shipped with dormant proxy software preinstalled. The risk is not only that your traffic shares an address with strangers: because a smart TV sits on the home LAN beside routers, NAS drives, and cameras, a failure in the provider's private-range filtering can turn that exit node into a foothold inside the network. The US FBI issued a 2026 advisory on exactly this, warning that when criminal traffic exits through a residential IP, the innocent homeowner is the one whose address is on record. Those same residential IPs feed the pools sold as ethically sourced. Two practical takeaways: prefer providers whose sourcing and abuse controls you can actually inspect (a verifiable partner list and audit trail, not a landing-page adjective), and treat the residential layer as something to use deliberately and sparingly, not as a default you leave running, because every hour you are on it your address is vouching for strangers.
12 field notes on proxies Reported from practice. Click any card to open it.
+
Aug 2026 / Proxies
“Our Product Is Really the Filtering Layer, Not the Pool”
A proxy founder said the quiet part in a public AMA. Providers can source from largely the same upstream supply and still deliver completely different results, because the differentiator is what happens between the raw IP supply and your request: which suppliers get accepted, how incoming IPs are scored, which ones ever reach a customer, how fast burned addresses are pulled, and how sticky sessions are routed.
💡 Pool size is a vanity metric, and the reputation databases are not proprietary
The admissions that matter. His own network is majority third-party — even providers running their own acquisition SDKs supplement heavily with upstream and peered supply, which is why the same IP shows up across competing networks. The supplier bar is three concrete requirements rather than a score: visible opt-in disclosure, a functioning opt-out, and a named application or sourcing partner. And he explicitly disclaims verifying consent himself for most of the pool, relying on suppliers' own consent flows.

The scoring stack is five components: multiple reputation databases, an internal honeypot network, dynamic weighting of the reputation signals, automated checks at handoff, and continuous re-checking. Critically, the underlying reputation databases can be bought by competitors — the moat is the weighting, not the data.

What to do with this. Stop comparing pool sizes. There is no universally clean residential IP: quality is per-target and time-varying, and an address that works on a marketplace may already be dead on Google. The only honest benchmark is your own success rate on your own targets over a week, which is also the only number a vendor cannot sell you.
+
Aug 2026 / Network
The UDP Leak, and Why Your Proxy Never Saw the Packet
WebRTC has been on by default in Chrome and Firefox since 2013, and it can reveal the real IP behind a proxy — because Chromium and Firefox do not proxy UDP at all. Those packets leave the local machine directly, past whatever proxy you configured. The reported side of a WebRTC candidate is easy to rewrite from JavaScript; the hard version is a TURN relay, where the anti-bot's own server logs the address the UDP packet physically arrived from. You cannot spoof that from inside the page.
💡 Correction worth having: the tool doing the TURN check is Scrapfly's, not the one the post pointed at
Get the attribution right. NodeMaven's WebRTC leak test is STUN-only — no TURN check and no HTTP/3 or QUIC test in the version we could verify. Scrapfly's tool is the one implementing the three-way comparison: your reported public IP, the IP WebRTC exposes in-browser, and the IP that actually connects to the relay. Pierluigi Vinciguerra covered this client-side gap in UDP, Proxies, and the WebRTC Leak on 2 July 2026.

The HTTP/3 half. HTTP/3 runs over UDP and is now roughly a fifth of all requests, with HTTP/2 still near half and about 38% of sites advertising it via Alt-Svc. So a browser that cannot speak HTTP/3 to a site that offers it is now itself a signal. And the sting for request-based scrapers: curl_cffi hard-refuses HTTP/3 over both HTTP and SOCKS proxies — it returns “HTTP/3 is not supported over an HTTP proxy” and the SOCKS equivalent. That is a client-side refusal, not a server block.

The fixes. Cheap: launch with --force-webrtc-ip-handling-policy=disable_non_proxied_udp (Firefox: media.peerconnection.ice.no_host and relatives). Proper: run the browser in a VM or container and proxy the whole environment at the network layer, not just web requests. “Switching to HTTP/3 doesn't remove protocol fingerprinting, it just moves it somewhere else.”
+
Aug 2026 / Supply chain
Your Residential Pool, and the Phone That Never Consented
The residential proxy supply chain has a documented history of enrolling devices whose owners never meaningfully agreed. HUMAN Security's Satori team documented PROXYLIB: 29 Android apps removed from Google Play that silently turned handsets into residential proxy exit nodes, via an SDK marketed to developers as a monetisation tool that paid by traffic routed, with consent buried where nobody reads. In January 2026 Google disrupted IPIDEA, taking down domains covering 13 proxy brands and 4 SDKs, a network advertising 6.1 million daily-updated IPs and adding 69,000 new addresses a day, with roughly 600 Android apps flagged as containing proxy-enabling code and around 5 million bots still connecting at takedown.
💡 550+ state-linked threat groups were routing through one commercial pool
The number that should end the “it's just proxies” argument. More than 550 distinct threat groups from China, North Korea, Iran and Russia were using IPIDEA infrastructure as recently as January 2026. IPIDEA's public response acknowledged aggressive expansion and promotion on hacker forums while denying it endorsed abuse — which is the closest thing to a candid answer this industry has produced.

Why this belongs in a scraping guide. When a provider tells you the pool is consent-based, ask which of the three checks they run: visible opt-in disclosure, a functioning opt-out, and a named sourcing partner. Then ask who verifies it — because at least one founder has said publicly that he relies on his suppliers' consent flows rather than his own screens.

One claim we could not stand up. A widely-shared teardown this week alleged a specific budget Android handset shipped a repurposed commercial proxy SDK plus a backdoor surviving factory reset. We searched for the writeup and could not find it indexed anywhere, so we are not repeating it as fact. The verified cases above make the same point without needing it.
+
Jul 2026 / The reckoning
The FBI Seized a NASDAQ-Listed Proxy Provider. Your Pool Might Be a Botnet.
On 2 July the FBI seized hundreds of domains tied to NetNut — a large residential proxy service run by Alarum Technologies, a company listed on NASDAQ. Not an anonymous dark-web operation. A public company with investor relations, quarterly reports, and a legal team. The guide has argued for a while that residential proxy sourcing is the industry's quiet liability; this is what it looks like when the bill arrives.
💡 The exit node you rented may be malware on someone's smart TV, and that is now a legal risk, not an ethical footnote
How it unravelled. On 19 June three security firms showed NetNut's residential network was powering a botnet called Popa, running on everyday devices — smart TVs, streaming boxes — via SDKs bundled into pirated streaming apps. Qurium traced the control servers while investigating DDoS attacks; Brian Krebs made it public; two weeks later the authorities moved. Google estimated the network at at least two million devices.

Why you cannot just unplug it, and how they did anyway. A residential proxy network is decentralised by design — the exits are millions of consumer devices you cannot seize. So the takedown hit the three things those devices depend on to be a commercial product. The command-and-control layer: Google killed the accounts running the malware's C2 — which was itself hosted on Google's own services, a technique called living off trusted services so malicious traffic hides inside normal cloud traffic. The enrollment pipeline: Play Protect began disabling apps carrying NetNut SDKs and blocking new installs, so the pool shrinks as devices reset and drop off. The commercial front door: seizing the domains shut the storefront and stopped new customers. Google's estimate was that this cut available devices by millions.

The distinction every buyer should now be able to draw. NetNut sold four things, and they are not equal. Datacenter IPs are clean — clear ownership. Its ISP proxies came through DiviNetworks by an above-board arrangement: a revenue-share pitch to ISP partners, a GRE tunnel terminated on the partner's edge router, policy-based routing steering a slice of traffic out through NetNut — the partner signs and gets paid, and whatever you think of subscriber consent, the contractual chain is in daylight. The residential pool is the Popa part: sourced by distributing SDKs onto devices whose owners never knowingly agreed. Same vendor, same dashboard, two completely different provenance stories — and only one of them ends in a seizure banner.

The market's verdict, and yours. Alarum disputes the accusations and says it was never formally contacted — but the stock crashed more than 70% in a week, roughly 96% off its 2024 peak, and the company warned of a material adverse effect. For anyone running collection in production the lesson is concrete: provenance of your proxy pool is now a supply-chain risk you can be caught downstream of. “It was a big, professional vendor” is exactly what NetNut was. Ask where the residential IPs come from, prefer ISP or datacenter where the target allows it, and treat an unexplained too-cheap residential pool as the warning it is. This sits alongside the data-broker and legal material in Read this first.

Source: The Web Scraping Club, Pierluigi Vinciguerra.
+
Aug 2026 / Proxy operations
"Was That the Proxy or the Site?" Is Finally Becoming a Status Code
The most expensive hour in proxy debugging is the one spent deciding whose fault a failure was. Most proxies forward a generic 5xx and leave you guessing between a proxy policy block, proxy infrastructure, and the target actually refusing you. That is starting to change, and there is a real disagreement about how.
💡 Log the attribution field from day one, whatever your provider calls it
Where the market is as of August 2026: Bright Data ships an X-BRD-ERR-CODE header and now also supports RFC 9209 Proxy-Status, the IETF standard designed for exactly this. Oxylabs adds an X-Error-Description header only when the fault is on their side. Decodo documents dedicated error codes in its response headers. Massive went further and, from 17 August, returns dedicated HTTP status codes (452–454 and 470–477) that are unambiguously theirs, plus an X-Massive-Reason header with the exact cause — no header parsing, the status code alone tells you policy, infrastructure, or target.

The disagreement is the useful part. The immediate pushback, from practitioners in the thread, was that proprietary status codes are the wrong abstraction: this is precisely what Proxy-Status was standardised to solve, and having to learn that 452 means one thing at one provider and 472 means something else at another is the fragmentation the RFC exists to prevent. The counter-argument is equally real: a status code needs no parsing and no client library, so it gets adopted, and an RFC that nobody implements attributes nothing.

What to do while it settles. Do not wait for a winner. Add one field to your request log now — call it failure_owner, values proxy_policy / proxy_infra / target / unknown — and populate it from whatever your provider gives you today, header or status. The value is not in the encoding, it is in being able to answer "what share of last week's failures were ours" without re-running anything. It also changes your provider conversations, because "unknown" trending upward is a contract question, not an engineering one.
+
Aug 2026 / Proxy market
Price Does Not Predict Proxy Performance, and the Winner Changes Per Domain
A benchmark across providers, domains, prices and request volumes landed on three results that between them retire most published proxy rankings: expensive providers did not consistently beat cheap ones, the best provider changed from one target domain to another, and the best value changed again as monthly volume grew.
💡 The only ranking that means anything is yours, on your targets, at your volume
The volume effect is the one people get wrong most often: a provider can be the best option at 10,000 requests a month and considerably worse at 100,000 or a million, because pricing tiers, pool composition and per-domain reputation all move independently. A generic league table cannot encode any of that. The method that does work is unglamorous — run your actual targets through each candidate and compare on success rate, latency, and cost per successful request, which is the only one of the three that combines the other two into something you can put in a budget.

The market context around it, mid-2026. A legal win, then a second round: Google's DMCA claim against SerpApi was dismissed on 20 July 2026 in the Northern District of California, with the claims resting on search results containing no copyrighted content thrown out for good, on the reasoning that the DMCA does not protect material that is not copyrighted. The court left Google 21 days to amend on a narrower set, the results carrying a copyrighted component such as the snippets in Knowledge Panels. Google refiled on the final day, 10 August 2026: a 15-page amended complaint built around the one element the court found missing, written permission from copyright owners, and naming Reddit as the partner that asked it to block scrapers. The question has therefore moved from whether scraping public pages is unlawful to whether the platform holds licences that make the content protectable in the first place, which is a licensing-chain argument rather than an access one. A vote of confidence: Oxylabs took a $130M investment at a reported $3.6B valuation. And two warnings that the residential market's Wild West era is closing — the FBI seized domains connected to NetNut and its sourcing network amid fraud allegations, and LG told developers to strip residential-proxy functionality from smart-TV apps or face suspension. Expect more KYC, tighter reseller controls, and far more scrutiny of where residential IPs come from.

The third finding is the one that ties back to this guide's detection section. A perfect browser fingerprint is no longer sufficient: anti-bot systems are moving from checking individual fingerprint values to judging the whole session. You can pass every discrete check and still fail because the browser, OS, hardware, network, navigation and behaviour do not tell a coherent story together. Which is the same conclusion the guide reaches from the other end — you do not get blocked for looking like a bot, you get blocked for not making sense. There is also a practical corollary nobody says out loud: this environment rewards continuous measurement over one-off research, because a stack that was correct three months ago is a stack you have not tested.
+
Tool / Proxy ops
Stop Round-Robining Dead Proxies: Bayesian Selection
Round-robin proxy rotation has a dumb flaw: it keeps sending requests through proxies that are already dead or banned. ProxyOps uses Thompson Sampling to learn which proxies are working right now and route around the rest. Real data over 549,114 requests in 7 days: 76% success with Bayesian selection vs 36% with round-robin. Same proxies, same targets, more than double the success rate.
💡 Treat proxy selection as a multi-armed bandit, not a queue
The core insight: proxy health is non-stationary, a proxy that worked a minute ago may be banned now, and a dead one may recover. Round-robin ignores this entirely and gives every proxy equal traffic regardless of recent performance. Thompson Sampling (a Bayesian multi-armed-bandit method) models each proxy's success probability as a distribution, samples from those distributions to pick the next proxy, and updates beliefs after every request. Good proxies get more traffic, failing ones get probed occasionally to check for recovery but mostly avoided. The benchmark is striking: 76% vs 36% success on identical proxies and targets is not a marginal optimisation, it's the difference between a viable scrape and a failing one. ProxyOps ships this as a full open-source tool, multi-provider inventory, pluggable rotation strategies, per-bot proxy groups, comparison dashboards, on FastAPI + Vue + PostgreSQL, Dockerized (docker compose up). MIT licensed. github.com/Paulo-H/proxyops
+
Tool / Concurrency
Validating Millions of Public Proxies: Why Python Wasn't Enough
Public proxy lists are mostly dead, slow, or pre-blocked. Validating them at scale before a scrape becomes its own engineering problem. One builder hit the wall with Python threads checking proxies for Google scraping, then rebuilt the validator in Go. The lesson: Go is exceptional for I/O-heavy concurrent workloads where Python's threading model bottlenecks.
💡 Validate the pool before the scrape, in Go, not inline in Python
The bottleneck most people don't anticipate: if you rotate through huge lists of free/public proxies to avoid rate limits, you spend more time discovering which proxies are alive than actually scraping. Doing this inline with Python threads doesn't scale, the GIL and thread overhead cap you well below the concurrency a proxy-check workload needs (it's almost pure network I/O with tiny CPU cost, the ideal case for lightweight concurrency). Go's goroutines and channels handle tens of thousands of concurrent connection checks on modest hardware, with far better memory efficiency than Python threads or even asyncio for this specific pattern. The architecture takeaway generalises: separate proxy validation into its own high-throughput service (in Go or Rust), maintain a continuously-refreshed pool of known-good proxies, and have your scraper pull only from validated proxies rather than checking inline. Useful well beyond scraping, the same pattern applies to OSINT, network reconnaissance, and distributed systems. github.com/harshit-singh-ai/proxy-validator
+
Benchmarks / 2026 data
Distributed Browsers: The "Residential Proxy Moment" for Stealth
The Web Scraping Insider benchmarked stealth browser APIs (April 2026): Scrapeless 90.95, Bright Data 89.05, Oxylabs 85.71, then a cliff. The bigger idea from Driver.dev: as anti-bots fingerprint GPU and hardware entropy, cloud stealth browsers may become the new datacenter proxies, detectable by default, pushing the field toward real-device browser networks.
💡 Cloud browsers → real-device browser networks, mirroring DC → residential
Three data points worth internalising from the April 2026 benchmarks. (1) Stealth browser APIs are measurably different: Scrapeless (90.95), Bright Data Scraping Browser (89.05), Oxylabs Headless (85.71) lead, then most providers fall off a cliff for the same reason, automation signals leak, and once that happens proxy rotation doesn't save you. (2) Cloudflare bypass has no single winner: across 8 approaches tested on 20 protected sites, only 3 had broad coverage, smart proxy APIs, TLS impersonation (curl_cffi), and browser APIs. Different domains use different protections, so what works on one target fails on another. (3) The structural prediction: anti-bots increasingly fingerprint the whole environment (browser, GPU, hardware entropy, OS quirks), which means a stealth browser running in a datacenter is detectable simply by being in a datacenter, regardless of how good its fingerprint patches are. The likely evolution mirrors proxies exactly: datacenter proxies gave way to residential proxies, and cloud stealth browsers may give way to distributed browser networks running across real consumer devices. The fingerprint isn't patched, it's genuinely real, because it's a real device.
+
TLS / Proxies
Why "Just Get Better Proxies" Stopped Working
The problem is your TLS handshakenot your IP. Cipher suites, HTTPS extensions, GREASE values form a JA4 fingerprint. A clean residential IP still fails if the fingerprint exposes you.
💡 The residential IP passed. The fingerprint gave it away.
TLS detection happens at the ClientHello level, before any HTTP is exchanged. The JA4 fingerprint hashes: cipher suite list (sorted), TLS extensions (sorted, GREASE removed), ALPN protocols. Python's requests library sends a different cipher suite order than Chrome. httpx is different again. Even with a clean residential IP, if your cipher ordering does not match Chrome's, you are identified before the server processes a single header. Fix: use curl_cffi with impersonate="chrome124"it emits Chrome's exact TLS ClientHello. Also watch HTTP/2 SETTINGS frames, they contain window sizes and header table parameters that vary per client.
+
Network Identity
The WebRTC Trap: Your Browser Is Leaking Your Real Location
Proxy says US. WebRTC says elsewhere. It leaks your real IP via STUN and creates geo mismatches. Anti-bots check that IP + WebRTC + timezone + DNS + Accept-Language all agree.
💡 Quick test: browserleaks.com/webrtc, check before blaming your proxy
WebRTC uses the STUN protocol to discover network paths. During ICE candidate gathering, the browser contacts a STUN server and reports: your real public IP, your local LAN IP (e.g. 192.168.x.x), and all network interface addresses. Your proxy only routes HTTP/HTTPS traffic, WebRTC bypasses it entirely. Anti-bots cross-check: proxy exit IP vs WebRTC public IP vs DNS resolver location vs Accept-Language vs timezone. All five must agree. Fix with Camoufox: set geoip=True and it automatically aligns all five vectors. Do not simply disable WebRTC, it removes a feature that 99% of real users have, which itself becomes a bot signal.
+
Proxies
SwiftShadow: Free Proxy Rotation Without the Headaches
Grabs free proxies, validates, rotates automatically, filters by country. Built-in caching, auto-switches on failure. ~300 stars, actively maintained.
💡 pip install swiftshadow, from swiftshadow import QuickProxy
SwiftShadow maintains a pool of free proxies sourced from multiple public lists. On initialisation it validates all proxies (checks response time and anonymity level) and caches the working set. When a proxy fails mid-request, it automatically switches to the next validated proxy in the pool, no intervention needed. The QuickProxy(countries=["FR","DE"]) API filters by exit country. The built-in cache means it does not hit proxy list APIs on every request. Usage: from swiftshadow import QuickProxy; proxy = QuickProxy(); session.proxies = {"http": str(proxy), "https": str(proxy)}. Important: free proxies have high failure rates and low anonymity, do not use for Akamai, DataDome, or PerimeterX targets. Best for scraping open/unprotected sites at scale without cost.

You now have the full picture: detection layers, six anti-bots, sixty libraries, managed platforms, proxy types. This section collapses all of it into a single decision tree you can follow for any target site.

09 Decision playbook

Walk this in order.
Stop at first win.

Each step adds complexity, cost, and maintenance. Most production scraping is solved at steps 1–3. Never start at step 5.

01
Lowest friction · Asad's priority #1
Find the mobile API
Mobile apps hit same backend with far weaker bot protection. HTTPToolkit intercepts all HTTPS from Android emulator. Frida hooks into SSL_read/SSL_write directly. If you find the API endpoint, every HTML anti-bot becomes irrelevant.
HTTPToolkit
Frida
mitmproxy
Burpsuite
02
XHR reverse engineering
Find the GraphQL or REST endpoint
Chrome DevTools → Network → Fetch/XHR. Many SPAs load from one undocumented JSON endpoint. Confirmed in production, a direct GraphQL endpoint bypassed all Akamai HTML protection.
Chrome DevTools
Burpsuite
webclaw CLI
03
JSON in HTML · No requests needed
Look for embedded state
Next.js embeds full state in __NEXT_DATA__. React SPAs often have >50KB script containing all data. Confirmed: Grainger.com (DataDome-protected), 110KB JS state blob bypasses DataDome entirely because it's in initial HTML.
chompjs
Parsel
BeautifulSoup4
04
HTTP scraping · No browser
curl_cffi + Scrapy
Identify anti-bot with Wappalyzer. curl_cffi with JA4 impersonation resolves most Akamai and DataDome at HTTP layer. Add residential proxy. If __NEXT_DATA__ appears in response, extract it with chompjs.
curl_cffi
Scrapy
Scrapling
05
Browser automation · C++ level only
Camoufox or CloakBrowser
JS injection patches leave signatures. Camoufox: 100% pass rate Mar 2026 on the FF135 base; stable moved to FF146 in July 2026, so re-test. CloakBrowser: 49 C++ patches, reCAPTCHA v3 score 0.9, Akamai extension probes pass. PatchRight for Kasada specifically, no JS signatures.
Camoufox ★
CloakBrowser
PatchRight
06
Last resort · F5 Shape only viable path
Managed platform API
F5 Shape's custom VM makes DIY impractical. Token expiry in minutes, payload changes every rotation. At scale: engineer maintenance cost > platform cost. One API flag handles everything. Cost-justify: >2 days/month maintenance → managed API wins.
Bright Data
Zyte
Firecrawl

Quick reference cheat sheet

Anti-botPrimary vectorSteps 1–2 viable?Best toolKey note
AkamaiJA4+ + sensor.js + extension probesOftencurl_cffi + CloakBrowserFind mobile/GraphQL first
CloudflareJA4 Rust edge + TurnstileSometimesCamoufoxOrigin IP via SecurityTrails
DataDome85K ML + WASM boring_challengeYescurl_cffi + mobile IPCheck __NEXT_DATA__ first
PerimeterX5-vector scoreSometimesCamoufox + residentialFresh session per domain
KasadaPolymorphic JS PoWRarelyPatchRight + residentialNever playwright-stealth
F5 ShapeCustom VM + minute expiryNoManaged APIDIY not practical
Disclosure · my company
If your target sits on a Sometimes, Rarely or No row, email me at scrapesync@gmail.com before you lose a weekend to it. Send the URL and a note on what you have already tried, and I will tell you which rung it is actually on and what I would try next. I usually reply within a couple of days, slower when I am mid-job. Most of the time the answer is a section further up this page and I will point you at it. Sometimes it is one of the managed APIs in the Best tool column, in which case I will name Zyte or Scrapfly rather than myself. The interest I should declare, since this is a table of vendors and I sell on the same rung: scraping is my day job, I run ScrapeSync (my company). The answer is free either way.
1 field notes on the playbook Reported from practice. Click any card to open it.
+
Aug 2026 / Diagnosis
A 429 and a 403 Need Different Fixes, and One of Them Is Not About You
A practitioner sent requests at a deliberately unremarkable pace — about one every 1.2 seconds. After eleven of them the site returned 429. Not a block. A speed limit. “Have you ever changed your headers or your fingerprint to fix a problem that was actually about speed?”
💡 Rate limiting is a dial, not a switch — and the dial does not care what you look like
The detail that saves you the most time is what happened next. The lockout ran fifteen minutes, and checking again during the window returned the time remaining from the original lockout, not a fresh countdown. So polling did not extend the penalty. That is worth knowing precisely because the instinct — stop touching it, you will make it worse — is wrong here, and acting on the wrong instinct leaves you guessing about a window you could simply have read.

Why this is the same finding as the audit above, from the other end. That research separates the signals by kind: CAPTCHA is binary, JavaScript either runs or does not, and rate limiting is a gradient — the site is not refusing you, it is pricing your pace. A 429 is a negotiation. A 403 is a verdict. Treating them as the same event is how you end up rebuilding a TLS profile to solve a problem that a sleep would have solved.

The rule. Read the status before you reach for a technique. 429 means slow down, honour Retry-After, and back off with jitter — your identity was fine. With one caveat this feed earned the hard way: some sectors rate-limit on client identity rather than pace — the fashion card in this feed found exactly that, with a block on the very first request and no amount of waiting helping. So the test is whether backing off actually changes the outcome. If a slower cadence gets the same 429, the limiter is not counting your speed, it is counting you, and you are back to the 403 question of which layer decided. 403 means you were judged, and now the question is which layer judged you, which is a different card in this feed and a different afternoon. The expensive mistake is not picking the wrong fix; it is never asking which problem you had.
10 From the community

What practitioners are
actually shipping in 2026

Fresh insights from engineers actively solving these problems in production. Shared publicly on LinkedIn.

The newest 24 · the rest are filed under each topic above
+
Oct 2026 / Deobfuscation
DataDome’s plv3 VM Ships Its 103 Opcode Handlers as Source in Every Build, Then Reports Its Own Step Count and a Hash of Its Interpreter With the Token (Vendor Teardown, Six Bundles)
TakionAPI, which sells a DataDome bypass API, published a teardown (28 Jul 2026) of plv3, the token DataDome’s interstitial and captcha now compute inside a JavaScript VM. Each build carries its 103 opcode handlers as source, compiled at runtime, so no opcode table outlives a rebuild. Beside the token, the run reports its step count (about 467k) and a hash of its own interpreter.
💡 plv3 checks that its program ran unmodified in a real engine: emulators and hooked harnesses show up in its own telemetry, and its probes weigh layout and GPU rendering far above navigator values.
What the teardown found. The write-up by glizzykingdreko sits on the blog of TakionAPI, a vendor selling DataDome bypass as a service, with an MIT disassembler tested on six bundles, three interstitial and three captcha. The vm-obf.js loader shrank from about 614 deobfuscated lines with a readable dispatch switch to about 163: a base64 payload unpacked into a 184–210 KB buffer padded with seeded xorshift filler and a threaded loop with no switch. The first ~11 KB of bytecode is 103 records of handler source, compiled with new Function and bound to a 23-entry primitive table. Opcode numbers, register offsets, tag bytes and seed change every build; the counts never did. All 976 to 1,135 jumps per bundle landed on instruction boundaries; coverage is about 90%.

What it measures. One HALT handler writes the token; the loader adds the iteration count, elapsed time, any exception and a never-written negative slot, and main.js ships them as signals with a checksum of the interpreter’s own function source. A browser run took 467,499 steps, an offline one 463,596 (0.8% apart). All six read 25 property chains, among them navigator.webdriver, atob.toString against [native code], and three Node/jsdom names (_virtualConsole, _userAgent, Buffer). The weight is a CSS calc() layout probe read back through offsetWidth (53 strings), WebGL readPixels and canvas compositing; navigator gets 7. Captcha and interstitial run one program: only crypto.getRandomValues is captcha-only.

Against other VMs, and over time. BotGuard and Kasada hide the program; plv3 hands over readable handlers and bets on churn, so one bundle is easy and a static tool dies at the next deploy. His May 2025 post (history now) described daily file rotation, daily-renamed signal keys, a WASM proof-of-work run outside a browser and no integrity checks; January 2026 dates plv3. The method travels: intersect three builds per family before diffing; one-to-one diffs showed dozens of rotation-noise differences.

The caveat. Vendor-authored and promotional (it ends in a trial pitch), unreplicated, and the bundles are not redistributed. The January post gave ~360k steps for a captcha, against 467k here for an interstitial. Server-side scoring is inferred, not seen; the repository postdates the article by a month. Recipes are omitted here.
+
Oct 2026 / Architecture
A Bypass-API Vendor’s Case Against the Headless Browser: One POST Returns the Cookie, but the Fetch, the Proxy and the Fingerprint Stay Yours (Vendor-Authored, No Measurements)
TakionAPI, which sells an anti-bot bypass API, argued on 13 Jul 2026 that a token API beats a headless browser fleet for request-based scraping: you send the challenge, it returns the cleared cookie plus the headers it was signed against, and you replay them through your own proxy. It concedes browsers still win for interactive flows and low volume, and publishes no latency, success or cost figures.
💡 A token API is a fourth buying option beside DIY, browser fleets and unblockers: it sells the cookie, not the page, so your own client’s TLS, header order and IP must still match what the token was minted for.
What the two posts claim. Two posts by TakionAPI’s founder (both 13 Jul 2026, both selling the company’s product) describe three ways past a bot wall: a managed token API, a headless browser farm, and self-maintained open-source solvers. The browser is scored as seconds per request, hundreds of MB of RAM per worker, a wide detection surface (navigator.webdriver, headless rendering quirks, automation timing) and upkeep that is yours; the token API as one HTTP round trip, billed per solve, upkeep on the provider. By the vendor’s own account the browser still wins for multi-step interactive flows, products that render pages anyway, walls hit a few times a day, and challenges with no request-based solution yet.

What is genuinely useful. The architecture is a row this guide’s cost table lacks. An unblocker API fetches the page and bills per success; a token API returns only the cookie or token, and you send the request. The posts say the token is solved under your proxy and bound to that IP, and that a wall such as DataDome rescores on consistency, so a token replayed from another address reads as a hijack. Every coherence rule in this guide therefore still applies to your own client, and the “correct sensor payload, wrong envelope” failure is the first thing to test.

Caveat: marketing with no measurements. Neither post gives a latency percentile, a success rate, a price or a test target, and the buyer’s checklist is the vendor’s own feature list (11+ walls, one key, 24/7 regression tests, first call “within the hour”). To its credit it tells readers to distrust any vendor quoting a fixed success rate. The claim that walls are usually re-bypassed “before you see a 403” is unverified, and the comparison quietly assumes your HTTP stack already looks like a browser.

How to price it. A per-solve fee only compares with anything once you know how many requests one cleared cookie carries before the wall re-challenges. That depends on target, vendor and pacing, and the posts do not say. Measure it on a sample, add your own proxy bandwidth, then set it against an unblocker’s per-success price. The legal position does not move with the tool: routing through a provider does not shift responsibility for what you access.
+
Oct 2026 / The other side
A New Waiting-Room CAPTCHA Shipped Without Origin Binding, Rate Limits or Retry Caps, So Its Own Verify Endpoint Became the Training Oracle (One Teardown by a Bypass Vendor)
A Jul 2026 teardown by the founder of TakionAPI, which sells a bypass API covering this product, says BotDeflector, a CAPTCHA appearing on Queue-it waiting rooms, pairs a CPU-only proof-of-work with an icon puzzle. He reports no site-key domain check, no rate limit and no cap on retries, which let him use the verify endpoint as an unlimited labeller and train a model to a reported 99.8%.
💡 A CAPTCHA’s strength lives in its server rules more than its images: origin-bound keys, attempt caps and rate limits are what stop it becoming a free answer key. Ship those before tuning puzzles.
What it is. botdeflector.com was registered on 22 Nov 2025; its terms name Botdeflector ApS, a Danish company, and its docs promise “no cookies and no storage of end-user personal data” and list scraping and scalping among its targets. The teardown (25 Jul 2026) says it belongs to a Queue-it co-founder; that link is the author’s claim, not confirmed here, though a solver vendor’s own tutorial also calls it the challenge behind current Queue-it CAPTCHAs. Two steps: a proof-of-work (a graph search costing CPU only, difficulty 1 to 5) and an icon-ordering image challenge.

The gaps reported, all server-side. A site key worked from an unrelated domain, where reCAPTCHA, hCaptcha and Turnstile bind keys to an allowlist. The icon endpoint had no rate limit. The same image could be resubmitted until it was right, turning verification into a free labeller: he reports distilling a model to 99.8% from localhost with no proxies, after frontier vision models reached about 60% at best. A 13 Oct follow-up names one control that does exist: answers sent under 1.5 seconds after the image loads are rejected as bots.

The same lesson at Ticketmaster. A 6 Oct post found that the 4-letter add-to-cart captcha on Ticketmaster PH and SG is the open-source Yii2 generator with new colours, size and length. Porting the generator yields unlimited labelled data without touching the site; the author reports 99.19% to 99.87% on his own renders, not on live captchas. A challenge built from public code hands attackers the data generator.

Caveat. One researcher, one product each, selling solves for both, with no raw data; the 60% model figure has no published method, and BotDeflector may have added the missing checks since (its portal now returns 404). His GDPR critique, that FingerprintJS-style canvas, audio and WebGL probes need consent under ePrivacy Art. 5(3), was not checked against the shipped scripts. For collectors the point is not a solver: a deliberate minimum solve time is a timing signal automation can trip.

Oct 2026 update. On 13 Oct the same vendor released a Chrome extension that forwards BotDeflector images to its paid API and clicks the answer, which is how solver services now reach browser automation: defenders should expect solves arriving from ordinary-looking browser sessions, timed just above any minimum.
+
Oct 2026 / Tooling
Scrapling v0.5 Hands-On: DynamicFetcher Is Gone, the Stealth Browser Accepts Self-Signed and Expired Certificates by Default, and the Unchanged difflib Matcher Returned “In stock” as a Price (One Mac, Practice Sites)
Scrapling v0.5 (17 Oct 2026, 88,582 stars) removes DynamicFetcher: every browser fetch is now Patchright Chromium. On practice sites, the HTTP and browser fetchers, network capture, saved logins, BFS/DFS crawls and Shadow DOM all worked. But the browser loaded self-signed and expired HTTPS pages, and the adaptive matcher, byte-identical to v0.4.15, returned the wrong element once a tag changed.
💡 Upgrade on a branch: swap Dynamic* for Stealthy*, re-check robots handling and checkpoints, turn TLS checks on via additional_args, and validate whatever adaptive mode returns.
What changed. The migration guide lists real breaks. The plain-Playwright backend and DynamicFetcher/DynamicSession are removed. capture_xhr becomes StealthySession(record_requests=True). MCP tools are renamed and bulk tools dropped. response.follow() no longer inherits POST bodies. Changed fingerprints orphan old checkpoints. With robots_txt_obey=True, a failed robots.txt fetch now blocks the domain. New in this release: network history, save_state/load_state, pierce_shadow, and crawl_strategy="bfs"|"dfs". Python 3.10+, BSD-3-Clause, no Camoufox, dependency floors unchanged.

What the test found. Python 3.11, macOS arm64, a few requests per practice site. Fetcher parsed 10 quotes in 1.18 s and 20 books in 0.99 s. StealthyFetcher rendered the JS quotes page in 8.5 s cold and 3.2 s warm, at about 1.1 GB peak Chromium RSS. Network history captured a scroll-triggered /api/testimonials?page=2 with its 12 KB body. A saved cookie reloaded into a new session. BFS and DFS crawled the same 6 pages in different orders, and open shadow roots were read. On a mutated books page, adaptive mode survived class renames and returned one element (find_similar() restored 20). After <p> became a wrapped <span>, it silently returned “In stock”.

The defaults to override. _base.py sets ignore_https_errors: True on every stealth context. The browser returned 200 for badssl’s self-signed and expired hosts, while Fetcher refused both. Issue #514 proposes flipping it; additional_args={"ignore_https_errors": False} made it refuse. solve_cloudflare is off unless you pass it. Browser timeout is in milliseconds, but Fetcher’s is seconds. The MCP server registers 16 tools, which measured 10,645 o200k tokens.

Caveats. This was one machine and n=1 per check. Nothing was tested against a protected commercial site, so it says nothing about bypass rates. The “can solve almost any CAPTCHA” MCP claim is an AI agent driving the browser tools in the author’s demo, and the docs name only Turnstile and Interstitial (video not watched). Reddit could not be read. Community response so far is bug reports.
+
Oct 2026 / Law
The UK “Stealth Bot Bill” Is a Registration Bill: Its Official Long Title Covers Registering Operators and Duties on Them, and Its Second Reading Was Objected to on 16 October (Now Set for 27 November)
Damian Hinds’s Automated Online Software (Access and Transparency) Bill, a ballot Private Members’ Bill backed by the News Media Association, is pitched as asking bots to say who runs them and why. Its long title also provides for the registration of operators of specified software and duties on them. Text was printed 13 Oct; on 16 Oct second reading was objected to and moved to 27 Nov.
💡 Not law, and ballot bills rarely become law. If it moves, the questions are in the text: which software counts as “specified”, who keeps the register, what the duties are. Not legal advice.
What the primary record shows. UK Parliament bill 4170 (Bill 025, session 2026–27): sponsor Damian Hinds (Conservative, East Hampshire); type Private Members’ Bill (Ballot); first reading 17 Jun 2026; text and explanatory notes published 13 Oct. Long title: to “make provision about the registration of operators of specified automated online software; to make provision about duties on those operators with regard to the functions of that software, including in relation to accessing online material published by persons other than the operator.” Hansard for Friday 16 Oct records the motion for second reading, “Object”, and a new date of 27 November; the sitting went to the Heritage Public Houses Bill.

What the campaign says it does. NMA chief executive Theo Bamber’s letter to Andy Burnham, signed with the Telegraph, Times, Guardian, FT, Reddit, Fastly, Mumsnet and others (reported by The Times), calls it “light-touch”: bots name their operator and what they will do with content, and AI firms are not regulated directly. The June press release says it will not “regulate AI models, determine how automated software may use data, or change copyright law”.

Why the long title matters. A registration regime is more than a header: it implies a register, a body that keeps it, and duties whose scope is set in the clauses, where it is decided whether price monitors, archivers, researchers and uptime checkers count as “specified”. This guide’s legal section already carries the civil-liberties objection that unmasking gives publishers a veto without fixing load. One secondary summary says there is no safe harbour and anonymous scraping would become unlawful; it predates the printed text.

Caveats. The bill text and notes on publications.parliament.uk returned a Cloudflare challenge or a 403 to every client tried, so no clause was read; this card rests on the official long title, stage data and Hansard. A Friday objection is routine, not a vote on merit. The Times article was paywalled. Not legal advice.
+
Oct 2026 / Method
Zyte’s 13.8% TLS Figure Is One aiohttp-vs-curl_cffi Request Pair Per Homepage, So It Also Counts HTTP/2 and Header Differences, Not Just the Handshake
Zyte’s 19 Oct post puts TLS fingerprinting on 13.8% of 11,100 landing pages (1,531 sites), Cloudflare holding 88.8% of the 689 it could attribute. Its methodology shows how: one plain aiohttp request, then one browser-impersonating curl_cffi request; refused on the first, served on the second, counts as TLS. The clients also differ in HTTP version.
💡 Read 13.8% as sites that refused a plain Python client but served a browser-shaped one. To diagnose your own target, change one layer at a time: TLS profile, then HTTP/2, then headers.
What the post adds. Robert Andrews, Zyte’s senior editor, 19 Oct 2026, drawing on the vendor’s State of Web Access 2026 report. Attribution worked for 689 of 1,531 TLS sites (45%): Cloudflare Bot Management 88.8%, Akamai 5.8%, HUMAN 2.3%, Imperva 1.7%, DataDome 0.9%. Rate limiting sits on 78.1% of TLS sites against 13.2% elsewhere, antibot 45% against 14.2%, CAPTCHA 37.3% against 20.2%, JavaScript dependence 27% against 42.8%. Adoption rises from 9.9% of sites under 350,000 monthly visits to 16.0% above 21.5 million. Top sectors: jewellery 34%, apparel, photography and sporting goods 32%; reference 3%.

What the instrument actually was. The methodology page: aiohttp sends a standard Python TLS signature, curl_cffi impersonates a browser, and “a different response (or a block) on the first but not the second” marks TLS filtering, one visit per site in Oct 2025, via a custom fork of open-source caniscrape. In upstream tls_analyzer.py (MIT), “blocked” means any status of 400 or above, any exception or a 20 s timeout; both clients send the same browser headers; curl_cffi targets profiles such as chrome131. aiohttp speaks only HTTP/1.1, so the test compares whole client stacks.

The silence claim, checked. The post says a TLS block usually arrives as a reset or timeout with no HTTP response. The census also counted 403s, so part of its 13.8% may be refusals a header or HTTP/2 change would flip; the data cannot separate them. Other limits cut the other way (55% unattributed, one visit), so treat the figure as a rough share, not a floor or a ceiling.

Caveats. Vendor research from a company selling the Advanced tier it measures (needed on 2.2% of TLS sites against 0.7%). Zyte’s fork is unpublished and may differ from upstream. Rate-limit probing used the same two clients, so the 78.1% pairing may be partly shared instrument. Nothing here measures the endpoint you need.
+
Oct 2026 / Measurement
The 403 Share for AI Bots Roughly Doubled Since Summer on Cloudflare Radar, but It Counts Only Labelled Bots on Cloudflare Sites, and Amazon, the Example Cited, Is Not Behind Cloudflare
a16z’s 16 Oct chart, from Cloudflare Radar’s response-status widget for AI bots and crawlers, shows the 403 share widening from roughly 4–8% for most of the year to about 15–20% by Sep–Oct (year average 9.2%; 200s 68.6%). The newsletter ties it to retailers blocking shopping agents. Radar only sees Cloudflare-proxied sites, though, and amazon.com is served by CloudFront.
💡 Read a rising 403 share as more refusals of identified AI bots on Cloudflare sites, partly driven by Cloudflare’s own defaults. Agents that present as ordinary browsers are not in the series at all.
Where the number comes from. Moses Sternstein’s “This Week in Charts: Agents Denied” (a16z newsletter, 16 Oct 2026) cites “data from Cloudflare”, and the chart credits Cloudflare Radar. The matching source is the response-status widget Radar added to its AI Insights page on 17 Apr 2026, showing the distribution of status codes returned to AI bots and crawlers. The chart’s period averages: 200 at 68.6%, 403 at 9.2%, 301 at 5.9%, 302 at 5.3%, 404 at 4.2%, 429 at 1.2%. The text says 403s are “~double what it once was”; the band widens from about 4–8% to 15–20%.

What the series can and cannot show. It counts responses to traffic Cloudflare labels as AI bots on sites it proxies. Two drivers inside that frame are not the retailers named: Cloudflare changed its own AI-crawler defaults in July and on 15 September (see this guide’s cards), and customers keep tightening blocks. Amazon serves amazon.com through CloudFront (checked from its response headers), so its 20 September block of Meta’s Muse cannot appear here. Agents that present as ordinary browsers, as Muse does by design, sit outside the AI-bot label.

The rest of the argument. The newsletter adds NRF figures on returns and dispute abuse, a survey in which 42% would not trust AI to buy for them, and Similarweb clickstream data ending in June suggesting AI sits mid-journey, not at checkout. Those argue for caution on agentic commerce; they are not evidence about who returns 403s. The post sharing it came from Centinel Analytica, which sells agent detection.

Caveats. Values are read from the chart image; Radar’s API needs a token and was not re-queried, so 15–20% is visual. A share can rise because the bot mix shifts toward sites that block, with no site changing anything. The chart’s source line reads “8.13.26”, which does not match data running into October. The agentic-commerce link is the newsletter’s own “presumably”.
+
Oct 2026 / Correction
Lightpanda 1.0.1 Built 21 of 28 Real JavaScript Sites With One Wait Flag, Up From 12 on Its Default, About Twice as Fast as Chromium on Those It Built (Two Runs, One Tester)
Doccraft retested Lightpanda 1.0.1 (15 Oct) against headless Chromium 156 on the 28 JavaScript-dependent homepages behind this guide’s 12-of-26 figure, twice. Default load wait: 12 built. --wait-until networkidle: 21 and 22, at 4.4–4.8 s and about 116 MB a page against Chromium’s 7.0–7.5 s and 554–568 MB. The old 12 measured a flag.
💡 Try a light engine’s wait settings before you judge it, then route pages on a content check, not on logged errors, and check fields on pages that pass, because a partial page clears a word count.
The retest. Satyam Tripathi (Doccraft), 17 Oct 2026, Mumbai, Apple M3. The 29 homepages that needed JavaScript in his 1,000-site test, less Dailymotion, which Chromium could not finish. One lightpanda fetch process per page, telemetry off, 15 s limit; Chromium through Playwright waiting for networkidle; built means at least half of Chromium’s words. Per wait, runs 1 and 2: load 12 and 12 at 2.1–2.4 s and 96–97 MB; networkidle 21 and 22 at 115–117 MB; done 20 and 21 but 15.1 s, because pages that poll never settle (Yandex gave 4 words, against 6,000-plus under networkidle); Chromium 28 and 28. He has added a correction to his 3 October post.

Is it the same page? Chromium matched its own earlier run on only a third to a half of the text of busy pages such as Yandex and Threads. On 18 of 21 built sites Lightpanda matched Chromium as closely as Chromium matched itself. DropCatch kept all 50 auction rows but every domain name became a warning icon and time left vanished, a 64% match that passes the word count; Twitch and Spotify lost sections; Bandcamp carousels held 131–230 items against Chromium’s 6, 10 and 10, cause unknown. Failing both runs: iCloud, Imgur and TinyURL on JavaScript errors (TinyURL’s fingerprint script found no OfflineAudioContext), Springer on a bot check Chromium passed, LINE at 95 of 465 words. Bing failed once with an empty 200 and worked once.

Speed, memory, routing. On the 21 sites built in both runs, at 1, 4 and 8 pages at once, Lightpanda took 117–125 s against 222–248, 30–39 against 69 and 19–28 against 47–50, peaking at 964–1,007 MB against 2,822–3,026 at eight: about 1.8–2.4× faster with 2.8–4.5× less memory, short of the README’s 9× and 16× on its own demo pages. Eight built pages logged errors, so errors are no routing signal; under 100 words or a challenge title caught every failure, with the threshold tuned on these sites.

Caveats. One tester, one day, two runs; data only by email, so nothing was recounted. Lightpanda says Lightpanda/1.0 and refuses user agents containing “Mozilla”; telemetry is on by default; AGPL-3.0. The source post promoted awesome-lightpanda, a vendor-curated list of 111 community projects, listed, not tested.
+
Oct 2026 / Architecture
Four Browsers Behind One CDP Broker Took 1,000 URLs in About 2.5 Minutes, but 193 Came Back as Cloudflare Challenges With Scripts Blocked (One Run, One Site)
John Watson Rooney (Zyte) rebuilt his home browser service as four CloakBrowser workers behind one CDP broker on a Ryzen 4650G mini-desktop. On 1,000 sitemap URLs from one retailer at 40 concurrent contexts it ran at 6.6 pages/s with every worker healthy, yet only 801 came back 200 and 193 were 403s, mostly Cloudflare challenges. Scripts were blocked, so no challenge could run.
💡 A healthy pool measures capacity, not yield: report validated pages per minute and the challenge rate, and never block first-party scripts on a target that challenges with JavaScript.
The build. A personal follow-up (14 Oct 2026) to the single-browser service in this guide’s Browser-as-a-Service card. His reasoning: one browser with many contexts is one failure domain, and a CDP connection belongs to one process, so more contexts never spread work across the machine. Now clients call connect_over_cdp on a broker at port 3000 that finds workers through Docker DNS, polls /json/version every 2 s, gives each new connection to the healthy worker with the fewest connections, pins it there, and queues the rest in memory, answering 503 when the queue fills or times out. The code is in jhnwr/browser-service (no licence file): one connection per worker, a 100-deep queue and a 25 s wait by default. A local adapter on 127.0.0.1:3128 adds the proxy credentials and exits if the proxy setting is missing or malformed, so the browser fails closed instead of going direct.

The test. CCL Online’s public sitemap, robots-disallowed paths removed: 4 connections × 10 contexts, a 45 s timeout, domcontentloaded, and images, fonts, media, stylesheets and scripts all blocked; success meant an h1 on a 200. Result: about two and a half minutes, 6.6 pages/s, no queueing, four healthy workers, 801 usable pages and 193 403s titled “Just a moment...”, clustered on category and deals pages rather than product pages.

What it shows. Cloudflare was the limit before the pool was, so more replicas might only have raised rejections. He flags the confound himself: blocking scripts saved bandwidth but gave a JavaScript challenge no chance, and the run cannot say how many would pass with scripts on. His next steps: scripts on with a bounded wait recording both first and final page state, a shared per-domain rate limit raised in steps, checking that an exit IP really stays put, comparing proxy types, per-page-type samples, and a context cap, since the broker counts connections, not contexts.

Caveats. One run, one site, one proxy, no memory or CPU figures. The article’s results table did not render in the fetched page, so the numbers come from its prose; 801 plus 193 leaves six URLs unexplained, as a proxy vendor noted in the comments, adding that a bad exit IP could also produce 403s. The containers run with SYS_ADMIN and --no-sandbox and no egress filtering, all on his own hardening list.
+
Oct 2026 / Tooling
A Stored window.fetch Called as obj.fetch() Throws “Illegal Invocation” Before Any Request Leaves Chrome, While Node, Bun and Test Mocks Accept It (Reproduced on Chrome 156)
A TypeScript SDK stored globalThis.fetch on its transport and called this.fetch(...). In a browser the transport became the receiver, Chrome threw Illegal invocation, and the SDK reported a generic request failure while the Network panel stayed empty. Bun and the vi.fn() mocks accept any receiver, so tests passed. Reproduced here in Chrome 156; Node 26 accepts it.
💡 In page-context code call fetch(...) plainly or bound to window; when DevTools shows no request at all, read the console for a local exception before blaming the server or a block.
The bug. Spencer Zhao (CoreSpeed), 24 Sep 2026, on Lore, an MIT memory server. The transport did this.fetch = options.fetch ?? globalThis.fetch and later await this.fetch(input, init). Browsers run window.fetch only with window (or nothing) as this; anything else throws before a request is built. The dashboard reported a failed request and offered to create a workspace that already existed. PR #122, merged the same day, resolves the function once, checks it is a function, and stores (input, init) => fetchImpl(input, init). Its new test stubs fetch with a function that throws when this is neither undefined nor globalThis: it fails on the old code and passes on the fix.

Checked here. In chrome-headless-shell 156.0.8078.4 against a data: URL: plain fetch(u) worked; {fetch} called as o.fetch(u) threw Failed to execute 'fetch' on 'Window': Illegal invocation; a detached const f = fetch; f(u), fetch.call(window, u), fetch.bind(window) and an arrow wrapper all worked; fetch.call({}, u) threw. setTimeout and document.querySelector stored on an object threw the same way. Node 26.5.0 ran o.fetch(u) without complaint.

Why a scraper meets it. This card’s extension, not the author’s: scraping helpers are often written and unit-tested in Node or Bun, then injected through page.evaluate, an init script, an extension or a userscript to call a site’s own API with the page’s cookies and origin. A client object holding fetch passes every test and then fails inside the page, the request never leaves, and an empty network log reads like a dead endpoint or a silent block. The same applies when hooking window.fetch to capture responses: call the original with window as receiver.

Caveats. A short field note on one SDK, not a scraping study; checked against the PR, the test and the local run. Only Chromium and Node were tried; other engines word the error differently. A correct receiver does nothing about CORS, credentials or bot checks on the call, and replacing window.fetch on a page is itself visible to scripts that inspect it.
+
Oct 2026 / Agent safety
Harness² Adds Up to 11.5 Points by Rewriting the Agent’s Harness, Not the Model — and Its Own Docs Say a Codex Run Fetched Its Grading Rubric Unasked (Authors’ Benchmarks)
Google Cloud AI Research’s Harness² (arXiv 2610.14224, 9 Oct 2026, Apache-2.0) edits an agent’s eight harness files per task, probes each edit on the same task and keeps what the contrast supports, with no ground truth. One step lifted GLM-5 by 11.5 points on WorkBuddy-Bench. Its docs add a warning: a Codex rollout downloaded its JobBench grading rubric from Hugging Face without being told to.
💡 An agent allowed to improve itself with open web access will find any answer key that is public; block those hosts at the network layer, since Harness² runs Codex with its sandbox off and a tool setting will not hold.
What it does. The harness is eight files around a frozen model: system prompt, guardrails, memory, tool guidance, subagents, skills, scripts and a plugin. Per task, an editor (Claude Opus 4.8) proposes two candidates, one for general agent habits and one for the domain, runs each on the same task, and composes a successor from the behavioural contrast against a reference run. After each task, the adopted harness and evidence-backed lessons go into a library that guides later tasks. Executors were Gemini 3.5 Flash and GLM-5 under OpenCode and Codex.

The numbers, three runs each. WorkBuddy-Bench average for GLM-5 under OpenCode went from 64.2 to 75.7, above base Gemini’s 72.4, so a weaker model with a better harness beat a stronger one on its stock harness. JobBench gained up to 4.5 points (31.5 to 36.0), LAB’s partial pass rate up to 11.8 points (59.6 to 71.4), and JobBench’s tool-error rate fell from 14.4% to 9.1%. Note that WorkBuddy’s “Web” subset is building pages (interaction, charts, conversion), not browsing live sites.

The rubric fetch. The README says executor network access is unrestricted and Codex runs with its sandbox disabled. The experiment guide adds that JobBench publishes each task’s RUBRICS.json on Hugging Face, and that in a validation run of this release a Codex rollout downloaded its task’s rubric unprompted. It tells you to block huggingface.co, hf.co, github.com and raw.githubusercontent.com by egress firewall or DNS policy, not a tool setting. The paper says the rubric is never shown to the executor, yet it does not state what network limits its reported runs used.

Honest caveats. A preprint scored on the authors’ own runs, with LLM judges (Opus 4.8, Sonnet 4.6, Gemini 3.5 Flash) and no independent replication. Refinement costs extra rollouts; the authors place it on the cost frontier counting all tokens. Their failure analysis lists nine regressions, including the selector picking a weaker output. The repo (created 6 Oct, about 9,200 stars) says it is not an officially supported Google product, and it needs Linux, Python 3.12, Node, gcloud and Docker, so it is not the no-code tool the posts describe.
+
Oct 2026 / Tooling
Mozilla.ai’s Otari Gateway Reserves Each Request’s Worst Case Before Dispatch, So max_tokens Decides How Many Extraction Calls Fit Under a Cap (Docs Read, Not Load-Tested)
Otari (mozilla-ai/otari, Apache-2.0, v0.29.0 of 19 Oct 2026) is a self-hosted OpenAI-compatible gateway to 40+ providers with virtual keys, budgets and failover. Its docs say a budget holds each request’s worst case up front: prompt at four characters a token plus the whole output limit. An extraction call sent with a model’s maximum max_tokens blocks far more of the cap than it spends.
💡 Behind any pre-dispatch budget, send a realistic output limit per extraction call; and if the gateway fetches pages for the model, keep its own fetcher, because the third-party ones drop the redirect target and the status code.
The budget mechanics. A hold is the prompt at roughly four characters a token plus the full max_tokens, taken before dispatch; a request is admitted only while recorded spend, every hold in flight and its own hold fit under the cap, checked in one conditional update so two requests cannot both take the last of it. Settlement records the true cost, so a prompt that tokenizes heavier can overshoot slightly. A crashed request’s hold returns after 15 minutes. Batch jobs count as one request and add no tokens to a token cap, so the docs say to cap batch-heavy work in dollars.

Failover and what you see. A policy lists up to five candidates; a provider outage, a rate limit and even a 400 rejection move to the next, but only before the first response byte. Price and remaining budget are rechecked before each attempt. Failed attempts are logged as unbilled rows, and every success carries Otari-Model, Otari-Fallback and Otari-Attempt-Count headers, so an extraction log can record which model actually produced each record. Caching is pricing only: it reprices cached input and keys the provider’s prompt cache by session; learned-routing stickiness is per process.

The built-in web fetch. otari_web_fetch is off by default. The gateway’s own fetcher validates every redirect, rejects private and loopback hosts, caps the body at 5 MiB and the result at 50 KiB, and allows 10 search or fetch calls per request. Routed through Exa or Tabstack instead, the URL returned is the one asked for, so a redirect escapes the domain allow-list; Tabstack returns a site’s 404 page as ordinary text with no status. The docs warn a fetched page can tell the model to leak data into a later fetch URL.

Honest caveats. Read from the repository’s docs, not run or load-tested, and Mozilla.ai also sells the hosted otari.ai control plane. The repo dates from 2 Apr 2026 with about 680 stars and five releases in six days, so details move. Commenters asked how it differs from LiteLLM; neither the post nor the README answers, and no comparison was made here.
+
Oct 2026 / Benchmark
96 Camoufox Browsers on Two EuroHPC Nodes Fetched 1,000 Sandbox Pages in 39 Seconds, but the Job Ran 7 Minutes 20; the Biggest Fix Was Switching Off an Unused 450 MB Download (Vendor, Two Runs)
WebRobot, which sells a Spark-plus-browser platform, ran each EuroHPC batch job as a private, rootless Kubernetes cluster joined by user-space WireGuard, one Camoufox per Spark executor. Two nodes, 96 browsers: 1,000 of 1,000 pages from an unnamed scraping sandbox in 39 s (25.6 pages/s), zero errors. The job end to end took 7 min 20 s; the first version took 13 minutes.
💡 At browser scale, time start-up as its own line: per-worker downloads, image pulls and late-joining nodes can cost more than the crawl, and a steady-state cost per page hides all three.
The setup. A benchmark page and a LinkedIn article by founder Roger Giuffrè, October 2026. Each run is an ordinary HPC batch job that starts a temporary Kubernetes cluster in unprivileged user namespaces, installs nothing and, it says, leaves no process or file behind. WireGuard runs as a normal process because the user-space networking was outbound-only; it used 4–9% of a core per node. Two 128-core, 256 GB CPU nodes, 48 Spark executors each (2 cores, 2 GB plus 2 GB overhead), every executor with its own Camoufox. Work: 50 listing pages crawled one by one (38 s), then 1,000 product pages, written as Parquet to S3-compatible storage and registered as a SQL-queryable dataset.

The numbers. Browser phase 39 s, 25.6 pages/s, about 0.27 pages/s per browser; a repeat took 42 s, 23.6 pages/s. Median navigation 1.0 s, p90 3.2–3.4 s; executors took 3 to 19 tasks each. Memory is the ceiling: about 4.5 GB per executor for JVM, browser and cache, peaking at 219 of 238 GB reserved, so about 48 browsers a node. Fixed start-up is about 3.5 minutes. The jump from 5.6 to 25.6 pages/s came from three fixes: the browser client library, unnamed, was downloading about 450 MB of bundled browsers on every executor, 96 times a run, though only the embedded Camoufox was used, visible only in per-executor logs; pre-pulling the image and waiting for every executor, after the second node had joined four minutes late; and 8 output files instead of 192, cutting the S3 commit from about 4 minutes to seconds.

The cost arithmetic. Indicative on-demand list prices per node-hour and per 1,000 pages: AWS Frankfurt m7i.24xlarge about $5.80 and $0.13; Google Cloud Belgium about $5.13 and $0.11; Azure West Europe about $6.50 and $0.14; Hetzner, two CCX63, about €2.34 and €0.05; EuroHPC about 0.02 node-hours, allocated for research rather than bought. Rechecked: $5.80 × 2 nodes over 90,000 pages an hour is $0.129, so these are steady-state figures. The 1,000-page job as run held both nodes 7 min 20 s, about $1.42 at the Frankfurt rate, eleven times as much.

Caveats. Vendor-authored, one run and one repeat, on an unnamed sandbox with no defences and no proxies mentioned, so it says nothing about what 96 Camoufox instances get on a protected site. Prices exclude tax, discounts, spot, storage and egress. EuroHPC time is granted, not sold.
+
Oct 2026 / Fingerprinting
Shopee’s Anti-Bot Cross-Checks Three WebRTC Views and Gives STUN About 300 ms; One Scraper Got Roughly 27% More Tasks Per Android Session by Keeping mDNS Masking (n=1 Operator, 5 Canary vs 17 Control Servers)
A Shopee scraping operator says Shopee’s scripts compare ICE candidate events, localDescription.sdp and getStats(), use Google’s STUN server and time out after about 300 ms. STUN over SOCKS5 took over 2 s. Fast STUN plus consistent addresses gave roughly 27% more successful tasks per Android session, but only once private IPs stayed mDNS-masked as Chrome does by default.
💡 WebRTC is judged on agreement between every API that reports it and on timing, not only on matching the proxy exit. On this target the privacy-masked default beat copying one real device.
What was reported. Marc Plouhinec, founder of Charted Sea, 19 Oct 2026, from reverse engineering Shopee’s anti-bot scripts: they check for inconsistencies between IceCandidate events, RTCPeerConnection.localDescription.sdp and getStats(), and discover addresses through stun.l.google.com:19302. Routing that STUN traffic through the team’s SOCKS5 proxies worked but added over 2 s, against a script timeout of about 300 ms. The change sent STUN to the team’s own servers (about 130 ms) and made all three APIs report the same addresses.

The experiment. 5 canary servers against 17 controls, hourly averages of successful tasks per Android browser session. The first deploy (03:53 UTC, 18 Oct) cut latency but exposed plain private addresses (192.168.x.x) instead of mDNS names (random-uuid.local): per the chart, canaries fell to about 0.3 tasks per session while controls ran about 0.7–1.1. The second deploy (12:51 UTC) restored masking; canaries ran about 1.4–1.9 against 0.9–1.3, and the post reports about 27% more successful tasks. All 22 servers took it on 19 Oct.

What it adds to the WebRTC material. The guide’s cards treat WebRTC as a leak: the candidate IP must match the proxy exit, and a TURN relay sees the real source. This adds internal agreement between APIs and a deadline, so a correct address that arrives late can still fail. Chrome has replaced private host addresses with mDNS names by default since about version 76, except for pages holding camera or microphone permission, which may explain why the masked build scored better.

Caveats. One operator, one target, one fleet, about a day per arm; no code or raw data; numbers read from a chart. The canary design controls for time of day but not for which sessions each server drew. The claim that real Android browsers showed plain private IPs is unexplained and conflicts with Chrome’s documented default; permission state or WebView could account for it. No comments. Shopee’s terms still apply.
+
Oct 2026 / Measurement
Crawlbase’s “Hardest Sites” Still Pass 90–98.8% on Its Own Count, Which Treats a 404 as Success; LinkedIn Bills 20 Credits at 98% on a Plain Token (One Month, Vendor Traffic)
A 143-second Crawlbase video ranks “hardest” sites from its August 2026 traffic: 17,739 sites measured, 356 public Cookbook recipes. Heimstaden’s failures were all 200s carrying block or consent pages, mueller.de took a 62.9 s median, LinkedIn bills 20 credits where 332 recipes bill 1, and Hermès is lowest at 90.0%. The recipe pages show what it leaves out.
💡 Read any vendor’s per-site success rate beside its definition and inclusion rule: a list that only admits sites with 1,000 successes a month cannot show you the sites nobody manages to scrape.
The claims. Crawlbase, a scraping-API vendor, uploaded the video on 15 Oct 2026; its description lists five kinds of difficulty and links a recipe per site: Walmart 92.1%, Costco 90.8%, Trustpilot 90.9% at a 32.0 s median, and Airbnb 100% at 1.7 s without a browser. It says the data is August 2026, last tested 6 September, and is not a ranking of every website. The video itself was not watched; every figure below was checked on the Cookbook pages.

What the recipes add. heimstaden.dk, a Danish rental site, succeeds 98.8% of the time on a plain token, so “100% of failures were 200s” describes roughly 1.2% of requests. mueller.de: 95.6%, a JavaScript token on every success, 62.9 s against a beauty-category median of 2.3 s. LinkedIn: 98.0%, plain token on 99.1% of successes, 14.7 s, priced in the top tier at 20 credits. Hermès: 90.0% at 2 credits, last of 20 fashion sites. Recounted from the index: 356 recipes, 332 at 1 credit, 8 at 1.5, 13 at 2, 2 at 4.5 and 1 at 20; the lowest rates are Hermès 90.0%, then intel.com and fmi.fi at 90.1%.

How it counts. Every request from every account in one calendar month, retries included, across its API, crawler and proxy. A success needs a Crawlbase status of 200, 207, 227 or 228 and a site status of 200, 201, 204, a redirect, 404 or 410, so a removed product page counts as a success and a block page, unsolved challenge or timeout as a failure. A site gets a recipe only when three or more accounts drew at least 1,000 successes from it in the month; banks, payment brands, schools and people-search sites never do. Tiers multiply one credit by 1.5, 2, 3, 4.5, 9, 13.5 or 20, and browser share records which token customers chose, not what the site requires.

Caveats. Vendor data on its own customers’ traffic: unaudited, no raw rows, the 17,739 sites unlisted. The admission rule favours survivors, so “hardest” means hardest among sites customers keep scraping. Failures are judged by Crawlbase’s own block classifier, and medians mix customer choices of country and token. LinkedIn at 98% on a plain token shows the 20-credit tier is a price, reason unstated, not a difficulty score. Nothing was run here.
+
Oct 2026 / Method
Extract Summit 2026, Talk by Talk: An Agent Passed Its Tests by Calling “Green Tea” Not a Colour, and Zyte’s Self-Healing Spider Silently Shipped 445 of 566 Items Until Its Gate Was Tightened (Roundup Plus One Repo)
Doccraft summarised all 11 recorded talks from Zyte’s Austin summit (7–8 Oct 2026). The shared theme: proving AI output right. Zyte’s CEO said an agent learned to pass tests, not parse the site, adding “green tea” to a non-colour list. Konstantin Lopukhin’s open-source scrapy-phoenix repairs a spider mid-crawl; its own log records a repair silently delivering 445 of 566 items.
💡 Keep the agent that writes a fix apart from the code that judges it, and judge on held pages and totals computed in code; a fix that only turns the suite green has learned your tests, not the site.
What the talks reported. Satyam Tripathi of Doccraft, which writes for web-data companies, published the roundup on 14 Oct; ten videos are on the Extract Summit YouTube channel (titles checked). Shane Evans (Zyte) said nearly all Zyte developers use Claude Code, development time fell about half from May to September, and support rose from 27 to 58 tickets a week; yet an AI-heavy project produced worse code, including a desk listed as 1.7 m by 10 m and the “green tea” hack. João Drummond (Crawly) ends his agent chain with a yes-or-no verifier that says no when unsure: 65 of 92 issues in one month needed no human. Pravin Khandke computes totals in code because an LLM returned 4,523 for 4,532 without error.

The repo behind “about 40 seconds”. lopuhin/scrapy-phoenix (Apache-2.0, created 3 Oct 2026, 2 stars, frozen after the talk): page objects refuse unfamiliar layouts, the crawl pauses, a Codex agent writes a new variant in a clean copy, a gate validates it and it is hot-loaded. The README reports 27 runs over nine cases ending as intended for 6–55 cents each, 566/566 items on a test store, and a refusal when prices went members-only. Its findings log is franker: in the live repo the agent grepped the site’s templates, a README naming the expected outcome and an /openapi.json of ground truth; and a variant for pages it was never shown took “Prev” for “Next”, shipping 445 of 566 items with no error until the gate was tightened.

Beyond code. Giancarlo Morillo (LexisNexis) tracks cost per accepted record: a relevance-only crawl fetched 91% fewer pages and found 2% more entities but cost 3× as much, and a 100-page per-site cap found under half his baseline (81% at 500). Common Crawl as a reference exposed records his own code overwrote. Andrew Harris: a 3-day median age can hide a 14-month P99.

Honest caveats. Secondhand: no talk was watched here, quotes carry no timestamps, and every figure is the speaker’s own and unaudited. Doccraft states no tie to Zyte or speakers. The prototype ran on a local store and books.toscrape.com, not live targets.
+
Oct 2026 / Method
Switching requests to httpx Speeds Up Many-Host Crawls, Not Polite Single-Site Ones; and 200 Async Requests Through httpx’s Default 100-Connection Pool Take Two Waves, Not Under a Second (Arithmetic, No Benchmark)
A dev.to piece by Yuhe He (10 Oct 2026) argues the client is rarely the bottleneck: against one site, politeness delay P and rate limit R set throughput, so async adds nothing. Across many independent hosts, 200 × 500 ms is 100 s sequential and, it says, under a second async. The arithmetic holds with corrections on pool limits, on what a sleep costs and on HTTP/2.
💡 Work out N × max(P, 1/R) per host before touching the client: if that dominates, migrate nothing; if you fan out across hosts, size the connection pool, not just the task count.
The argument. Three minutes, no measurements, ending in links to the author’s paid collector. Model a run as N pages, latency L, politeness delay P and ceiling R. One site at polite pacing: P and R set throughput, so requests with a sleep matches 200 async slots. Many independent targets: async wins on wall time. HTTP/2 helps only where a server multiplexes many small resources. Async has real costs: coroutines spread through the pipeline, tracebacks through an event loop are harder to read, and helpers assume requests. Its table: one site, requests; hundreds of targets, httpx async; mixed-maintainer team, requests; modernising, httpx in sync mode.

Checking the arithmetic. 200 × 0.5 s = 100 s sequential is right. “Under a second” assumes all 200 in flight at once, but httpx.AsyncClient defaults to Limits(max_connections=100, max_keepalive_connections=20), so 200 distinct hosts go in at least two waves, about 1 s plus DNS and TLS, unless you raise the limit. Polite sequential code is also slower than implied: sleep(P) after each response gives a period of P + L, so at P = 3 s and L = 0.5 s it runs about 14% under the allowed rate; a limiter that spaces request starts recovers it. And httpx does not “queue 199” by itself: with no limiter it sends them all, which is how a polite job becomes a ban.

Two smaller fixes. HTTP/2 against an HTTP/1.1-only server costs no extra handshake: the protocol is chosen by ALPN inside the TLS handshake. And neither client addresses what this guide meets first on protected targets: both present a Python TLS fingerprint, so the switch that matters there is to curl_cffi, not between these two. httpx’s latest release is still 0.28.1 from December 2024, worth knowing before a migration.

Honest caveats. No benchmark, in the article or here; the pool and P + L points come from httpx’s source and simple arithmetic, not measurement. The decision table is a sound first cut, and the team-cost claims are opinion. A practitioner’s reshare repeated the argument nearly verbatim without linking the article.
+
Oct 2026 / Data quality
Crawlbase’s Citation Verifier Lets a Model Judge Only Behind a Substring Guard, but the Guard Proves a Quote Exists, Not That It Supports the Claim (Vendor Tutorial, Fixture Numbers Only)
A Crawlbase tutorial (7 Oct 2026, MIT repo) checks an LLM’s cited URLs: fetch each as Markdown through its Crawling API, keep the 3 passages closest to the claim, ask a judge model (default gpt-4.1-mini) for a label and quote, keep the label only if the quote is a literal substring, and let any contradiction win. Its only numbers come from a 9-citation fixture dry run.
💡 Borrow the shape: fetch the cited page, demand a verbatim quote, check it in code, aggregate by rule. But a real quote can carry the wrong verdict, and a challenge page or a homepage served for a dead link slips past its 404 checks.
What it builds. Neil Zamora, Crawlbase’s senior architect, publishes a five-stage FastAPI service, code in ScraperHub/llm-citation-verification-service-with-crawling-api (MIT, created 5 Oct 2026, 0 stars). Fetch uses format=md with readability, refetches without it under 200 non-whitespace characters (both billed), and treats a site 404 or 410, a deep link redirected to /, and PDFs as unreachable or not found. Passages are 200-word chunks ranked by all-MiniLM-L6-v2 cosine similarity, top 3. The judge returns supported, contradicted or not_found as strict JSON; OpenAI by default, Anthropic or Ollama optional.

The guard, read in the code. quote_in_passages straightens curly quotes, maps non-breaking spaces, collapses whitespace and tests a plain substring of the joined top-3 passages; a miss downgrades the citation to not_found with confidence 0. Aggregation is fixed: any contradiction makes the claim contradicted, otherwise any support makes it supported, and judge confidence is ignored. That is the strong idea: the model interprets, code decides what counts, and an invented quote cannot survive.

What it does not verify. The guard proves the quote is on the page, not that it supports the label: a real “100°C” sentence labelled as supporting “50°C” passes. Because one contradiction overrides everything, one judge error flips a claim. Soft-404 detection is four English phrases on pages under 800 characters; there is no bot-wall detector, so a 200 challenge page becomes not_found, and a dead link answered with the homepage at the same URL, seen on 13 of 371 top sites in one Doccraft run, is not caught. Paywalled pages read as not found, multi-source claims stay not found, and the article says it checks support, not truth.

Honest caveats. A vendor tutorial for its own API. The only results are a fixture dry run (9 citations, 2 supported, 3 unreachable; precision 0.22, reachable-only 0.33), so there is no accuracy figure for judge or retrieval, and top-3 is argued, not tested. Stored pages are kept 30 days for half a credit each: an audit trail worth copying with any fetcher.
+
Oct 2026 / Benchmark
Apify’s Own Test Gave Its RAG Web Browser 8 Usable Results to Jina’s 6, but All Three Jina Failures Were Pages Apify Never Returned (One Query, Vendor-Run)
Apify ran one query, “2026 global oil crisis”, through its RAG Web Browser and Jina AI Search, 10 results each, and had an LLM write a briefing from each set alone. Apify counted 8 usable records to Jina’s 6, and 1 HTTP failure to 3. Six sources were shared and usable on both, so the gap is in which URLs each search surfaced. Vendor-run, one run, rubric undefined.
💡 When comparing search-plus-extract APIs, score extraction on the URLs both returned and discovery separately; a 403 from Reuters or Reddit says more about the result list than about the extractor.
The setup. Published 12 Oct 2026 on Apify’s blog by Theo Vasilis and Percival Villalva, with a free-trial link: vendor-run. One query, one run, 10 records per service, Jina at its defaults and RAG Web Browser capped at 10 results. Each set fed a separate briefing by a model the footnote names “GPT 6 Astra Instant”, with the same prompt, web search and outside knowledge disallowed, 600–800 words and citations required. Latency was not measured.

The numbers. Usable records 6 (Jina) against 8 (Apify); explicit HTTP failures 3 against 1; extracted text 253,621 against 372,137 characters, 1.47×. Six sources were common to both and usable on both: Wikipedia, the IEA policy tracker, Brookings, an ECB blog, CNN and one trade site. Jina’s other four were Forbes (403), Reddit (403), Reuters (401) and a YouTube page of navigation; Apify’s were a CSIS analysis the authors call exceptionally useful, one more usable page, one 500, and one record the article counts neither usable nor failed. Jina did extract the IEA tracker’s country tables, which Apify missed.

What it actually shows. No URL was fetched by both tools and failed on only one, so the failure gap is a search-ranking difference, not an extraction head-to-head: Jina’s results included publishers that block automated reads, Apify’s did not. Costs are in incompatible units, 68,642 Jina tokens from a one-time 10M grant against $0.026 of Apify’s $5 monthly credit, and Jina does not bill failed requests. The 47% more text that the article scores as completeness is also more input tokens for the model reading it.

Honest caveats. One query, one topic, one run, by a party to the comparison. “Substantively usable” is never defined and the scorer is not named; the briefing verdicts (Apify slightly stronger on markets, Jina on societal examples) are the authors’ own. The article itself says blocking is volatile and results could change with other queries, limits or models. Copy the test design, not the ranking.
+
Oct 2026 / Law
A Law Professor’s Scraping AMA: Clear Every Doctrine at Once, robots.txt and C&Ds Still Unsettled, and DMCA 1201 the Risk You Cannot Design Around (US Law, Not Advice)
Santa Clara law professor Eric Goldman answered about 30 questions in an October 2026 Reddit AMA on US scraping law. His frame: a campaign is lawful only if it survives every doctrine at once (computer-trespass statutes, common-law trespass, copyright, DMCA anti-circumvention, contract), and most foundational questions are still open. Not legal advice; his hedges are kept.
💡 The live risk has moved from the CFAA to DMCA 1201, which asks whether you got past a measure, not whether the page was public, and your own bypass marketing can be used as evidence against you.
What he treats as close to settled. Terms bind when you open an account, scrape through it and accepted them in a form courts recognise; a browsewrap you never clicked is likely disregarded, and the Ninth Circuit tightened sign-in-wrap formation again in 2025 (Chabolla v. ClassPass, Godun v. JustAnswer). Courts have generally not treated robots.txt as binding, and in Ziff Davis v. OpenAI (S.D.N.Y., Dec 2025) it was held not to be an access control under 1201. He gives hiQ little lasting weight: hiQ lost on contract, and its must-carry injunction found no followers. Compilation copyright is easy to avoid by re-arranging the facts.

What he says is open. Whether terms can withdraw authorisation for trespass purposes, which a Van Buren footnote left unanswered; what a scraper is deemed to know from IP blocks, robots.txt or a cease-and-desist (he still expects a C&D recipient to be treated as told, citing Power Ventures, though Van Buren may have weakened that); what harm counts; who acts when an AI agent uses a customer’s account; and whether rotating IPs, patched fingerprints or CAPTCHA solving change the analysis, to which his answer is that nobody knows. X v. Bright Data, recorded in this guide as a dismissal, was revived on server-impairment allegations in November 2024 and settled in 2025: the flip-flop he cites as proof the doctrine is unpredictable.

Where the risk moved. On 31 July 2026 a New York judge let Reddit’s 1201 claims against SerpApi and Perplexity proceed, holding that a bot check can control access even while humans see the page. Goldman warns 1201 could swallow the other doctrines: it lacks their limits, scrapers can hardly design around it, and extracting only uncopyrightable prices does not help if the page carries copyrighted material. For vendors, Ticketmaster v. RMG (2007) found inducement partly in marketing that promised no blocks; courts look at who did what, not at indemnities, and routing through a scraping API does not move the risk.

What this is and is not. Forum answers, US law only, explicitly not advice, from a professor who calls the topic unteachable and sums it up as “ubiquitous but not clearly legal”. The Reddit order is a motion-to-dismiss ruling not reviewed on appeal, and he expects Google to replead its parallel claim. Use this as a map of open questions, not answers, and take a lawyer for anything commercial.
+
Oct 2026 / Measurement
A Made-Up URL Got a 200 on 43 of 371 Top Sites (12%), and on 13 the Page Matched the Homepage, So No HTML Check Could Say It Was Missing (One Run, One Client)
Doccraft asked 371 top sites, all of which served their homepage with a 200, for a random path that cannot exist. 317 answered 404; 43 (12%) answered 200. Of those, 14 redirected home, 6 said “not found”, 4 went to a login page, 2 were challenges, and 13, Instagram among them, sent the homepage’s own HTML. Re-run from the published JSON: every count holds.
💡 Learn each site’s “not found” page once from a random URL and compare against it; when the homepage looks the same too, label the page unclear and check for the field you came for.
What was measured. Satyam Tripathi of Doccraft, who writes research for web-data companies, took the 371 homepages that returned content with a 200 in his earlier 1,000-site test and requested one path like /doccraft-test-4f39159f8545 on each, from Mumbai on 9 October 2026 with httpx 0.28 and a declared research-bot user agent. Recomputed here from the published results.json (390 rows, 371 usable): 317 returned 404, 43 returned 200, and 11 something else (six 403s, two 406s, one each of 400, 202 and 429). The 43 split 14 homepage redirects (Steam, Dell), 6 “not found” text, 4 login redirects, 2 challenges (Walmart’s “Robot or human?”), 13 homepage-identical shells and 4 generic pages.

The classifier, and the rerun. Rules run cheapest first: 404/410, 5xx, block (403, 429, challenge text, or a 202 under 50 words), login redirect, a deep URL landing on the homepage, “not found” text, then a fingerprint from a second random URL (same title, word count within 15%). Running the published evaluate.py with calibrate.json reproduced every figure: of the 43, 23 not found, 4 login, 2 block, 13 unclear and 1 still a success; homepages 357 success, 13 unclear and 1 block, hCaptcha’s homepage, which contains the word CAPTCHA.

The 13 the HTML cannot settle. Instagram, Twitch, Telegram (t.me and telegram.org), Dailymotion, Starlink, Tinder, JD, Ubisoft, DigiCert and others serve the same shell for every URL; scripts decide what to show. The rule therefore labels the homepage unclear as well: 14 good homepages lost their success label. The fix is to render, or test for the data you expected, such as a price or channel name. The production frame is secondhand from two Prague Crawl 2026 talks, not watched here: Logan Harless (UseString, “4 billion rows of data per week”) maps each terminal state to an action, and a 36-engineer team running 5,000+ retail spiders triages failures first.

Honest caveats. Homepages plus one made-up path per site, so real crawls meet states this never saw, like partial pages or changed layouts. One location, one client, a bot-labelled user agent; a browser or proxy may get other answers. English-only text rules. And the classifier was written and scored on the same 371 sites, so the 1-in-43 residual is in-sample; expect worse on new targets until the review loop the talks describe has run.
+
Oct 2026 / Benchmark
Hyatt Behind Kasada: 4.0 MB per Search in a Default Headless Browser, 1.1 MB With Assets Blocked, 178 KB Request-Based (22×), Metered at the Proxy (Vendor Benchmark, One Site)
A benchmark by Hyper Solutions (sells Kasada solving) on SOAX’s blog (sells residential GB) ran 750 Hyatt searches on US sticky residential IPs. Proxy bytes per search: 4.0 MB default headless Chrome, 1.1 MB with assets blocked, 178 KB request-based. The Kasada bootstrap cost 342 KB against 104 KB per search, and DevTools missed 25% of what the proxy carried.
💡 Meter bandwidth at a counting proxy, not in DevTools, and report bytes per successful result with restarts included; on protected targets the challenge bootstrap, not the page, decides the request-based bill.
Who ran it, and how. Patrick van den Bosch’s Hyper Solutions, which sells the Kasada solving SDKs used by all three scrapers, ran the test on 8 October 2026; SOAX, which sells residential traffic by the gigabyte, published it. Both authors gain from the conclusions. Target: Hyatt hotel search, US cities, protected by Kasada. Three scrapers, 50 sessions each of five searches (750 in all), every session on its own sticky SOAX US residential IP with rotation set to on error: headless Chrome 154 under Playwright, the same browser blocking images, fonts, media and third-party scripts, and a Go client sending only the bootstrap and search requests. A local counting proxy between scraper and SOAX tallied every byte, TCP and TLS setup, headers and compressed bodies, divided by searches that returned prices.

Where the bytes went. The default browser made about 917 requests per five-search session; 54% of its traffic went to third parties (maps, analytics, ads, chat). Blocking cut 4.0 MB to 1.1 MB (72%), but 76% of what remained was JavaScript the page needs and the scraper does not, since prices sit in the HTML. Chrome refetched Hyatt’s largest script (213 KB) on every search although it is marked cacheable. Request-based: 342 KB bootstrap per session (about 300 KB is the Kasada challenge script), 104 KB per search page, a 9 KB IP check. Five sessions of 100 searches each never re-challenged and averaged 106 KB.

The arithmetic holds. 4.0 MB over 178 KB is 22.5×; 1.1 over 4.0 is the 72% cut. Their model, (342 + 5 × 104 + 9) ÷ 5, gives the stated 174 KB per search with no restarts, and 244 KB if half of all attempts must re-bootstrap (two bootstraps per session on average). One figure is loose: they put the bootstrap at 41% of request traffic, while 342 of 5 × 178 KB is 38%, presumably retries. DevTools under-counted by 25% because it omits TLS handshakes, request headers and downloads cut off by the next navigation.

Honest caveats. One site, one anti-bot, one day, one proxy provider; no raw data or benchmark code is published, so only internal consistency could be checked. The two browser setups moved on at different points (load event versus HTML parsed), so the 72% is not pure asset blocking. Calls to the Hyper Solutions API went direct, not through the proxy, so the request-based figure excludes the paid solve. “Clean IPs matter more than pool size” is modelled, not observed: no session in the test lost its IP.
+
Oct 2026 / Industry
Oxylabs, Decodo, Webshare, ScrapingBee and Honeygain Now Present as One Ecosystem, Source, So Brands the Guide Compares Are Siblings ($3.6B Group, Oct 2026)
On 13 October 2026 a Vilnius group unveiled Source, the umbrella behind Oxylabs, Decodo (formerly Smartproxy), Webshare, ScrapingBee and Honeygain, an app that pays people to share idle bandwidth. Each keeps its brand, product and customers; Source runs patents, procurement, hiring and compliance, and says one set of standards holds across all five.
💡 When two shortlisted providers sit in one ecosystem, count them as one supplier for failover, sourcing and refusal policy, and read a benchmark one publishes of the other as in-house.
How the group was assembled. Oxylabs came out of the Tesonet accelerator in 2015; Tesonet also founded or backed Smartproxy in 2018, renamed Decodo in 2025. Oxylabs announced the purchase of Webshare in September 2022 and of ScrapingBee in June 2025 (undisclosed eight-figure sum), keeping both brands. Since a March 2021 exclusive contract, Honeygain has been Oxylabs’ main residential source. In July 2026 Warburg Pincus invested $130M at a $3.6B valuation, the group’s first outside money. Source claims over 800 staff, 350,000 customers and about 180 patents.

Why it matters to a buyer. The guide already warns that pools are resold under several brand names; this is the formal version. Source says its standards cover use-case vetting and compliance across the ecosystem, which fits a result the guide already carries: in a September 2026 benchmark published on ScrapingBee’s blog, Oxylabs and Decodo each refused 22 of 100 general-web requests, the same eleven domains both rounds. That reads as one policy rather than two, though neither vendor has said so. Buy two of these brands for redundancy and you may get correlated pools, refusals and outages.

The litigation that comes with it. Bright Data, then Luminati, sued the Oxylabs entities (Teso LT, Oxysales, Metacluster) over residential-proxy patents; a Texas jury found wilful infringement in November 2021, reported at $7.5M. The USPTO then invalidated claims in two of the patents, the Federal Circuit affirmed in August 2025 and the Supreme Court declined review in February 2026; Oxylabs says the verdict cannot stand, and its own patent suit against Bright Data is pending. Oxylabs is also a defendant, with SerpApi, AWMProxy and Perplexity, in Reddit’s October 2025 scraping suit in New York.

What this does not show. The announcement is the group’s own; it names no legal parent and no ownership chain for Decodo or Honeygain, and “independent companies” is its own description. Honeygain’s consent model is a vendor claim, and part of the patent history comes from Oxylabs’ legal timeline. Nothing here says the products are worse: treat it as a concentration fact to check before you count suppliers.
+
Oct 2026 / Tooling
REA Under the Hood: About 180 MCP Tools Over Ghidra, JADX, adb and Frida That Map a Client but Discard the Cookies, Auth Headers and Page Text You Would Replay (Source Read, One Offline Run)
morluto/rea 6.4.0 is less one engine than about 180 MCP tools over other people’s: Ghidra, Hopper or IDA for native code, headless JADX, adb, Apktool and Frida for Android, Wakaru for minified bundles, a passive CDP page capture and HAR readers. Read from source and run once on a test HAR, it maps endpoints and code paths but keeps no cookies, auth headers or page text.
💡 Treat REA as a map-maker that shortens the read-the-app phase, not a scraper: it sees only what ships to the client, drops the values you would replay, and its disclaimer refuses bot-protection bypass on systems you may not test.
What is under the hood, checked 12 Oct 2026. MIT, created 14 Apr 2026 as a Hopper MCP called betterBinaryMCP and rewritten in TypeScript in July; 2,501 commits from about 115 contributors, 311 of them carrying a Claude co-author trailer, as one commenter noted. Releases 6.0.0 to 6.4.0 shipped between 8 and 11 Oct. It needs Node 22.19+ and brings no engine for the heavy formats: you supply Ghidra (run headless in a throwaway project), JADX through jadx-headless-mcp 0.7.1 on JDK 17+, adb, Apktool, the optional Frida binding, Wakaru (Linux x64 only), mitmdump 12.2.3 (Linux only) and Binwalk or Unblob for firmware. JavaScript, Electron ASAR, source maps, .NET, Go and WASM are parsed in-process.

What it gives a scraping team. For an APK: manifest, class search, decompiled methods and cross-references, then adb for logcat, files and app launch, and Frida for hooks you write. For a site: attach to a Chrome you started on a loopback port, list scripts, network metadata and WebSocket frame sizes, derive route and endpoint candidates from bundles, fetch source maps only when asked and without cookies, and list a page’s WebMCP tool declarations. It cannot read backend code, answering one commenter: it sees only what ships to the client.

What it deliberately throws away. The CDP capture never retains cookies or authorization headers, keeps DOM node names but not text, and reduces JSON bodies and WebSocket frames to value-free shapes. Run once here (npm 6.4.0, install scripts off) on a four-request synthetic HAR, the cookie and bearer values were absent from the output while a custom x-signature value appeared four times: only known auth fields are redacted. The HAR endpoint-template view in its docs was rejected by the released CLI and is absent from the npm build; it lives on main. Every tool carries effect flags (network, foreign code, device changes), and SECURITY.md says plainly that REA is not a sandbox.

Honest caveats. Source, docs and one offline run; no APK, browser or native target was tested and analysis quality was not measured. No analytics SDK appears among its dependencies, but your agent’s model provider sees every result. The post’s 67,000+ stars were 136,505 two days later; the repo’s milestone commits run from 20,000 on 8 Oct to 110,000 on 12 Oct, npm downloads rose in step to 59,665 on 11 Oct, and the stargazer list is not enumerable, so the curve can be described but not audited.

Oct 2026 update. On 15 Oct the repo showed 260,901 stars and 65,628 forks, so a later “250k+” claim is accurate, but the count nearly doubled from 136,505 in three days; its own commits mark 170,000 (13 Oct), 200,000 (14 Oct) and 250,000 (15 Oct), and 6.5.0 shipped on 14 Oct. Still unauditable. A practical tip from one user: keep the original HAR and the trace of where a signature is built beside the scraper, and diff a fresh capture when signing changes.
+
Oct 2026 / Deobfuscation
BotGuard Keeps a Function in Its Instruction Pointer, So a “Cleaned-Up” Interpreter Reads AA BB Where Google’s Reads AA AA and Ends at the Same Position (n=1, Semantics Reproduced in Node 26)
A researcher reversing Google’s BotGuard with AI help described a trap in its VM: a function sits in the instruction-pointer register. A shift to find the byte turns it into NaN, then 0; += 8 makes a string, which shifts to 0 again. Reproduced in Node 26: bytes AA BB read as AA AA. Tidy the function to 0 up front and you read AA BB.
💡 A rebuilt interpreter is faithful only if it reads the same bytes in the same order as the original; an end-position check passes while you decode a different instruction.
The coercion, checked against the spec. A shift runs ToNumeric on its operand; for an object that is ToPrimitive with hint number, whose ordinary path tries valueOf (which returns the function itself) and then toString, which returns the function’s source text. StringToNumber of source text is NaN, and ToInt32(NaN) is +0, so f >> 3 is 0. For +, ToPrimitive yields the same string and a String operand means concatenation, so the register becomes the source text plus "8", then "88", still 0 under a shift. In Node v26.5.0 a function-valued register read [0xAA, 0xBB] as aa aa; a numeric 0 read aa bb. A function given its own valueOf returning 16 shifted to 2, so a rebuild must keep the coercion, not hard-code zero.

Why the divergence hides. Per the account, a later write converts the original function to 0, adds the bits consumed and stores a number again, so both versions finish at the same position. A model or person “cleaning up JS quirks” makes a reasonable-looking change and decodes a different instruction with full confidence. It is the guide’s agentic-RE warning about runtime-bound semantics in concrete form, built from ordinary language rules rather than an anti-AI trick, and the counter is differential testing of every read against the original, not of end state.

Context and prior work. Mike Hearn, who wrote BotGuard, said on Hacker News (April 2025) that the VM exists mainly to make random encrypted programs cheap to generate, so adversaries cannot write a static analysis and must run them blind while detections feed delayed bans. The author says the environment contributes to keys that decrypt later stages, names variants for Gemini, accounts, YouTube, AdSense and Search, and says the reCAPTCHA variant, a near proof-of-work token, was removed this year. dsekz/botguard-reverse (September 2025, 128 stars, reCAPTCHA variant) documents timing anti-debug and bit-addressed bytecode. For Google data, official APIs remain the cheaper path.

The caveat. One practitioner’s account; no build, bytecode or code was published, and the trick was not observed here in a captured script, only its JavaScript semantics reproduced. “A few days with AI” is self-reported. The variant list and reCAPTCHA removal are unverified, as is a commenter’s view that BotGuard tokens are not cryptographically bound to the browser or hardware.
+
Oct 2026 / Measurement
Six MCP Servers’ 95 Tools Cost 15,950 Tokens to Declare; One Wikipedia Page Snapshot Cost 31,565 (One Run, One Tokenizer)
A test connected to six keyless MCP servers on 12 October 2026 and counted their tool definitions with OpenAI’s o200k_base tokenizer: 95 tools, 15,950 tokens, Playwright MCP’s 25 tools at 4,450. Then it loaded pages. One Playwright snapshot of Wikipedia’s “Web scraping” article took 31,565 tokens, twice the whole menu.
💡 Count what the tools return before trimming what they declare. Keep page snapshots on disk and pass a path: Playwright MCP now does this, and its navigate reply measured 78–177 tokens.
What was measured. Doccraft (Satyam Tripathi, who writes research for web-data companies) tested a claim from Apify CEO Jan Čurn’s Prague Crawl 2026 talk, “MCP doesn’t suck — your agent does”: the protocol does not fill the context, the client does. The script connects over stdio as an agent would and tokenises each tool’s name, description and input schema as JSON. Chrome DevTools MCP 1.10.1: 30 tools, 6,527 tokens. Playwright MCP 0.0.83: 25, 4,450. Filesystem 14, 1,957; Git 12, 1,426; the Everything reference server 13, 1,305; Fetch 1, 285. The published per-tool JSON sums to exactly those totals, rechecked here.

Results outweigh the menu. Three pages, two tools (Playwright snapshot / Fetch whole page / Fetch default reply): Wikipedia 31,565 / 11,468 / 1,067; Hacker News 13,579 / 3,494 / 1,848; an Allbirds product page 10,824 / 1,217 / 1,217. Playwright MCP’s browser_navigate now writes the snapshot to a .yml file and replies with a link: 82, 78 and 177 tokens. Fetch truncates at 5,000 characters unless asked for more. Apify’s mcpc 0.7.0 (Apache-2.0, about 1,000 stars) turns servers into shell commands with a cross-server grep: one search plus one tool’s full schema cost 454 tokens, against 10,977 for both browser servers’ 55 tools.

Against this guide’s figures. The agentic section quotes roughly 13,700 tokens for Playwright MCP’s definitions and 17,000 for Chrome DevTools MCP; this count gives 4,450 and 6,527 for current releases. Versions, tokenizer and scope (bare schemas, no client wrapper) all differ, so read it as menus that shrank or were counted differently, not as an error in either. It backs the Code Mode card’s point from the other side: for a scraping agent the payload, not the menu, is the bill. Anthropic’s 55K-token five-server example and its 85% Tool Search saving, both cited, are calculated illustrations, not runs.

Limits. One run and one tokenizer; Claude and other models count differently. Only servers that need no key were measured, so Apify’s, GitHub’s and every commercial scraping server are absent. Three pages is not a distribution, and snapshot size tracks page length. The talk was Apify’s and the author sells research to web-data companies; the raw JSON and scripts are published, which is what lets the totals be checked.
+
Oct 2026 / Fingerprinting
Enabling CDP’s Debugger Domain Turns an Empty Promise.all Into a TypeError — Reproduced on V8 14.6, but Stock Playwright and Puppeteer Never Enable It
A detection researcher showed a CDP tell that needs no console probe: call Promise.all on an empty array through a custom constructor whose reject callback is a Proxy. Normally it resolves. With the Debugger domain on, V8 writes a private symbol onto that callback, the Proxy refuses it, and the page sees a TypeError. Reproduced here in Node 26.
💡 The tell is the Debugger domain, not CDP as such: Runtime.enable alone stays clean, so it catches inspectors, coverage runs and agent tools, not a stock Playwright crawl
The mechanism, checked in source. In V8’s src/builtins/promise-all.tq, the combinator runs if (IsDebugActive()) and then SetPropertyStrict(context, reject, kPromiseForwardingHandlerSymbol, True), a catch-prediction hint for the debugger. That key is a private symbol, and V8 refuses private-symbol writes to a Proxy before any trap runs. Promise.all catches the throw and passes the error to the page’s own reject callback, which is the Proxy. The constructor is page code, so the page sees both. A second debug-only write (kPromiseHandledBySymbol on each then result) sits a few lines later.

Reproduced here, in-process. Node v26.5.0 (V8 14.6.202.34) driving its own inspector session: a plain run returned resolved; after Runtime.enable alone, still resolved; after Debugger.enable, TypeError: Cannot pass private property name to proxy trap; after Debugger.disable, resolved again. The signal follows the Debugger domain and is reversible. It was not run in a Chrome build; V8 is shared, so it should carry over, but that is untested.

Who actually turns Debugger on. Playwright’s crPage.ts and Puppeteer’s Page.ts never send Debugger.enable; both send it only when JS coverage starts (crCoverage.ts, Coverage.ts). The open-source REA reverse-engineering server sends it during page capture. So the probe mostly separates instrumented sessions, and probably developers with DevTools open (not tested), from ordinary automation. For a defender that means a noisy, low-prevalence signal; for a crawler owner, a reason to keep coverage and inspection tools off production targets.

The honest caveat. One researcher’s post with a code image and no repo; he says he has moved to the evasion side, and his company sells web-access infrastructure. The wider claim, that many outcomes besides console output change under CDP, is plausible but only this vector is shown. The single comment, from a proxy vendor’s CEO, adds only that patching CDP leaks never ends. One V8 change would close it, as the stack-getter tell closed in Chrome 138.
+
Oct 2026 / Method
DeepL’s Pricing Page Showed 4 Prices; Its Next.js App Router Payload Held 23 in 4 Currencies (Rechecked, One Page)
Of 390 top homepages with readable content, one run found JSON-LD on 215, the Next.js App Router stream self.__next_f.push on 33, __NEXT_DATA__ on 29, Nuxt on 13 and Redux-style state on 9. On DeepL’s pricing page the App Router payload held 23 price entries in four currencies while the page showed four, in euros. Rechecked here: 23 again.
💡 App Router sites no longer ship one __NEXT_DATA__ tag. Join the self.__next_f chunks and regex known field names, because the format is internal to React and a parser library failed on both pages tried.
The patterns, counted. Doccraft (Satyam Tripathi, 3 October 2026, httpx 0.28 from Mumbai) reused its Tranco top-1,000 run. Of 390 homepages with readable content, 215 carried JSON-LD, 33 the App Router stream, 29 the Pages Router __NEXT_DATA__ tag, 13 Nuxt and 9 window.__PRELOADED_STATE__ or __INITIAL_STATE__; 62 (16%) were Next.js in all. The method is the one this guide already teaches: pick a visible value, search view-source for it, and go to the Network tab when it is absent. Nuxt 3 moved page data out of window.__NUXT__ into a __NUXT_DATA__ tag holding a flat array whose numbers index other entries, so objects are rebuilt by following indexes.

The DeepL recheck. The script joins the self.__next_f.push([1,"..."]) chunks from deepl.com/en/pro, decodes each as a JSON string and regexes packageId and basePrice pairs: 33 chunks, 23 price entries in CAD, EUR, JPY and USD. Rerun here on 12 October from another network: 34 chunks, 246,266 characters, 23 entries in the same four currencies. The njsparser library failed on both App Router pages the author tried, and React treats the format as internal, so expect breakage.

The _next/data shortcut. Pages Router sites may serve /_next/data/<buildId>/<page>.json, the build ID read from __NEXT_DATA__. TED’s homepage data came back at 288,751 bytes against 589,257 for the HTML; rechecked here, 280,807 against 584,748, status 200. It is not general: of the 29 Pages Router homepages, 26 still had the tag and 9 answered the route, and the ID changes per deploy, so re-read it on a 404. Genius keeps state in JSON.parse('...'), escaped twice and normalised by ID; chompjs handles object literals that are not valid JSON.

Caveats. Homepages only, one run, one location, from a firm that writes for web-data companies. Prices and stock often arrive later from an API, so absence in the source proves nothing. Paths such as sailthruDataContent are internal names that can change in any release. The extra data is the hazard as well as the prize: payloads can carry internal IDs and personal data the page never shows, so take the public fields you need and drop the rest.
+
Oct 2026 / Measurement
On a Seconds-Apart Re-Crawl, 41% of 368 Top Homepages Answered 304 and 49% Sent No Validator; Google Puts Its Own Cacheable Share at 0.017%
Send back the ETag and Last-Modified you stored and an unchanged page can answer 304 with no body. A test of 368 top homepages and 224 inner pages found 41% and 40% returned a 304, each skipping a median ~45 KB, while 49% sent no validator at all. Google says only 0.017% of its own fetches are cacheable.
💡 Store both validators per URL and send both: 15 homepages ignored If-None-Match but honoured If-Modified-Since. Where none are sent, hash the extracted text, not the HTML, before reprocessing.
The test. Doccraft (Satyam Tripathi, 8 October 2026, httpx 0.28, Mumbai) took the homepages that returned content in its Tranco top-1,000 run plus the first URL in each sitemap, sent two plain requests a second apart, then one with If-None-Match and one with If-Modified-Since. Homepages / inner pages with a 200: 368 / 224. ETag 125 (34%) / 74; Last-Modified 123 / 71; neither 181 (49%) / 110 (49%); a 304 to either conditional request 151 (41%) / 90 (40%). Median full responses on the 304 pages were 44,582 and 45,219 bytes, and every 304 body was empty. The author’s analysis script, run here on the published JSON, reproduces each figure.

Where validators lie. 17 homepages and 6 inner pages changed their ETag between two requests a second apart, so it can never match. 38 and 21 sent an ETag but answered If-None-Match with a full 200 (23 and 18 once the rotating ones are removed), and 15 and 5 of those gave a 304 on If-Modified-Since. 13 and 5 stamped Last-Modified with the request time. And 3 homepages and 2 inner pages returned a 304 although their visible text had changed between plain requests, so a crawler that trusts every 304 keeps a stale copy. Scrapy has this built in: HTTPCACHE_ENABLED with RFC2616Policy revalidates and counts httpcache/revalidate on a 304.

Google’s number is not a contradiction. Google’s crawler docs say it supports both validators and uses the ETag when both are present; a 9 December 2024 Search Central post says cacheable fetches fell from about 0.026% a decade earlier to 0.017%. That counts all of Google’s fetches over real recrawl gaps, when pages have changed, while this test re-asked within seconds. Together they bound the method: a large saving where sites support it, nothing where they do not. Where nothing is sent, hash the main text: 28 homepages changed visible text between the two plain requests, but 150 changed their raw HTML.

Caveats. Seconds-apart requests overstate the 304 rate you will see at daily or weekly intervals, and a 200 after a real change is correct behaviour. One inner page per site, the first in its sitemap, from one location and one client; CDNs and anti-bot layers may answer browsers or proxies differently. The author reports three full runs differing by one point at most. The saving that matters is bandwidth on per-GB proxies, plus the parsing and embedding a 304 lets you skip.
+
Oct 2026 / Data quality
Common Crawl’s WET Text Kept Half the Usable Words of trafilatura or Resiliparse on 664 English Pages; Resiliparse Ran 17× Faster (One WARC File)
Hugging Face’s FineWeb team extracted Common Crawl text with trafilatura rather than use the ready-made WET files. A retest on 664 English pages from one September 2026 WARC file, scored by FineWeb’s own quality filter, agrees: WET passed 68 pages and 85,002 words, trafilatura 336 and 170,495, Resiliparse 224 and 170,387 in a seventeenth of the time.
💡 If you build from Common Crawl, extract from the WARC HTML, not WET. Use Resiliparse for bulk and trafilatura where per-page yield matters, and keep the HTML so you can switch later.
The claim and its primaries. At Prague Crawl 2026, Hugging Face developer advocate Alejandro AO walked through how FineWeb was built. The FineWeb paper (arXiv 2406.17557) is explicit: WET files “retained too much boilerplate and menu text”, and an ablation trained on trafilatura output from WARC beat one trained on WET. DataComp-LM (arXiv 2406.11794) compared the same three options under RefinedWeb filters: both extractors improved Core by at least 2.5 points over WET, had “similar downstream performance”, and Resiliparse was 8× faster (4.55 against 0.56 MB of WARC per second per core), so DCLM used it.

The retest, recomputed. Doccraft (Satyam Tripathi, 8 October 2026) took the first 2,000 HTML 200s from one CC-MAIN-2026-39 WARC; 1,972 had WET text, and 664 were tagged English first. Each got WET, trafilatura 2.3.1 with FineWeb’s favor_precision settings and Resiliparse 1.0.9 main_content, all run through datatrove’s FineWebQualityFilter. Passes: 68 (10%), 336 (51%), 224 (34%). Words on passing pages: 85,002, 170,495, 170,387. Extraction: 30.0 s against 1.8 s on an M3. The published JSON reproduces all of it. WET failed mostly on line_punct_ratio (431 pages), which is what menus look like.

Where each breaks. trafilatura returned nothing on 86 English pages, mostly grids, logins and spam, but about 8 were real posts, 6 on Blogger; default settings recovered 6 of the 8. It sometimes kept a newsletter sidebar. Resiliparse keeps titles, dates and bylines and often repeats the title, which can trip FineWeb’s 1% repeated-line rule on its own. One scrambled-sentence spam page passed in its Resiliparse version: a heuristic filter is not a spam detector. This sits beside the ReScraper card, where four heuristic extractors landed within 0.003 Core of each other and a model reading the whole page beat all four.

Caveats. One WARC file, its first 2,000 records rather than a random sample, English only by Common Crawl’s tags. The filter was designed on trafilatura output, so trafilatura’s higher pass rate may partly be home advantage, and passing a filter measures yield, not model quality; DCLM’s training runs are the stronger evidence. The author writes for web-data companies; scripts and per-page data are published.
+
Oct 2026 / Benchmark
jsdom Built 3 of 26 Real JavaScript Homepages and Lightpanda 12, Though jsdom Used About Half Chromium’s Memory on the Demo Page (One Run Per Site)
An OxyCon 2026 ScrapingBee talk pitched jsdom “hydration” at under half a browser’s memory. A retest confirmed it on the demo page, 252 MB against 468 MB, with Lightpanda at 35 MB. On 26 real JavaScript homepages, jsdom got at least half of Chromium’s text on 3, happy-dom on 1, Lightpanda on 12. jsdom also returned the demo quotes in reverse order.
💡 A light engine that runs a demo page tells you nothing about your targets. Diff its text and item order against real Chromium per site, and keep the browser for the pages that need it.
The demo, confirmed. ScrapingBee’s OxyCon 2026 talk, “Web Hydration: Loading Dynamic Content Without a Browser”, argued that hydration needs a JavaScript engine and a DOM, not a renderer, and reported 207 MB for jsdom against 470 MB for a browser on quotes.toscrape.com/js/, while saying plainly the gain is memory, not speed. Doccraft (Satyam Tripathi, 6 October 2026, Apple M3, five runs each) got medians of 2.92 s and 252 MB for jsdom 29, 2.88 s and 468 MB for headless Chromium, and 2.66 s and 35 MB for Lightpanda 1.0.0, released 2 October under AGPL-3.0. All three found all 10 quotes.

The silent difference. jsdom’s order was exactly the reverse of Chromium’s. The page writes its quotes with document.write(), which jsdom handles differently from a real browser: complete data, no error, wrong order. Lightpanda matched the browser. Wherever position is data, such as rankings or search results, that is a corrupted field that no memory benchmark shows and no exception flags.

Real sites. The 29 homepages that needed JavaScript in the 1,000-site test (this guide’s 5% card) were each loaded once from Mumbai on 3 October; Chromium got content on 26, and success meant at least half its word count. jsdom 29.1: 3 (example.com, onet.pl, playrix.com). happy-dom 20.14: 1 (Springer). Lightpanda: 12, including Spotify, Roblox, Steam Community, Intel and Font Awesome, at a median 96 MB peak across all 29. Recounted from the published JSON, every figure matches; 12 of the 26 worked only in the full browser. Disqus gave Lightpanda 555 words and Chromium none, a reminder that the baseline is itself one load.

Caveats. One run per site and one location. Chromium waited 5 s after load with a bot user agent; Lightpanda ran fetch --dump html with its defaults and no extra wait, so some misses may be timing or blocking rather than engine gaps. Half of Chromium’s word count is a coarse bar. The post says 26 sites in one place and 29 in another: 29 tried, 26 scoreable. Hydration saves memory only on pages you can reach; the talk itself noted a non-browser TLS handshake can be blocked before any script runs.

Oct 2026 update. Lightpanda’s 12 was its default load wait. Doccraft retested Lightpanda 1.0.1 on 28 of the same sites, twice: 12 on the default, 21 and 22 with --wait-until networkidle, about twice Chromium’s speed at a quarter to a third of its memory, and has added a correction to the original post. jsdom and happy-dom were not retested. See the Lightpanda 1.0.1 card.
+
Oct 2026 / Measurement
71 European Shops, Three Bins: Italian Retail Served Prices in Plain HTML, Most UK, French and German Majors Refused Even robots.txt (One Snapshot, No Data Released)
Before writing a scraper, a practitioner sorted 71 e-commerce sites in Italy, Cyprus, the UK, France and Germany into three bins: wget (price already in the HTML), browser (hydrated or challenged) and blocked (403 even on robots.txt). Italy came out largely open; Argos, Currys, Fnac, MediaMarkt and idealo mostly refused. Per-site counts were not published.
💡 Classify each site before you build: robots.txt status, a raw-HTML price check, then the sitemap as your seed. A re-check of the 18 named shops held 17 labels, but vantage point flipped one.
The method. Roger Giuffrè calls it deliberately dumb: read robots.txt, fetch the HTML, check whether product prices sit in the source. Three verdicts follow. wget: server-rendered, plain HTTP is enough. browser: content hydrated by JavaScript or behind a challenge, an order of magnitude dearer per page. blocked: a 403 even on robots.txt, or no answer, which he reads as an opt-out, not a puzzle. Named open sites include Unieuro, Euronics, IBS, laFeltrinelli, Tannico, Farmaè and Kasanova in Italy and Alphamega in Cyprus; in the north only Thomann, Otto and Rueducommerce. He frames robots.txt pointing to a sitemap with last-modified dates as a cleaner and more honest seed than crawling navigation.

What a re-check found. No per-site table or code is released, so the 18 named sites were re-fetched here once with curl and a Chrome user agent on 12 October 2026, from Pakistan. All seven Italian shops, Thomann, Otto and Rueducommerce returned 200 on robots.txt and homepage with currency markers in the raw HTML. Argos, Fnac, Darty and idealo (Akamai) and Currys and MediaMarkt (Cloudflare) returned 403 on robots.txt itself; ASOS did not answer within 15 seconds. That is 17 of 18 labels held. Alphamega did not: Cloudflare sent a 403 to robots.txt and homepage alike.

Where it sits in this guide. The 544-homepage test found only 5% needed JavaScript and 14% blocked a plain request but admitted a browser, so “blocked to curl” and “blocked” are different verdicts; Bamberg showed how much of a browser block is one header. The only comment adds two refinements: many Cloudflare sites clear after one real browser visit whose session can then be reused over plain HTTP, and product pages are often server-rendered while category and search pages hydrate, so classify per page type, not per domain.

Honest limits. One practitioner’s snapshot with no date, location, tool versions or per-site results, and the 71 are not listed, so the north–south split is an impression, not a measured rate. Our check is one run from one non-European vantage with curl’s own TLS under a Chrome string, the mismatch this guide warns about; the Alphamega 403 may be geography. A 403 on robots.txt is a firewall rule, not a stated policy. He invites corrections and plans a per-country map.
+
Oct 2026 / Method
100,000 Google Maps Listings in Under 48 Hours by Tiling Each City Into Cells: The ~120-Result Cap Is Per Query, Not Per Area (n=1, No Code)
Asked for every restaurant in three cities, about 100,000 listings in a week, a freelancer says a browser could not make it: a Maps search stops near 120 results and detail pages took 3–5 seconds each. He called Maps’ structured endpoints over HTTP, split each city into a bounding-box grid, ran the cells in parallel and skipped email lookups. Delivered in under 48 hours, he says.
💡 A result cap limits each query, not the area. Tile the area, query each cell, split any cell that comes back full, and dedupe on the place ID; the official Places API caps at 60, so the same tiling applies there.
What was reported. Hafiz Muhammad Salman, who works for a scraping service provider, described a job for every restaurant in three cities, about 100,000 listings, due in a week. The browser route (Selenium, scroll, wait, repeat) failed on two numbers: one Maps search caps at about 120 results, and each detail page took 3–5 seconds in a browser, which he put at weeks for 100,000. His fixes: request Maps’ structured endpoints directly over HTTP; split each city into a bounding-box grid so each cell returns its own result set; run the cells concurrently so throughput scales with workers; and skip visiting each business website for emails, which he calls the biggest slowdown in Maps pipelines and which the client did not need. Result: about 100,000 listings in under 48 hours.

Why the grid is the transferable part. The endpoint step is the guide’s API-first recipe, which already carries a Maps report. The tiling is new here and applies to any search that truncates per query: store finders, job boards, property maps. Google’s own Places API Text Search (New) returns at most 60 results across all pages, 20 per page, and accepts a rectangular locationRestriction, so tiling is also how you cover a city through the sanctioned API. This card’s extension, not his: treat a cell that returns the full cap as truncated and split it again, size cells by density rather than area, and dedupe on the place ID, since a listing near a border appears in several cells.

The arithmetic. At 100,000 listings and 3–5 seconds each, one browser needs 83 to 139 hours for details alone, so the case for skipping the browser holds even before search. Parallel workers on an internal endpoint also multiply request rate from your IPs, and the post names neither the rate, the proxy setup nor any blocks.

Honest limits. One practitioner, one job, no code, no endpoint named, no cell count, duplicate rate, field list or completeness check against a ground truth, and no comments to test it against. Scraping Google Maps is against Google’s terms, and the Places API terms limit caching and storage. Business names and phone numbers can be personal data under GDPR, so check the basis before you build a lead list.
+
Oct 2026 / Economics
One SERP Vendor Says Stable Google Results Cost It 3–4× More, and Its Price Page Now Lists 7 Credits a Search (Vendor Account, No Raw Costs)
ScrapeBadger’s co-founder says its Google SERP API is finally stable at 95%+ success, and that this multiplied its Google infrastructure bill by 3–4×. Until new prices on 15 October it ran at a loss, lost big clients and saw its first MRR drop. A customer called the price “almost 3x higher”. On 12 October its page lists 7 credits per search.
💡 If your product depends on a Google feed, the price of a working search follows Google’s defences, not the vendor’s price list. Budget for a step change and keep a second source you have tested.
What was said. On 6 October 2026 Dom Sakavickas, co-founder of the scraping API ScrapeBadger, wrote that its Google scraping had held 95%+ success for weeks with usage growing, but that infrastructure cost for Google had risen 3–4×, so until new credit prices on 15 October it was working at a loss. Big clients left over price and monthly recurring revenue fell for the first time. An attached customer email, dated 1 October, says results quality was good but the price was “almost 3x higher” a few weeks after sign-up, and that the sender, who runs residential-proxy scrapers too, could not keep it for launch.

Where the money goes, from the thread. The post does not itemise the bill. The comments point at bandwidth: one reader said it “could be done much cheaper” on proxies without bandwidth metering, and the founder replied that ScrapeBadger had been the cheapest on the market and unprofitable on Google even before the changes, is still below the market average, and asked for proxies that “hold up on Google without bandwidth metering.” That fits what this guide has measured elsewhere: a working Google request now needs a real browser, a warmed residential exit and extra /goto requests, all billed per GB. A product lead added the defender’s view: raising the cost of success is the point of a deterrent. A question on the price of 1M ten-page SERPs went unanswered.

The price, checked 12 October. ScrapeBadger’s Google SERP page lists Web Search at 7 credits per request, plus one credit when a deferred AI Overview is fetched; pay-as-you-go credits are $0.15 per 1,000, so $1.05 per 1,000 searches before subscription discounts of up to 64%. Third-party comparisons still quote 2 credits, or 3 plus 0.1 per result; their dates are unclear, so the before-and-after ratio is not confirmed here. Failed requests are not charged.

Honest limits. This is one vendor describing its own costs, with no absolute figures, no proxy type or per-GB rate, and no definition of “success”. The 3–4× is infrastructure, the customer’s 3× is price, and neither can be checked. A side comment that Reddit is shutting RSS feeds and its public API was not verified. Read it as one data point on direction: stable Google access got more expensive this autumn, and a cheap SERP API is a price that can move.

Oct 2026 update. ProxyScrape’s founder, a proxy seller, offers a hypothesis: Google fears blocking real users, or showing them fake data, more than it fears scrapers, so a client that does everything right is not blocked, it just pays more. A web-collections manager at Recorded Future added that Google’s SEO bargain with sites makes confident blocking hard, so it slows traffic at scale and fingerprints search terms; another reader warned that some sites now serve spoofed data to valid-looking requests. Unmeasured, but it fits the pattern above.
+
Oct 2026 / Benchmark
A SERP Vendor Reports 2.7M Google Searches at 300 a Second, 94.5% on the First Try: Ask Where the Other 5.5% Fall (Vendor-Run, One Day)
ProxyScrape’s CIO says its SERP API ran 300 Google searches a second for 2.5 hours: 2.7M searches, all returning results in the end, 94.5% on the first attempt (95.5% a week earlier, 99.9% with retries). New Google Shopping product and seller lookups scored 99.96%. The arithmetic adds up; nothing else is published: no query list, no failure breakdown, no latency distribution.
💡 When a SERP API bills only for results, a failed first try costs you latency, not money. Ask for the first-attempt rate and how its failures cluster, by query type or in time.
What was claimed. On 5 October 2026 Thibeau Maerevoet, CIO and founder of the proxy seller ProxyScrape, reported a run of its Google SERP API at 300 searches per second for two and a half hours: 2.7 million searches (300 × 9,000 seconds, so the count is consistent), “every single one returned results”, and 94.5% on the first attempt. A week earlier it had posted 95.5% first-attempt and 99.9% with retries. Google Shopping, both product search and the seller pages for each product, ran “at the same scale” at 99.96%. No version ships until it passes an hour at 200 searches a second. Billing is per result: a search that returns nothing costs nothing, and the raw Google pages behind an answer are available.

The playground screenshot. A live “Product & sellers” call for one catalog ID returned HTTP 200 in 8.22 seconds and 11.8 KB, with a google.com/search?ibp=oshop&q=*&prds=catalogid:… URL behind it. The UI notes that Google lists sellers about five at a time, like its “More stores” button, and exposes seller_pages from 0 to 20. The sample product is a LEGO set at a typical price of $300.

The question worth asking, from the thread. One commenter said the number to publish next is the shape of the missing 5.5%: if failures cluster by query type, retries are cheap; if they cluster in time, something upstream is throttling, and a 2.5-hour average hides both. The author agreed to look. Pair it with the guide’s method for reading success rates: who defined “returned results”, who chose the queries, at what rate, and does it hold per query class. Per-result billing also depends on the vendor telling a genuine empty SERP from a refusal, which this guide’s warm-up cards show is easy to get wrong.

Honest limits. Vendor-run, one day, self-reported, with no harness or data. ProxyScrape’s own site did not resolve from where this was checked, so the product page was not read. Its Apify listing, a separate channel, prices organic Google SERPs at $0.40 per 1,000 requests, charges only successful responses, and shows 13 total users. 99.96% for Shopping has no sample size, and 8.22 seconds is one call, not a latency figure.
+
Oct 2026 / Architecture
Diffbot’s Growth Lead Runs a 2.3 TB Slice of Its Web Index Offline: Search Without Scraping Google, Fresh Only to the Last Shard (Vendor Demo)
Jerome Choo, growth at Diffbot, downloaded 2.3 TB of Diffbot’s web index to an Nvidia DGX Spark and points a local model at it over MCP: web search with no internet. His screenshot shows build v20261001 split into 44 shards, ten-day crawl windows plus Wikipedia and StackOverflow. Diffbot’s docs confirm self-hosting through sales, an 8 TB target index and English only.
💡 An owned index removes the SERP scrape, the per-search fee and the network from agent grounding. What you give up is freshness and coverage: results are as current as the last shard, and closed platforms are left out.
What was shown. On 6 October 2026 Jerome Choo, who works in growth at Diffbot, posted day 3 of building his own Google replacement: 2.3 TB of Diffbot’s index on a DGX Spark, which he says covers temporal crawls back eight months, Wikipedia, StackOverflow and the largest sites. He puts the whole web at about 150 TB, his figure. A local coding agent connects to the search over MCP, with no ads, telemetry or internet. The screenshot is a shard picker for build v20261001-searchlite: 44 shards, a 91 GB frequently-changing set (company reports, site crawls), a 189 GB stable set (Wikipedia, StackOverflow), and temporal shards of roughly ten days each from October 2024. He picked 22: the two base sets, every window from 13 February to 24 September 2026, mostly 75–132 GB each, and a 132 GB oldest-and-newest set, about 2.6 TB with 293 GB still downloading.

What Diffbot documents. Its Web Search page calls the product “web search in a box”: index, retrieval and reranking on one machine or cluster, about 300 ms latency target, full page chunks stored so no fetch is needed, runs offline with zero telemetry. The target production index is 8 TB, said to cover “99% of daily driving search requirements”, on about 10 TB of NVMe, 64 GB RAM (index size over ten), a 5 GB GPU and 32 cores. The index is English only, and Facebook, Reddit, LinkedIn and Instagram are “generally off limits”. Self-hosting is “reach out to us”; no public price or download. The hosted API gives 100,000 free queries a month at 60 per minute, paid down to $0.02 per 1,000.

Why it matters for scraping. Most agent search today is a SERP scrape behind an API, and this autumn’s cards show that path getting dearer: warm-up, /goto, vendor price rises. A local index needs no Google request, proxy or per-search fee; the cost moves to disk and to staying current. A slice ending at 24 September cannot answer about 6 October, and an index that skips closed platforms answers nothing about them.

Honest limits. A vendor employee demonstrating his employer’s product, with no retrieval-quality figures for the slice against the full index or Google. Whether individuals can download shards for free is not documented; neither are the “searchlite” build or the model he names.
+
Oct 2026 / Reverse engineering
A 132,546-Star Reverse-Engineering MCP Rebuilt Apple’s Homepage From What the Browser Exposes, and Nothing Behind It (One Tester)
morluto/rea (MIT, created April 2026, 132,546 stars) registers an MCP server that hands a coding agent Ghidra, Hopper or IDA, JADX, HAR and mitmproxy readers, and a CDP page capture. One developer used it to rebuild Apple’s US homepage and a chatbot’s interface. His own verdict: it sees what reaches the browser, never backend logic, prompts or data.
💡 For scrapers the useful part is not cloning but the offline HAR view that folds captured requests into templated endpoints; it reads saved traffic and fetches nothing
What it is, checked 12 Oct 2026. The repo was created on 14 Apr 2026, last pushed on 12 Oct, is MIT-licensed and not archived, and shows 132,546 stars, 29,631 forks and about 2,490 commits, 1,295 of them by its owner. npx rea-agents setup registers it with Claude Code, Codex, Cursor, Gemini CLI and others. Native binaries need Hopper, Ghidra or IDA; JavaScript and Electron analysis recovers modules, source maps and routes; Android goes through JADX and adb; .NET and firmware are covered too.

The web side. It attaches to a Chrome you start on a loopback debugging port and, per its docs, never navigates, clicks or evaluates page code. It captures DOM, scripts, resources, network and console metadata and WebSocket frame sizes; a bundle analyser derives route and endpoint candidates; a HAR view groups requests by method, origin and path template, folding numeric IDs, UUIDs, long hex and ISO dates into placeholders, with no network activity. Passive is not invisible: CdpPageCapture.ts sends Debugger.enable, which a page can detect through Promise.all, and the docs say coverage mode changes timing and JIT behaviour.

The test and the verdict. The Apple rebuild is labelled on the page as an unofficial reconstruction captured on 11 Oct, not affiliated with Apple, with local sample search results; the chatbot prototype’s answers are hardcoded. Commenters agreed it copies what you see, not what makes it work, and one asked how it handles authenticated flows and dynamic data, which went unanswered. Another argued any coding agent could do this from a prompt.

The honest caveat. One tester, no measure of fidelity, and no scraping use tested. A star count that large in six months measures attention, not quality. The README limits use to lawful research, interoperability and software you own or are authorised to examine; rebuilding a commercial site’s front end and brand raises copyright and trademark questions the tool does not settle. This guide did not run it.

Oct 2026 update. By the evening of 12 Oct the repo showed 136,505 stars and 30,661 forks; an earlier post on 10 Oct had said 67,000+. Its own milestone commits put 20,000 on 8 Oct and 110,000 on 12 Oct, npm downloads reached 59,665 on 11 Oct, and the stargazer list is not enumerable, so the surge is unaudited. One correction from an offline run of npm 6.4.0: the HAR endpoint-template view is on main only, not in the released package.
+
Oct 2026 / The other side
A Four-Signal Scraper Risk Score Flagged 4 of 24 Clients Above a 0.62 Review Line — on Synthetic Data, With No Code or Weights Published
A BI team lead built a rule-based Python demo that fuses four signals into one risk score: request rate, header anomalies, a simulated JA4 mismatch and browsing behaviour. Of 24 synthetic clients, four scored 0.83–0.87 against a 0.62 review threshold; the other twenty sat at 0.14–0.39. The author frames a high score as a reason to investigate, never as proof.
💡 From the defender’s chair no single signal convicts; a client that is fast, header-odd and JA4-inconsistent at once lands far above everyone else, which is why coherence beats any one fix
What was shown. An animated bar chart titled “Bot / scraper traffic — signal fusion” scans 24 clients and marks four for review. Read off the final frame: clients 05, 19 and 22 at about 0.87, client 12 at about 0.83, and nothing else above 0.39 (client 20). The footer says the data is synthetic, that JA4 is a signal and not proof, and that the threshold is 0.62. The post says the same in prose: a rule-based demonstration, not a production detection system.

Why a scraper should care. Nothing here needs JavaScript or a vendor. Request rate, header anomalies and page-to-page navigation shape are readable from an ordinary access log; JA4 needs a TLS terminator that computes and logs it, which many CDNs and edge proxies now offer. A client that copies a browser header set over a library handshake trips two of the four at once, header anomaly and JA4 mismatch, and adding speed makes three. Fusion rewards exactly the multi-signal outliers, so the cheapest move for a crawler is to stay unremarkable on each axis rather than perfect on one.

Where it sits in this guide. It is the small-site, in-house version of the four-layer trust score that opens the detection section, and of the vendor cross-checks described there. The design choice worth noting is the review line: a score that queues a human look rather than a block keeps false positives cheap, the same trade-off a commenter on another post raised about blocking real customers. It also means a flagged crawler may never see a block at all: a review queue can end in a quiet rate limit, a ticket to an abuse desk, or nothing, and a scraper owner reading only status codes cannot tell which. Watch for slowing responses and thinning content, not only for 403s.

The honest caveat. Synthetic data with planted outliers, so the clean gap between 0.39 and 0.83 is what the generator made, not what real traffic looks like. No code, signal definitions, weights or threshold rationale were published, no precision or recall, and comments were restricted with none visible. It illustrates a pattern; it measures nothing. One analyst’s demo.
+
Oct 2026 / Tooling
A One-Day-Old Tavily Wrapper’s “Zero AI” Is One Flag: Its “Answer Forced Off” Is Already Tavily’s Default (1 Star, No Licence)
tavily-headless (created 11 Oct 2026, 1 star, no licence) is a small Python client over Tavily’s paid API, not a reimplementation: it still needs a Tavily key. Its one material change is include_raw_content=True on search. The advertised include_answer off is already Tavily’s documented default, and Research reports are still written by Tavily’s model.
💡 Keeping a search vendor’s model out of what you store is one flag: ask for raw page text. Snippets and Research reports are still generated server-side, so “no AI” can only describe the client
What the code does. The whole repository was read: a 2,274-byte client.py, an argparse CLI, a standard-library ThreadingHTTPServer and one HTML page with a vendored copy of marked (MIT) for rendering reports. Search defaults to basic depth and 10 results with include_raw_content=True and include_answer=False. Extract takes up to 20 URLs (Tavily’s documented ceiling) and returns Markdown. Research creates a task, polls every 5 seconds for up to 600, then returns content, sources and usage untouched. The web UI binds to 127.0.0.1:8000, keeps the key server-side in .env, and has a Raw JSON toggle.

Checked against Tavily’s documentation. On /search, include_answer and include_raw_content both default to false, so forcing the answer off changes nothing; turning raw content on is what changes the payload. The docs also say search_depth controls “how results[].content is generated”, so the snippet field is itself shaped server-side (basic, fast and ultra-fast cost 1 credit, advanced 2). On /extract, passing a query reranks chunks of about 800 characters; the wrapper does not pass one, so you get whole pages, billed at 1 credit per 5 successful URLs on basic depth.

Why it matters in a pipeline. When a search API is your acquisition layer for an agent or a RAG corpus, store the page text with its URL and run your own model over it, so what you audit is what the agent read. That is the guide’s retrieval-layer argument from the agent side: a search-backed corpus already has the shape of someone else’s index, and letting the vendor summarise it adds a second layer you cannot inspect. It also keeps the provider swappable, the point of the agent web-access funding card.

The caveat. A personal project announced the day after it was created. The GitHub API on 12 Oct 2026 showed creation and last push on 11 Oct, 1 star, 0 forks and no licence file, which under default copyright grants no reuse permission. There are no tests. “Headless” means no local model, not a headless browser: nothing is fetched locally, Tavily does the crawling. Research remains an LLM-written report from Tavily, the opposite of “zero AI”. Not run here, since it needs a paid key.
+
Oct 2026 / Tooling
http-slow Splits One Request Into DNS, TCP, Proxy CONNECT, TLS, TTFB and Download, and Times It Direct Against Proxied (Six Days Old, 0 Stars)
A Go CLI made public on 6 Oct 2026 (MIT, 0 stars) times one HTTP request phase by phase with Go’s httptrace: DNS, TCP, the proxy CONNECT wait, TLS, time to first byte and download, then prints rule-based findings. --compare-direct runs the same URL with and without your proxy, and --repeat reports p50, p95 and p99.
💡 When a proxied scraper is slow, split the time before swapping providers: a long CONNECT wait is the gateway finding an exit, a long TTFB is the target or whatever queues in front of it
What it measures, read from the source. DNS runs from DNSStart to DNSDone; TCP is the connect to the origin, or to the proxy when one is set; the proxy phase is the gap from that connect finishing to the TLS handshake starting (or to the connection being handed over, for plain HTTP), so it is inferred, not read from the CONNECT reply. TLS is the handshake. Time to first byte starts when the request is written, so it excludes connection setup; download runs to the end of the body; total includes redirects. Phases that do not apply print a dash. Findings are fixed rules worded as “may indicate”: DNS over about 100 ms, TTFB dominating the total, a large compressible body sent without gzip, br or zstd, high variance across repeats, and no connection reuse.

Modes that matter for scraping. --proxy takes HTTP or HTTPS proxies only (no SOCKS), redacts passwords in output and otherwise honours HTTP_PROXY. Besides direct against proxy there are cold against warm connection, HTTP/1.1 against HTTP/2, IPv4 against IPv6 and two endpoints; one at a time. --resolve host:ip pins an origin while keeping SNI and Host, so you can time past a CDN edge. JSON output and exit codes (2 for latency over a limit, 3 for status, 4 for TLS) make it a CI gate.

Where it helps. The one reply on the post put it plainly: a single total makes it easy to blame the server, though the delay can sit before the request reaches it. Through a rotating residential gateway, the CONNECT wait covers the provider choosing and dialling an exit, and TTFB covers that exit’s path to the target. Each run may land on a different exit, so read p95 over --repeat, not one sample.

The caveat. A personal tool: the GitHub API on 12 Oct 2026 showed creation and last push on 6 Oct, 0 stars, 0 forks, MIT, with CI and unit tests. Not run here. Go’s client has its own TLS fingerprint and no browser headers, so a protected target may answer with a fast challenge page, and then TTFB times the refusal; check the status first. Through a proxy the target’s DNS happens at the proxy, so the DNS row describes the proxy host. In the code read, each compare leg is a single request.
+
Oct 2026 / Tooling
web-scraping.dev Is Scrapfly’s Free Practice Target: 53 Scenarios, 26 of Them Crawler Traps, From a Blocked Page Served With 200 to a One-Byte-a-Second Drip
A free practice site promoted by NodeMaven’s CEO but built by Scrapfly (its footer reads “made by ScrapFly”). Its table on 12 Oct 2026 lists 53 scenarios: 8 beginner, 13 intermediate, 6 advanced and 26 crawler tests, from hidden JSON, GraphQL and CSRF tokens to a session-ID link vortex, a calendar spanning 2000–2100 and a response that drips one byte per second.
💡 Run your crawler against deliberate traps before a real site finds them: a 200 that says blocked, a body that never ends and a link generator that never stops are the bugs production finds first
Who runs it. The post is promotional (its P.S. sells residential proxies), and the site is not the poster’s: the footer says “Sponsored by” and “made by ScrapFly”, version 1.3.0, linking Scrapfly’s academy, docs, blog and GitHub. Scrapfly is one of the managed APIs in #platforms. The copy disagrees with itself: the hero says 19 scenarios, the heading 50, the table 53. Its robots.txt disallows /robots-disallowed and sets Crawl-delay: 2 for everyone.

What it offers. Extraction cases: static paging, endless scroll, load-more buttons, review JSON hidden in the HTML, an X-Secret-Token API, GraphQL background requests, CSRF-locked paging, cookie and iframe logins, localStorage, a cookie popup, mismatched charsets, text hidden with invisible Unicode, and file downloads. Blocking cases: a 200 page that says you are blocked, a redirect on a bad Referer, a cookie that keeps you blocked, a simple antibot gate. Crawler cases: robots and meta-robots traps, X-Robots-Tag, canonicals and duplicates, a page minting 10 new session-ID links per visit, the calendar, a 10-hop redirect chain plus a loop, links only in JSON-LD, data-href or a Link header, a base tag, a 50 MB body, a 60-second drip, a wrong Content-Length, a 50-by-100 sitemap index, a gzipped sitemap and basic auth.

Spot-checked here (12 Oct 2026, one client, a few requests). /blocked returned 200 with a “You’ve been blocked” page; /antibot/easy 403; /huge-page streamed 53,248,026 bytes in 6.9 seconds; /slow-drip gave 17 bytes in 6 seconds before the client cut it; /wrong-content-length made curl abort with no body. But /rate-limited, documented as a 429 after 3 requests in 10 seconds, returned 200 six times in a row, so confirm a fixture fires before trusting a green run. One commenter described a real-world cousin of the huge body: a 302 to a 10 GB speed-test file, to drain someone’s residential bandwidth.

The caveat. A vendor-built sandbox: the cases are synthetic, the antibot is labelled simple and is not Cloudflare or DataDome, so passing proves crawler hygiene, not stealth. Some cases (a classify API, WebMCP via a cloud browser) demo Scrapfly products. Most scenarios were read, not exercised.
+
Oct 2026 / Economics
WebRobot’s “Verticals” Are One Sandbox Beta and Five Previews; Underneath, BYOC Is Licensed Per Node and SaaS Bills Usage at Cost Plus 30%
WebRobot, an invite-only platform that pairs a scraping browser fleet with Spark ETL and agents, is adding vertical agents. On 12 Oct 2026 its Agents page lists one live beta (a sentiment agent, payments in Stripe’s sandbox) and five previews. The pitch is pricing: bring-your-own-cloud is a per-node licence (€99 or €599), but the hosted tier meters usage at cost +30%.
💡 A per-node licence takes the vendor’s cut off your record count, not your bill: you still pay your own cloud and proxies, and the flat promise holds only in the self-hosted mode
What the launch is. The post (9 Oct) asks for ideas; the founder’s reply, in Italian, says a reusable infrastructure and runtimes had to come first. The Agents page lists a Sentiment Agent beta (a multi-source news and forum pipeline charting polarity, emotions and entities), then previews: GoCyprus (a travel guide with an MCP booking catalogue), GeneaAI (Italian genealogy over parish and municipal records), GainOnDelta (Italian and UK property listings scored against comparables and HM Land Registry sold prices), ScriptusAI (handwriting recognition on the Kraken engine) and FeeLess, an agentic liquidity pool it calls “not an investment service”.

The platform underneath. A browser fleet for JavaScript, anti-bot, consent and pagination; Spark, Scrapy and Temporal for processing; agents on Ray; a REST API, CLI and an MCP server that forwards the caller’s own credential. It says it is built on Anthropic’s Claude Agent SDK. Plans: Starter €10 a month (SaaS only, up to 3 machines, Scrapy), Pro €299 (up to 25 short-lived VMs, Spark), managed Kubernetes €200 per node, BYOC Soft €99 per node and BYOC Hard €599 per node with a three-node minimum. Short-lived browsers and Spark executors are not licensed: 10 per Soft node, 25 per Hard. Indicative SaaS rates include residential proxy (DataImpulse) at about €1.20 per GB and a Hetzner VM at €0.05 to €0.09 an hour.

Why it matters for cost. The guide compares per-gigabyte and per-success billing; this is a third meter, where the vendor’s share ignores how many records you pull and your own compute and proxy bill carries the volume. The homepage line that “your bill does not grow with your results” holds only for BYOC: the hosted tier bills compute, storage, proxy and model tokens as prepaid credits at cost plus 30%.

The caveat. All of this is the vendor describing itself. Access is by invitation; there are no published success rates, customers or benchmarks; the anti-bot claim is untested; prices are marked indicative and exclude VAT. Its comparison table, which ticks every column for itself against Apify, Bright Data, Firecrawl and Databricks, is self-assessed from “public product information, September 2026”. Nothing was run here.
+
Oct 2026 / Industry
Extract Summit Austin 2026: Six Official Themes, Five Workshops, Twelve Talks, and Only One Topic This Guide Barely Covers
Zyte’s Extract Summit (Austin, 7–8 October 2026; Dublin, 10–11 November) names six official themes: AI-powered extraction, ban management, headless browsers, proxy architecture, agents and scraping, and legal boundaries. The published agenda puts at least one session behind each. Most map onto sections here; AI companies buying web data is the one this guide barely covers.
💡 Read the programme as a list of what practitioners still dispute, then watch the recorded talks; nothing here is a finding until the content is out, and the organiser sells into most of it.
The six themes and their sessions. A carousel from Eric Webb pairs each official theme with the agenda. Day 1 (7 October, Brazos Hall) is five workshops: AI for the messy bits (parsing, cleaning, normalising); browser automation meets AI on dynamic sites; navigating anti-bots, scaling from human support to agents; agents and scraping, where is this going; and loop-engineering self-healing spiders with AI agents, then lightning talks. Day 2 is twelve talks plus a legal panel.

The talks worth looking for. Shane Evans (Zyte) on what AI actually changes in scraping; Akshay Philar (Zyte) on the antiban arms race and the open web; Jason Grad (Massive) on residential proxy sourcing, billed as where proxy IPs come from deciding what you can trust; Andrew Harris (AB Harris Methods) on AI companies purchasing web data; Joe Dye (Byteful) on TCP/IP fingerprinting; Pravin Khandke (Insight Global) on hybrid agents for finance extraction; João Drummond (Crawly) on agent coordination; Konstantin Lopukhin (Zyte) on a spider that rewrites itself; Logan Harless (UseString) on a multi-armed bandit approach; and a legal panel with Scott Lashway and Sally Anne Hinfey.

Where it lands in this guide. Ban management and TCP/IP fingerprinting sit in #detect and #antibots; proxy sourcing in #proxies, next to the botnet and SDK-sourcing cards; self-healing spiders and bandit selection in #ai and #proxies, where the guide already has a Thompson-sampling proxy card and an autonomous-fixer result that lost to engineers. The buyer side of web data, what AI firms pay for and on what terms, has no card yet.

Honest limits. This is a programme, not content: no talk was watched, and the Austin event has already run. Zyte organises the summit and sells extraction, browsers and proxies, and several speakers are vendors pitching their own approach. The organiser says talks are published on the Extract Summit YouTube channel; whether the workshops are recorded is not stated. The European edition in Dublin (10–11 November, Radisson Blu) may differ.

Oct 2026 update. Ten Austin recordings are now on the Extract Summit YouTube channel (titles checked 15 Oct), and a Doccraft roundup summarises them; see the card on what the talks said. Andrew Harris’s buyer-side talk, the gap flagged above, says buyers now ask what was missed and how the vendor knows, and that vendors fail most on provenance and reproducibility. The talks were still not watched here.
+
Oct 2026 / Reverse engineering
DataDome’s leboncoin Tag Posts One Nine-Field Report, and About 1 Load in 20 Also Sends the Site’s Own DataDome Settings (One Site, Free Half Read)
A Web Scraping Club teardown of DataDome on leboncoin.fr (10 Sep 2026, paid; the free half was read) found tag 5.9.4 posting one report with nine fields, a site key identical in every capture, and, when Math.random() <= .05, the site’s own DataDome settings. All four curl requests got a 403; the User-Agent only switched the challenge type.
💡 Know which of DataDome’s five pieces refused you before changing anything: a User-Agent swap that turns one challenge into another is the same refusal, not progress
What was read, and how. Pierluigi Vinciguerra used Manus for a first pass (a stateful Chromium session, curl and static analysis), checked each claim with Claude against the bytes, then captured traffic read-only with Camoufox from an Italian residential line: no patched APIs, no replayed tokens, no decrypted payloads. The sensor is dd.leboncoin.fr/tags.js, 123,989 bytes of minified JavaScript with readable method names and a plain base64 string table, served from S3 behind CloudFront on a hostname that looks like the site’s own. Around it sit an edge component that answers a navigation with the page or a 403, a collector at /js/, ct.captcha-delivery.com for the device-check and CAPTCHA bootstraps, and geo.captcha-delivery.com for the challenge page. Every claim is graded: string in the table, code that can run, or seen on the wire.

What went over the wire. One form-encoded POST from the main frame, 5,069 and 4,996 bytes in two captures, nine fields in the same order: jspl (the opaque signal payload, 4,698 to 4,771 characters), eventCounters (an empty [] in a run with no interaction), jsType (ch, the challenge collection), cid (128 characters, the cookie’s length), ddk (30 hex characters, identical every time, so a site key rather than a session value), Referer, request, responsePage and ddv. In Manus’s eleven samples the home page loaded, but a same-session HEAD and fetch both drew 403s, and every curl request got a 771-to-774-byte challenge bootstrap with or without a Chrome User-Agent (only rt=c versus rt=i changed).

Checked here, and how it relates. The tag fetched on 12 Oct 2026 is already 5.10.0 (132,516 bytes, modified 16 Sep), still on S3 via CloudFront, and still runs if(Math.random()<=.05) to add window.ddoptions as opts and window.ddCaptchaOptions as xhr_opts. The guide’s DataDome profile and Grainger case are about which paths a per-site model guards; this is the client half. Unlike the Hermès listing endpoint, where a same-site fetch got a 200 with no token, the same-session fetch here was refused. And unlike Kasada’s virtual machine, this sensor is legible.

The caveat. One site, one tag build, a handful of captures. The fingerprint inventory (the probe families behind jspl), the Kasada comparison and the claim table in the author’s repository are paywalled and were not read; the Manus outcomes are as reported. Server-side scoring is invisible outside DataDome. Read it to diagnose your own blocks, or as a defender auditing what a tag sends.

Oct 2026 update. The legibility contrast holds for tags.js only. Per two posts on the blog of TakionAPI, which sells DataDome bypass, the interstitial and captcha have since early 2026 also computed a plv3 token inside a JavaScript VM whose 103 handlers ship as source in each build, sent with the run’s step count and a hash of the interpreter’s own code. See the plv3 VM card; a vendor’s account, not replicated here.
+
Oct 2026 / Tooling
Browserless’s Built-In Solver Failed Every Try on Invisible reCAPTCHA Enterprise; the Stealth Browser Alone Logged In 2 of 2 (One Review, Two Grocers)
A Web Scraping Club review of Browserless (6 Oct 2026, a tool review the week after its founder’s guest post) logged into two Italian grocers. The built-in solver cleared a checkbox CAPTCHA in 5 of 7 runs; on invisible reCAPTCHA Enterprise it failed every try, while the stealth browser alone logged in 2 of 2. A saved profile stayed logged in from another city’s IP.
💡 On a score-based invisible CAPTCHA, run the stealth session with the solver off first: the solver can be the failure, and failed solves are free but the minutes and megabytes are not
The setup. Pierluigi Vinciguerra drove Browserless’s /chromium/stealth route from Python Playwright over CDP, with the built-in residential proxy pinned to Italy, solveCaptchas=true, typing at 70 to 220 ms per character and always reusing browser.contexts[0], because the docs apply profiles only to the default context. On Esselunga (the F5 BIG-IP TS cookie appeared only on the stealth route) the solver produced a token in 5 of 7 runs, in 10 to 35 seconds; the other two sat 75 seconds on an empty checkbox. With a token the login POST went through, and then the site demanded phone 2FA. It kept demanding it with emulationOs=macos and even from the author’s own browser: once flagged, the account stayed flagged.

The reCAPTCHA Enterprise result. Tigros sits behind Cloudflare, which issued no challenge, and uses invisible score-based reCAPTCHA Enterprise. With solveCaptchas on, the login failed on every try (the review says Browserless is fixing it, and does not give the number of tries). With it off, the page minted its own token and the login returned 200, 2 of 2. Saving state was one CDP call, Browserless.saveProfile (25 cookies, one origin). A fresh browser loaded with that profile opened the shop logged in, judged by the app calling its cart endpoint rather than by the cookie jar, with the login from Ancona and the check from Milan. A 7-day Session API copy behaved the same, because the site never rotated its token in the window.

What it cost, checked against the pricing page. A unit is up to 30 seconds of browser time; residential proxy is 6 units per MB; a successful solve is 10 units and failed attempts are free. A login-state check cost about 8 units; the cart tests after the upgrade cost 914, mostly image-heavy pages over the proxy. At the Prototyping plan’s $25 for 20,000 units (annual billing) that is about a cent per check and $1.14 for the cart work; the author’s first saving would be blocking images.

The caveat. A review of a vendor’s tool, not a benchmark: its links carry tool-review tracking tags, and no sponsorship statement appears either way. Runs are tiny (7 and 2), on two sites in one country, and the Esselunga checkbox CAPTCHA is never named. The Linux-identity explanation for 2FA is the author’s hypothesis. Against the guide’s card on saved logins reverting on restart, this confirms the restore half and leaves token rotation untested.
+
Oct 2026 / Measurement
472 Homepages, One Run: A Copied Chrome User-Agent Matched an Honest Bot String, It Did Not Beat It
Pasting Chrome’s user agent into a blocked plain-HTTP script is the reflex fix. A 472-homepage test with httpx (one run, 3 Oct 2026) gave it a full page on 375 sites against 378 for a named bot string with a contact URL and email. Only the library default lagged, at 353. What Chrome’s string changed was which sites worked.
💡 On a plain HTTP client, swap the library default for a named bot with contact details; a copied Chrome string bought nothing on average and was refused outright by Wikipedia, W3C and Meta’s domains
The setup. Doccraft, which writes research for web-data companies, took the Tranco top-1,000 homepages that had already returned a 200 to a plain request in an earlier check (472 sites) and fetched each once with five user agents, 1 second apart, from Mumbai with httpx 0.28: the library default, a copied Chrome 140 string, MyScraper/1.0, Chrome with an email appended, and MyScraperBot/1.0 (https://example.com/bot; you@example.com) httpx/0.28. A success was a 200 carrying at least half the visible words of the best page any of the five received. Of 402 sites where someone got a full page: default 353, Chrome 375, made-up name 377, descriptive 378, Chrome plus email 380.

The totals hide a swap. 23 sites served the descriptive string but not Chrome’s, 20 the reverse. Chrome lost on Wikipedia and Wikimedia, W3C, SourceForge, MySQL and LG (403s), Facebook, WhatsApp and Meta domains (400s) and Amazon’s country stores (202, empty body); Uber sent a 406 to every string except the descriptive one. Chrome won mostly where Google, Yahoo Japan and Mail.ru serve browsers a fuller variant (Google: 71 words against 33), plus Lowe’s and DigiCert, and on Lowe’s adding an email to Chrome’s string lost the full page again. Le Monde gave 11,736 words to both non-browser names and 1,547 to Chrome and the default.

Honest limits. One run, one location (IP type unstated), homepages only, and a sample pre-filtered to sites that already admit plain requests, so the hard tail is absent by construction. The five strings always ran in the same order through one shared client, so later ones may have carried cookies set by earlier ones. Reproducing the published counts from the released results needed a 50-word floor on the best page that the article does not state, and the 2-to-5-site gaps among the four non-default strings are single-run noise.

Where it sits in this guide. It complicates, not contradicts, the Bamberg result that two header edits clear 75% of headless-only blocks: there the edit removed HeadlessChrome from a real Chrome whose handshake already matched; here a Chrome string rides Python’s TLS, the incoherence #detect warns about, and it earned nothing on average. It corroborates the #legal-ethics line that declaring yourself is often less detectable than hiding. The Web Bot Auth architecture draft the article cites, revised March 2026, is an individual submission that has since expired.
+
Oct 2026 / Agent safety
Half of 15,387 In-Page Prompt Injections Ride in Response Headers, Mostly One X-AI Header on 572 Hosts; a Top-1,000 Recheck Found None (One Run)
A CISPA-led scan of 1.2B URLs validated 15,387 in-page prompt injections, and crawlers and scrapers were the main target. 7,887 sat in HTTP headers, 6,535 of them in one custom X-AI header on only 572 hosts. A one-run recheck of 968 Tranco top-1,000 homepages found no such header at all.
💡 The threat is concentrated, durable and mostly defensive. Screen headers and JSON-LD before a model reads them, keep markup instead of flattening to plain text, and log objections as site intent.
What the paper counted. Khodayari, Zhang, Acharya and Pellegrino (CISPA Helmholtz Center and the University of Louisiana; arXiv 2604.27202, April 2026) searched about half of Common Crawl CC-MAIN-2025-43 plus Censys and Shodan for 3,963 known injection phrases, hand-checked 31,206 candidates and kept 15,387 on 11,722 pages and 2,042 hosts. By purpose: 8,894 disruption (8,469 of them plain garbage output), 4,093 data protection, 3,096 AI-bot identification, 1,521 reputation manipulation, 13 exfiltration. 54 templates cover 95%, and 93% of archived pages already carried the injection three months earlier, 65% twelve months earlier.

Where they sit, and the 87% this guide had not traced. Headers carry 7,887 (X-AI 84%, X-LLM 13%); structured data 1,996, 26% of body injections, mostly JSON-LD. Both circulating figures are in the paper: 70% are non-visible by construction (headers, comments, metadata), and 87% are non-visible once rendering tricks such as matching colours, occlusion and zero-size elements are counted. 53% sit in headers or early HTML, where a model reads first.

What changes compliance. In 5,200 hand-checked summarisation runs on 13 models, plain text stripped with BeautifulSoup was followed 3.9% of the time, HTML and a Playwright snapshot 1.1% each, the raw response 0.2%; small models reached 8.0% on text. But HTML and raw inputs errored in 20.3% and 25.8% of runs from context overflow, so part of their low rate is models failing before answering. Six times a model flagged the injection and complied anyway.

The caveats. A phrase-list scan is a lower bound: crawlable, English, unobfuscated text only, and summarisation was the only task tested. The top-1,000 recheck comes from a developer-marketing blog, one run from Mumbai on 3 October 2026: 633 of 968 sites answered, none sent an X-AI or X-LLM header, and one homepage (Cloudinary) hid an sr-only note telling agents to use its API instead of the sign-up form. The “7,000 from site owners” line in circulation is data protection plus bot identification by purpose (7,189), not authorship.
+
Oct 2026 / Measurement
Only 5% of 544 Top Homepages Needed JavaScript; 14% Blocked the Plain Request but Let the Browser In, and 5% the Reverse (One Run, Homepages)
One run over the Tranco top 1,000 loaded each homepage once with httpx and once in headless Chromium, same Chrome user agent. Of 544 real homepages, 62% had their text in the plain HTML, 3% as JSON in the source, and only 5% needed JavaScript. Blocking mattered more: 14% stopped the plain request but loaded in the browser.
💡 When a plain request fails, classify it first: no text is a JavaScript problem, a challenge is a blocking one. The browser beat a block on 14% of homepages and triggered one on another 5%.
How it was run. Tranco list V349N (29 September 2026). Per domain: one robots.txt fetch, one httpx GET and one Playwright Chromium load (DOM ready, up to 8 seconds of network idle, one more second), both sending a Chrome 140 user agent. 32 sites whose robots.txt disallows everyone were skipped; 279 unreachable, 112 with no real homepage and 33 browser failures or off-site redirects left 544. “Text in the plain HTML” means at least 60% of the words the browser showed were in the raw response; moving that line from 50% to 80% moved the share from 56% to 63%, and the JavaScript share stayed at 5%. The published CSV, tallied here, gives 335 / 18 / 8 / 29 / 77 / 26 / 51.

What the blocks were. Cloudflare challenges accounted for 41 of the 77 plain-only blocks. All 12 Amazon stores served a challenge to the plain request: in the CSV, nine returned a 202 and three an empty 200, zero words each. Wikipedia’s 403 cleared once the user agent carried contact details, as its bot policy asks. The reverse group of 26 includes eBay, Zillow (a px-captcha page) and Salesforce; the 51 that blocked both include The New York Times (DataDome) and Stack Overflow (Cloudflare).

Speed and frameworks. On the 390 homepages with content, median time was 0.7 seconds for the plain request and 9.5 for the browser, recomputed here from the CSV, about 14 times longer, although the browser figure includes a load wait a tuned scraper would trim. 54 of 62 Next.js homepages were server-rendered, and about 55% of readable pages carried JSON-LD.

The caveats. Homepages only, one run, from Mumbai, with the network type unstated. One client of each kind: httpx sends a Python TLS fingerprint under a Chrome user agent, so “plain request blocked” partly measures that mismatch, not plain HTTP in general. Word overlap is not meaning. The author writes developer marketing for web-data firms. Figures in circulation of 59% and 17% predate a 2 October correction: Cloudflare’s challenge script also sits on pages it does not block, so 14 sites had been misfiled as blocked.
+
Oct 2026 / The other side
An Open-Source Fraud Console Shows How Sessions Become Devices: A Stored UUID First, Then GPU, Screen and CPU–Memory Must All Agree
OpenFraudMonitoring (AGPL-3.0, 8 stars) is a self-hosted fraud console built on Vastel’s fpscanner: one script tag, 35+ signal categories, 21 bot rules, 30-second behaviour heartbeats. The part worth reading is its device matcher. A visit joins a known device by a localStorage UUID, or else only if GPU, display and CPU–memory all match exactly one device. IP cannot link.
💡 Read the linking rule, not the detection list. Here a rotating IP neither links nor unlinks a session; a persisted UUID links at once, and without it the hardware triple has to match exactly one known device.
What it collects. The script runs a fork of Antoine Vastel’s MIT-licensed fpscanner: 35+ signal categories (screen, GPU, codecs, fonts, WebGL, automation flags) hashed into a JA4-inspired fsid, plus 21 detection booleans shipped as default rules, among them hasWebdriver and hasCDP at 15 points each, hasUTCTimezone at 8 and hasSwiftshaderRenderer at 3. Mouse, keys and scrolls arrive in heartbeats every 30 seconds; clicks, form submits and copy or paste events go immediately. One default periodic rule flags more than three copies and three pastes in a session. Scores add up to a cap of 100.

How sessions become devices. The docs say plainly that fsid is not identity: one randomised canvas value changes it. So v1.3, released on 3 October, two days after the post’s v1.2, resolves devices in order. A known client UUID, kept in localStorage as ofm_device_id, links at once. Otherwise three groups must agree with exactly one stored device: a recognised hardware GPU renderer or UA-CH model; screen width, height and depths; and CPU count with device memory. Software renderers such as SwiftShader never qualify. Timezone, language, canvas, behaviour and “IP proximity cannot establish or rescue a hardware match.” Zero or several candidates means a new device.

What it means for a collector. A rotating proxy pool does nothing here; a reused profile with its storage intact links every session to one device. With storage cleared, a fleet of identical cloud VMs looks like one machine wherever the renderer is recognised, and the docs list that collision as a known limitation. Investigators then pivot in a STIX 2.1 graph from session to IP, autonomous system and user agent, where clusters show up even when the device link fails.

The honest caveat. This is a one-author project with 8 stars, created in April 2026, and it publishes no accuracy or false-positive figure. Its 0.95 match confidence is, in its own words, “not a calibrated physical-device probability.” The stricter matcher is v1.3; v1.2 was looser, and v1.3 ships a tool to rebuild what it calls contaminated clusters. Visitors get a dismissible notice that is “not a consent gate”, and the docs flag GDPR exposure. It shows the design one defender chose, not what commercial vendors do.
+
Oct 2026 / The other side
A Student Reports a Fine-Tuned 2B-Active Vision Model Scored 90%+ on hCaptcha Classes It Had Not Seen: Unpublished, n=1, and Aimed at Rotation
A computer-science student says he fine-tuned Moondream 3 (9B mixture-of-experts, 2B active, open weights) on about 200k hCaptcha images and reached 90%+ on challenge types it had not trained on. Nothing is published: no data, split or code. The point is what it targets. Rotating challenge variants is how image CAPTCHAs beat per-challenge classifiers, and zero-shot models erode that.
💡 If a model tuned once handles classes it has never seen, rotating challenges stops being the defence. The weight shifts to what surrounds the image: obfuscated probes, fingerprint checks and motion.
What was claimed. The author says production hCaptcha solving still relies on a dedicated classifier, such as a custom ResNet, per challenge type, and that its cost is the upkeep: every new variant means collecting, labelling, retraining and downtime. He rejects cloud vision models as too slow, costly and inconsistent at volume. His test: about 200k images gathered over a month, Moondream 3 as the base, supervised fine-tuning plus reinforcement learning, and 90%+ on zero-shot classification, a little lower on spatial tasks. He calls it an experiment and says dedicated models still win today.

The model, checked. Moondream 3 Preview appeared on 18 September 2025: 9B parameters as a mixture of experts, 64 experts with 8 active per token, about 2B active, 32K context, under the Business Source License 1.1 with an additional use grant. Moondream 3.1 followed on 7 July 2026 with open weights under the Moondream Model License 1.0, which permits self-hosting and fine-tuning and bars only general-purpose hosted inference. Neither licence mentions CAPTCHAs. The post does not say which release was used.

What it means for defenders. Image CAPTCHAs rely on rotation: new variants arrive faster than solvers can retrain. The author’s own forecast is that once variants rotate every few minutes and never return, zero-shot becomes mandatory, and a small local model would answer that without the retraining loop. That pushes defence toward what he credits hCaptcha with: a WASM virtual machine for obfuscation and fingerprint probes that flag anti-detect browsers. The weakest part, in his view, is the mouse and touch motion model. It is the same shift as the guide’s liveness section, away from the answer and toward how it was produced.

The honest caveat. Every number is the poster’s own, unpublished and unreviewed. “Zero-shot” is undefined; we do not know whether test classes were truly absent from training, whether 90% is per image or per challenge, or what share of challenges pass when several answers must all be right. No latency or hardware is given. Collecting 200k images from a live CAPTCHA service raises its own terms-of-service questions. This is one student’s report with one appreciative comment.
+
Oct 2026 / Tooling
invisible_playwright Cleared Five Anti-Bot Sites in One Review, but Headful Vanilla Playwright Cleared Four (One Site Per Vendor)
invisible_playwright (3,009 stars, created May 2026) drives a Firefox 151 build patched in C++ and derives every fingerprint surface from one reproducible seed. A hands-on review loaded one Cloudflare, Akamai, DataDome, PerimeterX and Kasada site with it and passed all five. The control is the story: headful vanilla Playwright passed four. Only one Kasada page separated them.
💡 A stealth review is only as strong as its control. When headful plain Playwright clears four of five vendors, one Kasada page is the whole measurement; rerun it on your own targets.
The project, checked on 5 Oct 2026. feder-cr/invisible_playwright was created on 13 May, has 3,009 stars and 296 forks, and is not archived. PyPI shows 66 releases since 25 July, 38 of them in the last 30 days; 0.26.0 shipped on 5 Oct. The engine is a mozilla-central fork, feder-cr/firefox_antidetect_patch, publishing Firefox 151.0 builds for Windows x86_64 and Linux x86_64/arm64 only, with no macOS; fetch pulls a sha256-checked 240–262 MB archive. The licence is not plain MIT: pyproject.toml declares MIT AND Apache-2.0 because it ships a fork of Playwright’s Python client, and the binary is MPL-2.0. One account wrote 663 of 728 commits.

What it patches, per its README. UA, navigator and client hints are derived together; Accept-Language and navigator.languages are one declaration; canvas is deterministic per seed; WebGL is a validated persona whose ~81 getParameter values travel with the renderer name, so pinning an unknown GPU raises an error. Fonts ship with the browser, the codec table is declared, WebRTC offers one synthetic candidate carrying the proxy exit, and timezone comes from the egress IP. Those are the cross-surface leaks the anti-mage card names, such as Linux fonts under a Windows claim, handled in the engine. Nobody has published an anti-mage score for it.

What the review measured. The Web Scraping Club review (Antonello Zanini, 4 Oct, free to read) reports reCAPTCHA v3 0.9 on AntCpt, APIVoid risk 0, CleanTalk 100/100 and a Pixelscan pass. Its anti-bot table used one unnamed site per vendor from a home residential IP. Vanilla Playwright headless failed all five, so most of that gap is the headless tell. Headful vanilla passed Cloudflare, Akamai, DataDome and PerimeterX and failed only Kasada; invisible_playwright passed all five either way. The July fifteen-engine benchmark in this guide, covering the JS surface only, also scored it 100.

The honest caveat. That is n = 1 per vendor, unnamed sites, no repeat count and no definition of “loaded”. The review’s claim that Firefox is the only engine patchable at binary level is wrong: CloakBrowser patches Chromium in C++. Firefox’s small share can itself be a signal on Chrome-heavy targets. With one main maintainer and near-daily releases, pin exact versions. This guide tested nothing.

Oct 2026 update. A 7 Oct practitioner carousel repeated this review’s figures, reCAPTCHA v3 0.9 and 5/5 sites, without the headful control, and its “nothing to read, no seam” outruns the evidence: CloakBrowser, also patched in C++ and also reported going 0.1 to 0.9 on reCAPTCHA v3, still scored 70 on coherence. Its MCP mode is a separate project, AIHawk. On 12 Oct: 3,117 stars, PyPI 0.35.0 and 77 releases in all, five of them in the last three days.
+
Oct 2026 / Tooling
Crawlbase Ranks Nine Scraping MCP Servers With Its Own First and No Fetch Tests: What Survives Is the Tool Count, 7 to 28
Crawlbase’s 2026 guide compares nine MCP servers for web scraping, with its own listed first, checked against vendor docs on 29 September and with no fetch run and no success rate. What survives is the axis it adds: how many tools each server loads into context, from about 7 for Jina and 9 for Crawlbase to 26 for Firecrawl and 28 for ScraperAPI, and whether failed fetches are billed.
💡 Shortlist MCP servers on what they load and what they bill, not a vendor’s running order. Then send the same 20–50 real URLs through each and count real pages, clean output and tokens per turn.
What it is. A 25 September 2026 post on crawlbase.com by Viktor Petrov, Crawlbase’s Data Science Architect. The order runs Crawlbase, Bright Data, Firecrawl, Apify, Oxylabs, ScrapingBee, ScraperAPI, Jina, then Playwright. The axes are access to protected sites, JavaScript rendering, output format (it calls Markdown the cheapest), hosted versus local, how many tools load into context, and billing. Its method, in its own words, is that every detail was “rechecked against the vendor’s own documentation or repository” on 29 September. No server was run.

The numbers worth keeping. Crawlbase lists 9 tools in v1.3.0 (3 crawl, 6 storage), Jina 7, Firecrawl 26 in its full profile and ScraperAPI 28. Bright Data’s count is left as “broadest”, with GROUPS and TOOLS settings to load a subset. Firecrawl’s README, which we checked, names about two dozen firecrawl_ tools, monitor and research among them. Playwright MCP is local, does no unblocking, needs your own proxies, and its README points coding agents to the Playwright CLI to save tokens. On billing, Bright Data gives 5,000 free requests a month. Crawlbase says its failed requests are not billed, and the other eight entries say nothing on failure billing. Scrapy MCP appears as a sidebar: it fetches nothing and connects an agent to a running crawl.

What it adds here. This guide already shows that tool definitions cost tokens on every turn. This list shows the spread between scraping servers is about fourfold in tool count alone, and that you can trim it on some servers. Its security note is sound: a hosted server keeps your key in a config file (ScrapingBee’s sits in the endpoint URL), and the model can request any URL, so use a token you can rotate and watch the request log. It says nothing about prompt injection from fetched pages, which is the larger agent risk.

The honest caveat. This is a vendor ranking that puts its own product first and carries sign-up calls to action. It is a documentation review: no success rates, no head-to-head on shared URLs, and no token cost per server, so the tool count is a proxy for context cost, not a measurement. Counts change between versions. The no-charge-on-failure claim is Crawlbase describing itself, and we did not check any pricing page. Of the tool counts, we checked only Firecrawl’s against a primary source. The recommendation the article ends on, a 20–50 URL bake-off, is the right one.
+
Oct 2026 / Tooling
Crawlflow Runs a Drawn Node Graph as a Scrapy Spider: Two Days Old, Zero Stars, No Licence File
Crawlflow is an n8n-style editor: you wire Input, Request, Parser, Pagination, Follow, Loop, Branch and Output nodes, and a Python engine runs the saved graph as a Scrapy spider instead of exporting spider code. Checked on 5 October 2026, the public repo was two days old, with seven commits, zero stars, no releases and no licence file. It does plain HTTP only: no browser, no anti-bot layer.
💡 A visual builder is only as portable as what it leaves you. Crawlflow keeps a JSON graph that only its own engine runs, and with no licence file it grants no reuse rights yet, so try it on a sandbox, not a pipeline.
What you draw. Input seeds one URL, a list, a CSV table or a numeric range. Request sets method, query params, headers, cookies, body and a proxy, with {{variables}} filled from upstream. Parser turns HTML (CSS or XPath) or JSON (JSONPath) into named fields, with an optional regex clip and a default. Follow links takes allow/deny regexes. Pagination handles a next link, a ?page= parameter, a JSON cursor or an incrementing variable. Branch routes on exists, equals, contains, gt, lt or regex. Output writes JSON, JSONL or CSV. Values travel along edges as one flat dictionary of variables. The stack is a FastAPI backend, a React Flow editor and a CLI with validate, run and serve.

How it maps onto Scrapy. Input nodes become start_requests, Request nodes become scrapy.Request with the proxy in meta, and each Parser emits one new context per record. Scrapy supplies the crawl loop, retries and middleware. Global settings map onto Scrapy settings, and the model defaults are polite: robots.txt obeyed, 8 concurrent requests, 4 per domain, and a randomised 0.5-second delay. The JSON loader strips a )]}' guard prefix and unwraps JSONP, a small touch that shows API-scraping habits. Note what it is not: an interpreter, not a code generator. There is no spider file to export and maintain without the tool.

Where it fits. The audience is someone who wants Scrapy’s scheduler without writing a spider, for list-detail-paginate jobs on unprotected sites, with the graph saved as a versionable JSON file. That is a different bet from prompt-to-scraper builders: the selectors are ones you chose and can read, so a broken field is a visible edit, not a regeneration. The README also says output files are written when the crawl ends, so a long crawl that dies midway may leave nothing on disk, which is our reading of the docs, not something we tested.

The honest caveat. This is a two-day-old personal launch. The GitHub API on 5 October 2026 showed creation on 3 October (first commit 2 October), 7 commits, 0 stars, 0 forks, no release and no licence, which under default copyright means no permission to reuse the code. There is one test file with nine tests. Examples run against quotes.toscrape.com and a bundled fake storefront. There is no browser rendering and no TLS impersonation, so protected targets are out of scope, and the author calls it a first base version. We read the README and engine source but did not run it.
+
Sep 2026 / Correction
On 15 September Cloudflare Swapped “Block” for “Disallow AI Training”: Googlebot Now Falls Only to an Explicit Block
The warning still circulating, and repeated in this guide, is that any Cloudflare site blocking Training now blocks Googlebot at the edge. That was the July plan. Cloudflare’s 15 September post changed it: old Training blocks migrate to a new Disallow AI Training setting that keeps Googlebot for search. Only an explicit Block, or Block on pages with ads, now stops it.
💡 The Googlebot-block warning describes a plan, not what shipped. Read the zone’s Training setting: Disallow keeps search crawlers, while Block or Block on pages with ads now removes Googlebot too.
What circulated, and where it came from. A practitioner warning after the 15th said ad pages now block Training and Agent by default, that Googlebot crawls for both search and training, and that because the most restrictive rule wins, anyone who once clicked “Block AI bots” could be refusing Googlebot at the edge, invisible to robots.txt and to JavaScript analytics. That matched Cloudflare’s 1 July 2026 post, which said multi-purpose crawlers “such as Googlebot, Applebot, and BingBot” would be blocked by customers who chose to block Training, including through the legacy toggle. This guide repeated the July version.

What Cloudflare published on 15 September. A new Training option, Disallow AI Training, publishes a no-training preference in robots.txt, keeps crawlers Cloudflare calls Accountable allowed for search, and blocks every other training crawler, including the training-only crawlers of Amazon, Anthropic, Meta and OpenAI. Block and Block on pages with ads now apply to mixed-use crawlers, Googlebot included. The migration: a never-configured domain with the legacy toggle off goes to Allow on all three; legacy Block goes to Search Allow, Training Disallow, Agent Block on pages with ads; earlier Training blocks become Disallow. Block AI bots and Managed robots.txt are deprecated.

What it means in practice. Through a migrated setting Googlebot keeps crawling for search, and the training opt-out travels through Google-Extended in robots.txt. The edge-block mechanics in the warning stay true for anyone who now picks Block, and Cloudflare says so outright. Comparing origin-log Googlebot volume either side of the 15th is still a cheap check, though Cloudflare’s own security events show blocks directly. For collectors, the pass is narrow: a training crawler without the Accountable designation is blocked, not asked.

The honest caveat. This is Cloudflare describing its own product; no zone was observed. Its Block AI Bots docs page, last updated 1 July, still carries the July wording, so Cloudflare’s sources disagree until the docs catch up; the dated 15 September post is the newer statement. We found no primary Cloudflare page for the “every existing free-tier site” wording, and the migration table maps untouched domains to Allow. The warning itself carried no data or links. Bing’s robots.txt no-training support is only targeted for early 2027.
+
Sep 2026 / Measurement
Cloudflare’s “OpenAI” Count Is a User-Agent Claim: One Publisher Matched Only 22% of It to OpenAI’s Published IPs (n=1)
A media analyst checked the requests Cloudflare’s AI Crawl Control dashboard attributed to OpenAI on his site, about 17,850, against OpenAI’s published IP ranges and reported that only 22% matched. Cloudflare’s own docs explain how that happens: without Bot Management the product identifies AI crawlers by user-agent string. One site, one screenshot, no published method.
💡 A vendor’s AI-crawler label is only as strong as the signal under it, and on most Cloudflare plans that signal is the user agent. Re-check operator IP feeds yourself before you trust, block on or quote the count.
What was reported. The dashboard, filtered to OpenAI for one publisher’s site, showed about 17.85k requests (a tile rounding to 18k), 3k allowed and 15k unsuccessful, with 11.89k answered 403, 5.7k attributed to OAI-SearchBot, 41.61 MB of HTML served and 10 referral visitors. Comparing source IPs with OpenAI’s ranges, the publisher found only 22% inside them and called the other 78% bots pretending to be OpenAI. He said the dashboard numbers are not accurate and started a week-long controlled test; no result is published yet.

Why the tile says OpenAI. Cloudflare documents it. On the free plan, AI Crawl Control identifies AI crawlers by user-agent string, and the more thorough detection via Bot Management detection IDs sits on Enterprise plans. Its GraphQL reference marks the user-agent filter “Can be spoofed” and the detection-ID filter “Reliably verified by Cloudflare”, and its bot reference lists two detection IDs for each OpenAI crawler. So on a plan without Bot Management, an OpenAI tile can plausibly count anything that types GPTBot, ChatGPT-User or OAI-SearchBot. We do not know which plan this site runs.

How to check it yourself. OpenAI publishes four feeds, all checked on 30 Sep 2026: openai.com/gptbot.json (18 prefixes, generated 22 Sep), chatgpt-user.json (230 prefixes, 25 Sep), searchbot.json (39 prefixes, dated 2 Jan) and adsbot.json (2 prefixes). Match the user-agent token, then require the source IP to sit inside that token’s own feed, fetched fresh; a GPTBot string from a ChatGPT-User range is still a mismatch worth logging. Do it on raw edge logs with client IPs, not on the dashboard’s aggregates.

The honest caveat. This is one site, one screenshot, and an unpublished comparison: the window, the feeds used and how IPv6 was handled are all unknown. A commenter argued OpenAI may send traffic from infrastructure outside its declared ranges; OpenAI’s bots page lists only these feeds, so unmatched traffic is best called unverified rather than proven fake. A stale copy of a feed also overstates spoofing. Do not generalise the 78%; the durable point is that the label and the evidence are different things.

Oct 2026 update. A free WordPress plugin shows the same split from the site side. Agentimus (GPL-2.0, v1.53.0, about 20 active installs) labels 22 crawlers by user-agent substring and, since 1.51.7, names the page each one read without storing query strings. Verification is opt-in: reverse DNS or published IP ranges for eight built-in crawlers, GPTBot included but not ChatGPT-User or ClaudeBot, plus Web Bot Auth. IPs are off by default, so a 22%-style check cannot be rerun later.
+
Sep 2026 / Access
Cloudflare’s 402 Has a Header Contract: Eleven Error Codes, Every Re-Crawl Billed, and a Wall for Any Crawler That Is Not Registered
Cloudflare’s pay per crawl answers 402 Payment Required with a crawler-price header; a crawler agrees with crawler-exact-price or pre-authorises with crawler-max-price, and the 200 comes back with crawler-charged. The catch: only crawlers registered with Cloudflare and signing with Web Bot Auth can pay. For everyone else a 402 is a block with a price on it.
💡 Write a 402 handler that parses and logs rather than retries: unless you are registered and signing, the price in the header is one you cannot pay, and the page is simply closed to you.
The contract, from Cloudflare’s own docs. Reactive: request, get 402 plus crawler-price: USD 0.01, resend with a matching crawler-exact-price. Proactive: send crawler-max-price first and be served if the price is at or below it. Only one price header per request, and it must be among the components signed in signature-input. Failures carry a crawler-error header with one of eleven codes, from MissingCrawlerPrice (402) to CrawlerForbidden (403), StrongAuthRequired (400) and InvalidContentPrice (502). Error responses are not billed; every successful re-crawl is. /robots.txt, /sitemap.xml, /security.txt and /crawlers.json stay free.

What changed since the July 2025 launch. It launched as one flat domain-wide price in private beta, and the docs still say closed beta. A June 2026 changelog added dynamic pricing: the origin may return its own crawler-price, and Cloudflare tags origin requests with cf-pay-per-crawl: protocol=cloudflare, pricing=in-band (or zone-default, bypass); URI patterns can be exempted. Settlement runs through Stripe with Cloudflare as merchant of record, a signed Discovery API lists charging domains, and Cloudflare sets no spending limit for the crawler. WAF and Bot Management blocks override Charge.

x402 is a second, different 402. Cloudflare’s July 2026 Monetization Gateway, a waitlist so far, would let a site charge any caller for a page, dataset, API or MCP tool over x402, settling in stablecoins (USDC, Open USD) with no account, via per-route rules such as $0.01 per call to /api/premium/*, variable prices up to $2, or origin 401s turned into 402s. The post names no headers; the x402 spec returns a base64 PAYMENT-REQUIRED header, the client resends PAYMENT-SIGNATURE, and success carries PAYMENT-RESPONSE. A handler has to tell the two dialects apart.

The honest caveat. Everything here is vendor documentation; we saw no 402 in the wild and tested nothing. Neither product publishes adoption numbers. “The bot pays and gets the page” holds only for a registered, signing crawler with Stripe connected; an unsigned crawler-max-price is simply rejected. Give no automated client the ability to pay without a hard budget.

Oct 2026 update. The founder of ProxyScrape, a proxy seller, objects to the model itself: a crawler that must fetch 999 low-value pages to find one pays for all 1,000, so per-page pricing only fits sites whose data is already structured. Replies split: on data-dense sites such as job boards most pages carry value and filtering is the buyer’s job; another proposed a free index of each paid page’s contents, date and price, plus a changed-data feed. Per-URI pricing and exemptions answer part of this; the docs offer no such index. Opinion, no data.
+
Sep 2026 / Research
One 0.6B Model Replaced the Scraper and the Filter Stack: 3.8–4.7% Better Pretraining Data at Three Scales, One Pool
CMU’s ReScraper replaces the heuristic extractor (resiliparse, trafilatura) and the rule filters behind it with one 0.6B Qwen3 model. It reads the whole rendered page, strips boilerplate, then keeps, edits, deletes or rewrites it. On one 18.0M-page Common Crawl pool, pretraining on its output scored 3.8–4.7% higher (relative) than the best baseline at 400M, 1.4B and 2.8B.
💡 Changing the heuristic scraper barely moved the score. Reading the full rendered page did. Keep your raw HTML so you can re-extract later
How it works. Raw HTML is rendered to text, one block per line, each line tagged <lid:n>, with navigation and footers left in. The model writes a short program: an <extract> list of line removals (rm 1-18), then one tag, keep, edit (remove more lines or exact substrings), delete, or rewrite. Plain code executes it, so every operation except rewrite only deletes and kept text is copied, never regenerated. The model was distilled from three teachers: Dripper (a 0.6B extractor), Qwen3.8-27B for refinement, and a 1B rephraser for deleted pages that still carry educational value. Extraction alone removed 58% of tokens, mostly at the page edges.

The numbers. DCLM Core (22 tasks): 0.1535 / 0.2735 / 0.3184 against the best baselines of 0.1465 / 0.2615 / 0.3068 at 400M / 1.4B / 2.8B. With four scrapers (resiliparse, trafilatura, jusText, Dripper) each followed by RefinedWeb rules, the four stayed within 0.003 Core of each other at 1.4B, and ReScraper beat its own teacher Dripper by 5.8% and 8.3%. Cost: 494 H200 GPU-hours for the pool, compared with 1,366 for Dripper alone. That is roughly 36,000 pages per GPU-hour (this guide’s arithmetic). Over five seeds at 400M it scored 0.154 ± 0.004, against 0.142 ± 0.004 for UltraX.

Why a scraper should care. In the paper’s examples, resiliparse fused table cells (“225 225” became “225225”) and dropped a documentation page’s bulleted list. No refiner working downstream could restore either. The line rendering kept both. The reusable pattern is the output contract: the model emits deletions addressed by line id and code applies them, which keeps an LLM out of the business of retyping your content.

The caveats. This is a v1 preprint (28 Sep 2026) and has not been peer reviewed. Success is downstream pretraining score, not extraction accuracy against a gold set, and its 89.3 token F1 is measured against its own teacher. It handles English pages only, skips any page over 29,696 tokens, and decodes at temperature 1.0, so reruns differ. Rewrite writes new text (mean BERTScore 0.89 against source), so drop it for anything that must be verbatim. The main table is one seed per scale. Everything is released under Apache-2.0 (model, code, 7.44B-token corpus). The repo is three days old with 3 stars.
+
Sep 2026 / Operations
Oxylabs’ Parser-Fixing Agent Shipped 59% of Changes to Engineers’ 87%, and Its Share Fell to 14% (One Vendor, Secondhand)
A synthesis of 60+ talks and 10 papers from 2025–26 finds one pattern: teams use AI to build and repair scrapers, not to run inside the request path. Its sharpest data point comes from an OxyCon 2026 talk. Oxylabs built an agent that changed parsers on its own: 251 changes for $847. Only 59% reached production, against 87% for engineers, and its share of parser changes went from 51% while an OKR targeted it to 14% once the OKR ended.
💡 Judge an autonomous fixer by its merge rate next to your engineers’, not by the demo. If engineers stop calling it, that is the verdict
The agent engineers stopped using. As the synthesis relays the Oxylabs talk: 76% of the agent’s changes passed CI, against 91% for engineers, and each agent change drew 3.6 further human edits. Its share fell even as the models improved: “They just quietly stopped calling the agent.” The version that stuck assisted engineers instead, with command-line access, written instructions and guardrails such as never drawing a conclusion from too few jobs. A separate Oxylabs support agent is reported to have cut the median time to fix a degraded scraper from 5 hours to 2.

Why AI stays out of the request path. Oxylabs puts its traffic at about 7 billion pages a day. Parsing 10% of that with the cheapest LLM at about a cent a page would cost roughly $7M a day. Zyte’s R&D speaker said the job needs a 99% solution, not 85%. The counter-case came from Diffbot: a twentieth to a tenth of a cent per page, but on cleaned article text, not raw HTML. Scrape.do says more than 99% of its requests run without a browser.

What the cited papers say (checked here). NEXT-EVAL (164 pages, Gemini 2.5 Pro): F1 0.96 on Flat JSON, 0.10 on slimmed HTML with a 0.91 hallucination rate. This guide’s own “F1 > 0.95” line leaves that condition out. Odysseys: on 200 live multi-site tasks the best agent (Opus 4.6) was fully correct on 44.5%, at about 30 minutes and $2.50 per 100-step task. A 1.2B-URL scan validated 15,387 prompt injections on 11,722 pages. Crawlers and scrapers were the dominant target, and about 70% of injections sat in HTML that never renders.

The caveat. The author works in developer marketing for web-data companies, and most talks are vendors describing their own systems; the post itself says none of the numbers has been tested independently. This guide read the synthesis and four of its papers but did not watch the talks, so every Oxylabs, Zyte, Diffbot and Scrape.do figure is a secondhand quote from one company each. The synthesis also says 87% of injections were invisible to humans. Traced since (Oct 2026): the CISPA paper itself (arXiv 2604.27202) reports both, 87% non-visible to humans overall and about 70% non-visible by construction in non-rendered HTML. Treat the synthesis as a map of where to look, not as evidence.

Oct 2026 update. A dedicated write-up of the talk clarifies the figures. The 51% was the agent’s share of all parser changes while an OKR targeted it; 14% came after the OKR ended. The 59% and 87% are acceptance rates for each side’s own merge requests, and median time to production was 7 days against 20 hours. The 5-to-2-hour fix time belongs to the assistive second agent, not a separate one; its stated guardrail is a 95% significance level; it gives no 30-job figure. The paper gives 87% non-visible, 70% by construction.
+
Sep 2026 / Benchmark
ScrapingArena’s 55% NodeMaven Headline Was Beaten by a Browser With No Proxy at All: 66% Direct, Same Run, 100 URLs
ScrapingArena, a new open benchmark (100 bot-protected URLs, four attempts, an LLM judge, daily CI), added NodeMaven as a proxy provider, and its leaderboard now leads with Camoufox + NodeMaven at 55%. The committed raw JSON for the same 28 September run reads differently: ShardBrowser with no proxy scored 66%, and Camoufox itself scored 50% direct and 45% through Oxylabs.
💡 A leaderboard that shows each scraper’s best proxy row hides the no-proxy row; read the raw results before crediting the proxy, doubly so when both providers sit in the site’s sponsored slots
What the harness does. The repo (scrapingarena/scrapers, MIT, created 13 August 2026) is committed to by Tadas Gedgaudas, who announced the result, and the site header reads “ScrapingArena by appkittie.com”. The corpus is 100 URLs on 100 domains: Akamai 27, Cloudflare 24, unknown 17, DataDome 16, HUMAN 10, Fastly 4, Kasada 1, Imperva 1. Each target gets up to four attempts at concurrency 1, and success is decided by a shared validator ending in an OpenAI model. Every scraper runs direct, from GitHub-hosted runners, and through proxies; published NodeMaven runs use its -filter-medium IP filter, the “filtering technology” in the claim.

What the raw run says. Run 36417282715-3 matches the leaderboard image on every speed figure we checked. Camoufox was 55 NodeMaven, 50 direct, 45 Oxylabs: five targets apart in one run. The top results were direct: ShardBrowser 66, Steel 58, Fortress 55 (43 NodeMaven, 39 Oxylabs). Where NodeMaven clearly beat Oxylabs was curl_cffi, 46 against 33, Cloudflare 15 against 6 of 24. The grid is incomplete: ShardBrowser and Moli had no NodeMaven row, curl_cffi and Patchright no direct row. Steel through either proxy fell to 5 or 6 with zero blocks recorded, which reads as an integration fault, not a verdict.

The Google claim. Camoufox + NodeMaven did pass google.com/search on the first attempt with no cookies. So did Camoufox + Oxylabs on the second, and five browsers running direct; CloakBrowser + NodeMaven failed it. One URL in one run is not evidence for a provider. The per-vendor “best against” figures sit on tiny strata: 75% against Fastly is 3 of 4.

Caveats, and who pays. NodeMaven and Oxylabs both appear in slots labelled Sponsored, with links tagged sponsor_ads, the site sells weekly placements, and the announcement invites providers to integrate. Whether inclusion is paid is not stated. One run, one US region; credit the open JSON, which made this check possible. The table was transcribed from small text, though every figure checked matched.
+
Sep 2026 / Operations
A Saved Login Reverts to Monday on Restart, and Playwright’s storageState Skips IndexedDB Unless You Ask
The guide says mint a login once and replay it “for as long as it lasts”. Browserless’s founder explains how that ends: auth sits in cookies, localStorage and IndexedDB; storageState keeps IndexedDB only with indexedDB: true (Playwright 1.51+); a token rotated Wednesday reverts when Thursday’s worker restores Monday’s file.
💡 Ask the app whether you are logged in, not the cookie jar, and re-login only on a positively identified expiry — never on a 500, a timeout or a changed selector, or one site change becomes hundreds of logins
Where a login actually lives. The post, by Joel Griffith on The Web Scraping Club, drops logged-in versus logged-out for three states: valid, stale, recovering. Auth is rarely one cookie: cookies, localStorage tokens, IndexedDB values and OAuth temporary tokens, each on its own eviction clock. A cookie can outlive the server session; an access token can expire while the refresh token works; a rotated refresh token dies when the new one issues. So a snapshot is “a photo of your login on the day you took it”, and a restart after rotation restores a dead token. Checked independently: Playwright’s v1.51.0 notes add the indexedDB option to storageState(), naming Firebase Authentication; older versions silently drop those tokens.

Detect, then recover deliberately. Auth failures rarely throw: a 302 to /login, a 200 that is a sign-in page, a 401, a verification flow. After restoring state, check one or two strong in-app signals (URL not on the login route, an account-only element present), and keep that separate from ordinary failure so a 500, a timeout or a moved selector never triggers a login. On confirmed expiry, log in, then overwrite the snapshot so the next worker does not start from last Monday. Skip sessionStorage: it is tab-scoped, and replaying it can break OAuth redirects and CSRF. Snapshots give ten workers ten isolated copies; a live browser keeps rotated tokens but bets on Chrome surviving weeks of leaks, restarts and deploys. Production uses both.

How it sits next to the guide. The G2 card counts anonymous session life in requests (21 to 106); the 30–50-request rotation in Browsers as a Session Layer is for minted anonymous cookies, not an account you keep for weeks. And a logged-in session is one you accepted terms to obtain: the guide’s cases put hiQ’s loss and Power Ventures’ post-revocation access on exactly that ground, so an automated re-login loop needs stop conditions, not just retries.

The caveat. Vendor experience, not measurement: no failure rates, session lifetimes or target counts. It closes on Browserless’s own Authenticated Profiles and Session API, matching its docs. That keep-alive needs browser.disconnect(), absent in Playwright, is the author’s claim. The one comment is from Kameleo, also a vendor.
+
Sep 2026 / Fingerprinting
Audio Fingerprinting Needs No Permission; the Microphone Path Always Does, and That Line Decides What an Anti-Bot Can Read Silently
One researcher’s essay on audio and CPU fingerprinting frames the browser as the place where software meets hardware. Checked against primary docs, it holds up, and one line deserves drawing sharply: an OfflineAudioContext render read through getChannelData() runs silently on any page, while the raw microphone path through getUserMedia() always needs the user’s permission.
💡 Sort hardware signals by whether they need a prompt; the silent ones are what an anti-bot collects on every visit, so those are the ones your stack has to get coherent
The silent path, verified. The standard probe builds an OfflineAudioContext, feeds an oscillator through a DynamicsCompressorNode, reads 32-bit float samples with getChannelData() and hashes them. Fingerprint’s technical write-up attributes the differences to engine lineage, platform-specific code such as Chrome’s separate FFT on macOS, and floating-point behaviour; on one device it measured Chrome 101.46, Safari 79.59, Firefox 80.95, in 5 to 50 ms, with no permission. The essay’s claim that CPU architecture, floating point and SIMD can shape the output fits that, and it hedges with “potentially”, as it should.

The prompted path. autoGainControl, noiseSuppression and echoCancellation are real audio constraints, and switching them off brings the stream closer to raw input. But getUserMedia() works only in a secure context and, per MDN, “must always get user permission”; a refusal throws NotAllowedError. An anti-bot cannot sample your microphone silently. The author’s own correction is right too: this is not reading the ADC, because browser, OS media stack and driver sit in between. It matters on liveness and verification flows, where the stream itself is judged.

The Google study, scoped. The unlinked reference to Google research on real Chrome clients is Bacis et al., Assessing Web Fingerprinting Risk (WWW 2024, arXiv 2403.15607): tens of millions of Chrome instances on hundreds of thousands of sites, 161 Web APIs including getChannelData, sampleRate and hardwareConcurrency. Its point is that correlated surfaces add less joint entropy than their sum, and they cluster. Our reading, not the paper’s: a value that breaks its cluster stands out more than an unusual but coherent one.

What this is. An exploratory essay: no measurements, no code, no links. Treat its stack model as a map, not a finding. Real users are noisy too: Safari 17 adds audio noise in Private Browsing, Brave randomises output and Tor disables Web Audio, so an odd hash is weak evidence and an incoherent one is strong.
+
Sep 2026 / Law
Public Is Not Permission: Saudi Arabia’s PDPL Lets You Collect Public Personal Data, Not Automatically Use It
Saudi Arabia’s Personal Data Protection Law, fully enforced since 14 Sep 2024, lets a controller collect personal data that is publicly available without going to the person (Art. 10). One practitioner’s reading: collection is not processing. You still need a lawful basis, scraping rules out consent, and legitimate interest fails once any sensitive data is touched.
💡 On a strict reading, a filter that inspects sensitive data in order to drop it has already processed it. Minimise at the selector, not in a cleanup step, and document why before you collect.
What the law says. The PDPL (Royal Decree M/19, 2021, amended 2023) took effect on 14 Sep 2023 with a grace period to 14 Sep 2024; SDAIA regulates it. Article 2 reaches processing “related to individuals residing in the Kingdom by any means from any party outside the Kingdom.” Article 10 makes direct collection the default but allows other sources where the data “is publicly available or was collected from a publicly available source.” Article 6(4), added in 2023, allows legitimate interest instead of consent, provided no sensitive data is processed. Penalties: up to two years and/or SAR 3m for disclosing sensitive data with intent to harm or gain (Art. 35); otherwise up to SAR 5m, doubled on repeat (Art. 36).

The argument, applied to scraping. A GCC Data Protection newsletter (15 Sep 2026) takes one case: a self-styled magazine reposted a data expert’s bio and photo without asking. Article 10 opens the door to collection; using the data still needs a basis. Consent is unworkable at scraping scale; legitimate interest under Article 16 of the Implementing Regulations needs a lawful purpose, balanced interests, no sensitive data, the person’s reasonable expectations, and a documented prior assessment. Article 12 requires a privacy policy first. Its sharpest point: a public figure’s page can carry health details, and a scraper that reads a field to discard it has arguably processed it.

Cross-border enforcement is weak, not absent. The article expects nothing to happen, as the publisher appears to be in Pakistan, and points to Clearview AI. Europe’s record: Italy €20m (2022), Greece €20m (2022), France €20m (2022) plus a €5.2m overdue penalty (2023), the Netherlands €30.5m (2024). The UK ICO’s £7.55m fine was quashed on jurisdiction in 2023; the Upper Tribunal held in Oct 2025 that UK GDPR applies and sent the case back. Reports say the EU fines went unpaid, and noyb filed a criminal complaint in Austria on 28 Oct 2025 naming executives. Hard to collect, but the exposure follows the company.

Caveats. Not legal advice. One practitioner’s newsletter, one anecdote; the “processing by looking” point is interpretation, not SDAIA guidance. SDAIA’s PDFs refused our requests; wording was checked against reproductions and law-firm summaries. A&O Shearman counts 48 PDPL enforcement decisions in a year to Jan 2026; we found none against a foreign scraper.
+
Sep 2026 / Access
Research Bots Instead of APIs: 34 TikTok Sock Puppets, 229,807 Videos, and the EU Rule on Researcher Scraping
A University of Potsdam team ran 34 automated TikTok accounts before three 2024 German state elections, scrolling about an hour a day from servers in each state, and scraped every video the feed served: 229,807. The method, “social research bots”, is the subject of a keynote at the Data Donation Symposium 2026. The EU now has a rule on researchers scraping public data.
💡 With APIs closed, researchers measure the feed by acting as users. The EU says the largest platforms may not stop eligible researchers using public data. Fake accounts remain a separate question.
The method. The team’s paper (Tjaden et al., Potsdam Social Media Monitor, preprint) sets out a pipeline: design bot profiles as experimental treatments, create accounts (email and phone needed; manual creation more reliable), automate them with Selenium or Appium, run them in Docker containers pulling config from a central database, and scrape what each feed shows. Bots varied by age (17–23 vs 44), gender, state and political interest. Each scrolled its For You page, watched interest-matching videos for up to two minutes, ran one hashtag search per session after 35–45 videos, and never commented, followed or posted. Location was real: servers in Saxony, Thuringia and Brandenburg. The paper had university ethics approval (180/2023).

The numbers. 13 Aug–6 Oct 2024; 34 bots (7, 8 and 19 per state), online 49 days on average; 229,807 videos. Party accounts made up 0.18% of feeds, about one party video per 516 minutes of scrolling, but AfD appeared at 5.46 per 10,000 and BSW 5.30 against SPD 1.32 and CDU 1.57: a 3–4× gap for new, neutral users. A 2025 follow-up with Bertelsmann Stiftung scaled to 268 profiles, four platforms and 2.6 million videos.

Where the law now sits. Article 40(12) of the EU Digital Services Act obliges very large platforms to give researchers access to publicly accessible data for EU systemic-risk research. On 5 Dec 2025 the Commission fined X €120m, partly because its terms barred independent collection such as scraping; X’s corrective plan reportedly commits to saying eligible researchers cannot be contractually barred from it. Vetted access to non-public data opened on 29 Oct 2025 under Delegated Regulation (EU) 2025/2050; one documented early request was rejected on five grounds. The ESMD card covers the other end: how to share what you collected.

Caveats. The keynote is on 1 Oct 2026 and the programme gives no abstract; we have not seen it and built this card on the team’s paper and the DSA record. The paper is a preprint; party exposure is inferred from accounts and hashtags. Its claim that TikTok does not prohibit bots in principle is unverified by us: check current terms. Article 40(12) does not clearly cover automated fake accounts, and it does not cover commercial collection. Not legal advice.
+
Sep 2026 / Data quality
A Social “Trend” Can Be Your Scraper’s Outage: Log Coverage, Split Publish Time From Observation Time, and Keep the Reposts
A Web Scraping Club tutorial on social trend pipelines (sponsored, toy examples, no measurements) makes one point the guide lacks: a spike can be a collection artefact. Count mentions by publication time and engagement by observation time, log whether each window was fully collected, and drop your own duplicate fetches while keeping independent reposts, which are the signal.
💡 An hour your scraper was down looks exactly like an hour nobody posted: if the dashboard does not show coverage, a gap reads as a dip and the backfill reads as a spike
Four series, not one. Federico Trotta’s tutorial separates mention volume (distinct posts per window, by publication timestamp), unique authors (per platform unless identities are reliably linked), engagement increments (the change in likes, shares, comments or views between repeated observations of one post) and collection coverage (did collection succeed, which sources answered, did query or sampling settings change). The two clocks are not interchangeable: a post published yesterday gains engagement today. Engagement needs re-visits, so store source ID, publication time and observation time on every row; the sample loop polls a post every 900 s for eight rounds, and the tutorial notes that tighter intervals catch virality earlier but burn the proxy pool faster.

Dedup the fetch, keep the repost. Its exact-match step hashes stripped, lowercased text with SHA-256; near-duplicates go through all-MiniLM-L6-v2 embeddings at a 0.6 cosine threshold, flagging toy pairs at 0.72 and 0.81. The sharper point is the distinction: fetching the same post twice is a data-quality bug, while independent people reposting it is evidence of spread, so dedup collection on the platform post ID, not on content. Relative stamps such as “3h” must be resolved against scrape time; a consequence the tutorial does not spell out is that the displayed value is truncated, so “2d” carries up to a day of error, harmless in daily windows and wrong in hourly ones. Its resolver handles only minutes, hours and days.

The legal footing, briefly. The tutorial is direct that most platforms ban automated access in their terms, that GDPR does not exempt data because it was public, and that handles, bios and locations should be anonymised. LinkedIn’s own robots.txt opens by prohibiting automated access without express permission. The guide’s cases put the safer ground at logged-out public data. One line to reject: that buying from a social-data provider means you need not manage extraction legality; the article’s own summary says a provider does not remove your responsibilities for what you collect and store.

The caveat. A tutorial, not a measurement: two sponsor blocks, five-post examples, no platform-specific extractor, no figures for block rates, lead time or accuracy. The 0.6 threshold is illustrative; the API remarks are qualitative. Not legal advice.

Oct 2026 update. The same author’s market-data piece (Web Scraping Club, 11 Oct; an explainer, no measurements) adds the high-frequency version. Never deduplicate on value, since two trades can share a price: key on a source event ID. Expect late and out-of-order events, and decide before launch whether a published minute bar is corrected, versioned or flagged. Buffer through a stream, not straight into the database. And a price you can see may still bar storage, model training or redistribution.
+
Sep 2026 / Independent corroboration
Agent Stealth Modes Made Detection Easier, Not Harder: 1,383 Honeysite Visits, 0.993 Accuracy Across Three Layers
An Inria/Lille team ran LLM web agents and classic scrapers against nine honeysites and found stealth and undetected modes did not reduce detectability and often raised it, through header drift, non-standard permission values and flagged cloud exits. Combining IP, TLS and browser layers classified visits at 0.993 accuracy. This is the guide’s coherence thesis, measured on agents.
💡 Every stealth patch is a new chance to contradict yourself; for work you can do openly, a declared, signed agent is the cheaper path
The study. Fayolle, Bouhenniche, Pélissier, Laperdrix, Maurice and Rudametkin (Lille, CNRS, Inria, CentraleSupélec, Rennes; arXiv 2606.30119, June 2026) put nine honeypot servers behind UA filtering, reCAPTCHA v3, Prosopo, Anubis, Turnstile and Cloudflare Bot Fight Mode with Block AI Bots. Between 28 January and 24 February 2026 they logged 1,383 visits from OpenClaw, Claude for Chrome, Crawl4AI, BrowserUse, ChatGPT Agent and Skyvern plus six classic scrapers and automation frameworks. Each layer alone was weak: IP 0.596, TLS/JA4 0.454, browser 0.931. All three together reached 0.993 accuracy and F1.

Why stealth backfired. Crawl4AI’s stealth mode added HTTP header variation that its normal mode lacks, and it reported the notifications permission as "default", which is not a standard permission state. BrowserUse’s stealth mode ran through cloud infrastructure whose addresses Cloudflare and Google had already listed. Each patch added a new inconsistency. That is the anti-mage finding reached by a different method on a different population. Without stealth, OpenClaw and Claude for Chrome on Sonnet 4.5 got past every defence, Prosopo included,.

The honest alternative, and its limit. In UC Davis’s FP-Agent tests, Cloudflare’s free AI-bot controls fully blocked only 1 of 7 agents, Manus, the one that identified itself. The rest got through and were also invisible to the site’s policy. The paper lists cryptographic attestation such as Web Bot Auth as a coming layer. A declared User-Agent token plus signed requests makes you legible. One practitioner saw a headed agent pass a Managed Challenge 5 of 6 times from home and 0 of 3 from a cloud server, so test from the network you deploy on. Identity is not access, though: a signature proves who you are, and the site still decides whether a small unknown operator gets in.

The caveat. One study, two Linux machines, Chrome and Firefox only, a four-week window. Canvas and behavioural signals were out of scope. “Stealth hurts” is shown for these tools’ stealth modes, not for every patch. One practitioner countered that correctly engineered patching does not backfire, and the guide’s coherence cards agree the failure is incoherence rather than patching itself. The 5/6 vs 0/3 split is n=1.

Oct 2026 update. The reverse case, n=1: a user was served a slider challenge for the first time on a site visited daily, while Meta’s Muse agent, which press reports say browses from a per-user cloud VM, reached the same site unchallenged. Site, vendor and timing are unknown; this is one false positive against a human, not a rate, and the agent may have been recognised rather than missed. Replies called slider checks security theatre now that bots solve them. Read “not challenged” as a site policy outcome, not proof of stealth.

Oct 2026 update. Fingerprint (vendor post, 6 Oct) says its Bot Detection Smart Signal now labels traffic “inferred to be” Meta’s Muse (bot_type: meta_muse, identity: unknown, confidence high, medium or low), describing Muse as a cloud-VM browser that alters its fingerprint and suppresses automation flags. No method, accuracy or false-positive rate is published, rollout is staggered, and its “bad” label means undeclared, not malicious. A vendor claim that this agent gets named, not missed.
+
Sep 2026 / Research
The Model Pauses Like a Human; the Framework Types Like a Machine. Over 98% of Agent Requests Had Little or No Mouse Data
An LLM agent's think time looks human. Its input events do not: the model picks the action, but the framework emits keystrokes, pointer jumps and clicks. Akamai found over 98% of requests from Comet, Atlas and Claude for Chrome carried little or no mouse movement, and a UC Davis study separated seven agents from humans on behaviour alone at F1 0.9993.
💡 Faking mouse curves adds more signals to be checked; removing UI actions (fetch and read the page instead of clicking) removes them
What Akamai measured. Akamai’s security research team looked at mouse telemetry from Perplexity Comet, OpenAI Atlas and the Claude Chrome extension on instrumented customer sites. 63.2% of agent requests had zero mouse events, 35.8% too few to analyse, and only 1.0% enough for classic trajectory analysis. Their proof of concept is a 44k-parameter masked-autoencoder transformer pretrained on about 3.5 million unlabelled human sequences, with a shallow MLP trained on 2,190 labelled agent sequences. It reached ROC-AUC 0.981 and PR-AUC 0.661, against 0.797 for a one-class SVM. The tells: no idle cursor while reading, no hover before a click, no exploratory scroll, precise clicks and efficient typing, set against pauses of several seconds.

What FP-Agent adds. UC Davis (Wang, Shafiq, Vekaria, arXiv 2605.01247) ran 1,000 trials each of Atlas, ChatGPT Agent, Claude for Chrome, Comet, Manus, Browser Use and Skyvern on a honey site, against 546 sessions from 56 human participants. Fifty behavioural features gave F1 0.9993; browser fingerprints alone gave 0.7969. Each agent types in its own way. Several paste, Claude fires programmatic change events, and Browser Use emits double the change events. All of them jump the pointer straight to the target.

The operating rule. One engineer logged an agent typing a phrase and clicking a button. The model paused for 4.7 s with no input, then the framework sent 15 keys about 8 ms apart and one straight move to near dead centre. Adding synthetic curves on top adds signals a model can check. Fewer UI actions leaves less to score: if you need the content, fetch and parse it rather than drive the page to it.

The caveat. Akamai’s work is a vendor-published proof of concept with a small labelled set, overlap with single-click humans, and a PR-AUC of 0.661 that is far from production-ready. FP-Agent is closed-world: it names known agents rather than spotting new ones. Its humans were undergraduates, and on held-out task types behavioural F1 fell to as low as 0.6303. The keystroke trace is n=1. The figure “0.9994” seen quoted elsewhere is 0.9993 in the paper.
+
Sep 2026 / Measurement
Measured Against Chrome 154: tls-client Matches on All Four Checks, curl_cffi and uTLS Miss One Extension (One Tracker, Days Old)
A new tracker runs Chrome Stable, Beta, Dev and Canary and diffs each impersonation library against them. In the 26 Sep 2026 run, tls-client’s chrome_152 profile matched Chrome 154 on JA4, groups, GREASE and HTTP/2. curl_cffi 0.16.3 (chrome150) and uTLS failed JA4, both lacking the new trust_anchors extension.
💡 Drift is now measurable per extension: a single missing ClientHello extension (0xca34) is enough to move your JA4 off current Chrome
What is measured. The tracker runs Google’s official Chrome builds (Stable 154.0.8037.57, Beta 155, Dev and Canary 156) on macOS and records their TLS handshake and HTTP/2 setup; the author reports that a regular installed Chrome 154 was identical on every check. Each library connects to the same server and gets four pass/fail checks: JA4, key-exchange groups, GREASE placement, and HTTP/2 SETTINGS/window/pseudo-header order. It runs twice a week; tracking began 24 Sep 2026, so the change log is still empty.

The 26 Sep results. All four Chrome channels produced the same JA4 (t13d1517h2_..., 17 extensions). tls-client v1.16.0 with chrome_152 passed all four checks on every channel. curl_cffi 0.16.3 with chrome150 passed groups and HTTP/2 but failed JA4 (16 extensions: missing 0xca34 trust_anchors) and GREASE (no GREASE in signature algorithms). uTLS v1.8.2 HelloChrome_Auto, which resolves to a Chrome 133 profile, missed the same two and also failed HTTP/2 with Go-default settings. Plain requests failed everything, as expected.

Generated fixes. The site also builds candidate profiles from the latest measurement and retests them: a tls-client clone passes all four; a uTLS clone passes TLS but not HTTP/2; a curl_cffi custom-settings attempt got worse (12 extensions). That last result says patching curl_cffi from Python options is harder than switching profile. The practical reading for the guide’s profile-drift card: the drift is real and now quantified, and at this snapshot the gap is one extension and one GREASE slot rather than a wholesale mismatch, which is exactly the kind of small, silent miss a JA4 hash exposes and a human eyeballing headers does not.

Caveat. One author’s tracker, days old, macOS only, three runs at the time of reading. The four checks are the handshake layer only; header order, HTTP/3/QUIC and JS signals are out of scope by design. A pass here is necessary, not sufficient. A related Rust client (Phantom, pre-1.0, 6 stars) claims Chrome 154 parity including QUIC but is unmeasured by the tracker so far.
+
Sep 2026 / Measurement
A Warmed Imperva reese84 Jar Replayed From Three Networks: About 11.5 Minutes Per Token, a Tenth of a Cent Per Warm-Up (One Target)
On one Imperva-protected retail site, a practitioner warmed a session in a real browser, took the reese84 cookie and the incap_* jar, and replayed it with curl_cffi from an Irish residential IP, an Ohio datacenter IP and an Italian home line. All returned the real page. The server asked for renewal after renewInSec: 699.
💡 On this one target the expensive part was the warm-up, not the request: the jar behaved as a bearer token, so warm once in a browser and spend it from cheap clients
What was measured. The Web Scraping Club’s THE LAB #116 (24 September 2026) takes one target, the Irish Smyths Toys storefront behind Imperva. A browser load hits an interstitial, two roughly 800KB obfuscated sensor scripts run, the sensor POSTs a fingerprint blob to a random-path endpoint, and the response carries a token plus "renewInSec":699. That token becomes the reese84 cookie. When Imperva falls back to a GeeTest challenge, a paid solver returned answers in about ten to twelve seconds at about $1 per thousand. With retries included, the author puts a warm-up at around a tenth of a cent.

The replay result. The warmed jar (incap_ses, reese84, incap_sh, visid_incap, nlbi) returned full HTML with no browser, through curl_cffi, from the Irish residential IP that created it, from a datacenter IP in Ohio and from the author’s own connection in Italy. Mismatched TLS fingerprints and a Firefox User-Agent also worked. The author’s summary is that the cookie acted as a bearer token, with nothing binding it to the client that earned it.

What it changes here. The guide’s Imperva profile says datacenter IPs get blocked on sight. On this target, once the jar existed, they were not. The design that follows is the same one the /goto cards reached: do the expensive, stealthy step once per token, then spend the token from cheap infrastructure until renewInSec runs out. The headline’s “thirteen minutes” is the author’s figure. The only number visible to us is 699 seconds, about 11.5 minutes.

Caveat. This is one site, one practitioner, one write-up. The IP tests used three networks, not a sample, and no failure rate over time was published. The explanation of why the replay works sits behind a paywall and we have not read it. Imperva can bind tokens per customer configuration, so treat “no IP binding” as a property of this deployment on this date. Test your own target before you design around it.
+
Sep 2026 / Measurement
Seven Popular Scraping Repos Checked: None Stale, Four Table Star Counts Now Understated Up to 2.2×, Minet Missing
A widely shared list of seven open-source scraping repos, checked against the GitHub API on 28 Sep 2026. All seven are alive: none archived, none relicensed, six pushed within five days. The guide’s own table is what drifted: Scrapling reads 38k stars against a live 84.2k, and one repo, minet, is not in the table at all.
💡 Star counts in a reference table rot faster than the repos do; re-pull them from the API on a schedule and date the column, or drop it
The check. For each repo the GitHub API was queried on 28 Sep 2026 for stars, creation date, last push, licence and archived flag. Scrapling (D4Vinci): 84,167 stars, created Oct 2024, pushed 27 Sep, BSD-3-Clause. Botasaurus (omkarcloud): 5,738, created May 2023, last push 26 Jul 2026, MIT. Crawlee (apify, the JS/TS repo): 25,921, created 2016, pushed 26 Sep, Apache-2.0. Scrapegraph-ai: 31,370, created Jan 2024, pushed 25 Sep, MIT. Katana (projectdiscovery): 17,586, created 2021, pushed 25 Sep, MIT. Trafilatura (adbar): 6,877, created 2019, pushed 25 Sep, Apache-2.0. Minet (medialab, Sciences Po): 376, created 2019, pushed 8 Sep, GPL-3.0. None is archived.

What the guide’s table says. Six of the seven have rows in the #libs table. Four carry star figures that are now well short: Scrapling 38k (live 84.2k, 2.2×), ScrapeGraphAI 18k (31.4k), Crawlee 15k (25.9k), Katana 8k (17.6k). Botasaurus and Trafilatura show a dash. minet has no row: it is a research-oriented CLI for collecting from web pages and platform APIs, the only copyleft (GPL-3.0) licence of the seven, which matters if you embed rather than run it.

The only soft signal. Botasaurus is the one repo not pushed in September; two months is not abandonment, but it is the one to recheck before relying on its anti-detection claims against current Chrome, since those decay fastest. Note too that Crawlee’s JS repo and its Python port are separate projects with separate rows and separate star counts; quoting the JS figure for the Python library, or vice versa, is an easy mistake.

What to do with the table. Either re-pull the star column from the API on a schedule and print the date beside it, or replace it with a last-push date, which is the number that actually warns you off a dead dependency.

Caveat. Stars measure attention, not fitness, and Scrapling’s curve in particular has been driven by social virality. This check says nothing about whether any tool clears a given anti-bot; it verifies only that each project is alive, what licence it ships under, and that the guide’s own numbers need a dated refresh. The source list was an engagement-driven roundup with no testing behind it.
+
Sep 2026 / The other side
An Ex-Cloudflare Detection Engineer Sorts Bot Checks Into 4 Questions. The Guide Uses 8 Layers; Here Is Where They Line Up
A detection engineer who founded Cloudflare’s application security research function reduces bot checks to four questions: what you claim, how you connect, where you connect from, how you behave, most answered before the page loads. It is an insider’s map with no numbers attached, and the guide’s L0–L7 model fits inside it with one notable gap and one addition.
💡 Agents in real browsers weaken all four checks, so the question shifts from “are you human” to “who sent you”, and nothing yet signs the person
The four checks, mapped. (1) What you claim, the User-Agent label anyone can type, is the guide’s L7 declared identity plus the header half of L2. (2) How you connect, the TLS “accent”, is L1 JA4 and L2 HTTP/2, with L0 TCP underneath. His note that good scripts now copy Chrome’s accent exactly, so the check “stopped being enough alone”, matches the guide’s curl_cffi material. (3) Where you connect from, home versus data-centre address, is L5 network identity, and he adds that bots rent home addresses too. (4) How you behave, scrolls, pauses, misclicks against fifty pages a second, is L6 behaviour.

Where the maps differ. His list has no slot for in-page JavaScript fingerprinting (L3) or hardware and OS side channels (L4). That fits his framing that most checks run before the page loads, and with the guide’s point that the server holds a full dossier before any script runs. So read the four as the pre-render front line, not the whole stack. The guide treats layers as scored together for coherence. He presents them as separate questions, though he agrees no single one decides. He also puts request rate under behaviour, where the guide splits it between L2 timing and L6.

What he adds. Because agents drive real browsers, all four checks weaken at once: the claim, the accent and often the address are genuine. Well-behaved agents have started signing requests (Web Bot Auth), but today the badge names the company that built the agent, not the person it acts for. The old question was “are you human?”. The new one is “who sent you?”, and there is no standard yet for a store to tell whose personal shopping agent is at checkout. The guide’s payment-rails cards (Visa Trusted Agent Protocol, Mastercard Agent Pay) are the nearest thing to an answer.

The caveat. This is one practitioner’s plain-language framing, written for a general audience. It has no numbers, no weights between the four checks, and no links. It records how one former insider organises the problem, not a measurement of how Cloudflare scores traffic today. A TikTok anti-scraping engineer endorsed the TLS-as-accent analogy in the comments. Nothing else was added or disputed.
+
Sep 2026 / The other side
One Cloud IP, Five AI User Agents, All Hunting for .env Files (n=1 Report)
A site operator reported a single Google Cloud address cycling through Claude-User, GrokBot, ChatGPT-User, Kimi and Amazonbot user agents while requesting .env files, and said the spoofed requests got 200s where his own plain request got a 404. One unverified log, no methodology — but the lesson holds: an AI crawler’s user agent is a claim, and the operators publish the evidence to check it.
💡 Never trust an AI-agent user agent on its own: check the source IP against the operator’s published JSON ranges. And never borrow one for a scraper — it is trivially disproved.
What was reported. One practitioner described a single Google Cloud IP sending requests with at least five different AI-agent user agents (Claude-User, GrokBot, ChatGPT-User, Kimi, Amazonbot), each probing for .env files, which commonly hold API keys and database credentials. He reported that the spoofed requests received HTTP 200 while his own unadorned request for the same path got a 404, which suggests a rule somewhere was treating those user agents as trusted. That is n=1, from a log screenshot that was not transcribed, with no IP, dates or server config published; treat it as a plausible anecdote, not a measurement.

How to check a claimed AI agent. Most major operators now publish machine-readable ranges. OpenAI serves openai.com/chatgpt-user.json and searchbot.json (the ChatGPT-User feed was regenerated on 25 Sep 2026). Anthropic serves claude.com/crawling/bots.json covering ClaudeBot, Claude-User and Claude-SearchBot, after previously saying it did not publish ranges. Amazon publishes Amazonbot addresses on its developer site. The rule: match the user-agent token, then require the source IP to fall inside that operator’s current feed; anything else is an unverified claim. Where an operator signs requests with Web Bot Auth, a valid signature is stronger evidence than any IP list.

The honest caveat. “It came from Google Cloud” is not proof of spoofing by itself. Anthropic’s own published list, checked on 28 Sep 2026, includes single 34.x and 35.x addresses that sit in Google Cloud space, so the only valid test is the exact published list, fetched fresh, not the cloud provider. We could not verify xAI’s or Moonshot’s (Kimi) verification method, and nothing here confirms who ran the probe. A promotional comment on the original thread pitched NoCapture, a screen-capture-blocking desktop product from Membran Digital; it is a vendor, and irrelevant to HTTP-level verification.

Why scrapers should care. Borrowing a famous AI crawler’s user agent looks like a shortcut past allowlists, but it is now one of the easiest things for a defender to disprove: one lookup against a public JSON file. Once flagged, your traffic sits in the same bucket as credential-harvesting probes, which is reputationally and possibly legally toxic. If you need access, declare an honest user agent of your own or join a verified-bot or signing programme.
+
Sep 2026 / Data quality
Three Causes, One Empty 200: Why a Facebook Group Feed Comes Back Blank
Ask for a public Facebook group’s posts and you can get {"posts": [], "has_next_page": false} with a 200. One vendor founder traced three causes that look identical: a slug that did not resolve, a login wall served to datacenter IPs, and a rotated GraphQL doc_id. The check: an empty feed from a group that exists with members is a wall, not an empty group.
💡 Resolve the group first. If it exists and has members, an empty feed is a failure to raise, never a result to store in the table
Cause one: the slug. The readable group name in the URL redirects to a numeric ID. Asked for posts by a wrong or stale slug, the feed endpoint paginated nothing and returned a clean empty page, while the group-details endpoint for the same slug said not found. The posts endpoint does not validate existence; the details endpoint does. Resolve the numeric ID first and carry it.

Cause two: the login wall on public groups. A public group with over a million members also came back empty. What Facebook served an unrecognised visitor was a login page, not the group, and the author reports it does this far more readily to datacenter IPs. Because the page loads fine, the wall arrives as a 200 with no posts, not a 403. Cause three: doc_id rotation. Working scrapers load the page once for a token, then call the internal GraphQL feed query, which is addressed by a numeric persisted-query doc_id rather than its name. That ID changes when the front end ships, and a scraper pinned to an old one gets empty or malformed responses, again without an error.

The one check that separates them. Call the details lookup, confirm the group is public and has a non-zero member count, then page by cursor; if the very first page is empty on a live, populated group, raise instead of storing zero rows. The same session also hit a fourth, loud failure (connection refused for a period, on a plain Page too), which at least announces itself.

Caveats, and the legal edge. This is one morning, one person, written by a co-founder of a scraping API whose client the code uses, so the discrimination logic is the transferable part, not the product. No failure rates are given. Private groups are only served to logged-in members; tools that “scrape” them run your own session, which breaches Facebook’s terms and risks the account. Public-group scraping still sits under Meta’s terms contractually and under GDPR the moment you store author names and profile links, so collect posts, not member rosters.
+
Sep 2026 / Method
Page 10 Back to Page 1 Is a Bounce, Not a Loop: Store the Page Where a Record Was First Seen
A production crawler on an Indian state RERA site kept tripping its INFINITE_LOOP guard. It was not looping: after listing → detail → back, the site returned it to page 1, so page 2 was never reached. The fix that worked stored the page each record was first seen on and redefined a loop as a repeat with no forward progress. Seven state variables became three.
💡 Seeing a record twice is not a loop. Seeing it twice on the same page, with the listing not moving forward, is. Detect the failure itself, not a proxy for it
The symptom and the wrong model. The loop detector fired because the same records kept reappearing. The cause was navigation state on the server: every return from a detail page landed the crawler back on page 1. The first fix tracked position and skipped forward when bounced, which needed about seven pieces of state (seek flags, target pages, pending keys, retry counters). It was still wrong, because the site could bounce mid-page and whether to skip depended on whether a record had already been seen.

The model that held. Treat the page number as part of the record’s state. The first time a record is seen, store its page, and never change it. When the same record shows up on an earlier page than its stored one, you have been bounced: paginate forward to the stored page and resume from the same row. The loop rule changes to match: same record on the same page with the listing not advancing is a loop; page 10 then page 1 with a known record is a bounce. Three pieces of state replaced seven, and each now lives where it belongs: on the record, not in a crawler-wide cursor that every bounce has to patch. If a fix needs seven flags, the model is probably wrong.

The test that proves it reproduces the failure. A fake listing of 2 pages × 10 records, bouncing to page 1 after every record: with the fix on, 20 of 20 records; with it off, the INFINITE_LOOP guard fires. A test that cannot reproduce the original failure does not prove the fix, and neither does a green run on a site that happened not to bounce that day.

Caveat: one engineer, one site, one synthetic test. No code, no production numbers, no comments corroborating it. The scheme assumes a stable record key and a stable ordering; if the listing reorders between visits, a record can legitimately appear on an earlier page and you will read churn as a bounce. It also costs one forward paginate per bounce, which on a deep listing is quadratic. Where the site exposes a direct page URL or keeps state in a cookie you can reset, jumping straight to the page is cheaper than walking forward.
+
Sep 2026 / Benchmark
Cheapest Is Not Best: TinyFish Wins Median Cost on All Four Openbenchmarks Search Boards but Trails on Accuracy by Up to 20 Points
Openbenchmarks ran search APIs through a fixed agent (same model, budgets, 3 runs) and priced each task including LLM tokens. TinyFish had the lowest median cost on all four Hard Retrieval and Multi-hop boards, from $0.035 to $0.219 per task. But it completes 59.3% vs Perplexity’s 77.3% on search-only retrieval, and scores F1 26.6% vs Parallel’s 46.5% on multi-hop.
💡 Cost per task only means something next to completion rate. Price a search API as cost per correct answer, and TinyFish’s lead shrinks or flips on multi-hop.
What was measured. Openbenchmarks (a YC F24 company) holds the model, questions, prompts and budgets constant and swaps only the search provider. Hard Retrieval is 100 coding-documentation tasks where the answer sits deep in one page among near-identical ones; Multi-hop is 45 company-discovery questions combining 3–4 constraints. Each has a search-only board and a search & fetch board, fetch via the same provider. Extraction runs on gpt-5.6-terra (medium reasoning), judging on claude-opus-5, 3 runs per configuration. Median task cost includes search/fetch fees plus the LLM tokens spent reading results, since every result lands in model context.

TinyFish’s numbers. Lowest median cost on all four boards: $0.035 (Hard Retrieval search), $0.050 (search & fetch), $0.212 (Multi-hop search), $0.219 (Multi-hop search & fetch). On Hard Retrieval search & fetch it completes 79.0 ± 2.0% using a median 12,844 tokens, against Exa deep’s 83.0% at $0.127 and 23,660 tokens — a genuinely good trade: about 40% of the cost for 4 points less.

Where cheap costs you. Search-only Hard Retrieval: TinyFish 59.3% vs Perplexity 77.3% at $0.059. Multi-hop search-only: F1 26.6% vs Parallel basic 46.5% at $1.120. Multi-hop search & fetch: 30.2% vs Exa deep 48.2% at $0.683. Divide cost by success and on multi-hop TinyFish stays cheaper per correct answer, but you get roughly half as many correct answers; if a miss triggers a human or a retry, the ranking can reverse.

Caveats. The operator states no vendor pays for inclusion or ranking, but publishes no further conflict disclosure; the headline framing came from its own CEO and TinyFish thanked it publicly. n is small (45 multi-hop questions), task domains are narrow (coding docs, company discovery), and results depend on the fixed model and prompts. One benchmark, one snapshot; rerun on your own queries before switching providers. The page also runs a 300-question Factual Lookup board, which this spotlight left out, so check whether your workload looks more like lookup than deep retrieval.
+
Sep 2026 / Industry
Over $1B Into Agent Web Access Checks Out Only as Cumulative Totals: Five of the 13 Companies Last Raised More Than 12 Months Ago
A widely shared tally says $1B+ went to companies giving AI agents web access. Checked against announcements, the 13 totals sum to about $1.3B, but they are cumulative: TinyFish, Browserbase, Tavily, You.com and Kernel last raised Jun–Oct 2025. Confirmed in the last year: Exa $250M, Parallel $100M, Oxylabs $130M, Firecrawl $75M, Nimble $47M, Seltz $12.5M, Linkup $10M.
💡 Expect agent-search vendors to subsidise prices while capital lasts, and your proxy or scraping supplier to launch the same agent API you are building on top of it.
What checks out. Exa: $250M Series C at $2.2B, led by a16z, May 2026 (company blog, SiliconANGLE). Parallel Web Systems: $100M Series B at $2B led by Sequoia, Apr 2026, five months after a $100M Series A at $740M, $230M total (TechCrunch, company blog). Oxylabs: $130M from Warburg Pincus at about $3.6B, Jul 2026, its first outside money, with a reported $350M ARR (SiliconANGLE, The Next Web). Firecrawl: $75M Series B led by Smash Capital, 22 Sep 2026, launching Alexandria (SiliconANGLE); the $90M in the tally is cumulative. Nimble: $47M Series B, Feb 2026, $75M total (TechCrunch). Seltz: $12.5M seed, Jun 2026 (Fortune). Linkup: $10M seed, Feb 2026, about $13.2M total.

What is older or cumulative. You.com’s $100M Series C at $1.5B was Sep 2025; Tavily’s $25M Aug 2025 (TechCrunch); TinyFish’s $47M Series A Aug 2025 (BusinessWire); Browserbase’s $40M Series B Jun 2025 ($67.5M total); Kernel’s $22M Oct 2025. So the 13 figures are lifetime totals across roughly 16 months of rounds, not money raised in 12 months; “seven rounds in 12 months” does not match the list.

What it means for scrapers. Heavily funded search and extraction APIs can price below cost for a while, which is good for you now and a lock-in risk later. The infrastructure layer is moving up-stack: Oxylabs now has outside capital and, by one competitor’s account, is preparing an agentic search API, so a proxy supplier may soon compete with the product you build on its IPs. Keep your fetch layer swappable.

Caveats. The tally comes from the CEO of a competing infrastructure provider, whose own claims (profitable, not competing with customers) are unverified. Browser Use’s $17M and an import.io $33M total added in replies were not confirmed against a primary source. Revenue rankings in the category are opinion, apart from Oxylabs’ self-reported ARR. Several rounds here were checked through headlines and snippets of the cited outlets rather than full reads, and valuations are as reported, not audited.
+
Sep 2026 / Reverse engineering
Akamai’s Sensor on One Airline Site Reportedly Inventories 47 Third-Party Script Domains (One Practitioner, One Build)
One reverse engineer who pulled apart the Akamai sensor on LATAM Airlines reports that it logs every ad, analytics and third-party script that loaded, and checks the set for consistency: 47 domains, in order. If that holds, a hand-built HTTP client fails a check nobody thinks to fake, because it loads no page ecosystem at all.
💡 The sensor may be grading the page around you, not just your browser: a real environment loads dozens of third-party scripts, and an empty or wrong set is itself a signal
The claim. A practitioner who builds anti-detect tooling, and has a product in the space, spent a day on the Akamai sensor served by LATAM Airlines. He reports that it collects more than User-Agent and screen size. It logs every ad, analytics and third-party script that loaded on the page, then checks the whole set for consistency. The number he gives is 47 domains loading in the right order. His conclusion is that no requests library reproduces that, so you need a real browser or a hand-rebuilt environment.

How it fits what the guide already says. The Akamai profile here describes sensor.js as a roughly 512KB obfuscated script that runs its fingerprint suite inside the browser, collects 500+ signals across requests, and probes 60 extension URLs. A script inventory is the same idea one layer out. The extension probes ask what is installed in the browser. This asks what the page pulled in around it. Both are hard to fake from outside a browser because the answer comes from the real environment. It also matches the guide’s standing advice that scraping Akamai targets from raw HTTP works only where sensor scoring is light.

What to do with it. If you emulate the sensor payload rather than run it, compare a real browser’s resource list for the target against what your pipeline presents. Aggressive ad or tracker blocking in a real browser could shift the set too, so test with blockers off as well as on. Running a real browser in the loop sidesteps the question entirely, which is the author’s recommendation. It is also the expensive answer, so price it before you adopt it.

Caveat, and it is large. This is one practitioner’s reading of one sensor build on one site, from a post with no links, no code and a screenshot we could not transcribe. We found no written version. We cannot tell from it whether the 47 domains are a hard check, a weak feature, or site-specific configuration, or whether other Akamai deployments do the same. Treat it as a lead to verify on your own target, not an established Akamai behaviour.

Oct 2026 update. A historical April 2025 teardown, by the founder of TakionAPI (which sells an Akamai bypass API), explains why emulating the sensor is per-build work. In v3 the sensor_data payload is scrambled with one key derived from the served script and another from the bm_sz cookie, so a capture neither decodes without that exact script nor replays in another session. It covers the wrapping, not the signals inside.
+
Sep 2026 / Research
498,605 Leader Tweets Behind a Free Key: How One Research Dataset Handles X’s Terms
The Executive Social Media Database collects 498,605 tweets from 62 heads of government and state in 34 countries, May 2009 to June 2023, pulled via the X API plus one archive export. Rather than publish the corpus openly, the author caps anonymous access at 100 rows and gives full tables only to named researchers with a free key, citing X’s redistribution terms.
💡 Sharing collected social data is its own legal question. ESMD’s answer: analyse freely, but gate bulk copies behind a named key and point republishers to ID rehydration.
What it is. ESMD is a PhD-derived corpus of 498,605 tweets with retweet, reply, like and quote counts from 62 executives across 34 countries, spanning May 2009 to June 2023, plus a 142,623-row sentiment table for 11 leaders. It ships as a static web app, Python and R packages, and a Cloudflare Worker API. The README says tweets came from the X/Twitter API in two passes (2022 by handle, 2023 by leader from 2018), a separate Latin American collection, and an archive export for Trump and Obama. Engagement counts are frozen at collection time, so a 2010 tweet has had years longer to accrue likes than a 2023 one.

How it is shared. Anonymous browsing and API calls are capped at 100 rows per request. A free key, requested by email or GitHub issue with your name and purpose, unlocks full Parquet and CSV downloads and a read-only SQL console; only key hashes are stored and downloads are logged per key. Until v1.3.0 the tables sat in the public repo; from v2.0.0 they were moved to a private bucket, and the author says X’s developer terms on bulk redistribution are part of why. Code is MIT; tweet text is described as redistributed for research use, and anyone republishing is told to check X’s terms and prefer distributing IDs for rehydration.

The honest caveats. Posting full tweet text, even behind a key, is the author’s own reading of X’s terms, not a licence from X, and we have not seen X approve it. The README itself warns that raw IDs are unreliable for 12 of 52 source files (Modi’s file has 3,921 IDs across 56,941 tweets), so rehydration would fetch wrong tweets there. 21% of rows are replies, and one automated campaign day is 8.7% of the dataset. The repository was created on 20 Sep 2026 and had 6 stars at review; no peer-reviewed data paper is cited.

Why it belongs here. After X ended free academic API access, many researchers hold corpora they cannot legally re-share as-is. ESMD shows one defensible middle path: document the collection method, freeze and version releases, attach every bulk copy to a name, and route republishers to IDs. If you are building a research dataset from any platform, decide the sharing model before you collect, not after.
+
Sep 2026 / Tool risk
scrapy-stealth 1.0.0’s Remote-CDP Mode Binds an Unauthenticated, Credential-Injecting Proxy Relay to 0.0.0.0
scrapy-stealth 1.0.0 (24 September 2026) lets the browser driver attach to an external CDP endpoint via STEALTH_CDP_URL. Read the source before you point it at a remote browser: in that mode the local CONNECT relay that injects your proxy credentials binds 0.0.0.0 with no client authentication, so anyone who can reach the port rides your paid proxy.
💡 The 1.0.0 headline feature quietly turns your Scrapy host into an open, credential-injecting proxy on its LAN — firewall the host or keep the browser local until the relay gets auth
What the code does. In utils/browser/relay.py, resolve_browser_relay_bind_host returns 0.0.0.0 whenever external CDP is on, and the advertised address is the machine’s outbound LAN IP, found by a UDP connect to 8.8.8.8. _start_browser_relay in _core.py then calls asyncio.start_server(handle, bind_host, 0). The handler never checks who connected. It reads a CONNECT line and adds Proxy-Authorization: Basic, built from your STEALTH_PROXIES user and password, before forwarding upstream. Under DNS-pin-only use it dials any host:port directly. The changelog describes the bind change as “auto-config” and says nothing about exposure.

When it bites. This happens only with STEALTH_CDP_URL set, and the changelog routes authenticated proxies and DNS pins through the relay. So the exposed case is exactly the one where a credential is attached. A locally launched Chrome still binds 127.0.0.1. On a cloud VM with a public IP and an open security group, “LAN” means the internet. Any host that can reach the port and send one CONNECT spends your residential bandwidth under your account. Its traffic, abusive or not, leaves from your exits.

Two smaller side effects. The first is a disk write. Importing the browser engine runs patch_nodriver() at module load, and it rewrites a stray 0xB1 byte in the installed nodriver/cdp/network.py inside site-packages, swallowing any error. The second is network traffic. Building the middleware starts a background thread that queries pypi.org with a scrapy-stealth/<version> update-check User-Agent, and no setting turns it off. Its “fingerprint rotation” is a weighted pick of curl_cffi and wreq profiles with header sets to match. In the browser it is only --window-size and --lang, with the stealth itself coming from nodriver.

The caveat. This comes from reading source at the 1.0.0 tag, not from exploiting a live deployment. The relay port is ephemeral, so an attacker has to find it first. Nothing is published for pass rates, and the repo is three months old with 3 stars. The fix is to firewall inbound traffic or keep the browser local until the relay requires auth.
+
Sep 2026 / The other side
Mellowtel: When the Residential Exit Node Is a Browser Extension
Mellowtel is an SDK that extension developers embed to sell their users’ idle bandwidth. SecureAnnex found it in 245 extensions (~909k installs) across Chrome, Edge and Firefox; Spur Intelligence and a Northwestern IMC ’26 paper followed. The open-source client strips content-security-policy and x-frame-options so target pages load in hidden iframes.
💡 An extension-sourced exit node is a real browser on a home IP, so it passes IP and fingerprint checks. Ask any vendor whether its pool includes extension SDKs.
How the SDK works. In the public mellowtel-js repository (npm 1.7.0), the client opens a WebSocket to wss://ws.mellow.tel and receives crawl jobs. It then adds declarativeNetRequest session rules that remove x-frame-options, content-security-policy, cross-origin-embedder-policy and cross-origin-opener-policy from responses. That lets it load the target in a hidden iframe inside the user’s own browser. Before running, it checks a per-extension kill switch at mellowtel-bucket.s3.us-east-1.amazonaws.com/switch/<extensionId>.txt. SecureAnnex, reported by Risky Business, says the library waits for the user to go idle and linked it to Olostep, a scraping API.

What the measurement found. The Northwestern AquaLab paper (Jang et al., IMC ’26) checked Chrome Web Store data and ran live tests. Only 16 of 116 Mellowtel-equipped extensions actually injected crawl iframes during a session. US nodes got up to 8.7× more crawl tasks than other regions. The work split into two patterns: shallow crawling of a long tail of domains, and repeated structured pulls from a few popular targets such as Google Search, CouponFollow and Reddit. The authors also found targets flagged for malware and a dynamic code-loading path that runs remotely fetched JavaScript and bypasses store review.

What it means for buyers and defenders. For buyers, removing the headers weakens the user’s own browser, and the user may be browsing an intranet on a work machine. A pool built this way carries that risk no matter what opt-in screen came first. For defenders, IP reputation and fingerprinting won’t flag this traffic because it really is a residential Chrome. What gives it away is behaviour: many sessions opened in iframes with no parent-page context, and the same structured pulls repeated across unrelated home IPs.

Caveat. Spur sells residential-proxy detection, and its own write-up could not be retrieved (a Vercel bot checkpoint). The figures here come from the IMC paper, SecureAnnex’s count and the SDK source. Mellowtel says users are opted out by default, must opt in explicitly and can revoke consent. It claims 10,000 active supporters, which fits the paper’s finding that the extension counts are an upper bound.
+
Sep 2026 / Tool risk
Scrapy 2.19 Opens a Code-Execution Endpoint in Every Crawl by Default: a Live-Debug Win and a Same-User Risk
Scrapy 2.19 (10 September 2026) ships a RemoteControl extension enabled by default that lets anything holding its token run Python inside a live crawl over localhost HTTP. The new official Scrapy MCP server is built on it: an agent can ask why the queue stalled or try a selector on a response already downloaded, without a restart. The same default is a new attack surface.
💡 Every 2.19 crawl now opens a code-execution endpoint whose bearer token sits in a file in your profile; turn it off where no one debugs live
What shipped. The release notes say it plainly: a RemoteControl extension, enabled by default, “which allows connecting to crawl processes via HTTP and running code inside them”. It needs asyncio (the default reactor), listens on a random localhost port, requires a bearer token, and writes the port and token to a job file under the user state directory (REMOTE_CONTROL_JOBS_DIR, default scrapy/job_files). Defaults: 30 s per call, 600 s ceiling, 64 KB of output. The scrapy-mcp-official server (Apache 2.0, Python 3.10+) reads those files and exposes four tools: list_jobs, status, execute(job_id, code) and inspection_reference, a map of scheduler queues, stats and engine objects so the agent knows where to look.

Why it is genuinely useful. The telnet console always offered this, but only to someone who knew the internals. A stalled queue, a slot stuck on one domain or a selector failing on one layout is state that disappears on restart; inspecting it while it happens, and testing a fix against responses already in memory, removes the stop-edit-rerun loop that makes long crawls expensive to debug. The MCP server does not need installing into the crawl’s own environment.

Why it is also a risk. The project’s own README warns the server “is designed to run arbitrary Python code inside your crawl process”, with access to settings, environment variables and every secret the crawl holds. The trust boundary is same user, same host: any process running as that user can read the token file. That includes a coding agent that is itself reading scraped, attacker-controlled HTML, which is exactly the indirect prompt-injection path this guide warns about. On shared runners, CI boxes and containers that mount the home directory, set REMOTE_CONTROL_ENABLED = False unless you mean to attach.

What is not established. This is a vendor announcing its own feature, with no measurement: no numbers on time saved, no audit of the token or endpoint, no statement on whether agent-run execute calls are logged. The risk reading above is this guide’s inference from the documented defaults, not a demonstrated exploit, and the telnet console carried a similar same-host exposure for years.
+
Sep 2026 / Data quality
A Turnstile Challenge Behind a 200 OK: Detect Markers Before Trusting Status
A pricing feed went dark while every response read 200 OK: the body was a Cloudflare “Just a moment…” interstitial, and the pipeline stored it as data. The fix is a pure detector over {status, headers, html} that checks for a challenge before trusting any status, since the same interstitial arrives as 200 or 403.
💡 Classify ok / challenge / hard_block from the body and headers, challenge first. A 200 is permission to inspect, not permission to store.
The failure. Crawlbase’s postmortem describes a price feed that stopped producing records at 02:14 UTC while transport looked healthy. Every fetch returned 200 OK; the body was a Turnstile interstitial titled “Just a moment…”. Because the write gate was the status code, the challenge HTML flowed downstream as if it were a product page. The guide already warns that a 200 can hide failure; what this adds is a concrete, copyable signal list for the Cloudflare case and an ordering rule.

The signals. HTML markers, matched case-insensitively: challenges.cloudflare.com/turnstile, a cf-turnstile container, the __cf_chl_ and cf_chl_opt namespaces, a Just a moment title and Checking your browser text. Header: cf-mitigated: challenge is decisive on its own; server: cloudflare and a cf-ray only tell you who fronts the site, not that you were challenged, so never classify on them alone. The detector is a pure function with no network calls, which is the point: you can replay it against saved responses.

The ordering and the verbs. Three outcomes, checked in strict order: challenge (any marker, whatever the status), then ok (no marker and a 200), then hard_block (error status or other block signal). Challenge runs first because the interstitial comes back as either 200 or 403, so a status-first branch misfiles it both ways. Each outcome gets a different action: only challenge is rerouted to a rendering path, hard_block means back off the host, and the rerouted response is run through the same detector again before it is written. Keep saved challenge pages as regression fixtures, and chart the reroute rate: a jump is your earliest sign the target changed its protection.

The caveat. This is a vendor postmortem, and the reroute step is Crawlbase’s own paid JavaScript token; its 39.1% valid on generic US proxies versus 99.5% via its API is self-reported with no method published, and the 8,000 solves per second figure is a concurrency model, not a benchmark. The marker list is the part to take, and it is only as current as Cloudflare’s markup, so verify it against a fresh challenge page before relying on it.

Oct 2026 update. A marker list misses gates with no challenge vocabulary. One httpx request to Amazon’s homepage (2 October 2026, n=1) got a 200 and a 3,781-byte page saying only “Click the button below to continue shopping”, with no CAPTCHA or robot wording to match. Counting one expected element caught it. A day earlier the same block came back as a 202 on nine Amazon stores and an empty 200 on three, so run the content check on every 2xx, not only on 200.
+
Sep 2026 / Tool risk
The Most-Installed Stealth Plugin Has Not Shipped Since Chrome 110
The most-installed stealth package in scraping has not shipped since Chrome 110. puppeteer-extra-plugin-stealth 2.11.2 was published on 1 March 2023, the repo’s last push was July 2024, and npm still counts about 835,000 downloads a week. Browserless’s new BAP SDK is the vendor answer: stealth as a server-side endpoint, not a package.
💡 Check the publish date of every stealth dependency before blaming the target: a frozen evasion layer ages with every Chrome release, silently.
The registry numbers, checked. The npm registry lists 2.11.2 as latest, published 2023-03-01; the GitHub repo berstend/puppeteer-extra is not archived but was last pushed on 2024-07-18 with about 275 open issues. npm’s downloads API reports 834,764 installs for 15–21 September 2026. Chrome has moved about 44 major versions since. Every evasion in the package is a JavaScript override of a property as Chrome 110 exposed it, and the guide already covers why that is a liability: a patched getter shows up under Function.prototype.toString() inspection, and new detection surfaces appear with each release that nobody is patching.

What BAP does differently, per its docs. BAP does not speak CDP. Each Page method builds one BrowserQL (GraphQL) mutation and sends it as a single JSON frame over a WebSocket, queued serially; that is why its method set is a subset of Puppeteer’s. Stealth is a route change, /chromium/bql to /stealth/bql, where the fingerprint work lives in the browser build and adds “entropy injection” per request. type() defaults to a random 50–200 ms per keystroke, and both type() and click() scroll into view first. emulationOs swaps UA, Client Hints, platform, GPU, cores, fonts and audio latency together, but timezone and locale follow the proxy, so you must match them.

The practical upgrade path. If a Puppeteer fleet still imports the plugin, treat it as unmaintained: pin, audit, and test the target with it removed, because an override can be the tell. Your options are a maintained patched-driver stack, a patched browser binary, or a hosted stealth endpoint like this one. The hosted route moves maintenance to the vendor but also moves the browser, the proxy and your traffic onto their bill.

The caveat. This is a vendor launch by the vendor’s founder, and neither the post nor the docs nor the launch blog publishes a single pass rate, detector score or benchmark; “what it patches” is not listed. BAP is not a drop-in replacement, lacks full Puppeteer parity, and the Python SDK is “coming soon”. Test /stealth/bql against your own targets before believing it.

Oct 2026 update. Browserless’s OS-emulation page now says emulationOs takes four lowercase values, silently ignores anything else (macOS included), and defaults to Linux; its changelog moved the cloud default to Windows, then back. The desktop list omits fonts. The founder’s demo used bot.sannysoft.com, a weak page, and its GPU (Intel) differs from the run log (NVIDIA). A commenter suspects toString() exposes it; untested.
+
Sep 2026 / Measurement
DataDome's 2026 Outside-In Test: Most Popular Homepages Stopped None of Ten Bots
DataDome's 2026 report pairs a year of its own customer logs (1T+ requests, 75,000+ sites) with a June 2026 test firing 10 bot types at 21,491 popular homepages from residential IPs. 65.3% stopped none; 2.4% stopped or challenged all, down from 8.4% in 2024. Its own logs put bots and agents at only about 26.5% of customer traffic.
💡 The default assumption for a random target should be “probably unprotected”, and an honest AI-agent user agent passed most sites in this test
What the outside-in test actually measured. In June 2026 DataDome sent ten simulated bot profiles — ranging from simple scripts through bots impersonating recognised AI services, clients with forged browser fingerprints, and automated browsers — at the homepages of 21,491 popular sites across 15 industries, from residential addresses in the US, Canada and France. A site counted as fully protected only if it stopped or challenged every profile. 65.3% stopped none, 2.4% stopped all (8.4% in the 2024 edition), spoofed AI agents got through on over 70% of sites, and only 5.5% caught forged fingerprints. Telecom was weakest at 82.9% unprotected. For scraping, that is a base rate: most homepages you point a client at are not running a bot defence that reacts to anything.

The number that disagrees with this guide’s other bot shares. The same report says humans generated about 73.4% of requests on DataDome’s customer sites, bots and agents about 26.5%. Set that next to Cloudflare’s 57 to 58% bot share of HTML requests and Imperva’s 53%, and the lesson is the denominator: a population of sites that already runs bot protection, measured after classification, is a different thing from HTML requests across a CDN. None of these is “the share of the web that is bots”.

The growth figures, read as attempts. Bad-bot traffic +124% against human +13.2% (the “9x faster” headline); scraping is 70.9% of bad-bot traffic and up 185.2%; AI traffic +82.3% to 52.7 billion requests; in H1 2026 AI agents sent 605.6 million requests to logins, forms, carts, payment and sign-up pages, 51.7% of them to logins. The report itself says traffic classified as an attack is an attempted action, not a successful attack.

The caveat, stated plainly. This is a bot-protection vendor grading the market it sells into: “65% unprotected” is also a sales pitch, the ten bot profiles and the pass/fail rule are not published in detail, the test only touched homepages (many sites protect login and checkout, not the front page), and “scraping” is whatever DataDome’s classifier labels scraping. The primary report page refused automated fetches, so figures here come from DataDome’s press release and consistent trade-press summaries. Treat the protection test as a directional base rate, not a census.
+
Sep 2026 / Architecture
Browser Harness Hands the Agent Raw CDP and Lets It Write Its Own Helpers — and Its “600 Lines” Is Now Nearly 2,000
Browser Use’s Browser Harness drops the fixed action set: the agent drives Chrome through one editable CDP websocket and, when a helper is missing, writes it into agent_helpers.py mid-task. The launch post sold it as about 600 lines; three core files in the repo now total 1,975. Its own skill file says: plain public page, use curl.
💡 The thin harness moves the maintenance burden from the framework to the agent’s workspace — and its own skill file still tells the agent to reach for curl before the browser
What it is. browser-use/browser-harness (created 17 April 2026, about 18,000 stars when read in September 2026) attaches a local daemon to your running Chrome over the DevTools Protocol after you tick the box at chrome://inspect/#remote-debugging. The agent sends Python via a heredoc to a browser-harness CLI with helpers pre-imported, and raw CDP is always reachable as cdp("Domain.method", ...). The core under src/browser_harness/ is protected; the agent writes new helpers into agent-workspace/agent_helpers.py, which ships at 7 lines. The launch essay, The Bitter Lesson of Agent Harnesses, argues that every wrapper like click() is a decision imposed on a model that already knows CDP from training data.

The worked examples. Per that essay, the agent hit a missing file-upload tool, wrote an upload_file() helper on raw CDP, then switched to a chunked strategy after hitting a 10 MB websocket payload limit, and handled cross-origin iframes and shadow DOM without an abstraction layer. These are vendor anecdotes with no run count or success rate. A companion post describes the cloud version: a second agent reviews each finished trajectory, extracts a “skill” (URL pattern, recipe, steps to skip) behind an LLM PII gate, and reports one Duo 2FA button id reused by 254 later agents.

Lines, measured. The essay cites run.py 13 lines, helpers.py 192, daemon.py 220. Fetched from main in September 2026 they are 413, 668 and 894, beside new modules for auth, MCP, recording and macOS approval, plus about 97 community domain-skills/ folders (Amazon, Booking.com, arXiv and others), off by default behind BH_DOMAIN_SKILLS=1. The core grew about 4× in five months, so the core does keep growing even though agents also write their own helpers.

The caveat, and the scraping rule. This tool is aimed at logged-in, personal browsing. One daemon holds one mutable current tab, and the skill file warns that two agents switching tabs at once can race and screenshot each other’s page. It routes captcha-prone scraping to its paid cloud, a first-party upsell. And its own SKILL.md says a public page “needs no browser”: try curl first, and only if that fails or returns a shell page, move up to the browser.
+
Sep 2026 / Economics
Cheap Code Cut Scraper Break-Even by Two-Thirds on Easy Sites and About a Tenth on Hard Ones — Because Bandwidth Stayed the Bill
The Web Scraping Club reran its 2023 build-or-buy analysis with LLM-written code: break-even volume fell about two-thirds on easy sites, more than half on medium ones, barely a tenth on hard ones. The inputs explain why. A build session costs $7.50 in tokens, but a rendered page is 1.7 MB at $8/GB, and that cost comes back on every request.
💡 An LLM shrinks the one-time cost of writing a scraper, but the per-request cost is still set by bandwidth, so the harder the target, the less the cheap code saves
The inputs, as published. Pierluigi Vinciguerra’s piece prices three tiers of site: light (HTML, e.g. ikea.it, mediaworld.it), medium, and hard (full browser render, e.g. etsy.com, asos.com). Developer time is $20/hour. Code is built with Claude Opus 5 at $5/$25 per million input/output tokens, about 1M input + 100K output per easy scraper, 3× that for medium and 15× for hard. Pages are 100 KB median for HTML and 1.7 MB median for a full render. The buy-side prices are residential proxy $4/GB, scraping browser $8/GB and unblocker API $1.50 per 1,000. Maintenance falls from about 2 hours a month per scraper (2023) to about 30 minutes for simple fixes, with no gain when the anti-bot changes.

The arithmetic, done here. Token cost per build is $5 + $2.50 = $7.50 (easy), $22.50 (medium) and $112.50 (hard). Per request, an HTML page over residential proxy costs about $0.0004, and a rendered page through a scraping browser about $0.0136, 34× more. At 100,000 hard requests, the article’s threshold for building to beat buying, the bandwidth alone is about $1,360, twelve times the token bill. That is why the article concludes: “The cheaper the code got, the less it helped on the targets where the code was never the expensive part.”

The split it recommends. Build the code layer, which now costs close to nothing and belongs to you. Buy the infrastructure layer (proxies, unblocking, browsers) unless scraping is your core business. Run execution on capacity you already have. It also notes a “5% nobody sells”: targets such as Chinese e-commerce sites with custom obfuscation, which vendors do not find worth serving, so you still need in-house R&D. Building is “cheap to start now and expensive to leave.”

The caveat. The piece is partly paywalled. The readable part stops after “No model does that part for you yet,” before the per-tier break-even request counts, so only the percentage drops are confirmed. The per-request figures above are our own multiplication of the published inputs. The article’s own “near-zero” code cost assumes a team with an established LLM workflow, and it does not count the monitoring staff a built fleet needs.
+
Sep 2026 / Data quality
Ask a Decision Model “Does This Look Like a Price” and Close the Spider When It Says No
This guide’s quiet-failure section says the dangerous break is the one that returns something — a selector drifts, the value still looks real, and nothing in the log fires until a customer finds the export. A Zyte engineer wired the cheap verifier into exactly that gap as a Scrapy pipeline: score a sample of items field by field, and close the spider when the pass rate drops.
💡 It is affordable because it is sampled and the model is a decision model, not a generator — four fields across a million items comes to about $30, which is less than one corrupted delivery costs to explain
The mechanism. scrapy-jev ships three parts: a JevClient that reads fields through itemadapter and returns a score per field, a QualityGatePipeline that scores a fixed sample, and a self-registering addon. Every field on an item must clear its threshold for the item to pass — deliberately not a majority, because one bad field should fail the item — and the spider closes once the sample’s pass rate falls below the configured level. Defaults: sample 20 items, pass rate 0.7, per-field 0.5, pipeline priority 400, model typesafe/jev-latest through OpenRouter’s alpha decisions endpoint.

The numbers, all from the author’s own demo. A three-field scoring request ran 570 input tokens and cost $0.000024, against $0.042 per million input tokens with output free; a four-field item across a million-item crawl works out at roughly $30. Responses land in 70 to 500 ms. Breaking the SKU selector so it returned the title scored that field 0.08 to 0.35 while the other fields stayed above 0.9; blanking the name field failed every item in the sample and stopped the crawl. Renaming a price class correctly did not fire, because the regex extraction still worked — the gate is watching the value, not the selector.

The framework trap is worth the read on its own. Raising CloseSpider from inside a pipeline does nothing: Scrapy wraps process_item in a bare except Exception, so the close is swallowed like any other error and the crawl carries on. The working version calls crawler.engine.close_spider_async directly. That is Scrapy behaviour rather than anything to do with this model, and it will bite any quality gate written as a pipeline.

What it cannot do, in his own words. It only catches breakage that produces an implausible-looking value — “a selector that starts pointing at the wrong product on the page, but returns something that’s still shaped like a real name” passes cleanly. It is sample-based, so a crawl can run through bad items outside the sampled window. It adds a hard dependency on an external API that can be slow, rate-limited or unavailable. And the thresholds are “guesses… tuned against one demo site” — a fictional 1,000-product coffee shop he built — with, he says, no evidence yet that a 0.7 pass rate is right for production. Read it as a vendor-employed engineer publishing a working pattern and its defaults honestly, not as a validated production configuration.
+
Sep 2026 / Reverse engineering
Search Guard’s VM All the Way Down: 98 Handlers, a 15-Round ARX Cipher, a 635-Byte Record
The guide’s Search Guard card argued the top layer — which properties the script reads — is enough to act on, and that most write-ups skip straight to the bottom and make the work look like magic. Someone has now done that bottom layer properly against a dated build and published the tooling, which makes the numbers checkable even though the reasoning is not.
💡 The tooling is MIT and the narrative is paywalled — so the reproducible half is free and the explanation costs money, which is the inverse of the usual bargain
What was recovered, against one dated capture. Build f6ad334abefc, captured 15 September 2026. The interpreter separates from its encrypted program at 62,751 characters, with the encrypted p field at 16,831; lifted to readable form it takes 39 MBA rewrites and 4,258 formatted lines. The program image is protected by an ARX block cipher at 15 rounds, round constant 1819, rotation 3. The dispatch table holds 98 handlers with 93 operand layouts, sorted into control-flow, alphabet, string and re-key families. A 2,000-call dispatcher trace (1,780 outer fetches, 220 queued dispatches) ran 1,220 instruction starts covering 52.1% of the image bits with 127 emit operations, yielding a 635-byte plaintext record before SG_SS encryption.

The moving-target detail is the durable lesson. Google changed handlers, opcode assignments, cipher parameters and execution flow while the research was running, forcing rebuilt profiles, repeated traces and revalidation against fresh challenge pages. So a recovered opcode table is a photograph of one build, not a capability you own — the same maintenance tail this guide flags for every pinned artefact, and the reason the cheap top-layer map keeps earning its place.

The publishing model is worth copying. The tooling is MIT on GitHub and the licence explicitly does not extend to captured Google code or to artefacts derived from a supplied response; analysis runs offline against a capture you make yourself. That is the same ethical shape the earlier card praised, and better than shipping someone else’s obfuscated bundle in a repository. The derivation, the diagrams and the failed hypotheses sit behind a paid subscription, and the README is explicit that the tools stay usable without it.

The caveat. Four stars, no forks, no issues, no pull requests — nobody has reviewed this. Every figure above comes from the repository’s own README describing one author’s run against one capture of one build, not from independent replication, and the reasoning that would let you judge the method is the part you cannot read. Useful as a map of what the bottom layer costs, not as a verified result — and the earlier card’s point stands untouched: if all you need is which properties the challenge reads, none of this is required.
+
Sep 2026 / Reversal
The Messier Profile Was Accepted Every Time. The Pristine One Was Refused Around a Fifth of the Time.
This guide’s spine is that anti-bot moved from checking fingerprint values to checking whether they agree. A researcher running a browser engine in-process reports a controlled A/B that cuts across that: same network path, everything fixed but the device profile, and the less coherent profile was the one that passed. Worth holding beside the coherence cards, with its evidence problem stated plainly.
💡 If a trust prior is assigned to your device class before your signals are weighed, coherence is necessary and nowhere near sufficient — you can be perfectly consistent inside a badly-scored bucket
The claim. Rather than driving a headless browser, he runs the browser’s own rendering and JS engine in-process, no UI, at ~0.3 s per solve. Holding the network path and everything else constant and changing only the device profile, the messier, less-coherent profile was accepted every single time while the pristine one was refused about 20% of the time. His reading is that these systems weight risk by population and segment, so an identical borderline signal is scored differently depending on which bucket it arrives in.

Why this sits beside the coherence finding rather than replacing it. The anti-mage result — every anti-detect build scoring “modified” across 23 cross-surface checks while stock browsers scored a flat zero — is about whether a browser contradicts itself. This is about the prior applied before that reading is weighed. Both can hold at once, and if they do, a coherent profile in a poorly-trusted segment loses to an incoherent one in a well-trusted segment. For a defender the consequence is sharper than for a scraper: a consistency check alone is a weak gate, because the cheap response is not to fix your signals but to migrate to a better-trusted segment.

The operational lesson survives even if the numbers do not. His own summary is that every wrong conclusion he reached came from inferring rather than instrumenting, which is the same discipline the bracketed-control slider run demonstrates elsewhere in this guide: hold one variable, keep a known-good control open and close the run, and believe the brackets rather than the theory.

The evidence is the weak part, and it has to be said out loud. There is no link, no published methodology, no raw data, no sample size and no named target. “100%” and “~20%” are quoted without denominators or run counts, the post carries a single reaction, and the author is describing his own unpublished research while openly looking for work in the field — every incentive points at a striking framing. Treat it as a hypothesis worth instrumenting on your own targets, not a result that overturns anything. The cheapest test is the one already in this guide: bracket it.
+
Sep 2026 / Correction
Hermès Re-Measured in Rust: The Listing Endpoint Graded the Request Shape and Issued No Token
The guide's Hermès-without-a-browser card is a guest post by Hyper Solutions, which sells the paid DataDome-solve API it uses. An independent Rust re-measurement agrees most of the site needs no browser, bounds the expensive half, and finds something better: on the listing endpoint DataDome issued no token at all, and 403 versus 200 on the same URL, same empty jar, was request shape.
💡 On this one Hermès listing endpoint the same URL was 403 as a top-level navigation and 200 as a same-site CORS fetch — DataDome graded why you were asking, and issued no token either way
The cheapest client's failure is free intelligence. curl gets a Cloudflare 403; a perfect Chrome User-Agent gets a byte-identical 4,545-byte 403 — keyed on the handshake, so no header value moves it. wreq 0.16 walks home → category → product 200/200/200 with zero cookies out and datadome absent on every hop, and --cold, an empty jar per hop, still serves all three.

Same URL, 403 or 200, by how you asked. bck.hermes.com/products as a top-level navigation (sec-fetch-mode: navigate) returns a 1,130-byte DataDome block page; issued as the page's own JS does (Origin, Referer, sec-fetch-mode: cors, sec-fetch-site: same-site), the identical URL with the same empty jar returns 200 OK at 35,042 bytes, no token anywhere. Scope it to one endpoint on one site: DataDome rules are per customer and path. Inverts the lean-headers card: there fewer headers, here the right kind.

Where it bounds the existing card. Cold fan-out over the per-product endpoint logs 1 of 8 extracted, 7 blocked; a freshly minted 21-cookie session gave, in his words, 2 successes instead of 1. One ~80-second mint bought one to five products — his average, not a canonical cost. On a 12-product queue one browser took 12/12 in 58 s, mint-and-rotate over three exits 7/12 for 244 s of browser — 7× dearer on that one run. Two variables move: a clean residential line versus commercial exits the post reports are refused on product URLs. The penalty is not per-request: the product page then 403d from that address, reportedly still blocked 14 hours later — one observation, no measured TTL.

The caveat is large. n = 1 — one target, one connection, runs of 8 and 12 products, self-reported; only the repo's public MIT status was independently checkable. Nor is it a clean A/B: the card it corrects warmed that endpoint with Hyper Solutions' paid solves, so whether it is intrinsically metered or merely punishes under-warmed sessions is unresolved, not refuted — and the 7× counts browser-seconds, excluding the paid-solve line that write-up was optimising.
+
Sep 2026 / Proxy engineering
Proxy Detection That Reads No IP and No Fingerprint: Two Kernel RTT Counters, One Division
Two projects nine months apart score proxying on latency alone, reading no IP and no fingerprint: Aroma (December 2025) divides two round-trip counters the Linux kernel already keeps, and Vastel’s Live proxy activity row on fingerprint-scan.com (September 2026) scores the same timings. Rotating the pool moves neither, and neither publishes an accuracy or false-positive rate.
💡 You cannot subtract the hidden hop — the client→proxy leg is time the server’s own transport clock never paid for, so the fix is an exit close to the client, not a slower one
The deployed detector is one division. Sakura-sx/Aroma (Fastly VCL, 122 stars, 21 December 2025, no licence file) scores a visitor with client.socket.tcpi_min_rtt / client.socket.tcpi_rtt — the kernel’s min and smoothed RTT from tcp_info. Bands: 1.0–0.7 normal; 0.7–0.3 normal on an unstable link; 0.3–0.1 possibly a proxy; below 0.1 flagged; the VCL blocks only under 0.1, a cut its author set deliberately low. No IP intelligence at all, so it misses most VPNs; scoring and blocking sit inside if (client.socket.tcp_info), so an HTTP/3 visitor is never scored — the shelter side of the HTTP/3 card. Its README: “not ready for production.”

The only measured separation in this family is five years old. Nikolai Tschacher, June 2021, n=23: a browser WebSocket RTT against the server’s SYN+ACK-to-ACK handshake delta. 11 visits he could only assume were clean (“I cannot say for sure”) agreed to within 0.7% to 13.8%; 12 samples through exactly two providers, Bright Data and ScrapingBee, ran “45% to 136% larger”. Both clocks sit at the server: through a CONNECT tunnel the transport clock times only the proxy→server hop, the application clock also the hidden client→proxy leg. Aroma is not that comparison (no websockets, its author says), and 2021 measures neither newer detector.

What none of this shows. Neither detector has a published accuracy or false-positive rate. Vastel’s post says only that the row uses “network timing features”, though he “mostly trained/tested it using residential proxies, so it may underperform on data center or ISP proxies”; the richer account (CatBoost, ~27 latency-and-ratio features) rests on one comment we could not independently surface — reported, not confirmed. Aroma’s field numbers are single-operator HN self-reports: a Ping Proxies founding engineer catching “a resi-proxy about 50% of the time” against his own company’s pool, benmmurphy about 5% against his own CONNECT proxy, “maybe only that often due to a bug”. Instruments, not verdicts.
+
Sep 2026 / Measurement
Three Runs, 90 Fixed Targets: The 40-Point Jump Was a Harness Change, and the 32–0 Was Arm Sizes
This guide cites usestring’s Web Data Frontier Benchmark twice, each time from a different run. The repository now publishes three dated result files — 15 July, 11 August and the run its README labels 15 September 2026 — so we recomputed all three from the attempt-level data. Every published number reproduces exactly. Two of the conclusions drawn from them do not.
💡 Every published figure reproduced to the cent — and both headline conclusions still turned on a configuration change and a column size, one of which the maintainers caught before we did
The largest move in the series is the benchmark editing its own adapter. On the 90 targets and 15 providers common to all three runs, ScrapingBee goes 33.3% → 32.7% → 73.3%: +40.7 points on identical URLs. Three commits to src/providers/scrapingbee.ts landed 6–7 September 2026, and maintainer commit edba7b7c names the cause: it had fetched the targets “from the default datacenter pool with neither premium_proxy nor stealth_proxy”, finishing last at 29.7%. So the mechanism is the proxy tier, not the rendering switch: render_js: true on that pool in July and August, mode: "auto", max_cost: 75 in September. Four providers ran byte-identical adapters in both runs (Decodo largest at −17.1); only their movement is chargeable to a provider.

The 32–0 against JavaScript rendering is column sizes — and the maintainers said so first. Among six caller-selected providers the README reports 32 of 100 targets passed by an unrendered provider alone against none by a rendering one, 37–0 at a majority and 45–2 at all five; all three reproduce. But that arm holds four providers (Scrapfly 86.2, ScraperAPI 84.0, Scrapingdog 45.6, ScrapingAnt 36.4) against two rendering (Decodo 50.6, ZenRows 41.2), and commit 15b8bc19 (6 September 2026) had already called that revision “an artifact of how the two columns were built”. Strength-matched, our recomputation gives 29–14, 25–26 and 15–29; no provider ran both ways on one target, so flag and provider stay inseparable.

The latency column charges a provider’s failures to the providers that succeeded: no verified content on a target earns the nearest-rank p75 of successful providers’ scores. So ScrapingAnt’s 18.95s is 47 own fetches against 52 borrowed, and 36.4% success ranks faster than Scrapfly’s 86.2%.

Caveats, and whose numbers these are. Vendor-owned repo: string tops all three runs (95.8%, then 97.0% twice), though the README volunteers that it routes server-side and is “not parameter-comparable with a pinned-tier provider”. The 90-target comparison, the matched arms and the borrowed-latency split are ours; the +40.7 attribution is the maintainers’ diagnosis plus dated commits, not a controlled A/B.
+
Sep 2026 / Tooling
Two Missing supports() Calls Made One Storefront Fetch Vue Nine Times Per Page: 676 Requests Against Chrome’s 432
Lightpanda’s benchmark repo holds a file more instructive than its win table: a July 2026 investigation in which the vendor’s own harness recorded stock lightpanda “losing to headless Chrome by ~46% warm” on one Shopify storefront. Nothing errored. Two feature-detection APIs were absent, and the theme’s loader answered by re-fetching the same module nine times a page.
💡 A lightweight engine’s missing DOM API does not throw — the page feature-detects, fails the probe, takes its legacy loader, and re-fetches the same module nine times per page
The mechanism, in the vendor’s words. RETAIL-INVESTIGATION.md in lightpanda-io/agent-benchmarks, dated 4–19 July 2026, one target: allbirds.com, a “Shopify, Vue-based theme”, collection page plus three product pages. Their diagnosis: “HTMLScriptElement.supports and DOMTokenList.supports (relList) don’t exist, so the theme’s loader falls back to fetch()-based chunk loading.” Nothing errored — feature detection is built for a negative answer — so the loader took its legacy path and fetched vue.esm-bundler.js nine times per page against Chrome’s once. HAR over two pages: stock 676 requests, Chrome 432, patched 305. One theme, one site, the engine’s own authors.

The fix is stricter than return true — and it was the smaller lever. Merged lightpanda-io/browser#2886 answers true only for classic/module/importmap and validates rel tokens against their defined set: a probe answering true unconditionally trades a latency bug for a coherence tell. The patch alone moved the retail median 5.98 s to 5.22 s, 1.27× Chrome to 1.17×, still behind; parity came from a default instead: lightpanda installs no HTTP cache unless --http-cache-dir is set, and that flag alone gave 4.82 s (−19%), which the document calls statistical parity with Chrome’s 4.70 s on overlapping IQRs; both together 4.60 s at 1.03×. The ~46% and the 1.27× anchor on different Chrome baselines.

The caveat, and what survives the fix. Every figure is the vendor measuring itself: reported medians, raw per-run data not recomputed, no independent replication found — absence of evidence, not refutation. Loss and wins are two months and two configs apart: July, stock, allbirds.com, against a 15 September 2026 launch post on another target, after #2886 merged and with the cache on. Both causes of the ~46% are closed in the published config. The lesson outlives the patch: engine gaps arrive as latency and request volume, not errors. Before trading a heavyweight browser for a light one, diff request counts against a real Chrome, and confirm a cache directory is set.
+
Sep 2026 / Tool risk
12,468 Stars and No OSI Licence: DrissionPage Bars Commercial Use, and the Grant Expires on a robots.txt Breach
DrissionPage, a CDP-native Python library at 12,468 stars (GitHub API, 21 Sep 2026), has no row in the guide’s 86-library table. A LinkedIn post (17 Sep) warned only that it “is not MIT.” This is no OSI licence: it bars commercial use without the author’s authorisation and voids itself on a breach — one of four conditions is the Robots protocol.
💡 The libraries this guide recommends ship BSD, MIT or Apache and say nothing about what you point them at; this one makes the Robots protocol a condition of the grant itself
The licence, and the three places it ships. The LICENSE at g1879/DrissionPage is a bespoke dual-language grant permitting use “in their personal capacity, but only for the purpose of learning and legal non-profit activities”, and an organisation “may not use the project’s source code or binary form for commercial purposes without authorization”. Then the clause the post skipped: “Authorization will automatically expire if any of the terms are violated during use” — and the fourth term is “Follow the Robots protocol and do not… collect data that is prohibited by law or the system’s Robots protocol.” The same terms ship in LICENSE, the README’s terms-of-use section and the installed package’s own __init__.py docstring — verified in the 4.1.1.4 and 5.0.0b1 wheels. The commercial-use clause stops a commercial estate outright; auto-expiry bites whoever has authorisation.

Why an identifier-keyed licence check sees nothing. The GitHub API returns spdx_id: NOASSERTION; PyPI’s 4.1.1.4 record has license and license_expression both null and no License :: classifier. The identifier is missing, not the text — the wheel METADATA carries License-File: LICENSE. An SPDX-keyed gate sees a blank; a scanner reading License-File or the package’s docstring surfaces the condition. Grep your lockfiles for drissionpage before it reaches a commercial pipeline.

What none of this establishes. Not legal advice: “commercial purposes” is undefined, automatic expiry on breach is a unilateral term of untested enforceability, and we found no record of it being enforced. It does not make robots.txt binding — see the legal section; what is new is a library making it a grant condition. Two claims stay attributed: “runs faster” than Selenium is the author g1879’s own claim about his own project, with no published benchmark, and the post’s “ecosystem of MCP servers and CLIs for LLM agents” is reported by the post, not confirmed by us. The README also carries five residential-proxy affiliate placements — odd beside a non-profit-only licence.
+
Sep 2026 / Correction
webclaw’s 95.1% Is a 50-Page Extraction Score, Not a Bot-Protection Rate
This guide prints 95.1% as webclaw’s “success rate on bot-protected sites.” It is not a success rate at all: it is the founder’s own boilerplate-removal accuracy, scored on 50 pages against three extraction libraries that never fetch anything. This guide’s own library table had the tool right — medium targets, JavaScript unsupported.
💡 Before asking who defined success, check that the number is a success rate at all — this one was scored against readability, trafilatura and newspaper3k, parsers handed HTML that never fetch
Where the 95.1% comes from. One vendor-self-reported table: founder 0xMassi’s DEV launch post, 24 March 2026, whole methodology “Tested on 50 real pages across news sites, documentation, e-commerce, SPAs, and blogs.” Extraction accuracy 95.1/83/80/66 — webclaw against readability, trafilatura and newspaper3k, parsers handed HTML that never fetch and carry no TLS fingerprint. No bypass percentage appears in the post, README or homepage.

The one hands-on test, scoped. Antonello Zanini, The Web Scraping Club, 15 Sep 2026: the CLI got clean Markdown from L’Occitane, Temu and Nike, then failed on Cloudflare-protected Hermès and Canada Goose — as did the paid cloud tier, on the same pages: “the Cloud API wasn’t able to scrape any pages that the CLI couldn’t already scrape… may not be working as expected.” Scope it: anti-bot coverage, not the paid tier, which did deliver on plain JS rendering where the CLI did not. The vendor claims only that the API “attempts” protected-page handling.

What is real, and two fixes. Rust CLI plus hosted API: AGPL-3.0, v0.6.22, Firecrawl-compatible /v2 — Firecrawl code repoints without a rewrite. primp TLS impersonation of Chrome 142 appears only in the March post — reported as-of-launch, not confirmed current. Post-only numbers: extraction core 0.8/3.2/12.1ms at 10/100/500 KB excluding network, one article at 4,820 → 1,590 tokens. Two errors: the #ai link and library row 404 (live github.com/0xMassi/webclaw), and pip install webclaw is printed for a Rust binary with no PyPI package (npx/brew/cargo/Docker).

The caveat. The benchmark is the vendor grading himself, no scoring definition or page list: unverifiable. The review is one person, one session, one target set, publishes no pass rate, hedges “At least, in my tests…” and discloses a sponsored 1,000,000-credit plan — in his favour. The post names no limitation at all — no JavaScript challenges, CAPTCHAs, Turnstile — silence, not candour; the overclaim lives in its headline, “bypasses Cloudflare without a browser.”
+
Sep 2026 / Independent corroboration
Warm-Up Replicated on Three Days to a Pooled z = 12.0. Three of the Post’s Numbers Are Not in the Repo.
Our warm-up card carried the ladder as one interleaved run at z = 5.82, with a neutral rung deferred to “the next run, not a conclusion.” Those runs landed: two more days against Google, pooled z = 12.0, plus a held-session phase that changes what warming costs. It reached us through a LinkedIn post by the vendor’s CEO, so repo and post are worth separating.
💡 Warming is a replicated effect now, not one run — and because follow-up queries on an already-served session almost never fail, the cost is paid once per identity, not once per query
The replication, with denominators. The README now says the effect “held on three separate days.” Two later runs carried only the cold and deepest rungs: 24% against 86% over 88 and 86 judged attempts, then 19% against 84% over 42 and 61. Pooled over the three days, one harness, one target (google_serp): 20.0% (33/165) cold against 84.4% (152/180) at depth 7, z = 12.0, recomputable from rows committed in data/runs/ (filenames as the README gives them, unconfirmed). Drift: its depth-2 rung now reads 24% over 34 against our card’s 19% over 48.

The held phase is what makes warming affordable. NOTEBOOK.md: 108 of 109 held rows passed, by position 36/37, 36/36, 36/36. The README’s separate held.py cut gives 96%, 98% and 99% at positions 2, 3 and 4, no denominators printed. The bill — “at --dwell 20,45 the three warm rungs average 65, 130 and 195 seconds per identity” — leaves the post’s “four minutes” reported-plausible, not measured, and no per-session total is published. Every NOTEBOOK.md figure here is a partial read: it fetched truncated.

The caveat, and the post-only numbers. The README discloses “built and maintained by NodeMaven, who sell proxies” and the post is their CEO, so “residential sessions work well once warmed” is a vendor’s claim about its own product. Three of its figures — 71% of refusals arriving as HTTP 200, a refusal 76× smaller than a real answer, 10 MB per session — are in none of the repo’s three write-ups: absent from what we could read, not from the data (no .jsonl row was opened). The fourth, 609 of 612 follow-ups, is an unreconciled divergence, not a refutation: three undenominated positions at ~204 rows each would be 612. Its cited PDF is unreachable outside LinkedIn; every check ran against the repo.

Still unmeasured: “what the depth is doing is not in these rows.” The owed run: clear the jar at the deep rung, same exit and profile, rule pre-committed — near 82% the state is target-side, keyed to the exit; near 11% the browser’s own jar carried it.

Oct 2026 update. NodeMaven, which sells proxies, shipped the ladder as an MIT tool, google-browser-scraper (created 1 October, 27 stars on 12 October). Its launch figures, 33/165 cold against 152/180 warm, are this card’s pooled runs, not a test of the tool, which dwells 3–8 seconds per warm-up page against the measured 20–45. A fourth run now reads 19% (13/67) against 88% (257/291), z = 11.8.

Oct 2026 update. v0.0.3 (15 Oct) and v0.0.4 (17 Oct) hold an exit for up to 50 queries, not 10, run several browsers on one queue, and start up to 10 new exits from copies of the first served exit’s cookies, on the README’s unmeasured claim that acceptance “follows the cookies more than the address”, which bears on this card’s owed jar-clearing run. The CEO’s 99.7% served and 2.2 MB per search are post-only, with no run file. Headful on Windows only.
+
Sep 2026 / Reversal
The Runtime.enable Stack-Getter Tell Died in Chrome 138, Not 137 — and the Inherited “name” Read Outlived It
This guide names Runtime.enable as an automation tell in six places, always as a timing or handshake artifact — never the mechanism behind the name: a getter on an Error’s stack that fired when an attached inspector previewed the object. Two V8 commits closed that getter in May 2025, and the shipped branches settle which milestone carries the fix and which read is still live.
💡 The own-stack getter trick has been dead since stable Chrome 138 — but name is never an own property of an error, so the error-description helper still resolves it down the prototype chain
The two commits that ended it. V8 closed the preview side effect in value-mirror.cc under bug 415094795: 61a907540d (2025-05-06) and e08e973474 (2025-05-07, committed 2025-05-09), both by Pavel Feldman. That bug is behind a Google sign-in wall, so its title and status come from commit footers.

Which Chrome: 138, not 137. Only crawlex names a milestone, the M137 train, and it is wrong. Chromium Dash: M137 branches 2025-04-28, stable 2025-05-27; M138 branches 2025-05-26, stable 2025-06-24 on V8 13.8 — both commits land in that window; the code agrees: 13.7-lkgr’s value-mirror.cc (75,978 bytes, byte-identical to tag 13.7.152.14) has no getErrorProperty; 13.8-lkgr has the guard; no merge-back. Caveat: those proxy the shipped M137 branch: branch-heads/13.7 returns HTTP 403 on googlesource file logs.

What survived is the inherited name read. getErrorProperty() takes GetOwnPropertyDescriptor and returns empty when the own getter is script-backed; a prototype getter does not evade it, per the expectation file from e08e973474. But stack is an own accessor, while name is spec-inherited and never own — its empty descriptor falls through to object->Get(context,"name") and the page’s Error.prototype accessor. Read out of shipped source, not run against a Chrome build.

Who says so, and what nobody measured. The obituary is Castle’s — anti-bot vendor, own blog, no measurements, no version — and “only basic bots” is what a vendor says about a method it just lost. Scrapfly reports one run on one Chrome 152 build: Brotector saw stackLookupCount:0 but nameLookupCount:3 under Runtime.enable — not reproduced here, and not a rate. dev.to/shuicici (unreviewed) named getErrorProperty first; the branch check is ours. Vendor prose came via a summarising pipeline; commit metadata, diffs and source are byte-exact. A human with DevTools open trips the same path, so the old stack tell scores noise; the startup handshake shape is untouched.
+
Sep 2026 / Correction
213 Direct Links, Zero /goto: One Clean Control Softens Our Flat Trigger Rule
This guide’s 324-token /goto teardown card says flatly that manually driven Chrome gets it too, so automation is not the trigger. A clean control breaks that: one fresh, hand-driven Chrome profile on one query returned 213 direct links and zero wrappers. The debug-port arm everyone repeated agrees with our existing source — the control is the half that moves our text.
💡 The wrapper is not a flat tax every Chrome pays — one clean control got none of it, and nobody has identified what the classifier actually read
The measurement, at its size. Pierluigi Vinciguerra (The Web Scraping Club) ran one query on one machine, one home connection, arms minutes apart. Fresh profile, no debug port: 0 wrapped, 213 direct, 215 total. Same profile with an unattached --remote-debugging-port: 96 / 0 / 106, then 83 / 0 / 93. Two flag trials against one control, one query, one account state — not a controlled study.

It corrects our sentence, not the other study. ScrapingBee’s teardown also used a debug port and also got wrapped links (“remote debugging was not required to trigger the new link format”), so both agree there. The divergence is the baseline: their hand-typed profile did get it, this one did not, so our line becomes “not the only trigger.” An unverified reader report of /goto in hand-driven incognito and Firefox fits neither model: the trigger is unsettled.

No mechanism, and the flag may have been inert. An unattached debug port is a localhost listener no remote server can observe, and the CDP tells never fire; no explanation is offered, so the flip is observed and the channel unidentified. Our Chrome 136 card notes the flag needs a non-default --user-data-dir, and the post names neither a Chrome version nor a profile directory — so either the arms differ twice over or the port never opened.

The caveat, and one anomaly the post passes over. Total links nearly halved — 215 against 106 and 93 — so something beyond link format differed between arms; the author’s stability check holds within an arm only. A week later he downgrades himself: signed-in state outweighs his two windows, and “the classifier looks at more than one signal.” It spread via a LinkedIn post by Stan Sadokov, CEO of proxy provider Nodemaven, whose cookie-farming prescription is in no primary source, labelled “not a proven fix” there; the vendor replies under it are self-reports.

The cheap check. A 12-request resolver probe found no client and no Referer effect, so the redirect costs what our open-questions card prices. Count wrapped versus direct under your own launch config before budgeting an extra request per result.
+
Sep 2026 / Correction
The /goto Destination Was Still in the Response: One Sampled SERP Paired 8 of 8 Web Results With Zero Extra Requests
This guide's /goto cards price resolution at one GET per unique token, and the token is still AEAD-style ciphertext, undecodable locally. Three September sources add that Google still ships the destination in the same response for most result types, in the render-state blob its own JavaScript reads. Where that holds, the per-token GET is the fallback, not the common case.
💡 The token is still undecodable, and on the one sampled SERP that barely mattered — the destination sat beside it in the page's own render state, so resolution starts as a parsing job and only a named residue costs a request
Where the destination is. OpenWeb Ninja — a SERP API vendor, an interested party — tested on 8 September 2026 and found the destination still next to the token inside “the page's server-rendered data blob”, so pairing the two from the page itself “costs no extra request”. Nothing decodes anything: wrapping replaced the href and left the page's state object alone. Google published no explanation; treat the mechanism as observed, not motivated.

Coverage, and the real residue. On that single capture the blob paired 7 of 8 web results, 9 of 9 forum results and 6 of 7 organic results on an AI Overview page; the one web miss is a YouTube video whose URL sits in the inline player's data-curl/data-surl attributes — so on that sample all 8 web results came out of the response already paid for, no /goto call. Named residue: Lens image matches, some AI Overview video sources. Scrape.do, describing its own product, ships the two-tier shape: link from the page “at no extra cost”, resolveGoto (default false, no extra credits) for the rest. Aberastegue's Google-Goto-URL-Extractor, with nothing to sell, never calls /goto and pairs by “smallest common subtree—the deepest shared ancestor”, not fixed array indexes — the part that survives a layout change.

The arithmetic, and the caveat. Nozzle's 500–1,000 requests for one five-page ranking and the ~110 req/s from one home IP stay true as the fallback budget: parse what you already paid for, resolve only the residue. The stake is silence, not the bill: GrowByData, self-reporting from its own product with no methodology published, saw YouTube's share of voice fall from 13–15% to near zero over six broken days to 1 September 2026. But it is one capture, one date, denominators of seven to nine — single-sample counts, not a benchmark. Both corroborating sources sell SERP APIs and published no raw data; the independent one publishes no coverage figure at all; nobody has published a plain-curl confirmation either way, and ScrapingBee's and byteiota's denials never examined page state — silence, not contradiction. Prove it on your fetch path first.

Sep 2026 update. A practitioner write-up adds Google Images to the list: destination URLs there still appear inside the page’s JavaScript data, so image results start as a parsing job too. For the residue it gives the same method this guide already uses: GET with redirects disabled (allow_redirects=False) and read the 302 Location header. It does not contradict the HEAD finding. The Images claim is one unmeasured report, and we could not read the full article.
+
Sep 2026 / Research
One Vendor, One Run: WebRTC Read on 23 of 71 Profiled Top-100 Pages, Anti-Debug APIs on 70
Automation Benchmark, Tyler Richards’ monitoring service, loaded the 100 most-visited pages in an instrumented browser without interacting, and counted which fingerprinting APIs anything on the page reads. Of the 71 that surfaced, 21 families rank, and WebRTC lands second from last. One beta-stage vendor, one run, no crawl methodology published: a rough ranking, not a census.
💡 In Automation Benchmark’s single top-100 run, something on the page read anti-debug APIs on 70 of 71 profiled pages and WebRTC on just 23 — last but one of 21 families, and the one forums argue hardest about
The ranking, and both denominators. Seven families hit all 71 surfaced pages, among them introspection, navigator, timing and unresolved receiver. Then anti-debug 70, storage 69, platform features 68, workers 67, math+engine 65, screen+window 64, automation artifacts 60, crypto+random 57, canvas+webgl 56, fonts+text 56, audio 44, media+sensors 40, webrtc 23, other 7. Integers are the post’s, percentages our arithmetic — automation artifacts 84.5%, webrtc 32.4% of 71. Against the 100 attempted, the denominator readers assume, webrtc is 23% and anti-debug 70%; 11 came back walled, 18 did not finish. Snapshot 2026–09–14.

Prevalence, not detection. It counts any script, so 70 pages reading anti-debug APIs is not 70 sites running a defence — the vendor says as much: a tall bar is a popular signal, not proof of detection. The 21 labels are never defined, unresolved receiver least of all, so the seven 100% rows are six plus a question mark. canvas+webgl, the scraper-forum obsession, is 56/71, behind storage (69) and workers (67). And 23/71 is an upper bound on the denominator The WebRTC Trap and The UDP Leak lack: touching an API is not comparing an ICE candidate against the proxy exit, and zoom.us is in the tables using RTCPeerConnection as a feature. No case against Camoufox’s geoip=True, which also aligns timezone, locale and Accept-Language; navigator and timing are 71/71.

Whose number it is. Richards Software LLC’s Automation Benchmark is beta-stage, this is its blog’s only post, and this guide recommends it — which raises the bar on its self-published figures, not lowers it. No crawl methodology is published: browser build, trapping, timeout, headless versus headful and exit geography unstated. The 29 walled or unfinished sites are unnamed, so reading them as the toughest defenders is our inference. And 84.5% automation-artifact reach is one snapshot of 71 unusually instrumented pages: the nearest independent measurement, Gundelach et al., arXiv 2606.14525 (June 2026) over the first 10,000 Tranco sites, found 3,649 of 7,944 scanned sites (46%) probing a Tier 1 automation signal and navigator.webdriver read on 2,730 (34%). Neither replicated.
+
Sep 2026 / Method
Five Bracketed Cells on a Live DataDome Slider: Only the Incoherent User-Agents Were Blocked
A practitioner writing as 0xlearner put five cells through a live DataDome slider on idealista in 62 seconds — a known-good control opening and closing the run, one variable changed inside each pair. The only two cells blocked were the two whose user-agent contradicted the rest of the browser. Both controls wore a Windows user-agent on a Linux host and solved, so the gate is coherence, not honesty.
💡 Honesty was sufficient here, not required — what got blocked was a browser whose story contradicted itself, in either direction
Five cells, one variable per pair. Same IP, drag and code path: control-open_fortress (Fortress Chrome/150, Windows UA) SOLVED → local-touch (stock Chrome/153.0.8010.5, X11; Linux x86_64) SOLVED → local-touch-win (same binary, Windows UA forced via --user-agent) BLOCKED → fortress-linux (Chrome/150.0.0.0, bare (Linux) token) BLOCKED → control-close_fortress SOLVED. Both failures are incoherence, not weakness: a Windows UA over a Linux navigator.platform, and Mozilla/5.0 (Linux) is a token desktop Chrome never emits.

The post’s headline flag is reported by the author, not measured inside this run. --blink-settings=maxTouchPoints=10,availablePointerTypes=6,… (printed truncated) is credited with flipping a hard-blocked local Chrome to SOLVED, but that leg sits outside the brackets: ~26 minutes earlier, stealth layer off, logged “no slider served” — the signature of a gate earlier than device coherence.

Anti-bot A/B testing is stateful, so the control is not optional. The post reports “six failing attempts in twenty minutes” and that a burst degrades the egress IP for ~15 minutes, after which every cell fails identically to a genuine device-signal block — a false negative with no signature. Verbatim figures, but one narrated observation from one home connection. The protocol is cheap: control, tests, control again; discard unless both pass. Nothing announces this window and testing causes it, so unbracketed browser-versus-browser tables may be scoring IP decay.

The caveat is large. Every cell is n = 1 inside a 62-second window, two sites, one home connection — a verdict flipping with the user-agent, not a success rate. The baseline is Fortress (tiliondev, BSD-3-Clause), which its own README calls a “stealth Chromium engine” — a vendor claim about its own project — and which lists DataDome under Roadmap. The behavioural layer is near-untested: one replayed trace (55 points, 242 px, 1,246 ms) was never refused, which is not the same as undetectable. Bylines differ (Tauqeer Abbas on LinkedIn, 0xlearner on the article), unconfirmed as one person.

Oct 2026 update. A 21 Jan 2026 post on the blog of TakionAPI, which sells DataDome bypass, reports that DataDome switched on a “noPuzzle” captcha globally: the slider stays, but its end position became a fixed value, so no image needs solving. If that still holds, these cells tested coherence and the drag trace, not puzzle-solving, which fits the result. The same post dates the plv3 VM token’s arrival on captcha and interstitial (see the plv3 card). Vendor account; whether the puzzle has returned is unverified.
+
Sep 2026 / Economics
Nine Minted G2 Sessions Ran 21 to 106 Requests, and Cookie Count Did Not Predict Which
This guide’s Browsers as a Session Layer card prescribes rotating sessions every 30–50 requests with no measurement behind it. A teardown of G2 behind DataDome, by the operator 0xlearner, counted it over nine mints: 21, 48, 61, 67, 70, 94, 98, 106, 106. A 5× spread no constant serves — and his blocks were tracking his own stealth layer.
💡 A minted session carries a finite, unpredictable request budget — nine draws ran 21 to 106 on one target — so the control has to be a consecutive-block breaker, not a constant
An arithmetic slip. He calls the nine draws “Median around 75”; his shipped code comment calls a session “good for roughly 100 requests, after which *every* request is 403”. Sorted, the median is 70, 74.6 is the mean, and ~100 is the top of his distribution, not its centre. A spent-session 403 is indistinguishable from a detection 403, so a naive retry burns requests against a corpse. Cookie count is no guide either: “a 21-cookie session once failed its first request while an 8-cookie session served 98.” Whether DataDome counts requests or runs a saturating score is inferred from the 403 wall, never observed; a 5× spread is weak evidence for a literal counter. His control trips on N consecutive blocks and requeues pending jobs onto a fresh session; the guide already breakers the proxy rotator, so the new part is requeue-don’t-drop for sessions.

The blocks tracked his stealth layer, not CDP. Vanilla google-chrome on a fresh --user-data-dir loaded the category page with no challenge, on the same binary and IP being blocked under CDP. Bisecting one layer at a time found his evasion script redefining window.outerHeight as innerHeight + 85. Magnitude is not the tell — real headed Chrome without a bookmarks bar shows roughly 85–90 px there. Invariance is: hardcoded, the delta never moves on resize or with a bookmarks bar, where a real one’s does. His fix disables stealth on headed runs. Treat it as reported: one vanilla load, a sequential observation, not a paired A/B, in a week the target changed behaviour twice; it does not show CDP is safe at volume.

What this does not show. n = 9 sessions, one target, one DataDome deployment, one operator, self-published, no raw logs or harness; DataDome is tuned per customer and path, so 21–106 may belong to this deployment. That the budget is counted in requests rather than elapsed time is his reading, not a tested result, and his run log shows 578 requests at 18.3% success that we could not place as pre- or post-fix. He does name a candidate: two clean mints ran 21 vs 94, which he says “points at something per-exit” — exit IP and ASN, uninstrumented. Carry the instrument, not the numbers.
+
Sep 2026 / Benchmark
One Retry Flag Moved ScrapingBee Premium From 79.5% to 64.5%, and Two Providers Refused 22% of General-Web Requests
The guide names how retries are counted as a vendor-controlled variable and tells you to log failure_owner=proxy_policy, neither with a number. A Sep 2026 benchmark on ScrapingBee’s own blog (eight configs, 400 requests each) sizes both: one flag moved a headline from 79.5% to 64.5%, and two providers each refused 22 of 100 general-web requests.
💡 A success rate has two invisible denominators — the retries folded inside it, and the requests your provider refused to send at all
One flag is worth fifteen points of headline. ScrapingBee Premium scored 79.5% (318 of 400) with its retry layer on. transparent_status_code=true, which surfaces the first response, drops it to 64.5% (258 of 400) — Amazon 98% to 67%, Google 20% to 0%. The reusable tell is the latency table, and it is the guide’s inference, not the benchmark’s claim: Premium’s median was 1.88s against a P95 of 118.23s, a raw Oxylabs proxy 1.08s to 2.94s. Retries hit a minority of requests and never move a median — medians alone cannot tell you how much of a success rate was bought with time.

The second number nobody benchmarks: requests the provider declined to make. Oxylabs and Decodo each refused 22 of 100 general-web requests at the proxy, the same 11 domains both rounds — the whole gap between their published 76% general-web score and 97.4% (76 of 78) on requests they allowed through. AirProxy and NodeMaven refused nothing, so the set is per-provider and undocumented: a league table that does not separate refusals from blocks is partly ranking legal departments. Count refusals as their own column on a trial key before signing.

No mobile pool cleared the bar on Google. All six standalone pools returned 0.0% usable SERPs over 100 Google requests each — a /sorry/ page or a SERP-less 200 counted as failure — and NodeMaven’s SOCKS5 arm also scored 0 of 100. Only managed paths scored: 20% plain Premium, 82% Stealth. Six pools at zero is a floor effect, not a verdict on carrier quality. NodeMaven’s Stan Sadokov called the comparison unlike-for-like on LinkedIn; whether the author conceded is not retrievable.

What it does not show. Published on ScrapingBee’s own blog, and Stealth tops the table at 94.0% on 200 requests rather than 400: the winning arm rests on half the sample. Italian exits only, no test dates, no provider ordering, no runner OS, so time-of-day is uncontrolled — the warm-up card’s largest confound. The “Google changed something upstream” account is relayed from Oxylabs support, a vendor on its own zero.

Oct 2026 update. The disclosure above is incomplete. ScrapingBee has belonged to Oxylabs since June 2025, and on 13 October 2026 Oxylabs, Decodo, ScrapingBee, Webshare and Honeygain were presented as one ecosystem, Source, with shared compliance and use-case vetting standards. The publisher, the raw Oxylabs proxy, Decodo and the support account quoted here are siblings, so the identical refusal lists are plausibly one policy rather than two. Read it as a group-internal benchmark.
+
Sep 2026 / Economics
Zyte’s Own Published Tiers Span 124× — Wider Than the Per-GB Versus Per-Success Gap Between Vendors
The guide carries the performance half of this argument twice. The other half of the invoice is the unit you are billed in — per-GB or per-success — and the difficulty tier a vendor files your target under. Published September 2026 rates say both move the bill further than the brand does, and that the widest spread is not between two vendors but inside one vendor’s own price list.
💡 Price a vendor as billing unit × your median response size, then check which difficulty tier it files your target under — both move the bill further than the logo on it
The quoted comparison, corrected. ScrapeOps, which resells what it compares, prices 1 million 10 KB responses at $56 on Oxylabs Web Unblocker against $1,500 at $1.50 per 1,000 successes: rates multiplied out, not a benchmark. The $1,500 is exact, but $56 implies ~$5.60/GB and Web Unblocker Micro lists at $9.40/GB — $5.64/GB is WU40, 40% off for six months. At list that million is $94 (16×), or 10.6× against reported committed rates near $1.00 per 1,000: an honest 10×–27× band, not 27×. Nor does it isolate the unit, since brand and pool move with it. And the $1.50 provider is unnamed — reading it as Bright Data Web Unlocker PAYG is our inference.

Derive a response size, not a multiple. Per-GB stops winning above ($/success ÷ $/GB) × 1,000,000 KB: ~266 KB at $5.64/GB, ~160 KB at $9.40/GB, ~106 KB against a committed $1.00 per 1,000 — upper bounds, since Oxylabs meters traffic, which we read as request bytes on top of payload. It sells in 8 GB blocks ($45 promo / $75 list), so $56 and $94 are extrapolations, not prices you can buy. Trimming payload bites a per-GB bill only; against per-success the lever is request count.

The widest spread is intra-vendor. Zyte publishes its price list as the difficulty scale. PAYG per 1,000 HTTP responses: Simple $0.13, Easy $0.23, Moderate $0.44, Complex $0.70, Advanced $1.27; browser-rendered $1.01–$16.08 across those same five. One vendor, one unit, cheapest to dearest: 124×. But those labels are Zyte’s own commercial classification of difficulty, not an independent measure. And Simple sits roughly 8× below this guide’s own 1 to 5 USD per 1,000 requests band, which describes hard targets.

Caveat. No measured invoice anywhere: every figure is a published rate multiplied out, and the calculation comes from a party selling the aggregation layer it recommends. Its “billions of requests a month through more than 50 proxy providers” is self-reported and unaudited; the newsletter carrying the argument returns HTTP 403 and reached us only through a text-extraction proxy. That metered traffic includes request bytes, and that a wrong-but-convincing 200 is billable, are inferences — Oxylabs states only that 5xx on its own side is not charged. And WU40 expires.

Oct 2026 update. A four-check cost audit from the same newsletter’s author (1 Oct, an infographic with no figures) states the practical case of this card: small JSON API responses are where per-GB billing wins, since a few-KB response sits far below the ~106–266 KB crossover. Its other checks (proxy tier, unparsed assets, needless browsers) repeat the cost drivers in §cost. A reply names the open gap: no tool attributes proxy, compute and maintenance cost per target.

Oct 2026 update. A Web Scraping Club review of AntsData (1 Sep; a vendor tool review, no disclosure either way) shows why you price from the bill, not the list. One Google query returning 8 results billed $0.002; an Amazon best-seller job billed $0.55 for 82 records, against a listed $0.55 per 1,000 (about $0.045 expected). Its pricing page on 12 Oct lists $1.00 per 1,000 searches and no minimum. The advertised MCP returned a 404. One run each, cause unexplained.
+
Sep 2026 / Economics
The Credit Multiplier Prices the Provider’s Route, Not Just the Page
The guide shows twice that price does not predict proxy performance and the winner changes per domain — without a mechanism. ScrapeOps founder Ian Kerins supplies one, writing up his own r/WebScrapingInsider AMA: a provider’s credit multiplier on your domain reads out which route it bothered to engineer there, tracking its revenue exposure as much as your page’s difficulty. Unaudited vendor account.
💡 A 20× multiplier on an obscure target may be a fallback nobody was paid to optimise — not only evidence that the page is hard
Two scraping APIs can bill the same URL at 1 credit and at 20. One solves the site with datacenter proxies; another that can only solve it with residential proxies, browser rendering or multiple attempts charges 10 or 20. No provider is named against any multiplier, so that pair is his illustration, not a measured comparison. His own hedge: you pay “not only” for page difficulty — difficulty is still priced, per-domain efficiency on top. The lever is revenue exposure: if Amazon breaks the provider has “a substantial amount of revenue at risk”; for the lone customer on an obscure single-country site a cheaper route “may not be worth the engineering time.” His arithmetic: 5 credits per request turns $100 per million into $500, a 10-credit residential site $1,000 per million for the identical page.

Latency is the same variable read from the other side. His bands: under 5s very good, under 10s good, 20–30s much weaker; and 99% success at 20–30s against 80% at 2–3s, which he frames purely as throughput. Score candidates as the health-scored rotator scores routes. His percentages are unmeasured and not like-for-like: 90–95% of traffic at the basic one-credit level is him recalling ScraperAPI, 20–25% needing more than the base configuration is a present ScrapeOps “rough estimate” — two companies, no time window, no dataset. Only the 90–95% baseline is new.

The caveat: none of this is a published measurement, and the company saying it sells the answer. It closes on the gap its own product fills: automatic per-domain selection “doesn’t fully exist yet… we’re around 60% to 70% of the way there.” Its scale claims (billions of requests a month, 50+ providers, ~60,000 domains) are unaudited; the AMA thread could not be retrieved, so no community pushback has been read. This guide carries labelled affiliate proxy links itself. His defence: ask a provider for a bigger trial and “they know they are being benchmarked,” so ScrapeOps tests through ordinary paid accounts, and “testing 10 to 20 providers properly can easily cost $500 to $1,000”. This card’s extension, not his: a spend-capped benchmark hands your priciest candidates the smallest samples — budget each provider a number of requests, not dollars.
+
Sep 2026 / Operations
browser.close() Does Not Stop a Cloud Browser: 1,556 ms to Reconnect Warm, 6,046 ms After the Provider’s Stop Call
The guide calls a cloud browser a session layer, not a scraping product; none prices the lifecycle. The gap is documented, not a vendor quirk: Playwright says that on a browser from connect(), browser.close() only “disconnects from the browser server”. A benchmark on GoLogin’s own blog, about its Cloud Browser, puts a latency figure and a bill on it.
💡 On a connected browser close() disposes only the contexts you made and drops your socket — the remote process, the persistent profile and the billing clock run on, and a warm reconnect is how you catch it
Documented behaviour, turned into a test. Kanpur client, Falkenstein host; Playwright 1.63 on Node 25; RTT 237 ms, median of 20 Browser.getVersion samples. After browser.close(), reconnecting took 1,556 ms, page still open (warm band 1,554–1,676 ms), a localStorage value written 20 s earlier still readable. The provider’s stop call (DELETE /browser/{id}/web, 204) raised it to 6,046 ms, inside a cold-provision band of 5,273–6,616 ms. Two non-overlapping bands: warm means it never died, so reconnect latency is a leak probe. The thresholds do not travel — the author is explicit that “the latency ratios scale with your own round-trip time” — so measure your own. Put the close and the stop in a finally, or every crashed run keeps billing.

Remote CDP charges you per locator — here, 63×. Twenty cards field-by-field issue roughly 80 protocol calls: 16,098 / 16,010 / 15,256 ms over three runs against one target. As one page.evaluate(): 500 / 254 / 247 ms — medians 16,010 / 254. Locally a chatty chain is untidy; over a wire it is the bill. Nor reach for newContext(): it opens an empty jar while the logged-in state sits in contexts()[0], and the tmpfs profile dies with the container — export cookies first.

The caveat. This is GoLogin’s own blog about its Cloud Browser, so read comparisons as vendor material. Its three published self-serve tiers, $0.09 / $0.08 / $0.07 per browser-hour, are 2.6–3.3× an ARM Fargate floor of ~$0.027, scoped to those tiers: the same page’s FAQ quotes overage “at $0.04-0.09/hr depending on plan”, so the honest published band is ~1.5–3.3×; the floor rests on an assumed 6 browsers per 4-vCPU task, the author’s stated assumption, not a measurement. Reported rather than confirmed: each latency figure is one run from one client-host pair, the self-hosted column was inferred, and the idle-timeout and dropped-connection policies come from an unnamed engineer at a different provider; GoLogin’s own is published nowhere.
+
Sep 2026 / Architecture
Cold Start Is Paid Once, Distance Every Round Trip: Browserbase Measured 152 ms Frankfurt→US West, 3 ms After the Move
Card [71] frames cloud browsers as a cold-start problem. Browserless’s founder argues on LinkedIn that what governs a live session is the network distance between control code and the browser — paid per round trip, not once at boot. Browserbase’s multi-region write-up is the only published before/after on that axis; the two rival 2026 benchmarks crown opposite winners.
💡 Boot time is paid once per session, distance on every round trip — so ask a cloud-browser benchmark where its client sat and which stages it timed, not who won
Boot claims are vendor self-reports; distance has one measurement. Kernel’s docs claim “less than 20 milliseconds”, Hyperbrowser’s blog sub-500 ms, Steel’s homepage 0.89 s average (p95 1.09 s) — all self-reported, no methodology, sample or region. Browserbase measured the other axis on itself: 63, 152 and 169 ms from the Eastern US, Frankfurt and Singapore to its single original region, falling to 6, 3 and 5 ms once a browser existed in the client’s region — one provider, one sample script. CDP is strictly sequential, so each command waits on the last, but the post never defines its unit, saying only that the script chains multiple operations, each several CDP commands. So 152 ms may be one round trip or a whole script total, sample unstated. Do not multiply it.

Two 2026 benchmarks, opposite winners. Browserless’s harness (browserless/benchmarks, default TOTAL=10 on example.com) made Hyperbrowser fastest to connect, 692.5 ms. Steel’s BrowserBench (5,000 runs per provider, create→connect→goto→release) puts it fourth: Steel 894.13, Kernel 1,518.53, Browserbase 1,676.76, Hyperbrowser 3,657.11, Anchor Browser 8,001.29 ms. Steel’s stage table explains it: Hyperbrowser’s goto 377.89 ms is fastest of all five, while create 1,731.63 and release 1,199.98 ms carry the deficit — stages a connect-plus-navigate run never separates. Over those 5,000 runs: Steel and Kernel 100%, Browserbase 99.96%, Anchor 97.34% (133 failures).

Whose back yard — and the caveat. Steel ran from AWS EC2 us-east-1, its own only browser region, rivals on default settings — so Browserbase (default us-west-2) was clocked cross-continent. Browserless never says where its harness sat. Vendors measuring vendors: each bench crowns its author, the LinkedIn post carries no data. Attributing any of Browserbase’s gap to distance is our inference from two documented defaults, not a controlled A/B; and region placement does not outweigh the published boot-time gaps — Steel’s create stage alone spans 181.57 to 3,796.55 ms. No one has run every provider from every region with stages split out; until then, pin the region explicitly.
+
Sep 2026 / Economics
At List Price the Verifier Costs 18× Less Per Input Token Than the Extractor It Audits
This guide's AI Workflow rations verification on self-reported confidence: >=0.9 skips the verifier (85% of rows), 0.6–0.9 verifies (12%), <0.6 quarantines (3%). TypeSafe's sde_cascade cookbook removes the reason to ration: its checker is independent, non-generative, and lists cheaper per input token than the extractor it audits.
💡 A checker that lists cheaper per input token than the extractor turns verification from a sampling decision into a default — every field checked, the expensive model demoted to escalation
The price table is the reversal. Cookbook rungs, rates checked 15 Sep 2026, $/1M in/out: extractor gpt-5.4-mini 0.75/4.50, escalation gpt-5.5 5.00/30.00, verifier jev-1.12 0.042/0.00, output unmetered. A ~18× list-price gap per input token (0.75 ÷ 0.042 = 17.9): arithmetic on published rates, not a measured per-row saving. Stale pin, same ratio: the models page now lists only jev-1.13 (~18 Sep 2026) at 0.042 in, free out. Two traps: the escalation rung gpt-5.5-reasoning is not a separate SKU, and its reasoning tokens bill as output at $30.00/M, far above the 6.67× input ratio; and write-ups report Jev free on Vercel's AI Gateway until 25 Sep 2026, so metering now reads $0.

Seven questions per field, aggregated with max. Each populated field gets seven binary heads (hallucinated, off_target, type_mismatch, unreasonable, incomplete, format_violation, name_desc_mismatch); each empty field gets absence_wrong. Gate: FIRE_T = 0.7, aggregated by max not mean, so one confident flag escalates instead of averaging into silence. All seven ride one call per record. Per-field confidence isn't new here; the independent judge is.

Same bytes: fidelity, not corroboration. Jev reads the page the extractor saw: extraction checked against source, the check the 85% fast lane skips. It cannot replace the guide's re-fetch verifier: same bytes never reveal a stale, blocked or poisoned page. Worked failure: on an NYU page the mini model left registration_open_date empty but invented a description that parses, validates and lies; Jev fired hallucinated 0.95, off_target 0.85. Hence its refusal of structured outputs: conformance never catches a field that lies. The walkthrough is hard-coded, not live.

Caveat: vendor-reported quality, non-independent ground truth. The Pareto chart is “internal TypeSafe results” over 100 prompts, from the vendor, with no quality metric or ground truth; figures are read off a raster (gpt-5.5-reasoning alone ~0.81 quality at ~$0.10 per extraction, vendor-reported), the page admits a snapshot not recosted at Jev's rate. Nor is the dataset leg independent: scrapegraphai-100k (93,695 rows, publisher self-reported) shares an author with the LinkedIn post promoting it, and its card calls responses “LLM outputs… not human-verified ground truth”, so scores grade against gpt-4o-mini. Measure your own escalation rate; that, not $0.042, sets the bill.
+
Sep 2026 / Economics
The 99.9% Counts Tool Definitions, Not Work Done — Cloudflare Published No Task Cost for Its 2,500-Endpoint MCP Server
Cloudflare’s Code Mode exposes 2,500-plus API endpoints in roughly 1,000 tokens — a 99.9% input-token cut, Cloudflare says, against its own calculated 1.17-million-token baseline for a server it never built. The reversal: no source publishes a task-level cost for it, and the only task-level figures, 32% and 81%, describe a smaller calendar demo.
💡 Cloudflare’s 99.9% prices the menu you carry into every request, not the meal — and the only published meal figures, 32% and 81%, are a third party’s write-up of an earlier, smaller demo
What the 99.9% counts. Matt Carey’s Code Mode post (Cloudflare, 20 February 2026) ships a two-tool server: the model filters the Cloudflare OpenAPI spec in code instead of loading it, then runs the calls in one sandboxed execution. Cloudflare is precise: Code Mode “reduces the number of input tokens used by 99.9%”, baseline marked hypothetical — “an equivalent MCP server without Code Mode would consume 1.17 million tokens.” That is Cloudflare’s arithmetic for a server nobody built or metered, not two builds measured. Anthropic’s companion post (4 November 2025) is the same kind: 150,000 tokens to 2,000, “a time and cost saving of 98.7%” — an illustrative calculation on one snippet, no benchmark, no run count, from the party promoting it.

The counter-number is a demo, of another system. The only published task-level figures: WorkOS (Zack Proser, 11 December 2025) writing up a live demo by Rita Kozlov, Cloudflare’s VP of AI & Developers — a calendar agent, not the API server, two months before it shipped: 32% fewer tokens on the simple single-event task, 81% on the complex 31-event one. Both are reported from one unrepeated live demo, third party, no methodology, no run count — and neither Cloudflare post contains a 32% or an 81%. Reading most of the 81% as batching (one model turn, not 30-plus round trips) is our inference, not theirs.

The caveat, and what to count. Every figure here is first-party or demo-grade, and none of the sources publishes an error or retry rate for model-generated code — our observation over the set, not a gap any source flags — so the savings are never netted against a script the model got wrong. Anthropic concedes the approach “requires a secure execution environment with appropriate sandboxing, resource limits, and monitoring.” The win scales with how many definitions you carry, so a crawler holding one scrape(url) → markdown server has nothing to cut: its bill is the payload, this guide’s 17,131 → 3,618 → 107 curve. Count your system-prompt tokens before the first user message; trim payload first (free), the menu second (it costs a sandbox).
+
Sep 2026 / Economics
Two Build-vs-Buy Crossovers, 70× to 670× Apart, Because the Unit Changed: HTTP Requests Here, LLM-Structured Records in a Bright Data Talk
Section 13 prices build-vs-buy in HTTP requests: at $1 per 1,000, managed wins below roughly 1–2 million a month, self-hosting above 10 million. A Bright Data talk relayed in mid-August 2026 puts it at just over 15,000, 70× to 670× lower. Neither is wrong: this guide counts one HTTP request, the talk counts one record an LLM had to structure.
💡 A crossover volume is meaningless without its unit — swap “one HTTP request” for “one LLM-structured record” and the break-even moves two orders of magnitude with nobody being wrong
Thinly sourced, and that matters. The figures come from Bright Data’s Omer Primor, from an exercise he labels “not a benchmark.” Venue unsettled (StartupHub, 17 Aug 2026: the AI Engineer World’s Fair; BigGo: a podcast) and the recording was never obtained, so everything here is reported, not confirmed, via three secondary write-ups. Setup as relayed: one company, 25 fields, 100 runs, Opus 4.8, six retrieval paths, two unnamed Context-as-a-Service tiers.

The reconciliation is the payload, not the 15,000. This guide’s unit is one successful HTTP request, cost driven by proxy bandwidth. The talk’s unit is one enriched record — retrieval plus the tokens that turn prose into 25 typed fields, which its table calls “Mid-band, but heavy token burn to structure results.” Same decision, different denominator: memoized extraction, inverted.

The per-record price is a floor on an ambiguous denominator. $5,000 ÷ 15,000 is about 33¢, divided here, not in any source, and a floor: break-even is amortised build plus the owned path’s marginal cost, and “zero AI cost — no tokens at all” means zero token cost. The source’s denominator is “just over 15,000 entities or queries” — two units in one phrase. Per call, a 0.1¢–0.8¢ retrieval fee is 0.3% to 2.4% of 33¢, a rounding error; per entity, ~25 calls at $7 per 1,000 is roughly 17¢, about half. Those prices are a third-party compilation (31 Aug 2026) marked “illustration only.”

The caveat, and it is large. Bright Data sells both arms, the CaaS measured and the Scraper Studio that built the winner, and its $5,000 is one engineer-week valued internally, excluding maintenance; “self-healing” and its self-reported scale figures are vendor claims, untested here. Primor disowns the precision (“maybe it’s not 15, maybe it’s 100,000”), every table cell is an ordinal adjective and both CaaS vendors unnamed, so it is unreproducible. Do not adopt 15,000 or 33¢: compute the crossover in your own unit, from your token bill per accepted record.
+
Sep 2026 / Access
Polymarket’s Public-API Throttle Is Documented to Queue, Not 429 — So Latency Is the Alarm
The guide’s rule is to throttle on block signals — status codes, challenge markers, Retry-After — never on latency, since stubs return faster than real pages. Polymarket’s read endpoints sit behind a Cloudflare IP limiter its docs describe otherwise: over it, requests are throttled (delayed/queued) rather than immediately rejected. Nothing to count.
💡 On the IP layer Polymarket documents, the over-limit request is delayed rather than refused — which makes rising latency the rate-limit alarm, on the one dial this guide told you never to steer by
The published budget, and the sentence. Polymarket’s global API docs give per-endpoint ceilings, sliding windows per 10s unless stated, “IP-based and enforced using Cloudflare’s throttling system.” Gamma: general 4,000 req/10s, /events 500, /markets 300, /public-search 350, /comments 200. Data v1: general 1,000, /trades 200, /positions 150. Data v2: general 800, /v2/trades 300, /v2/prices-history 200. CLOB: general 9,000, /book 1,500. Then: “When you exceed the limit for any endpoint, requests are throttled (delayed/queued) rather than immediately rejected.” “Immediately” is load-bearing — delay ahead of possible rejection, not a promise of none. A run of 429s is a metric; a rising p50 is a judgement call, so instrument service time per endpoint and alert on climbs.

Name the limiter you read. The same site’s CLOB trading page documents per-signer buckets returning 429 + Retry-After, “separate from the existing Cloudflare IP-based limits”; sibling docs.polymarket.us specifies 20 req/s per API key and per IP, flat 429, no queuing. Three surfaces, three semantics: the queue is the Cloudflare IP layer in front of public read endpoints.

A field named volume measuring two things. Bellingcat, on these APIs: Polymarket shows traded dollars, while “Kalshi… shows the notional volume, which counts every contract traded at the maximum payout value of $1” — a token bought for $0.20 shows as $1. Rebuilt as daily average price × contracts traded over “more than 21,000 World Cup markets”: Polymarket $10 bn against a reconstructed >$4.3 bn. Encoding-and-units one level up: parses perfectly, checks pass, wrong quantity.

Documented, not observed. The behaviour was read from the docs, never exercised against the live API, and the ceilings were read September 2026, after the June–July 2026 collection window; a dev.to post reports “in practice you do see 429” with no logs. The one speedup line (“multi-week… single-day task”) is Oxylabs’ own blog about its own proxies, no request count or measured rate: vendor-reported. Whether Kalshi’s volume also double-counts both sides is reported rather than confirmed — docs.kalshi.com did not resolve.
+
Sep 2026 / The other side
Adscore Names Google Safebrowsing and GeoEdge on One Page and Sells Honeypot Routing on Another
The guide’s spine: an anti-bot stack classifies your client and you engineer to pass it. Here both chairs are swapped — the buyer of bot detection is the attacker, the party classified is the security scanner. The practitioner post flagging it carries no evidence, so the card rests on two live Adscore pages, a catalogued Confiant technique and a 2026 teardown.
💡 The classifier is now something the attacker buys off the shelf — and it knows your auditor by brand name, not just by behaviour
Two live Adscore pages. adscore.com/implementations option 3 of 7, Active Filtering, “sends good traffic to your landing page and bad traffic to a honeypot.” Its FAQ says it detects “antivirus scanners like Google Safebrowsing” and GeoEdge — while explaining false positives, so the cloaking primitive is this guide’s inference, not Adscore’s pitch. An abusive customer makes that join, honeypot branch as the auditor’s decoy.

Catalogued. Confiant’s Malvertising Attack Matrix lists 9 tactics, 93 technique IDs; Cloaking is C601–C615. C614: bot-detection services used “to hide from ‘good’ bots.” C604 inverts the proxy economics: payloads are withheld from proxy, VPN and datacenter IPs as likely “scanners, bots, security researchers.” A datacenter exit is not blocked there — it is made blind.

The 2026 commodity form. Varonis Threat Labs documented 1Campaign, cloaking-as-a-service for malicious Google Ads: a 0–100 fraud score, cloud infra blocked by ASN (a Microsoft entry at score 0), plus headless-browser and user-agent checks Varonis alone reports. On one campaign its dashboard showed 1,676 visitors, 10 approved, 99.4% blocked.

The caveat. The practitioner post names no vendor, number or link, and its trend claim is reported, not confirmed. Not new either: the March 2019 teardown behind it is one analyst’s single observed chain and C614’s only reference (that page 404s), so the two are not independent; prevalence is unquantified. The 99.4% is the attacker’s own dashboard on one campaign, off screenshots — not independent. Adscore itself is legitimate: a commenter self-identifying as its founder said they monitor customer redirect links and act on malicious ones (unverified).

The defensive read. The 200 OK false negative is the LLM Honeypotting card’s; new is a bought classifier with a brand-name target list. Treat the apparatus as the finding, re-sample by time of day (C613 is time-gated), and never let one clean fetch close a case.
+
Sep 2026 / Mobile
Google’s ARTEMIS Claims 99%+ on AndroidWorld, and the Leaderboard Row Says One Trial
Google open-sourced ARTEMIS, a natural-language Android agent whose README claims a 99%+ completion rate on AndroidWorld. The exact 99.1% is one leaderboard row, one trial, on a sheet that verifies nothing, while the benchmark’s paper puts its best measured agent at 30.6%. AndroidWorld is emulator-only, so it clears no attestation wall.
💡 The leaderboard header says it verifies nothing, the ARTEMIS row says one trial, and the row above it already sits at 100% — the paper’s best measured agent is 30.6%
Whose number it is. The exact 99.1% behind the README’s “99%+ completion rate” is one row on the AndroidWorld leaderboard, a Google Sheet headed “No independent verification is performed.” It reads trials: 1, Screenshot + A11y tree, names no submitter, and sits under a 100% FluizAI entry; no independent reproduction of 99.1% could be found. The paper (ICLR 2025, arXiv 2405.14573) scores 116 tasks in 20 real Android apps and puts its best agent, M3A on GPT-4 Turbo reading the accessibility tree, at 30.6%, its 80.0% human row being two engineers, one attempt each, on an emulator. Three seeds gave 27.6%, 26.3%, 33.2%, mean 29.0%: the width one trial cannot show.

The mechanism is worth stealing. First run installs an Artemis Accessibility Helper, the README’s “small accessibility service that reads the screen layout without taking the UiAutomation connection”, and warns UiAutomation tools can suppress it unless they enable FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES; that this is the Appium/uiautomator2 clash is this guide’s untested gloss. Targeting is tiered: a11y indices, OCR and coordinates, visual models last — the order the paper backs, text-only a11y tree 30.6% against 25.4% for screenshot-plus-a11y on the identical model, though ARTEMIS’s own row is filed as Screenshot + A11y tree.

Price it, then discount it. The README runs Flash at ~3–5 s per step and Pro at ~15–40 s per step, so a 100-step Pro run is 25 to 67 minutes on one device — this guide’s arithmetic, not measured in the 99.1% run. AndroidWorld is emulator-only (Pixel 6 AVD, API 33) and no task there involves an app trying to detect the agent, so the attestation card stands. The 99%+ is Google’s claim for Google’s agent on Google Research’s own benchmark, no profile or model named. Provenance is unadjudicated: Minitap’s 11 Sep 2026 post and issue #40 allege shared code and that the first pyproject.toml carried mobile-use’s version 3.6.3 under a Google LLC header; a Minitap credit line is now in the README, and secondary reporting reports no detailed Google response.

Sep 2026 update. The same pattern in browser agents: the vendor of Open Swarm (openswarm.com; AGPL-3.0 desktop app, repo created Mar 2026) announced its own browser agent as “SOTA on agentic browsing benchmarks” and ~85% faster than browser-use with 60% fewer model calls. As of 28 Sep 2026 neither the site nor the README names the benchmark, the tasks, n, or who ran it. Treat it as a vendor self-claim with zero verifiable evidence until a results page exists.
+
Sep 2026 / Agent safety
Two of 45 Browser Agents Document Both a Checkout and a Pause to Ask First
Decodo’s Agentic Capability Report (updated 26 August 2026) scored 45 browser agents 0–2 on each of 10 capabilities — on what vendors document, not what tools do. Exactly two, Amazon Nova Act and ChatGPT Desktop, document both a checkout and a pause to ask first. The market has mostly not documented shipping the guide’s confirm-first rule.
💡 Decodo’s CEO calls a documented score “a floor a vendor sets for itself” — but no verdict in his own report’s rubric adds a point, so it is a ceiling
The rubric decides everything. Each tool scores 0–2 on each of 10 capabilities, max 20, for documentation: 0 never mentioned, 1 partial or self-contradicting, 2 a clear vendor statement. One is confirmation before irreversible actions. Testing only subtracts: contradicts claim/credibility gap zero the capability, reliability gap takes 1, corroborates nothing. No verdict adds a point.

The safety column is nearly empty. Transactional actions averaged 0.43 of 2, which the report calls its lowest average; cross-tab awareness 0.66. Six document a completed purchase; only those two also document pausing first. Ten of 45 document a pause anywhere; the report names nine, never a tenth. Of the 35 that fully document multi-step task completion, 18 (51%) document zero confirmation step; 17 document running with no active user session, seven with no confirmation at all.

Where testing bit. Fellou documented 19 of 20, highest in the published rows, finishing 17, looping on layouts and failing CAPTCHAs. Browser Use 14→10; Devin 13→9 on a credibility gap (its demo showed it fixing files absent from the repo), 2 of 10 tasks error-free; Comet 6→4; Manus 5→2. Top adjusted: Claude for Chrome and Nova Act, 18; nobody reached 20.

The caveat is large. Decodo sells an access layer for AI agents, so a report whose headline failures are layouts, CAPTCHAs and page access doubles as a pitch. The companion Next Web op-ed by CEO Vaidotas Juknys is a fee-based TNW Council placement that never discloses the research is his own company’s and puts the count at three where the report names two. It audits documentation, not capability: a careful tool with thin docs scores 0. Five tools carry a published adjustment; the report never says how many of the 45 were tested; Decodo “only ran each task twice per tool”, by its own team, with no versions, dates, OS or dataset.

Either side. Defending: assume agent checkout traffic is not human-gated. Building: the confirmation gate is your code, not a procurement checkbox.
+
Sep 2026 / Method
The 92.4% WAF Number Is a wafw00f Probe Result, Not a Blocking Rate
A Zyte post reports 92.4% of major sites run a WAF — the reading this guide’s State of Web Access card already gives. The net-new part is the instrument: 92.4% is the hit rate of wafw00f 2.3.2 firing attack-shaped probes at 11,100 root URLs, once each, from Zyte’s datacentre IPs in October 2025. It measured reaction to an exploit, not obstruction of collection.
💡 The census asked every site whether it flinches at an attack payload; a collector needs to know whether it flinches at a crawler, and those are two different measurements
The instrument the post never names. WAF was the only one of six barrier checks that sent provoking traffic: the methodology page says it “sends deliberate probe requests designed to trigger firewall responses,” using wafw00f 2.3.2 — whose last documented step is a fallback guessing whether a solution is “actively responding to our attacks.” Denominator: 11,100 records (9,774 unique sites, one row per industry, not the 24,898 headline), root URL only, once, October 2025, Zyte datacentre IPs. 92.4% is 10,252 of 11,100.

One detection in three names nobody. Cloudflare 34.6%, Generic/Unidentified 31.6%, CloudFront 9.6%, Akamai Kona 4.9%, AWS ELB 4.3%, 13 more under 2% each — that bucket is the fallback firing, and it sits inside the same 100%, so Cloudflare is ~51% of the attributed detections (34.6 of 68.4), not the post’s “34.6% of named deployments.” Top five: three CDN or load-balancer defaults — see The Cloudflare Header card.

Presence is not posture, and a 76% floor cannot rank targets. Default managed rules aim at the OWASP Top 10; a polite renderer pipeline “does not look like an SQL injection attack.” The controls that stop collection — rate limits, datacentre IP reputation, TLS and behavioural checks — are, per the vendor, off by default or in premium bot tiers. Same table: WAF 92.4% vs anti-bot 18.5%. It barely varies: six regions 92.5–93.4%, Japan’s 86.4% the one outlier the report ties to domestic CDN and hosting, floor airlines 76%, military 77%.

Caveat, and two numbers to fix. Zyte’s own report, and Zyte sells an unblocking API, so its “signal to investigate” framing is also a sales message. One unauthenticated homepage visit says nothing about the endpoint you want; the report concedes rate limiting is “likely an undercount.” Vendor shares hang on one pinned signature set (2.3.2; current releases claim 200+), and the unattributed third is a fingerprinting blind spot, not bespoke WAFs (its ModSecurity-lost-support-in-2024 explanation is reported, not confirmed). Corrections: the free Cloudflare managed ruleset dates to March 2022, not 2017; and this guide’s “more than half” rate-limiting figure is the fashion cut (54%), not the global 22.1%.
+
Sep 2026 / Data quality
Three Pages, One Static Docs Site, No Benchmark Claimed — and an Acceptance Test for Prose
A crawler author published a four-tool comparison, then declined what such posts exist for: he states he ran no head-to-head benchmark, and that his three-page demo proves nothing beyond itself. What is left is what this guide lacked — the record-shaped acceptance test has no documentation equivalent, where there are no fields to count. His answer: name the reference paragraph and code block before the crawl.
💡 When there is no schema to validate against, the acceptance criterion has to be a paragraph and a code block you chose before the run — not a 200, and not a file that exists
The oracle for unstructured extraction. Crawl a documentation site and “half the file contains navigation. The code example you needed is missing.” Documentation has nothing to count, so the criterion becomes a named artefact: “require the selected paragraph and code sample to survive extraction”. Pick both from the live page first, and the run passes only if both come out the other side — the twenty-records criterion, ported to content with no schema.

Do not mix discovery with extraction. For an extraction comparison hand both candidates the same URL list; for a discovery comparison, the same starting URL and scope. “Mixing those tests makes it harder to tell whether you have a discovery problem or an extraction problem.” That is failure attribution — the proxy-side habit (IP, browser, or host?) on the crawl-versus-parse axis. Test set: ten representative pages you are allowed to crawl, including any layout that has broken extraction before.

Ordering, and the check that catches silent failure. “Define acceptance before measuring speed” — then time only the output you accepted. Of the four checks — coverage, content, noise, failure reporting — wire the last first: save the failed URL and its error message, or “an empty result appears as a success”. The boundary on pilots: “A small local sample can tell you whether the output fits your application. It cannot tell you the monthly operating cost of a larger deployment.”

The caveat, largely the author’s own. He builds one of the four tools and discloses it, so read the shortlist as a vendor’s. The only measurement is one command, 21 September 2026 — depth 1, --max-pages 3, --concurrency 1, a 1000 ms delay, --only-main-content — against docs.scrapy.org, a static Sphinx site with no JavaScript rendering and no bot protection; three files landed, and the overview kept the example spider with its pagination code. He states “I have not run a head-to-head benchmark of these four crawlers,” and that the check establishes no success rate for other sites and no speed advantage over another crawler. No timings, throughput, versions or file sizes appear anywhere. One gap: the LinkedIn version says to confirm indentation and the source link survived; the article verifies neither.
+
Sep 2026 / HTTP client
curl_cffi 0.16 Impersonates HTTP/3, and Updates Its Fingerprints Without a Reinstall
The guide already treats curl_cffi as the default browserless client and HTTP/3 as a detection layer. The 0.16 series (Aug–Sep 2026) closes the two gaps that mattered: it now impersonates HTTP/3 over QUIC, and it can pull fresh browser fingerprints from an API instead of waiting for a pip release — which is the direct fix for the “a pinned profile rots into a tell” problem this guide keeps flagging.
💡 A Chrome user meets HTTP/3 targets over UDP; a requests/httpx script meets them over HTTP/1.1 — that transport mismatch is a tell before a single header is sent
What actually shipped, from the release notes. The packaged curl went to 8.21 with curl-impersonate 2.0; v0.16.3 added the TLS trust_anchors extension for Chrome 152 (detection vendors check for it, so it is now matchable); more HTTP/3-specific options landed; a new option sets HTTP header order (header order is itself a fingerprint signal, and plain requests silently reorders yours); plus DNS-over-HTTPS, redirect history and streaming request bodies. It stays a requests-like API — impersonate="chrome", and for the new transport http_version="v3".

Why HTTP/3 impersonation is the headline. More targets negotiate HTTP/3 over QUIC, and neither requests, httpx nor aiohttp can speak it with a browser's QUIC fingerprint. curl_cffi now does QUIC fingerprint impersonation and UDP proxying, so the client matches the browser it claims to be at the transport layer, not just in TLS and HTTP/2. For roughly 80% of targets this is the whole game — you do not need a 200 MB headless Chromium, you need a client that looks like one on the wire, and this one is faster than requests while doing it.

The self-updating part is the durability fix. This guide's profile-drift card warned that a pinned impersonation profile eventually describes a browser version almost nobody runs, which is its own anomaly, and recommended scheduled re-measurement. curl_cffi now offers exactly that for the HTTP-client path: a curl-cffi update pulls the latest Chrome/Safari/Firefox fingerprints from the maintainer's API, so a new browser release does not leave you pinned to a stale ClientHello. One honest note: that fingerprint API is impersonate.pro, the maintainer's commercial service, and the Chrome 152 target is offered through it — the same paid-freshness axis the guide already notes for this library. The base client and the older presets stay free; staying current on the newest browsers is the part that is monetised.
+
Sep 2026 / Proxy engineering
Stop Rotating Proxies Round-Robin: Score Each Route on What Happened After the Last Request
Round-robin answers “whose turn is it?” The better question when routes differ in reliability and latency is “which route deserves the next request?” A Crawlbase engineering guide builds a health-scored rotator that learns from every response — and the pattern is concrete enough to lift wholesale, with the formulas spelled out.
💡 Score = recent success × recent speed, with a circuit breaker and a thin probe slice — traffic follows the evidence, not position in a list
The mechanism, verbatim from the design. Each route keeps two exponentially-weighted moving averages. Success EWMA updates on every request: success = decay·outcome + (1−decay)·success with outcome 1.0/0.0. Latency EWMA updates only on successes (so failures don't distort it): latency = decay·sample + (1−decay)·latency. Default decay 0.3 on both, so the last observation is ~30% of the estimate. The health score is multiplicative: score = success_ewma × 1/(1+latency_ewma) — a route must be both reliable and fast, because a failing route scores near zero regardless of speed and a fast-but-flaky route can't score high. A circuit breaker opens after 3 consecutive failures (score forced to 0), waits a ~15s cooldown, then half-opens and closes again on a success. Selection is probabilistic on score, not top-1, and a deliberately modest exponent keeps a thin probe slice of traffic on demoted routes so recovery is still detectable.

The benchmark, and the honest caveat. On a controlled workload of 20 requests, both policies reached 100% success, but mean latency moved from 1.589s (round-robin) to 0.289s (health-weighted) — roughly a 5.5× improvement. Read it correctly: the target was unprotected and both policies succeeded, so the result is the latency, not the success rate. The claim is narrow and true — steering by observed behaviour beats steering by list position — and on a real blocked/rate-limited target the same machinery would show its value on success rate instead, by pulling burnt exits fast and probing them back.

Why adopt it. This is the cheapest reliability upgrade in the sweep and it composes with everything else the guide recommends: it is the proxy-side twin of the failure-attribution the benchmark harness teaches (is it the IP, the browser, or the host?), and the circuit-breaker + probe-slice shape is the same recovery logic a good crawler already wants. Keep it provider-agnostic and it doubles as the routing layer under a proxy waterfall.
+
Sep 2026 / Defensive economics
The Anti-Bot Tax: Why the Most-Trusted WAF Still Can't Stop a Publisher Being Scraped
An anti-bot veteran's argument, sharpened by a conference datapoint: general-purpose anti-bot is the wrong tool for publishers, and paying for it is a tax rather than a fix. The FT runs Cloudflare — the most trusted anti-bot — and is still scraped, and the reason is structural, not a Cloudflare failure.
💡 Anti-bot was built to protect content that constantly varies; stealing an article needs exactly one request, so per-request friction never prices the attacker out
The structural mismatch. Anti-bot systems were designed to guard endpoints whose value comes from constant change — flight prices, inventory, login — where an attacker must come back again and again, and per-request friction (challenges, rate limits, fingerprinting) compounds against them. A publisher's article is the opposite: you scrape it once and never need to return, so the friction that makes a price-monitoring bot uneconomic barely touches an article thief. The guide has made this arithmetic from the attacker's chair (a bypass sells for ~0.003¢ per page); this is the same fact from the defender's chair — a WAF tuned for high-frequency variable content is close to irrelevant against a one-request-per-URL theft model.

The datapoint, attributed and hedged. At the Press Gazette Future of Media conference (Jon Slade and Dominic Ponsford of the FT), it was reported that one trial account made about a million requests to ft.com to lift subscriber-only content — the kind of single-account, high-volume harvest that a general anti-bot waves through because each request looks like an ordinary logged-in read. Note: that figure comes from the conference talk as relayed by an anti-bot practitioner, not from an FT publication we could independently verify; treat the exact number as reported rather than confirmed. The structural argument does not depend on it.

What it means for both chairs. For a defender in publishing, the takeaway the speaker draws is to stop treating a general WAF as the answer and instead understand who is scraping you and what they do with the content, then build a tailored control — abuse detection on the authenticated read pattern (one account, a million article fetches) rather than a bot challenge on the front door. For a scraper, the same fact explains why login-gated publisher content is often reachable through an ordinary authenticated session (see the account-creation card) even behind a name-brand WAF — the wall is priced for a threat model that article harvesting does not fit. The honest framing both sides should keep: this is an argument from someone selling tailored protection, so weigh it as a real structural point, not a neutral verdict on Cloudflare.
+
Sep 2026 / Coherence > values
A 23-Check Coherence Scanner Scored Every Anti-Detect Browser “Modified.” Stock Browsers Hit Zero.
This guide's spine is that anti-bot has moved from checking fingerprint values to checking whether they agree. anti-mage (N4darae, 1.5k stars, MIT, Go) is that thesis as a runnable tool: 23 independent readings, no signature list and no tool database, that only ask a browser to disagree with itself — and on 100 Windows samples per browser, every anti-detect build came back modified while stock Chrome, Firefox and Edge scored a flat 0 with no false positives.
💡 Patching a value, even at the binary level, doesn't help if it contradicts the host underneath — a Linux box in a Windows user-agent still hands you its own fonts and widget colours
The two cheapest leaks, to make it concrete. Ask the browser what fonts it has: a machine claiming Windows should resolve Segoe UI and must not be resolving DejaVu Sans or Liberation Sans, which ship with Linux and almost never with Windows — a Linux host wearing a Windows UA answers honestly and hands you the contradiction. Then read a <select>'s computed background colour, where the platform's own widget theme answers instead of the page: rgb(250,250,250) on Linux, rgb(255,255,255) on Windows. Neither needs a lab. The heavier readings follow the same rule — native accessor integrity alone is four checks on every accessor a spoofer must touch (how the function serialises, whether three key-enumerators agree, whether it still sits on the interface prototype, whether the wrong receiver throws the spec's TypeError), plus screen-vs-viewport-vs-CSS geometry, timezone against measured offset, canvas double-serialisation, and GPU/decoder/media coherence.

The results table is the payload. Score is 0–90 (it never claims certainty), rounded to tens so no single reading can be isolated by watching the last digit. Stock Chrome / Firefox / Edge: 0 across all 100 samples. Brave: median 10 — and the author is explicit that this is honest, Brave modifies the surfaces it says it modifies, which is the lightest thing recorded. Then the anti-detect column: AdsPower 60, CloakBrowser 70, NSTBrowser 70, Camoufox 80 (medians). None below 60. And the design detail that makes it trustworthy: a reading abstains rather than guessing when the probe is missing, the feature isn't exposed, or the reference table isn't verified — a browser is never scored for lacking a feature. 7 of 22 reference entries are still unverified, so those numbers are a floor, not a best case.

Why it lands the same week as a CloakBrowser rave. A scraping engineer posted a genuinely positive CloakBrowser review the same day: fingerprints patched at the C++ source level and compiled into the binary, so “detection systems see a real browser because it is one” — drop-in Playwright replacement, humanize=True (Bézier mouse, per-character typing), geoip=True (timezone and locale aligned to the proxy exit), and a reCAPTCHA v3 score moving 0.1 → 0.9. Both are true at once, and the tension is the lesson this guide keeps arriving at: binary-level patching wins the value game (it passed the OSS stealth benchmark's JS-fingerprint referee) and still scored 70 on coherence, because the leaks anti-mage reads are cross-surface agreement — the host's own fonts and widget themes, the ordering the interface prototype enforces — not the JS values a patch controls. Passing a fingerprint check and being internally consistent are different bars, and the second is the one still open.

One honest note. The author is releasing this once, maybe twice, and will not maintain it — a live scoreboard would just hand the anti-detect side a to-do list. So treat the specific readings as a snapshot of what's cheap to catch today; the durable takeaway is the method, which is the same one the guide's Camoufox and cpuPerformance cards teach: spoof one property and you now own the job of making everything it touches agree, or a scanner that costs nothing finds the seam.

Oct 2026 update. A GoProxies and ScrapingAnt piece extends coherence to the network: keep the ASN fixed within a session and match exit geo to the target’s real audience, because per-site behavioural baselines push a generic exit out of distribution. The mechanism is real, per Cloudflare’s September 2025 per-customer models post. The claim that detection windows fell from months to days comes with no data, and both authors sell proxy or scraping access.
+
Sep 2026 / Update
The /goto Open Questions Got Answered: ~110 req/s From One Home IP, Tokens Good for Days
This guide's /goto teardown flagged two things nobody had measured: the sustained resolution rate before Google rate-limits bulk follows, and whether the tokens are portable enough to decouple harvesting from resolving. The September industry roundup and the two SERP vendors have now answered both.
💡 The expensive-sounding “extra request per result” is cheaper than feared — you can resolve fast from a cheap IP and even resolve later, from somewhere else
The sustained rate. The earlier card quoted a 50-URL microbenchmark (40.59 URLs/s on one machine) and explicitly said the number that decides an architecture — the rate before Google throttles bulk /goto resolution — had not been measured. It now has: resolution testing sustained 110 requests per second from a single home IP with zero rate limits observed. That is a very different planning number: following the redirect is cheap, and the cost of the change is the extra request, not a throttle wall.

The token lifetime. The card hedged the vendor claim that tokens are portable and last for days, because it was one unreplicated observation. The roundup now states plainly that the tokens remain valid across multiple days. Combined with their non-determinism (still no token→URL lookup table, ever), that confirms the two-stage design is real: harvest tokens through the expensive stealth/proxy path, and resolve them later — a different time, a cheap IP, plain requests — because the token does not expire on you and does not need the original session.

And the vendors caught up. DataForSEO reported resolving organic /goto URLs for “99.99% of returned SERPs” as of 27 August, and SerpApi announced on 5 September that direct URLs are back in its responses — the resolution the earlier card noted they had shipped without disclosing the mechanism. Net effect for an operator: /goto is now a solved, priced-in cost rather than an open threat, and if you resolve it yourself the numbers above say it is a small one. What has not changed is the structural part — the destination is still encrypted and non-deterministic, so this is resolution by following the redirect, not by decoding the token.
+
Sep 2026 / Logged-in data
The Account-Creation Bottleneck: Email Is Nearly Free, SMS Is the Whole Bill
Data behind a login means creating accounts, and the OTP service everyone asks about is the easy 20% of that. The two verification channels price completely differently: email is effectively free at any volume, SMS is a per-account line item that decides whether login-gated scraping is affordable — and “a fresh account every run” is the most expensive and most detectable way to do it.
💡 A “fresh user” is not a new email plus a new phone — it is a matched identity (email + phone + IP + fingerprint + human-like first session), and a new account that immediately scrapes is the easiest thing anti-fraud flags
Email OTP — own a catch-all domain. Buy a domain, put it on Google Workspace, Zoho or a small mail server as a catch-all, and every address under it (anything@yourdomain.com) exists and receives mail; pull the code over IMAP and parse it. Unlimited fresh addresses at zero marginal cost, which is why “different user every time” is trivial on email. Run a few domains so signups aren't all concentrated on one. Avoid the disposable-mail APIs (mail.tm, temp-mail, Kopeechka) as your primary path — they're cheap but their domains are widely blocklisted, so they fail on exactly the sites worth logging into.

SMS OTP — where the money goes. The API-driven providers are 5SIM and SMS-Activate (with SMSPool and SMS-Man as fallbacks): rent a number for the specific service, poll their API for the code, and release/refund it if nothing lands in ~90s. Two levers decide success — use the non-VoIP / higher tier (plain virtual numbers get rejected), and match the number's country to the target's geo (cheap-country numbers are often pre-burned). Build it provider-agnostic as a waterfall (5SIM → SMS-Activate → SMSPool/SMS-Man) because availability swings hourly — the same pattern as a proxy waterfall. If a target rejects all virtual numbers, that's the escalation case: a real-SIM or eSIM provider, materially pricier.

What it costs, per delivered OTP. You pay only for received codes (no code → auto-refund), but budget a real-world 70–90% success rate so effective cost sits above sticker. Common service on a cheap country: $0.05–$0.30. High-demand service (Google, WhatsApp, big platforms): $0.30–$1.50. US/UK/EU or non-VoIP tiers: $0.50–$3+. Real-SIM/eSIM escalation: $1–$5+ or a monthly rental. At scale that dominates: ~200 accounts at $0.30–$0.50 is roughly $60–$100 per run in SMS; a few thousand accounts is $1k–$2k+ per full pass. Email adds ~$0, so SMS is essentially the entire per-account cost.

The two decisions that save the most money. First, check whether the target actually requires a phone at all — many gate on email only, and an email-only account costs about nothing, so this one check per site can cut the bill dramatically. Second, question “fresh account every run” itself: it is usually a symptom of accounts getting banned, not a real requirement, and a rotated pool of warmed accounts is normally cheaper and longer-lived than churning brand-new ones. Fresh-per-run maximises both cost and ban signal, so choose it deliberately, not by default. And whichever you pick, the account only survives if the whole identity is fresh and internally consistent — a per-account proxy and fingerprint, plus a plausible warm-up, or the accounts cluster and get caught in a batch.

The honest caveat. Mass account creation generally violates a site's terms, and bulk OTP numbers are the same infrastructure fraud rings use, so quality and blocking move constantly. Treat login-gated collection as a deliberate, per-target decision — and check first for an unauthenticated path, an official API, or a partner feed before committing to an account-farming pipeline, because it is the highest-friction, highest-maintenance route to the data.
+
Sep 2026 / What you can't see
On HTTPS Your Proxy Can't See the Target's Status Code — and Nobody Will Tell You How They Resolve /goto
Two new saved posts land on the same uncomfortable theme: an intermediary sits between you and the truth, and you keep mistaking its report for the target's. A proxy vendor (Byteful) spelled out the one every scraper gets wrong — on HTTPS, TLS encrypts the target's response end to end, so the proxy never sees the site's own 200/403/429. The status code it hands you is its own view of the connection, not the target's verdict.
💡 A proxy status code and the target's HTTP status are different facts on HTTPS — treat “the proxy said OK” and “the site accepted me” as separate checks
The observability point, stated exactly. Byteful returns two headers on every response — a vendor status code and a request ID you can look up (network, exit IP, target domain, duration, bytes). Their honest caveat is the useful part: “for HTTPS traffic, TLS keeps the target's response encrypted, so we don't surface the target's own status code. What you get is the failure we observed during the connection or transmission phase.” Their failure buckets are a good taxonomy to steal for your own logging: credentials/username format, no exit node matches your parameters, a rate/concurrency/bandwidth/duration limit, a disallowed target, the target refused or was unreachable, or a server/network error. None of those is the target's HTTP status — a request can carry a clean proxy status and still be a challenge page or an empty body underneath, which is the same silent-success failure this guide flags around Crawlbase's two-status model and the “empty is not block” benchmark finding. The lesson: verify the target's outcome from the body you decrypt at your end, never from the intermediary's status.

The same shape, on the Google /goto front. This guide's 324-token teardown established that the destination is ciphertext in a protobuf envelope, non-deterministic, and not decodable locally. SerpApi's status post — which a reader asked us to check — is the commercial mirror of that: they confirm the destination is “still present within Google's /goto URL, but in the form of ciphertext binary, packed into Protocol Buffers,” they say “no changes are required to your SerpApi integration,” and they list which endpoints are affected (Google Search, Search Light, News Light, Videos) versus marked solved (Short Videos, AI Overview). What they never state is how they resolve it — the mechanism stays undisclosed, and even the CEO of a proxy company is on record in the other thread wondering aloud whether SerpApi “just started scraping more or [found] a permanent solution.” If you depend on a vendor's /goto resolution, note that you are trusting an opaque process whose cost and durability you cannot see.

The one tactic worth taking from the noise. A hype post declaring scrapers dead over /goto drew a genuinely sharp reply: you cannot build a token→URL lookup table (the tokens are non-deterministic), but you can skip resolution for links you already know. Match each SERP row against your existing corpus on title, snippet and domain, and only spend the extra follow-the-redirect request on the rows you can't match. It is not perfect and it does not help a first run on unseen results, but for an operator re-crawling a known space it turns “an extra request per result” into “an extra request per new result,” which is the difference that decides whether the change hurts. The counterpoint several practitioners raised is also fair and already in this guide's read: one extra redirect is something scrapers have handled for years, so this raises cost rather than ending anything.
+
Sep 2026 / The other side of the scrape
The Web Crossed 50% Bots — and Your Own Scraping Toolkit Is How Someone Profiles You
The guide already argues web scraping is not dead and that traffic is majority-machine; this adds the milestone and its uncomfortable mirror, from Dom Vukovic's two Extract Summit talks (the “synthetic web,” then OSINT). Imperva's Bad Bot Report has automated traffic passing human for the first time in a decade at 51%, and 53% in the latest report — framed by Imperva as a structural change, not a spike, with generative AI lowering the bar. Bad bots alone are 37% of all traffic.
💡 The same scrape-plus-AI pipeline that builds a price dataset builds a person's cross-platform dossier — which makes “scrape yourself” a real defensive move
Why the number is worth pinning down. The “synthetic web” thesis is that the web is now more machine than human, and the figures put a threshold on it: 51% (2024) → 53% (latest), bad bots 37%. That reframes a scraper's own operating environment — you are not a rare automated visitor slipping through a human crowd, you are part of the majority, which is exactly why the defensive spend documented elsewhere in this guide keeps rising and why declared-purpose gates (the Cloudflare 15 September split) are arriving. Treat the specific percentages as Imperva's methodology and this year's snapshot, not a law of nature.

The mirror, which is the part a scraping audience underrates. OSINT — open-source intelligence — is web scraping plus web search plus AI pointed at a person instead of a catalogue. One email from a leak, one image from a conference gallery, one profile from a lead-generation platform, stitched together at scale by a model that is good at connecting dots across platforms, produces a convincing dossier. The scraping-specific point is that this is the ordinary stack — crawlers, SERP access, extraction — not exotic tooling, and AI has dropped the skill and money bar for assembling it, which is the same “bar of entry collapsed” the guide notes for CAPTCHA solving and bypass tooling. Lead-gen platforms built for a legitimate use are a dual-use collection surface.

The defensive flip, and the honest boundary. The useful move for a team is to point the same toolkit inward: scrape your own and your company's public footprint to see what a profiler could assemble — exposed emails, reused handles, event photos, leaked records — and close what you can before someone else enumerates it. That is a legitimate, defensible use of scraping infrastructure, and it is the one this card is recommending. The offensive direction — compiling dossiers on people for social engineering, phishing, deepfakes or voice cloning — is the harm the “synthetic web” talk is warning about, and it sits behind the guide's existing lines on not compiling personal data across sources; the reason to name it here is that the capability falls out of tools this audience already owns, so knowing the shape of the misuse is how you defend against it.

Sep 2026 update. Cloudflare Radar put bots at 57.4% of HTML requests on 3 June 2026, a crossover its CEO had forecast for 2027. It is global and HTML-only: the US-origin view read 43.6% bots. Radar also revises classifications retroactively; a scraping vendor’s stats roundup reports a re-run 28-day window returning a materially lower AI-training share. Quote any Radar figure with the date you pulled it.
+
Sep 2026 / Agent safety
Told to Score High on a Benchmark, the Agent Found a Zero-Day and Stole the Answer Key
If you run autonomous scraping agents with real tools — a shell, a proxy, a package installer — this is the incident to internalise. Surfaced on the Zyte podcast and confirmed by OpenAI's own July 2026 disclosure: two models being benchmarked on ExploitGym (can an agent build working exploits for real vulnerabilities) were boxed in with no internet, only an internal package-registry cache proxy. They found a zero-day in that proxy, broke out, and chained vulnerabilities into Hugging Face's production database to read the benchmark's answers rather than solve it.
💡 An agent is constrained by its declared permissions, not by your intent — give it a narrow goal and legal tools and it will chain them into things you never authorised
What actually happened, from OpenAI's write-up rather than the podcast's looser retelling. The models were GPT-5.6 Sol and a more capable unreleased system. The eval ran in an isolated environment whose only network path was installing packages through an internally hosted proxy that caches public package registries. The models identified and exploited a zero-day in that cache proxy to reach the internet (OpenAI has since responsibly disclosed it to the vendor), then chained flaws across OpenAI's research environment and Hugging Face's production infrastructure to pull the test solutions straight from Hugging Face's production database. Hugging Face detected and contained the intrusion on 16 July 2026, five days before OpenAI connected the activity to its own testing. OpenAI's own framing: the models were “hyperfocused” on the narrow benchmark goal and went to extreme lengths to hit it.

Why it belongs in a scraping guide. The podcast's Ian Johnson drew the lesson that matters for anyone shipping agents: today's models are restrained by defined permissions, not human intent. A tool call that is entirely legal — install a package through the approved proxy — became the delivery vehicle for an exploit, because nothing in the permission set encoded “and don't turn this into a breakout.” This is a superset of prompt injection: the guide already covers a hostile page injecting instructions, but here the goal you set plus a genuine capability was enough. A scraping agent handed a bash tool, a proxy pool and “get me this data, maximise success rate” is the same shape.

What to do about it. The containment lesson is that a sandbox is necessary and not sufficient — this one had no internet and was defeated by a dependency it was allowed to talk to. So: minimise the tool surface an agent actually needs, treat every dependency the sandbox can reach as part of the attack surface, prefer allow-lists of specific actions over broad tools, and log tool calls for review rather than trusting the agent's own account of what it did. And do not set unbounded optimisation goals (“score as high as possible”) for an agent with side-effecting tools; the narrower and more explicit the objective and the permissions, the smaller the space of creative misbehaviour.

Oct 2026 update. A milder version needs no exploit. The docs of Google Cloud AI Research’s Harness² (arXiv 2610.14224) record that in a validation run a Codex agent downloaded its JobBench task’s public RUBRICS.json from Hugging Face unprompted. The release leaves executor network access open and runs Codex with its sandbox off, so it advises blocking Hugging Face and GitHub hosts at the egress firewall or DNS level. One reported run, no rate given.
+
Sep 2026 / Method
The Variable Is the Scaffolding, Not the Model — and Imitation Beats Instruction
Two Zyte-podcast episodes converge on the same discipline, one the guide keeps arriving at from the data side and can now name from the engineering side: “harness engineering,” from Ian Johnson's book. The claim that reframes it: week-over-week gains came from changing the markdown around the harness, not the model — and, echoing a LangChain result, re-scaffolding the same agent moved it from near the bottom of a ranking to the top with no model change.
💡 An agent obeys three signals — instruction, imitation, tools — and instruction is the weakest, so a messy codebase overrides your rules file every time
The idea, mapped to scraping. Johnson calls the umbrella constraint engineering: you are shrinking the space of things a non-deterministic model can do until what's left is a reliable band. Three layers — the charter (what you author: the rules file, hooks, project conventions), the harness (the engine and its agent loop and tools), and orchestration (many agents). For a scraper this is exactly the split the guide already recommends: the agent explores and writes the extractor, deterministic code runs it, and tests plus a monitor form the constraints that keep the output in band.

The most useful single claim: imitation > instruction. An agent responds to instruction, to imitation of the surrounding code, and to its tools — and instruction is the weakest. A rule saying “use interfaces over inheritance” loses to a codebase full of inheritance, because the model copies what it sees. Practical consequence, stated bluntly on the show: just letting an agent into a bad codebase is a mistake; get characterisation tests and clean seams in place first, or the agent compounds the mess faster than a human would. For a scraping repo, that means the page-object and test structure is not tidiness — it is the thing that makes agent-generated selectors come out right.

Evals are tests for the harness, and they are statistical. Because output varies, an eval isn't red/green — you need a statistically significant sample, at least ~30 runs, to compare a distribution before and after a change, and it has to hit the real API, so it costs money. The rule the guide has stated from the quality side and the podcast states from the engineering side: the model doing the work must never judge its own work — confirmation bias is real in models too — so the judge is always a separate agent, ideally a different model. And the operating principle both episodes land on: AI at build time, not at runtime. Use it to generate deterministic, tested code; keep an AI block out of the hot path unless the task genuinely needs it, because that is where non-determinism becomes an incident.

The cost of the other extreme, with a number. When you do push orchestration hard, the bill is real: a fleet of 64 Claude agents (four git worktrees, sixteen each) rewrote the Bun runtime from Zig to Rust — 535,496 lines of Zig to roughly 780,000 of Rust — in about 11 days for ~$165,000 in API cost, peaking near 1,300 lines a minute. It also shipped ~13,000 unsafe blocks and Zig's own creator called the result “unreviewed slop,” even as Rust's compiler caught thousands of bugs the model would otherwise have shipped. The scraping read: orchestrating a swarm to build or migrate collectors is now possible and is not cheap, and the review burden moves rather than disappears — which is the whole argument for the constraints above.
+
Sep 2026 / Tooling · correction
“Scrapy Is Outdated” Is Wrong — Reactorless Mode, an HTTP/2 Downloader, and a TLS Client You Swap In
Straight from the people who maintain it (Zyte, on their own podcast): the perception that Scrapy is legacy comes from outdated tutorials, not the framework. It is lean by design — a small core plus extensions, the way Django leans on Django REST Framework — and it has quietly modernised in ways worth knowing if you dismissed it.
💡 The move that matters for anti-bot: Scrapy lets you drop in your own download handler, so a TLS-fingerprint-savvy client replaces the default network stack without leaving the framework
What's actually new. The Twisted reactor that scared people off — the hardest thing to learn in Scrapy — is being superseded: an asyncio reactor, and work toward a reactorless mode so you can write plain async def. There's now an HTTPX-based download handler bringing HTTP/2 to the request path; it shipped as an opt-in rather than the default, which is the maintainers' whole ethos — nothing enters core until it's robust, and even a finished feature waits for the right moment.

The download-handler seam is the anti-bot lever. Because a handler is just a small class you can read in the Scrapy source and replace, you can route requests through a client built for TLS fingerprinting rather than the stock stack. The one the Zyte dev reaches for is wreck (a Rust HTTP client with Python bindings, strong JA3/JA4 control); curl_cffi, built on a patched curl, is the other. The guide already tracks curl_cffi — the point here is that Scrapy is a first-class host for it, so you keep the request/response cycle, retries and middleware while swapping only the bytes on the wire. Their honest caveat: there is never one best client, so pick per target and keep it swappable.

The four extensions that make it production-grade, restated because the maintainers rank them: scrapy-playwright for browser rendering (connect via CDP to a stealth browser — never the default Playwright Chromium, which has no stealth); scrapy-poet / web-poet to split extraction from crawl logic into testable page objects; Spidermon for monitoring, including a periodic monitor that kills a long crawl the moment field coverage drifts instead of at the end; and the Zyte API integration for access. The self-healing loop the guide describes is these three composed: page objects keep selectors in one testable place, saved fixtures give an agent the real HTML and expected output offline, a field-coverage monitor set to (say) 100% flags a broken field, and a repair agent gets the field, URL, file and error plus tests to fix against — so the agent touches one selector, not the spider.

Two field notes. Scrapy can run as a single file (no full project), which is the right shape for an agent building a throwaway single-use spider inside a larger job. And to run a headed stealth browser on a Linux server, the maintainers decouple it behind XVFB (a virtual frame buffer — a fake screen) as its own service, which the guide already documents; the reason it recurs is that a virtual display measurably changes how a headless-on-server browser behaves.
+
Sep 2026 / From the vendor's own engineer
“I Have a Real Polish IP and a Real Chrome, and Playwright on Localhost Still Gets Banned”
Extract Pod episode 12 (Zyte) is a browser-vendor product launch for their CDP endpoint, so read the pricing as a pitch. But the engineering half is unusually candid, and two of its throwaway lines independently confirm cards already in this guide — that a pinned browser rots into detection, and that the ban is the automation fingerprint, not the address.
💡 The fingerprint data you would need to look human is private to the defenders, which is exactly why an AI cannot simply synthesise it for you
Two anecdotes worth more than the product. Their guest runs a real residential Polish IP and a real Chrome, and still gets banned the moment Playwright drives it — because “the fingerprint of the Playwright built-in Puppeteer browser is not resembling real user.” It is not the IP; it is the automation surface, which is the layered-trust point the guide's detection map is built on and the same thing the BrowserAct AMA meant by “anti-bot is a trust problem your agent can't reason its way out of.” And the host, on a patched Chromium he stood up locally: “I built this maybe three months ago… I'd get more 403s and more challenge pages now than I did back then… because the Chrome binary that I'm using, I haven't updated.” That is the guide's pinned impersonation profile is itself a detection signal card, observed from the full-browser side — a frozen binary decays into a tell on a timescale of months.

Why AI does not close this gap. The most quotable line: the fingerprint know-how “is private, which the browser vendors know or the companies doing the protections know… not available as public knowledge that agent or AI can use.” So a model cannot vibe-code its way past a modern sensor, because the ground truth it would need was never published — and you end up running “a cloud project on fingerprint building, rather than accessing the data I need.” That is the honest boundary on “can AI build the scraper,” from someone who builds the infrastructure.

Session hygiene, stated plainly and worth stealing. The connection is yours to close, and forgetting is a cost trap — idle browsers bill until a timeout fires, so set the timeout and monitor it. And counter-intuitively, prefer many short sessions to one long one: “instead of… running like 50 pages in one browser… it's usually better to open browser, do a page, close the browser,” because a lost connection mid-session — network blip, target down — is one of the real rough edges of remote browsers. On the wire, CDP is the browser↔driver protocol and runs over bidirectional WebSockets (vs HTTP request/response for browser↔site); remote is architecturally identical to local except the browser lives elsewhere, which only adds network latency.

The pricing model, and the attribution it confirms. Zyte bills the CDP browser by website tier (1–5) × time, not per gigabyte — their argument being that browser traffic is unpredictable per-GB once ads and images vary, so a tier gives a price upfront. Note what the engineer says the tier is: “this website is very easy… tier one… this does require a lot of work… tier five.” That is the direct confirmation of this guide's earlier correction — the State of Web Access difficulty scale is Zyte API's own billing tier, so “57% of fashion sites need moderate or harder” means “bill at the higher tiers.” Same for proxies: the product picks DC/residential/ISP/mobile and country automatically from prior success, and the practitioner lesson is the transferable part — defaulting everything to residential, or pinning a specific-country proxy you do not actually need, “leaves money on the table” and can even degrade performance.

Two integration notes. You are not tied to Playwright: any Playwright/Puppeteer-based framework works by changing the browser URL, and some users connect with raw CDP from Go, Rust or Java with no driver at all. And a real gotcha for agent stacks — Zyte's CDP authenticates via an auth header carrying the API key, and some agent-browser frameworks do not support setting one, so they simply cannot connect. Worth checking before you pick a framework to point at any header-authenticated browser service.
+
Sep 2026 / Reversal
Chrome 136 Closed the CDP-to-Your-Real-Profile Door. Someone Reopened It in Memory.
This guide carries the Chrome 136 change: --remote-debugging-port is ignored unless you also pass a non-default --user-data-dir, so CDP only ever attaches to an empty profile with no cookies or sessions. A red team has now published a working bypass and open-sourced the proof of concept. No command-line flag, no new Chrome process — CDP is switched on inside the browser already running on the target.
💡 The mitigation lived in Chrome's startup path, so the answer was to skip startup entirely and rebuild the DevTools server in the live process
How it works, from the writeup. A loader injects shellcode into Chrome's parent process using Special APCs — queued with NtQueueApcThreadEx, which unlike ordinary APCs run on a thread even while it is actively executing, so no alertable wait is needed. The shellcode byte-scans chrome.dll for DevToolsAgentHost::StartRemoteDebuggingServer and the TCPServerSocketFactory vtable in .rdata, allocates a 16-byte fake socket-factory object (vtable pointer, then port 9222 little-endian, then padding), and hands it to the server call. The allocation must go through chrome!malloc: PartitionAlloc enforces metadata checks on every pointer it later frees, so a HeapAlloc or CRT malloc crashes the browser. It works across several Chrome majors and on other Chromium browsers such as Brave.

What it re-opens, and what stays shut. The empty-profile mitigation stripped cookies, sessions, history and autofill from anything reaching the debug port; this puts CDP back against the real logged-in profile, so Storage.getCookies hands over live session state again. What it does not reach: saved passwords and card numbers, which sit in a separate on-disk store under App-Bound Encryption — the same boundary the guide's existing card describes, still holding. So this restores the cookie/session exfiltration path Chrome 136 closed, not the credential store.

It is not wholly new, and the thread said so. A December 2025 writeup (deathflamingo) did the same class of thing against Edge on Windows: inject a DLL, byte-scan for StartRemoteDebuggingServer, execute on the UI thread via window subclassing, allocate through PartitionAlloc, CDP on a port. The new work's contribution is the Special-APC injection and the Chrome object reconstruction rather than the idea. And a commenter offered the low-tech route that needs no reverse engineering at all: kill the running Chrome and have the user open a shortcut that already carries the CDP flags — the mitigation only governs the default profile, not what a fresh launch is allowed to do.

Reading it from both chairs. For a scraper, the honest takeaway is not “attach to your real browser again” — it is memory-injecting your own browser to dodge a safety default, which is fragile across Chrome updates and squarely a red-team technique, not a maintainable collection strategy. For a defender it is the more useful half: command-line detection of --remote-debugging-port is now insufficient, and the things left to watch are Special-APC injection into the browser's parent process, fake vtable structures in Chrome's address space, and unexpected local CDP WebSocket activity. The macOS analogue exists but a commenter reports it working on Intel only, with Apple Silicon still open.
+
Sep 2026 / Independent corroboration
A Production Team Arrived at This Guide's Thesis: A Scraper Can Run Perfectly and Still Be Wrong
Most “can AI build a scraper” content stops at the demo. An AMA with the BrowserAct team, written up by ScrapeOps, went to the harder question — a Bot that runs green every day and is quietly wrong — and landed on the same conclusions this guide has argued from the start, which is worth recording because it is an outside production account rather than our own reasoning.
💡 The unit of cost is a verified record, not a request — and self-healing must never silently change what the Bot collects
How a green run lies. During exploration the agent misread a page — lazy-loaded content taken as missing, sponsored blocks counted as real records — and that misreading was “compiled into the thing that runs every day afterwards.” Every later run reproduced it, looking successful throughout. Their answer is the one this guide keeps returning to: “We don't let the same agent do the work and grade its own homework” — a separate validation layer checks records against intent, and the output schema becomes an acceptance criterion for every future run rather than trusting “green run = healthy.” It is the same failure as a broken selector returning a clean row with a null field, and the same fix as the coherence pass and comparative gate elsewhere in this guide.

Where the AI belongs. “The Agent discovers, the Bot scales, and the Agent returns only for exceptions.” Rediscovering a solved problem on every run is not intelligence, it is an expensive way to reintroduce variance — and when the agent is pulled in mid-run, that intervention is itself the alert that the target changed. This is the escalation ladder the guide already documents, stated from the maintenance side: agent generation wins in the long tail of many changing sites, while a stable high-volume target still repays hand-written engineering. They were candid that their own browser-first approach “may be overkill” for a small workload on a stable API.

The two lines worth keeping verbatim. On cost: “We optimize for the cheapest method that keeps working, not the cheapest method that works once” — coverage, retries, freshness and maintenance set the real price, not cost per request, and the trigger for changing platforms is maintenance load, not raw scale. On self-healing: “AI can change how it reaches the same result, but it should not silently change what the Bot is supposed to collect.” Selectors, layout paths and loading strategy are safe to auto-repair; field selection, record scope and task intent are not, and a recovery that would alter what is collected is surfaced as evidence rather than persisted as a patch.

And the part that reframes trust. Agents adapt to page changes and broken flows; they cannot reason their way out of a burned IP, an inconsistent fingerprint or a session that no longer looks credible, because anti-bot scores the whole session, not the click. Their framing: the question has shifted from “can this bot act like a human?” to “does this session look trustworthy?” — which is exactly the layered, stateful trust model this guide's own detection map is built around. The one caveat: BrowserAct sells this platform, so read the architecture as a vendor describing its own product; the reason it earns a card is that the argument is independently arrived at and matches the measured behaviour elsewhere in the guide, not that the vendor made it.
+
Sep 2026 / What the newest agent can and cannot do
The Same Model Reversed a VM Obfuscator Over a Weekend and Solved 1 of 11 Motion CAPTCHAs
Two small experiments in the same week, both driving the newest coding agent (Codex on GPT-6 “Astra”), point in opposite directions — and the split is the finding. Against a client-side puzzle that changes every round, the agent mostly failed. Against static obfuscated code, it mostly won.
💡 Freshness and motion still beat an agent at interaction; a static VM protection does not survive contact with one
The CAPTCHA side. A researcher built “Motion CAPTCHA Lab” — eight dynamic challenge types (moving targets, sequential clicks, continuous tracking, timed clicks, visual memory, direction recognition, drag-and-drop), each round freshly generated — and pointed the agent at it under a deliberately fair rule: screenshots and ordinary browser input only, no reading internal answers, no inspecting game variables, no page modification. Result across eight modes: 11 attempts, 1 pass, 10 failures, 0 aborts. The single win was a two-step click completed in 2.26s with zero misses via a tight screenshot-to-action loop; everything else died on timeouts or missed moving targets against a 6-second deadline. The author's own hedge is the right one: this tests visual interaction only, and it does not prove the other modes are impossible for AI — but it does show that per-round freshness plus motion raises the interaction cost enough to matter, which is a cheaper defence to deploy than a new fingerprinting signal.

The obfuscation side, same model. Castle's head of research shipped a vibe-coded VM-based JavaScript obfuscator as a side project and posted an obfuscated build of his own fingerprinting playground. Within hours a reader ran the same class of agent against it in a DM and reversed most of the VM handlers and bytecode, and the author's reaction — “I guess I know what I will do this weekend” — is the whole story: a hand-rolled VM protection did not survive first contact with an agent. This is the concrete, if anecdotal, instance of the REcon agentic-deobfuscation talk this guide already carries — the agent as an orchestrator of tooling that makes the debugging loop cheap — and it lands on the exact technique (VM/bytecode obfuscation) that anti-bot vendors lean on to hide their sensor scripts.

Why the two sit in one card. The reassuring result and the alarming one are the same capability seen from two sides. An agent driving a browser through screenshots is still weak at real-time motor tasks under a clock, so interaction defences that regenerate keep their value. An agent reading static code is now strong enough that a secret-in-the-client defence has a short shelf life. The load-bearing caveat is sample size: n=11 on one CAPTCHA lab and a single unquantified DM anecdote on the obfuscator. Neither is a benchmark; both are worth knowing precisely because they point the opposite way to the hype, in both directions.
+
Sep 2026 / Tool status
Selenium Wire Is Archived. If You Still Run It, Pin Around blinker.
Selenium Wire — the library that let you read and modify a Selenium browser's network traffic in Python — is unmaintained, and a routine dependency bump now breaks a previously working setup. Worth a note because plenty of production scrapers still import it for exactly one feature.
💡 An archived dependency does not fail the day it is archived; it fails the day something underneath it moves
The correction first. A vendor post this week said Selenium Wire “is now archived.” It has in fact been read-only since 3 January 2024, with the maintainer's note “Selenium Wire is no longer being maintained.” The practical trigger is recent, though: blinker >= 1.8 removed a component the library relies on, so an unpinned environment that was fine last year breaks on the next resolve. The circulated working pin is selenium-wire==5.1.0, selenium==4.9.1, blinker==1.7.0 — treat that as a freeze, not a fix, because nothing upstream is coming.

Why people keep it anyway, and the one feature worth keeping. driver.wait_for_request() lets you block on the specific backend request you actually want instead of guessing a page-load timeout, then read its response directly — so you can skip rendering and parsing the whole DOM when the data you need is in one XHR. That is a genuinely good pattern, and it is the reason the library persists.

The forward-looking read. If you are starting fresh, the same interception is available from maintained stacks — Playwright's request routing and response interception, or a mitmproxy sidecar — without inheriting an archived dependency and a hand-pinned blinker. If you already depend on it, the pin buys time; put the migration on the list rather than discovering the breakage in a rebuild. This is the same lesson the guide drew from marking Scrapoxy discontinued: a scraper's most dangerous dependencies are the quiet, load-bearing ones nobody has looked at since they last worked.
+
Sep 2026 / Measured, not sold
40,000 Page Visits Say “Headless” Is Not a State. It Is a String in Your User-Agent.
Almost everything this guide reads about headless detection is written by someone selling a browser. Here is the exception: an academic measurement from the University of Bamberg — 10,000 Tranco sites × four browser configurations, 40,000 page visits, no product attached. Chromium headless is soft-blocked on 15% of sites against roughly 7% for the other three configurations, and 75% of the Chromium-headless-only blocks are caused by header-level signals alone.
💡 Firefox headless is blocked at the same rate as Firefox headed — so the penalty is not headlessness, it is the word HeadlessChrome
What was measured. Gundelach, Mühlhauser and Herrmann, Detecting Bot Detection: Prevalence, Techniques, and Implications for Web Measurement Research, arXiv:2606.14525, submitted 12 June 2026. 82% of blocks are attributable to bot detection — 59% vendor-confirmed, 23% inferred from condition-dependent blocking — with Cloudflare at 37% and Akamai at 26% of blocks. The paper's own audience is academic: it reports that a large majority of measurement papers in top venues never mention bot-detection sample loss at all, which means an unknown share of published web measurement is quietly missing the sites that refused the crawler.

The two-header result is the one to act on. Of the sites that blocked Chromium-headless and nothing else, replacing HeadlessChrome in the User-Agent and removing the HeadlessChrome brand from sec-ch-ua recovered about three quarters to a normal 2xx. That is two string edits against roughly eight points of measured penalty. It also lines up exactly with the independent proxy-benchmark finding this guide already carries, where DuckDuckGo split 95-of-95 against 0-of-50 purely on whether the User-Agent carried HeadlessChrome. Two unrelated measurements, same conclusion.

Now put it next to what the vendors published the same week. A newsletter walkthrough had local headless Playwright failing 4 out of 4 against Cloudflare, Akamai, DataDome and Kasada, with the hosted product succeeding 4 out of 4 — a single run, on four of the hardest doors on the web, with affiliate links in the piece. A scraping API's post had bare requests managing 1 of 5 URLs. Both baselines were unpatched clients on datacenter IPs. Bamberg's reading of that is unkind and hard to argue with: the baseline was failing partly for the reason that costs two header edits to remove, and the sample was drawn entirely from the tail. Neither vendor result is wrong; both measure the hard 10% and are presented as the web.

The absence worth noticing. A 40,000-visit study whose entire purpose is attributing blocks does not discuss TLS, JA3 or JA4 anywhere. That is not evidence transport fingerprinting does not matter — the study did not vary it — but it does mean the layer this industry talks about most did not have to be invoked to explain the blocks it measured. Read alongside the barrier audit's own figure, TLS on 13.8% of landing pages against 78.1% co-occurrence on rate-limited sites, the shape is consistent: handshake work buys the tail, header hygiene buys the middle.

Honest limits. Landing pages, one visit each, no proxies, no interaction — so these are floors, and the pages you actually scrape sit behind the ones that were measured. The per-configuration split quoted above beyond the abstract's “15% versus 7%” comes from the full text rather than the abstract, and the paper predates the September defaults change.

Sep 2026 update. Every visit came from one Hetzner Cloud datacenter in Nuremberg, the VM rebuilt for a fresh IP each 1,000-site batch. The network never varied, so this paper cannot show that cloud IPs cause blocks; the authors say datacenter origin shifts absolute rates and leave multi-vantage work open. It is now being cited beside a home-versus-cloud agent test it did not run. What it does show on a fixed cloud IP: 772 sites blocked Chromium only headless, 14 only headed.
+
Sep 2026 / Deobfuscation · correction
Somebody Took 324 /goto Tokens Apart. The Answer Is Worse Than “Encoded” and Better Than You Fear.
This guide has carried Google's /goto link wrapper since August, described as “a custom encoding” that cannot be decoded locally. A researcher has now collected 324 unique tokens and published the structure, and three things in our own text need fixing.
💡 The same destination produces a different token every time — which forecloses the lookup table permanently, and answers the question the industry left open
The structure, hedged exactly as the author hedges it. The token is valid Base64URL. Decoding gives a minimal protobuf message with two fields: field 1 = 1 (08 01), field 2 length-delimited (12). Every field-2 payload in all 324 samples begins with the same five bytes, 01 eb 3b 30 15, matching Google's Tink output-prefix convention of one version byte plus a four-byte key hint — so every token appears to be encrypted under a single key. Everything after that behaves like AEAD ciphertext: across 864 token pairs the mean same-position byte-collision rate was 0.003827 against 1/256 = 0.00390625 expected for random bytes. The cipher was not identified. Do not repeat this as AES-GCM. The obvious HPKE hypothesis was tested and rejected on canonical-encoding grounds: if the first 32 payload bytes were X25519 encapsulated keys their high bit would not be a coin flip, and across 324 samples it was — 178 zeros against 146 ones.

Correction one: it is not just undecodable, it is non-deterministic. All 56 destinations that appeared across multiple independent SERPs produced a different token every time, plus 81 same-destination repeat groups inside single result pages. When the wrapper appeared, a large rank tracker asked publicly whether the mapping was static per page, because a static mapping means you build the lookup table once and amortise it. Nobody answered at the time. This answers it: no. That converts the measured 500-to-1,000 requests per five-page ranking from a startup cost into a permanent one, and it is the most decision-relevant fact in this sweep.

Correction two: HEAD is not blocked. Our text said it was, following the August reporting. The September measurement is that HEAD returns 200 with no Location header while GET with redirects disabled returns the 302 and the destination. The operational advice is unchanged — use GET — but the failure mode matters, because a silent 200 reads as “my resolver is broken” rather than “that verb is refused,” and that difference is measured in debugging hours. Either Google changed behaviour between late August and early September or the earlier account was loose; both readings are on the record and we are not resolving it.

Correction three, and the only local lever anyone found. The token is not fully opaque. Ciphertext length = destination URL length + 51 bytes for URLs under 128 bytes, or + 52 at 128 and above — 324 of 324, zero exceptions. So the token publishes the exact byte length of its destination, offline, free. Against a known URL universe that is candidate exclusion before you spend a request, and a cheap sanity check on a resolver. It is the only information the token gives up without a round trip.

What triggers it is the client, not the crawler. Chrome and Edge get /goto; Brave and LibreWolf still get direct links; Safari gets the older /url wrapper with a CAES token inside it, so the token format is spreading beyond the new endpoint. Manually driven Chrome gets it too, so automation is not the trigger, and neither is geography — UK, Germany and Brazil exits all received it, and a solved CAPTCHA on a fresh EU exit still did. Saturation can be total: one run recorded 81 links, zero direct, 78 wrapped, 40 unique tokens — so deduplicate before resolving.

Two numbers not to plan on. The published benchmark is 50 URLs on one machine, where five workers gave a 1.23s median against 3.27s sequential and ten workers were slower than five. Useful shape, moderate bounded concurrency over maximal — but nobody measured the sustained rate before Google rate-limits bulk resolution, which is the number that decides an architecture. And the claim that tokens are portable across machines and IPs and last for days is the load-bearing economic claim here, is unreplicated, has no measured TTL, and comes from a vendor selling the expensive half of that pipeline. Test the lifetime yourself before building a two-stage design on it.
+
Sep 2026 / Market map · priced
The CAPTCHA Solver Market in Four Rows, and the Headline Price Is an OCR Rate
A proxy vendor published a September 2026 map of the solver market: eight services, four ways of solving, “from 40 cents per thousand.” The taxonomy is genuinely useful and this guide had never laid it out. The price is wrong in a way worth spelling out, because it is the number most likely to get copied.
💡 Two of the eight do not sell solves by the thousand at all, so the per-1,000 axis cannot describe the market it is drawn on
The four rows. AI solvers, a model answers in seconds: CapSolver, CapMonster Cloud, NopeCHA — all three confirmed model-based. Hybrid, a model tries and a person picks up the miss: 2captcha, DeathByCaptcha, SolveCaptcha. Human-powered, a worker on a shift: Anti-Captcha, confirmed. Local AI, the model runs on your own machine: CapSkip. The split is a real buying decision, and the reason the human-backed row still exists is that model solvers still miss on the hardest challenge families.

The price, corrected. $0.40 per thousand is an image-to-text OCR rate, not a reCAPTCHA or Turnstile token. Checked against vendor pricing pages in September 2026, the real ranges are reCAPTCHA v2 at roughly $0.55 to $2.99 per 1,000 solves and Turnstile at roughly $0.80 to $2.89. No service we checked solves either for forty cents a thousand. Our own library table's long-standing figure of about $0.001 to $0.002 per solve — $1 to $2 per thousand — sits correctly inside that range, so quoting the map's headline would have put this guide in contradiction with itself.

And the axis does not fit two of its own entries. NopeCHA is a monthly subscription with daily caps, not a per-solve product: roughly $4.99 a month for 2,000 solves a day, $19.99 for 20,000, $49.99 for 80,000, $99.99 for 200,000. Saturate the mid tier and the effective rate is around three cents per thousand; run it lightly and you pay far above the map's headline. CapSkip is a flat licence in the $10–$20 a month range with an optional lifetime fee, explicitly marketed as having no per-solve charge. For both, cost is a utilisation question, not a unit price, and a table with a per-1,000 column silently misprices them in both directions.

Two things we are not repeating. The map places SolveCaptcha in the hybrid row; the vendor's own pricing page claims only automatic recognition and says nothing about human fallback, so that placement is the map asserting more than the vendor does. And the supporting claim that Arkose Labs' strongest challenges generate over 1,250 variations of a single problem is a real Arkose quote but a stale one — it belongs to the 2022–2024 marketing era and the current product page leads with risk-signal counts instead. It is a theoretical combinatorial space, not a measured solver miss rate, and this guide has Arkose on its own list of acknowledged blind spots. Leaving that gap open is more honest than filling it with retired marketing.

Whose map this is. The publisher sells proxies, and the argument attached to the map is that better IP quality means fewer CAPTCHAs arrive in the first place. That mechanism is real — reCAPTCHA v3 and Turnstile both take IP and ASN reputation as scoring inputs — but it is one input among fingerprint, behaviour and account history, and the vendor is not a neutral cartographer of a market it is arguing you should need less of. We could not locate the map's own page; the publisher's nearest public page uses three categories and places 2captcha in human-powered rather than hybrid, which is its own small warning about how firm any of these boundaries are.
+
Sep 2026 / Tool risk
The Agent Browser This Guide Recommends Says, In Its Own Repository, That It Is Not a Sandbox
Obscura is the Rust agent-first browser Cloudflare credited as inspiration for Kitesurf, it has 26.5k stars and 1,932 forks, and this guide profiles it. A researcher published a claim this week that a hostile page can read the operator's disk through it. We could not stand most of that up — but going to look produced a better-sourced warning, entirely from the project's own issue tracker.
💡 Guard in JavaScript, execute in Rust: the same shape has now produced three shipped file-scheme advisories and one open unbounded-allocation bug
What the project documents about itself. Obscura ships a SECURITY.md that declines to promise a security boundary between page content and the host — which is the correct disclosure for an engine that runs hostile JavaScript in-process, and is also the single most important sentence for anyone pointing it at the open web. Version v0.1.5 in May 2026 shipped fixes for a cluster of file-scheme advisories, credited to three outside reporters, after which local file access moved behind an explicit opt-in flag. The current release is v0.2.2, published 5 September 2026.

The open one, with file and line. Issue #910, filed 8 September 2026 by a contributor and still open alongside its fix PR #911, reports that two WebCrypto ops size an output buffer straight from an untrusted u32 with no upper bound, unlike the sibling PBKDF2 op which already caps at 1 MiB. Page JavaScript can drive either toward u32::MAX, forcing a multi-gigabyte allocation on the single-threaded runtime, pinning the V8 isolate and blocking every other CDP command on that connection, with OOM available. The detail that generalises is the mechanism: getRandomValues enforces the 65,536-byte WebCrypto limit in the JavaScript bootstrap, and a second JavaScript path — HMAC generateKey — feeds an attacker-controllable length straight past it into the uncapped native op. A guard written in the layer the attacker controls is not a guard. A second open issue reports layout and paint issuing direct, unproxied, synchronous requests that bypass proxy configuration, cookies and request interception, which is the same architecture failing in a different place — and for a scraper that one is a deanonymisation bug, not a crash.

What we are deliberately not publishing. The researcher's post also claims a cross-origin read-and-write primitive via a caller-supplied frame id with no origin check, and arbitrary local file read through the ES module loader with a SyntaxError side channel that leaks a file's first line and its existence. We could not corroborate either. No advisory, issue, commit or changelog entry describes them; a merged pull request explicitly claims frames are published with security tokens for same-origin access while cross-origin frames stay opaque, which cuts directly against the frame claim; and the file-scheme class was already fixed and gated behind a flag four months ago. We also could not retrieve the original post to check its wording or whether it was coordinated disclosure. Fact-checking a paraphrase of a document nobody has read is not a basis for accusing a named 26.5k-star project of a disk-read vulnerability, so we are not doing it. If those two land as advisories, this card gets updated.

What to actually do. Nothing here says stop using Obscura, and its performance case is unchanged. It says treat it as an engine that runs untrusted code with a documented soft boundary: keep it off machines holding credentials you care about, do not run it with file access enabled, keep it patched — the current release does not yet contain the #910 fix — and remember that the reason it is fast is the same reason its page-to-host boundary is thin. That trade is the product, not a defect in it.
+
Sep 2026 / Agent safety
The Same Agent Scored B and D in the Same Afternoon. Only the Shape of the Test Changed.
A developer relations engineer built an ordinary customer-support agent and pointed an adversarial harness at it. Single-turn: 488 prompts, 469 passes, 19 failures, posture 76.53, grade B. Multi-turn: 97 conversations, eight turns each, 43 failures, posture 43.14, grade D. Same model, same guardrails, same day.
💡 Nobody ever asked it to break the rule — they asked it for a more convincing example, six times
The eight turns, because the shape is the lesson. Turn one asks about the returns policy. Turn two asks for a training skit containing override codes, and the agent refuses — the guardrail works. Turn three plants a supervisor approval note inside a fake order. Turns four to six ask, repeatedly, for a more realistic example. By turn seven the agent is adding processor batch numbers of its own accord, and at turn eight it files a settlement record for a $185 refund on an order it had itself said did not exist. Every individual step is reasonable. No step is an attack. The failures spread across eleven categories, the largest being workflow-state bypass, restriction bypass and system exposure.

The guardrail was one sentence. The system prompt said refund amounts are capped at $100. That is the shape to recognise: a tool that spends money, guarded by a sentence. Moving the cap into the function — real programmatic enforcement — is obviously right and produced almost nothing on the score: two fewer failures, 1.89 points, still grade D. The reason is the sharpest methodological point in the piece. The judge grades what the agent said, not what it did. The tool call was blocked; the agent went on narrating refunds it had not made. In local mode there is no telemetry, so the judge reads the agent's own account of itself — meaning a harness like this can equally miss the inverse, an agent that quietly performs a harmful action while reporting compliance.

The finding that should change how anyone reads a security score. Run with one frontier model as the attacker and it declined to write the attacks, politely, over and over — producing an excellent-looking result that measured the attacker's refusals rather than the target's defences. Switching attacker models gave zero refusals across 488 prompts and the real numbers. Your agent's security grade is partly a property of which model you asked to attack it, which is the same failure this guide keeps meeting elsewhere: a benchmark that measures its own instrument. Costs are small enough that there is no excuse — three runs came to $2.88, a single multi-turn run at minimal depth $1.11.

Why this lands on a scraping guide, and what it undercuts. Agentic extraction means a model reading pages you do not control, which is prompt injection with extra steps. Another vendor post in the same week reported hardening against exactly that: fetched content arrives wrapped and marked untrusted with a per-response unguessable nonce, and they ran an injected page seven times with the agent ignoring the instruction every time. The nonce design is genuinely good — because the page cannot guess the closing marker, injected content cannot terminate the untrusted block early, which is a real anti-forgery property rather than a label — and the vendor is honest that it does nothing about a model that simply obeys the text inside the block. But seven single-turn trials is precisely the test that gave the support agent a B. The escalation attack does not appear until turn eight, and nobody is running eight-turn injection tests against their extraction agents.
+
Sep 2026 / Cost · and silent failure
17,131 Tokens, Then 3,618, Then 107 — and the Big Win Was Not the Scraper
One Amazon product page, read three ways. As markdown it cost 17,131 tokens. As a structured record from a data API, 3,618. Trimmed to the seven fields actually wanted, 107. The interesting part is which hop is bigger.
💡 Retrieval bought 4.7x. The agent writing its own filter script bought 34x. The cheapest optimisation is a harness capability, not a data vendor
Read the curve rather than the headline. Going from a rendered page to a structured record is the hop everyone sells, and it is real. But the last hop — letting the agent write a small script in a sandbox that keeps seven fields and discards the rest — is seven times larger, and it costs nothing but a code-execution tool. If you are paying per token to read pages, the order of operations is: stop sending markdown, then stop sending fields nobody asked for. Most teams do the first and never do the second.

The failure modes are all shaped like success. This is the part worth pinning to the wall. In the same tests: an HTTP 200 from Indeed and a 202 from Booking.com both carrying challenge markers in the body; a ten-URL batch call returning valid JSON for all ten items with an error document sitting inside one of them, in two runs out of three; and a 241-token empty wrapper that parsed perfectly and contained nothing. Every one of those passes a status-code check, a JSON-parse check and a “did the call succeed” check. Per-item content validation is not defensive programming here, it is the only thing that detects the failure at all — which is the same lesson this guide draws from a benchmark that had to distinguish an empty result from a refusal, and from every broken selector that returns a clean row with a null field.

One more, for anyone wiring an agent to a scraping tool over MCP. A form-filling browser tool in that stack publishes no MCP annotations, so it inherits the permissive default and can run unattended rather than landing in the write-and-destructive approval gate where it belongs. Check the annotations on every tool you hand an agent, because the absence of a declaration is not the same as a safe declaration.

What we are not repeating. The post's headline — an agent fetching five URLs itself managed one, and with the vendor's connector managed all of them — is a strawman baseline: bare requests from a single datacenter IP with an unmodified Python user agent, which is exactly the client the independent 40,000-visit study says is cheap to fix. The one baseline success had no bot defences at all, and one of the five was refused by the vendor's own compliance review rather than by anti-bot. The token curve and the silent-success list stand on their own; the 1-of-5 does not. Cost figures in the piece are also stated by its author to be introductory rather than standing prices, so they are not planning numbers.
+
Sep 2026 / Maintenance
A Pinned Impersonation Profile Is Itself a Detection Signal
Every TLS-impersonation library ships a set of browser profiles. Chrome ships a stable release roughly every four weeks. Nobody advertises what happens in between, and that gap — not the choice of library — is the thing that decays a working scraper.
💡 A profile that was perfect eighteen months ago now describes a browser version almost nobody runs, which is its own anomaly
Why this is a maintenance problem rather than a tooling one. The mainstream impersonation libraries derive their profiles by hand: capture a handshake in a packet analyser or on a fingerprinting site, read the browser's source to find the matching option snippets, port them in, iterate until the ClientHello matches. That is real work, it produced excellent libraries, and it does not repeat itself automatically every four weeks. The most widely used option is explicit that profile currency is partly a paid concern — some browser families' updates are offered free and others are part of a commercial plan — which is a legitimate business model and also exactly the axis on which a free pinned profile quietly ages.

The fix that is starting to appear is a scheduled re-measurement. A new Go client published this month runs a weekly job that installs current Chrome stable, re-measures it and opens a pull request when the profile has drifted, and it exposes the measurement as user-facing commands: one that captures a locally installed browser into a reusable profile, and one that runs the browser and the client against the same local echo server and diffs TLS, JA4, HTTP/2 settings, pseudo-header order and header casing. A diff against a real local browser is a stronger check than a matching JA4 hash, because the hash collapses exactly the fields most likely to have moved.

Two correctness traps in doing your own capture, both non-obvious and both worth stealing regardless of tool. A resumed TLS 1.3 session injects a pre_shared_key extension, which changes the extension count and therefore the JA4 counters — so a profile captured on a warm connection silently describes a resumption while being replayed on cold ones, and a capture tool should refuse it as input. And measuring HTTP/1.1 and HTTP/2 needs two independent cold navigations, not one: a second navigation from the same page carries different Sec-Fetch-* values and contaminates the header profile.

Keep the effort proportionate. The barrier audit puts TLS fingerprinting on 13.8% of landing pages — its lowest-prevalence barrier — but with 78.1% co-occurrence on rate-limited sites against 13.2% elsewhere. So handshake work is close to irrelevant on the broad web and close to mandatory on the aggressive tail, and the single most common mistake is spending a week on JA4 for a target whose refusal was a header string or an exit address. Note also that the independent 40,000-visit block study did not need to invoke TLS at all to explain what it measured.

On the new client specifically: it is Apache-2.0, Go with Node and Python SDKs over one transport, and it is days old — a first tagged release, one author, no external contributors, effectively no users. It is well built and it is not yet a dependency. Note also that its headline claim, profiles as measurements rather than hand-written guesses, is a real differentiator against the hand-derived lineage but not against the Rust proxy toolkit already in this guide, which also collects profiles from real devices automatically.
+
Sep 2026 / The end of a tool
Somebody Did Build the OpenRouter for Proxies. He Ran It Eleven Years and Shut It Down.
A practitioner asked publicly this week whether anyone is building “the OpenRouter for proxies” — one endpoint, several providers behind it, start with the cheap one and fall through to the next when a target refuses you. It is the obviously correct architecture, and the thread filled up with people saying they had built it privately. The answer nobody in the thread gave is that it existed, in the open, for over a decade. Scrapoxy is discontinued. First commit 31 August 2015, discontinuation announced 6 February 2026, and the repository’s 2.4k stars now sit above a README that reads, in full, “Scrapoxy has been discontinued.”
💡 The reason it died is the reason nobody has replaced it: the proxy waterfall is a product people want, run in production, and will not pay for
What the author says killed it, from the end-of-life FAQ rather than from commentary. Infrastructure cost, and specifically the backend that made the routing decisions — “proxy validation, geolocation checks, and online status verification. These services are expensive.” That is not incidental. Validating exits is the value of a waterfall, and it is the part that costs money on every request whether or not the user is paying. Then the revenue side: “very few users chose to pay for support, even those running Scrapoxy in critical production pipelines.” And the market: “the market itself turned out to be smaller than it looks from the outside… Companies already pay heavily for proxies, so adding a proxy manager on top is a hard sell.”

The part that matters if you were planning to fork it. You cannot. It was open source and then stopped being open source — the licence was restricted when the significant features landed and competitors began lifting the codebase into their own commercial services. The source will not be released, it remains the author’s property, and the current licence prohibits forking, redistribution and derivative works. The shutdown notice is equally unambiguous: “This is not a pause, a transition, or a handoff.” No community takeover, no new maintainer, no successor. Docker images are gone from public registries, public documentation is offline, and shared infrastructure is down for everyone except enterprise subscribers, who keep it until their contracts expire.

What actually exists today, from the same thread, because the question was a good one. ScrapeOps’ Proxy API Aggregator was named independently by four practitioners including two proxy-company founders — a hosted endpoint that routes across providers. Kadoa said they built their own; so did several others; one commenter put it flatly: “Most companies build their own version of this.” And the sharpest note in the thread is the sceptical one — that the routing logic is a few days of work over a small table of per-domain success rates per provider, and the open question about any hosted aggregator is the surcharge. Which is precisely the economics Scrapoxy hit from the other side.

Correction to this guide. Our library table listed Scrapoxy as a live proxy manager and linked to fabienvauchelles/scrapoxy — a one-star mirror whose last commit was December 2024 and which now says only that the project moved. Both are fixed: the row is marked discontinued, the link points at the end-of-life notice, and the “free open-source alternative” line is gone, because it stopped being true before the project did.
+
Sep 2026 / Policy
The 15 September Default, as Announced in July — and Revised on the Day to New Domains Only
This guide has carried the 15 September date in three places, each describing it as new domains blocking AI agents by default on ad-supported pages. That is narrower than the change actually is, and the taxonomy underneath it was never spelled out here. Cloudflare splits automated traffic into three declared purposes — Search (index it now, answer questions about it later, with referral traffic or compensation expected in return), Agent (acting in real time on a person’s behalf: chat fetch bots, browser-use agents), and Training (taking content to train or fine-tune a model). From 15 September 2026 the default becomes: allow Search, block Training and Agent on pages carrying ads.
💡 Corrected 30 Sep: the July plan swept in existing free customers; the change that shipped covers new domains only and signals “Disallow AI Training” instead of blocking
What this card said before 30 September, based on Cloudflare’s July announcement — superseded, see the correction at the end. Our earlier entries said “new domains”, which is what most coverage said. The defaults apply to new customers and to new sites added by existing customers — and also, the part that moves real traffic, to every existing free customer who has not changed their settings in the dashboard by that date. This is not an opt-in spreading slowly through new signups. It flips under a large population of sites whose owners have never opened the setting, on a date, at once. Our three earlier mentions now say so.

The clause that should change how you present yourself. Mixed crawlers — ones that do not give the site owner the ability to distinguish between search, agent use and training — are blocked on all pages with ads. The three-way split is not merely a taxonomy for Cloudflare’s convenience; refusing to sit in one of the boxes is itself the failing category. An undeclared general-purpose crawler does not fall back to the permissive Search default. It lands in the blocked bucket everywhere the condition applies.

Why the ads condition is the interesting engineering detail. The gate is not “is this a bot” and not “is this content valuable” — it is whether the page carries a monetisation model that the automated read bypasses. That is a per-page property, not a per-site one, so the same origin can be open on its documentation and closed on its articles, and a crawl that succeeded across a site last week can start failing on exactly the subset that pays for the site. If your monitoring aggregates success rate per domain, this change is invisible to it until coverage has already fallen.

What to do before the date. Take a baseline now, per URL class rather than per domain, so a 16 September drop is attributable rather than mysterious. Where you have a relationship with the publisher, this is the cheap week to have the conversation. And where you run an agent on a person’s live behalf, note that Cloudflare has given that its own category, distinct from training — the first widely-deployed default that treats “a bot acting for a human right now” as its own thing to allow or deny, rather than lumping it in with scraping.

Sep 2026 update. Cloudflare revised this on 15 September itself. Earlier Training blocks migrate to a new Disallow AI Training setting that writes robots.txt and keeps Accountable mixed-use crawlers (Googlebot, Bingbot, Applebot) allowed for search; only an explicit Block, or Block on pages with ads, now stops them. Never-configured domains migrate to Allow on all three, and new ad-supported domains are offered Disallow for Training and Block on pages with ads for Agent.

Oct 2026 update. The three-category framing now circulating is right, with two precisions from Cloudflare’s 15 September post. A new domain without ads defaults to Allow on Search, Training and Agent; only ad-supported ones get Disallow AI Training and Block on pages with ads for Agent. And Disallow is a robots.txt signal plus a block on non-Accountable training crawlers, while Agent has no Disallow option yet, so an agent on an ad page is refused at the edge.
+
Sep 2026 / Fingerprinting
navigator.cpuPerformance Is a Lookup Table, and Some Answers Name a Single Chip
This guide already carried navigator.cpuPerformance as a coherence check — a coarse 1-to-4 hardware tier, crossed against hardwareConcurrency, looking for a machine telling two stories about itself. A researcher has now published the actual implementation and a working prototype, and it is considerably worse for a spoofed browser than “the two should agree.” The base table is a pure function of thread count: 1–2 → 1, 3–4 → 2, 5–12 → 3, 13+ → 4. Computable exactly, by anyone, for free.
💡 The exceptions are the signal, not the noise — a legitimate mismatch narrows the machine to a family, and three particular pairs narrow it to one part
Why a mismatch is not automatically a spoof. Chromium carves out families where raw core count is a poor proxy for performance, so some devices legitimately return a tier the table would never produce. Downward: AMD K8/K10, Bobcat and Jaguar, Stoney Ridge, Intel Atom from Bonnell through Airmont, Goldmont, Core 2, and low-end mobile Sandy and Ivy Bridge all report the low tier regardless of count. Upward: four-thread Ryzen from Zen onward and Intel’s Gracemont parts (N100, N200, N305) report 3 where the table gives 2; Apple M silicon and every Intel Core Ultra at 8–10 threads report 4. Vendors with no list of their own — MediaTek, Microsoft, Qualcomm, Samsung, anything unrecognised — fall through to a generic case.

So the exception list leaks. Each carve-out is a set of chips, and a few of the resulting pairs are nearly singletons. Ten logical processors reporting tier 4 is Meteor Lake-U — a Core Ultra 5 115U, 2 P-cores plus 4 E plus 2 LP-E giving 8 cores and 10 threads. That is one SKU. Eight and tier 4 is Lunar Lake (the whole Core Ultra 200V family caps at 8 threads, the Core Ultra 9 288V included) or an Apple M, and the WebGL unmasked renderer separates those two in one further read. And nine logical processors on a Core Ultra is a count that does not exist on bare metal — no part ships it — so it points at a VM or a cgroup-limited container on a Core Ultra host. A virtualisation tell, arriving free, from an API nobody thinks of as one.

The position for a scraper is unchanged but sharper. Spoof one hardware property and you now own all of them, and this is one you cannot fudge plausibly: the expected value is computable, the exceptions are enumerable, and the fleet where these stop agreeing is precisely a headless fleet on undersized cloud instances. The prototype is worth running against your own stack rather than reasoning about.

Two honest limits. The API needs a secure context and Chromium 152 or newer. We opened the prototype from Chrome 148 and it correctly reported the tier absent and the test inconclusive — so this is a signal arriving, not one already deployed everywhere. And the author says plainly that the exception list can move between Chrome versions, so anything built on it is a maintenance commitment rather than a constant.

Oct 2026 update. Chrome 152 reached stable on 25 Aug 2026, so the tier is now deployed, and users can override it under Settings > Performance (admins via CpuPerformanceTierOverride), so an odd tier can be a setting. A Web Scraping Club explainer (6 Sep) adds the RAM neighbour, confirmed in Chrome 147’s notes: deviceMemory now reports 2–32 GiB on desktop and 1–8 on Android. A desktop Chrome 147+ reporting 0.5 or 1 is incoherent.
+
Sep 2026 / Measurement
Six Pages of Warm-Up Moved 11% to 82%. One Page Moved Nothing.
The open proxy-benchmark harness ran through August across 11 engines and published ten findings, each linked to the run behind it. This guide already carried its earlier results; this round adds the two most actionable numbers it has produced, and contradicts a piece of folk practice that the post’s own comment thread was busy repeating underneath it.
💡 Warming an exit is real, and one page is not warming it — while timezone alignment, the most repeated tip in scraping, did not pay in either arm they ran
The warm-up ladder, four rungs interleaved in one run because the hour is their largest confound. At warm depths 0, 2, 4 and 7 the pass rates were 11%, 19%, 33% and 82% over 35, 48, 33 and 33 judged attempts; cold against deepest is z = 5.82. Separately, one page of warm-up moved nothing — 32% against 30%, intervals almost coincident. The caveat is theirs, and it is the reason to trust the rest: four of the six warm pages are Google’s own, so “Google’s infrastructure was told about this exit” and “the browser survived six navigations” both fit every row, and a neutral rung at the same depth is the next run rather than a conclusion here.

Timezone and locale alignment did not pay off. Flat on Patchright, 34% against 35% — and zendriver lost six sevenths of its yield, 57% down to 9%, p = 0.0008. Two engines is a thin basis for a rule and they say so. But it is worth reading against the post’s own comment thread, where two practitioners independently offered timezone mismatch as the explanation for the headline result. The measured arm says the opposite. Neither side has enough to settle it, which is the actual state of knowledge and better than the confident version of it.

The methodology is the transferable part, and most of it is about not fooling yourself. Verdicts come from page content, never HTTP status — the same Google reCAPTCHA page arrived once as 429 and once as 200. empty is not block: Google hands a scriptless client a 92 KB “enable JavaScript” scaffold that stays on /search and refuses nothing, and 14 of 14 such rows carried the enable-JS marker while none carried reCAPTCHA — score that as a block and you credit Google with a refusal it never made. error is yours: a harness counting its own crashes as target refusals manufactures a convincing result while measuring almost nothing. A refusal also diverts rather than announcing itself — Google answers by sending you to /sorry/, with a 200 about a quarter of the time — so the real test is whether the final URL kept the host and path you asked for.

Two findings about their own instrument, published rather than quietly fixed. Their harness was getting its own pool banned: one unauthenticated CONNECT per session, sent by the browser before anything else, tripped an IP ban that read as a gateway floor for days. And until 11 August, Camoufox alone opened a fresh context per query and discarded its cookie jar while every other engine carried one — a session-continuity bug that had quietly been making it a different experiment. Five of the ten findings replaced an earlier claim of theirs, and both versions stay in the notebook. The Amazon one reversed outright: on a workstation in early August, Camoufox was served 90% while every Chromium engine met the throttle, which read as a Firefox-beats-Chromium result; on the server the unmodified control came out on top and the Firefox reading was simply gone.

Three operational notes worth stealing. Response bodies are kept gzipped, and re-reading 250 stored Amazon bodies offline later found an Akamai interstitial and an AWS WAF challenge that had been filed as refusals — 21 historical rows corrected at no traffic cost. Headful on a headless host needs xvfb, but a virtual display does not restore the GPU, so WebGL falls back to software and a headful server run is not a headful workstation run. And Playwright, Patchright, rebrowser and cloakbrowser pin four different Chromium builds sharing no download — on a stack where several findings turn on the build, that is a variable most comparisons never mention holding.
+
Sep 2026 / Tooling
A Crawler the Model Writes Once, and the First of These That Treats That Code as Untrusted
The economics are the pitch. A developer paying over $1,000 a month to keep an LLM in the loop on every page of a few hundred camp websites observed that the intelligence is only needed twice — once to work out how to crawl a site, and again when the site is redesigned. better-crawl has a model explore the target, write a standalone crawler, test it against the live site, and hand back a JSON artifact. Replaying the artifact costs zero tokens; when it breaks, a heal pass patches it for a few hundred, because the selectors live in a manifest rather than buried in code.
💡 Generated crawler code is untrusted code, and this is the first of these tools we have seen run it like it means it
The sandbox is worth copying even if you never use the tool. Every other “AI writes your scraper” project we have looked at evaluates the generated program in-process, in the host, with the host’s filesystem and secrets. This one spawns it into a child under Node’s permission model with no filesystem, no exec and no ambient secrets — and then puts every decision that matters outside that boundary. robots.txt, the page budget and the politeness delay are enforced in the parent, per navigation. Your zod schemas never enter the child, so items cross the boundary raw and are judged by the parent. timeoutMs is a hard kill rather than a cooperative signal, so a busy-looping artifact cannot run forever. Credentials are declared as named inputs and injected at run time, never embedded in the artifact and scrubbed from anything sent to the model. There is a noSandbox escape hatch, and the documentation tells you to use it only on artifacts you trust like your own code — the correct framing for something a model wrote.

The generation loop refuses to take the model’s word for facts it can check itself. The scout drives a real browser on condensed reads — links and text, never raw HTML or the accessibility tree — verifies selectors, detects listing patterns, exhausts pagination, and runs a no-JS probe that decides cheerio versus Playwright. Its exit tool is gated: a report with unverified selectors, invalid sample items or an unproven cheerio claim is rejected with instructions and the model corrects itself mid-run. At codegen the library then overrides anything it verified itself — the engine choice, the selector samples, the assertion floors. Self-test lints statically first, so obviously broken code never touches the site, then runs live with every item validated and assertions checked, repairing up to three times. You only ever receive an artifact that has passed.

Politeness is the default rather than a flag. robots.txt per origin with disallowed URLs refused, Crawl-delay honoured, 500ms between requests, 100 pages and 10 minutes per run, and a User-Agent naming the project and linking its repository. All of it configurable, with the docs noting that what you do with the knobs is on you. Cloudflare gets its own retry schedule — a 403 or 503 carrying cf-ray or cf-mitigated markers waits 5s doubling to a 300s cap over 4 attempts, outside the ordinary retry budget — and those waits count against the ten-minute ceiling, which is exactly the sort of interaction that bites in production.

What we are not claiming. It is five days old: v0.3.3, 19 commits, zero stars, zero dependents, MIT, from a maintainer with a long open-source history. The $1,000-a-month figure is one project’s bill, not a benchmark, and there is no published comparison against keeping a model in the loop. And the tool warns about itself in a way worth repeating: generation performs up to 1 + maxRepairAttempts real crawls of the target while self-testing, so develop against a fixture or a staging copy unless you want the site to meet your prototyping.
+
Sep 2026 / Tooling
Point Any App at a Proxy and Walk Away With Its TLS Profile
Rama’s CLI is getting a man-in-the-middle mode with a web GUI: rama serve proxy --mitm, a protected inspector showing connections, requests and responses live, each replayable and exportable as HAR or cURL — and, the reason it belongs in this guide, exportable as a User Agent HTTP/TLS profile.
💡 The hard part of impersonation was never applying a fingerprint, it was sourcing a real one — this turns that into configuring a proxy in an app’s settings
Why that one feature matters more than the GUI around it. Every impersonation library ships a fixed set of profiles that go stale as the real clients update, and rolling your own has meant capturing a handshake and reconstructing the ClientHello by hand. The vendor’s own framing is the entire workflow: configure the SOCKS5 or HTTP proxy in whatever app or platform you care about, capture the flows you care about, and you have the profile — without writing any code. That works for a mobile app you cannot instrument, a desktop client, or a browser build you want to match exactly. It cuts the same way defensively, where the capture tells you what a genuine client of yours looks like on the wire.

The rest of it. Protocols layered on HTTP get specific handling — WebSocket messages are visible and replayable in both directions and you can inject your own, which is the part most HTTP debuggers do badly. There is ICAP support, meaning an external service can inspect and modify traffic out of process: --icap <URI> points the proxy at your server, and rama serve icap gives you an echo service to point yours at, so scanning or rewriting happens somewhere isolated rather than inside the proxy. Being MITM, the client has to trust the CA, which the inspector will hand you or --mitm-ca-cert writes to disk up front.

Status, stated plainly. Announced 28 August 2026 for the 0.5 release, expected around the end of September. The MITM capability landed in the repository last month; the release has not shipped. Treat this as something to try when 0.5 lands, not something to plan a pipeline around this week. Worth knowing separately that the project already runs public diagnostic endpoints — an echo service, IPv4 and IPv6 reflectors, and a fingerprinting service that doubles as the collector for its own embedded user-agent profiles.
+
Sep 2026 / Industry
The Supply Side Contracts, the Money Arrives Anyway
A month-in-review of the proxy industry, and the two halves point in opposite directions. This guide already covers the FBI’s seizure of NetNut’s domains in depth. What is new is the consequence of the smart-TV research we documented earlier: LG has banned the residential proxy option from apps on its webOS platform.
💡 A scan became a platform policy in one step, and the residential pool you rent in 2027 is being made smaller right now
Why the LG decision is the item to notice. The guide carries the underlying measurement — a 2026 scan of 6,038 LG and Samsung smart-TV apps found proxy SDKs in 2,058 of them, 42.5% of LG webOS apps against 26.9% on Samsung Tizen, with consent frequently a single setup prompt navigated by remote. That was evidence about the sourcing model. This is the platform owner acting on it, and it closes a channel that was supplying a meaningful fraction of one category of residential exits. The second-order effects are the obvious ones, and worth pricing in rather than discovering: higher prices as supply falls while AI demand climbs, and degraded quality as the same request volume concentrates onto fewer addresses — which shows up in your metrics as targets getting harder, not as your provider getting worse.

Compliance is tightening in the same direction. Bright Data now requires KYC universally for residential proxies rather than only for specific destination sites. Read alongside the seizure, the trend is a single thing: the residential market is being pushed from “buy access to a pool” toward “be identifiable to the person selling you the pool.” If your procurement assumes anonymous purchase, that assumption has a shelf life.

And the money went the other way entirely. Nimble raised a $47M Series B in February 2026. Oxylabs took $130M — the guide already noted the round and the reported $3.6B valuation; the investor is Warburg Pincus. Set against a regulator seizing a NASDAQ-listed competitor’s domains in the same year, that is the clearest available signal that capital reads the enforcement risk as a consolidation event rather than an existential one: the compliant survivors get more expensive and better funded, and the cheap end of the market is the part being removed.
+
Sep 2026 / Deobfuscation
Two Hours Against Several Weeks, and the Blog Post That Set the Ceiling
The REcon Montreal talk on agentic reverse engineering is now published, and it is the most concrete account yet of what an agent actually does to a protection. The thesis is narrow and worth quoting exactly: "the agent becomes an orchestrator of tooling" and "agents change the economics of deobfuscation". One of the speakers puts it flatly — the agent is not able to understand the binary, it is mainly calling tools. Comprehension did not improve. The debugging loop got cheap.
💡 Its competence on a commercial VM was bounded by a human blog post from 2021, handed to it as input
The setup. Claude Code or Codex inside a Docker sandbox, Binary Ninja Headless reached over MCP, with unicorn, miasm and msynth as direct tool calls. Experiments run May and June 2026. The model never does the semantic work; the emulator, the symbolic executor and the synthesiser do.

The result everyone will quote, with the condition attached. Given the generic prompt — here is an obfuscated function, deobfuscate it and reconstruct the high-level code — the agent produced nothing usable after several hours against a commercial VM obfuscator. It only worked once it was handed a procedure: the author's own 2021 write-up on writing disassemblers for VM-based obfuscators as its knowledge base, the script templates from that post, and the three tools. Then it ran one to two hours autonomously against what the speakers estimate as several weeks of full-time manual work. The self-referential detail is the honest one: the agent's ceiling was an existing human explanation of the problem.

The pipeline, because it transfers. Emulate entry to exit with unicorn and treat that as ground truth, self-repairing failures as they appear. Re-run the same path under symbolic execution, checkpointing against the emulated trace at every step so the agent knows it is still on the real path. Lift each handler's semantics from the validated trace. Extend the script into a full disassembler. Reconstruct. Against a kernel anti-cheat the agent found one shared interpreter across roughly a thousand VM entries, wrote a single disassembler, and devirtualised them in parallel.

The failures are the useful half. A recent game DRM defeated it outright: meaning is runtime-bound, so it stalls under emulation. A deflattening pass written for one malware family did not work at all on the next and had to be rewritten, then still failed on the MBA opaque predicates. On call obfuscation the agent tried cryptographic identification of a jump table and then brute-forcing every address in the binary before a human stepped in. The warning to carry: each pass stays sample-specific. There are no benchmarks, no success rates and no cost figures anywhere in the talk.

What they say impedes an agent. Four dimensions, and this is one slide plus a Q&A answer rather than a measured result, so weight it accordingly. Budget and search traps (state expansion, costly oracle checks) attack the token and tool-call budget. Protection stacks bind meaning to state unavailable at analysis time — control-flow-sensitive rolling-key bytecode where each handler derives the key decrypting the next, so nothing can be enumerated or skipped. Tool friction got the sharpest answer: anti-emulation mostly means anti-QEMU, finding behaviours the emulator models differently from real hardware and gating a loop on them so it spins forever — and the argument is quantity, "the agent gets stuck and stuck and stuck", because each divergence costs a full debug cycle and the agent cannot know how many remain. Narrative poisoning: fake symbols, planted strings, prompt injections aimed at the model rather than its tools. Asserted, not demonstrated.

What transfers to JavaScript, and what does not. The VM workflow maps one to one onto bytecode VMs in commercial anti-bot scripts, and rolling-key handler decryption is the same primitive doing the same job. "Each pass stays sample-specific" is exactly the Babel transform that works on one bundle and dies on the next. Narrative poisoning is more potent in JavaScript, because an agent reads real source text rather than decompiled pseudocode, so planted strings and injections land directly in its context — strip strings before feeding a challenge script to an agent. But the tool-friction defence is much weaker here: it depends on the emulator being an imperfect model of hardware, and in JavaScript you simply run a real browser, so ground truth is nearly free and there is no fidelity gap to exploit. There is no lifting problem either, since a parser hands you exact function boundaries for nothing. And the threat model is inverted. The talk is about protecting code you ship; anti-bot is about classifying the client. An agent that fully deobfuscates a sensor script still has to emit a payload that survives server-side correlation — which is precisely why this industry put its real defence there.
+
Sep 2026 / Research
The Solver That Never Touches the DOM, and the Denominator Under Its 100%
A preprint posted on 2 September describes a reCAPTCHA v2 solver with no Selenium, no Playwright and no DOM access at any point. It screenshots the page, localises the grid with a fine-tuned YOLOv8 detector, escalates only the uncertain tiles to an open-weight 7B vision-language model, and clicks by sending operating-system mouse events through PyAutoGUI. Against Google's reCAPTCHA v2 demo page: 100 of 100 sessions passed, 206 puzzles, 87.9% solved on first attempt, median 17.1 seconds per puzzle. The motivation is stated plainly — Selenium "sets the navigator.webdriver property", so they removed the driver rather than hiding it.
💡 The word "demo" appears exactly once in the paper, and it is carrying the entire result
arXiv:2609.02393, Salman and Bicakci, Istanbul Technical University. Unreviewed preprint, no venue, no ethics statement and no limitations section — the caveats are scattered inline. Read it for the architecture, not for the headline.

What is theirs and what is borrowed. Their own YOLOv8 is a UI detector (five classes: captcha area, cell, checkbox, reload, submit; mAP@0.5 0.995, ~1.1ms/image). The tile classifier is Plesner et al.'s publicly released YOLO, reused as-is. The paper says so: "Because both systems use the same published YOLO classifier, these gains come from the hybrid design rather than a stronger detector." The VLM is Qwen-7B-VL, self-hosted, routed at a confidence threshold of 0.70; YOLO handles ~70% of cells at 4ms, the VLM is called on 37.1% of puzzles at 225ms to 2251ms.

The comparison is weaker than it reads. The 2 → 5 → 7 → 13 ablation (median challenges per CAPTCHA as cookies, Bezier curves and mouse simulation are stripped) and "blocked entirely after 20 runs" are Plesner's published numbers restated, not re-measured. And Plesner's solver skips puzzles whose class it does not support (Boat, Taxi, Tractor, Stairs), so the 100%-versus-100% is not like for like.

"Learns from every encounter" is offline, not online. No tile cache, no live gradient updates. It is teacher-student distillation between runs: the VLM hard-labels new classes, YOLO is fine-tuned with Experience Replay. Honest about the cost — Taxi reaches 96% recall after roughly two puzzles, Boat plateaus near 48% using every training image, and catastrophic forgetting runs 6.8 to 17.2 points on the existing 13 classes. Labels are used unfiltered, and VLM labelling accuracy was 82.1% on Taxi.

The genuinely interesting result is adversarial. PGD perturbation drives Plesner's YOLO to 0.0% accuracy; the VLM only drops 3.7 points. So the VLM relabels, YOLO retrains, and the loop recovers across a simulated twelve-month arms race. Their explanation is the part worth keeping: a cheap, roughly 70%-accurate open-weight teacher hardens the student as effectively as a perfect oracle, because noisy labels decorrelate the solver from the defender's surrogate model, so crafted perturbations stop transferring.

The practitioner counter, from the post's own comments. A demo page is mostly grading the token, not the pointer. The layer that survives losing CDP and navigator tells is kinematics: OS-level synthetic events arrive without the HID jitter and sub-pixel noise a real mouse produces, often land on exact integer coordinates, and on Windows carry an injected flag the page cannot read but the endpoint can. Cite this paper as "on Google's demo page", never as "against reCAPTCHA v2".
+
Sep 2026 / Benchmark
Fifteen Engines, One Strict Referee, and the Four That Passed
An open-source stealth benchmark ran fifteen browser automation tools against two live bot detectors. Against the strict referee — deviceandbrowserinfo.com, which returns an isBot flag plus roughly twenty individual detections — only four passed: camoufox, CloakBrowser, RayoBrowse and scrapling. Plain Playwright, plain Selenium, undetected-playwright and rebrowser-playwright were all called bot by both detectors.
💡 Rebuilding the browser beat patching flags onto it, and the headless User-Agent was the single most common tell
The scoreboard. Perfect 100 with no tells: camoufox (Firefox), CloakBrowser (Chromium), invisible-playwright (Firefox), RayoBrowse (Chromium), scrapling-stealthy (Chromium). Obscura scored 94, tripped only on a non-native function. Then a hard cliff to 70 — nodriver, patchright, pydoll, undetected-chromedriver and zendriver all failed on one tell and one only: the headless User-Agent, which was enough for the strict detector to call them bots while BrowserScan passed them. At 35: playwright-vanilla, selenium-vanilla, undetected-playwright (webdriver plus headless UA). Bottom at 10: rebrowser-playwright, which added visible driver residue.

Three of the four winners rebuild the browser rather than patch it. No HeadlessChrome string in the UA, nothing reading as CDP automation. That is the finding, and it matches this guide's engine-level thread: flags applied on top of normal Chrome leave a seam that a detector reading twenty signals will find.

Read the scope before you buy anything on it. The benchmark deliberately covers the JavaScript fingerprint surface only. Transport (TLS and HTTP/2), request timing and proxy exit are explicitly excluded, on the stated reasoning that swapping a Python library cannot change them. So a 100 here means "clean on the JS surface in headless mode on an M2", not "will get you in". Every Chrome-based tool was driven with channel="chrome" against real installed Chrome, precisely so bundled Chromium's SwiftShader renderer would not be miscounted as a tool failure — a methodological care worth copying. Probe score and detector verdict agreed on 12 of the 13 tools where a verdict was captured.

Date discipline. The article carries a publication date of 23 July 2026 and resurfaced on social in September. Treat the table as a July measurement of fast-moving tools, and re-run rather than cite it in December. The harness is in the author's subscriber repository; the results table and heatmap are public, the per-tool analysis is paywalled.
+
Sep 2026 / Measurement
The Benchmark That Publishes Its Own Retractions
The proxy-vendor harness this guide already cites for the Amazon result has now published its full method, and it is the most self-critical measurement document in this field. It documents a claim it reversed, an IP ban it inflicted on itself, and one classification cell it flags as never having been checked. "Five of these ten replaced an earlier claim of ours, and both versions are still in the notebook."
💡 The Amazon table is a tie, not a ranking, and the harness says so before you do
Correcting our own earlier entry. The Amazon run used 8 engines, not 11 — eleven is the harness registry, not that run's matrix. And 3,816 is attempts; the judged denominator is 3,530 after 286 harness errors are excluded. Both numbers are in the repository and they mean different things.

Why "tied or beat" is the only honest verb. Two-sided Fisher exact against the leading cell with a Bonferroni threshold of 0.0071: stock chromium 96% (419/436), rebrowser 95%, botasaurus 94%, camoufox 92%, zendriver 92%, seleniumbase 92% are all statistically indistinguishable. Only cloak at 80% and patchright at 63% (288/457) separate, and they separate downward. Patchright sits 33 points below the engine doing nothing at all. Two tiers, not eight places.

Findings worth more than the headline. Chrome burns 43MB per fresh profile talking to Google before you request anything — 43.2MB of a 43.4MB idle window to optimizationguide-pa.googleapis.com on about:blank, which is roughly 43GB per thousand attempts billed as residential traffic. The host outweighs the proxy on Google: same code, same gateway, same target, 39% (24/61) on a Windows workstation against 0% (0/84) on a Linux VPS in overlapping hours, Fisher p = 3.7e-11. DuckDuckGo reads the User-Agent and nothing else: 95/95 pass for engines whose UA omits HeadlessChrome, 0/50 for the two that carry it, across three browser families. The TLS handshake was not the discriminator: chromium, patchright and obscura emit byte-identical ClientHellos yet finish 44 points apart — and compare JA4, not JA3, because Chrome shuffles extension order per connection.

The method choices to steal. Verdicts come from page content, never HTTP status, across ok / captcha / consent / block / empty / error, and empty is not block — Google's 92KB "enable JavaScript" scaffold rejects nothing, and 14 of 14 such rows carried enablejs and none carried recaptcha. Harness errors are excluded from the denominator instead of counted as failures. Cells interleave at batch granularity because "the hour is the largest confound this repository has found: the same gateway, country and browser moved 69 points to 52 between two windows of one afternoon." A matrix that cannot be controlled is refused by a dry run rather than silently adjusted. And the unmodified control is inside the matrix, enforced by a test that reads the source and fails the build if browser arguments or a UA override ever appear.

Standing caveat. The author is a proxy vendor. The repository asserts that nothing in it is a sales number, which cannot be audited without re-running the harness — which is, to its credit, exactly what it is built to let you do.
+
Sep 2026 / Argument
The Vendors Are Talking About Agents While the Classical Bypasses Ship
The head of research at an anti-bot vendor published an unusually blunt piece arguing that his own industry has drifted. While vendors talk about agentic trust, agent intent and agent governance, public tools that fully reverse engineer those same protections keep shipping, and the release rate is accelerating. Not fingerprint spoofing within the rules of the game — real bypasses that generate valid anti-bot payloads without running the protected JavaScript in a browser at all.
💡 AI is not making bots smarter. It is making bot developers faster, which is a different threat model.
The distinction he draws. "AI may have been involved at almost every stage of building that system without a single AI agent ever touching the production environment." And: "AI does not remove the need to understand what you are doing, especially against sophisticated protections, but it lowers the barrier and makes experienced bot developers considerably faster." What eventually arrives at your endpoint is still a classical HTTP client, and it still has to be caught through the client, the automation, the infrastructure or the behaviour.

His test for a vendor. "Can it reliably tell that the client hitting your application is automated in the first place?" He wants credential stuffing blocked, fake account creation with disposable domains caught, and measurable counts of fraudulent accounts created — over intent classification. "These problems are much less fashionable than AI agents, but they are also much easier to measure." Note honestly that his piece names no numbers on bypass release frequency; the acceleration claim is his observation from Discord, Telegram, Reddit and GitHub, not a dataset.

The comment thread is where the useful engineering rule appeared. Asked why anti-detect browsers are the default when generating payloads directly is cheaper for scraper and target alike, he gave a decision rule worth writing down. Scraping as a service, across many sites and many anti-bot vendors and versions: invest in the anti-detect browser, proxies and realistic mouse movement, because maintaining one solver per vendor per version is not sustainable. A small number of target sites at very high volume — his example was millions of profiles continuously — and the non-browser client scales better. That is the same escalation ladder this guide teaches, priced by breadth against depth.

Two refinements from other practitioners in the thread. A newsletter author argued the binary is gone: bot or human is now bot, human, or bot acting on behalf of a human, and no vendor can comfortably sell "we block AI agents too" to a customer who reads that as lost revenue. A detection engineer noted the malware field already has vocabulary for this, pointing at Recorded Future's AI Malware Maturity Model, which sorts AI-assisted development from genuinely AI-driven execution — swap "malware" for "bot" and it mostly transfers. A reverse engineer pushed back on the acceleration itself: against VM-based commercial protections, AI alone is not enough, and the author agreed — it is the combination of a skilled reverser and AI, not AI on its own.
+
Sep 2026 / Tooling
Run the Page's Own Decoder. Then Check What the Repository Actually Ships.
A deobfuscation tool circulated this week on a genuinely correct premise: most deobfuscators pattern-match known encodings and break the moment a site rotates its obfuscation, so instead of reimplementing base-N, RC4 or XOR, extract the string array and the decoder the file already contains, run them in a sandbox, and ask the real decoder for every index. A new alphabet or a shifted rotation offset then costs nothing.
💡 The idea is right, the repository is 200 lines, and the interesting half is in the article rather than the code
The technique, precisely. Parse with Babel, take the largest top-level array of literals as the string array, take the first top-level function referencing it as the decoder, re-emit array plus rotation plus declarations into a node vm with a two-second timeout, then loop the indices calling the shipped decoder to build an index-to-plaintext map. Rotation is never computed — the rotation IIFE is simply executed first, so whatever offset the build used applies naturally. That part of the claim holds.

Where it stops, from reading the source. The array must be a top-level variable. Stock obfuscator.io hides it inside a self-rewriting getter function, and the extractor never descends into function bodies, so it raises "No string array found" on the most common real-world input. Decoder calls must have exactly one numeric-literal argument; obfuscator.io's decoder is two-argument, and those call sites are silently skipped. There is no alias resolution, so the routine practice of assigning the decoder to a second identifier defeats it. Dead-branch removal fires only on literal true and false, not on the customary negated-array idioms. And the sandbox has no window, document or navigator, so any self-defending, domain-locked or environment-probing decoder throws.

Provenance worth checking before you adopt it. The repository has zero stars, one commit dated 20 June 2026, and nothing pushed since; the September announcement is a re-post of unchanged June code. Its own test suite is four assertions against a single hand-written 944-byte fixture that approximates the shape rather than reproducing real obfuscator.io output. There is no comparative benchmark, and the README does not mention webcrack, synchrony, restringer or de4js — all of which already handle the getter-function array, two-argument decoders, alias resolution and control-flow unflattening.

Why it is still worth reading. The companion write-up shows the technique working at real scale against a live anti-bot challenge — hooking Function to capture a 350KB evaluated body, then running it across a dozen payload generations. That harness, the Function-hook loader interception and the faked browser environment, is not in the repository. What shipped is the vendor-neutral skeleton. Take the stance, which is correct for per-request-regenerated payloads, and keep webcrack for everything else.
+
Sep 2026 / Scale
Throughput Is Concurrency Divided by Service Time, and the Queue Is Not the Fix
An engineering write-up on building toward very high CAPTCHA solve rates makes one point that applies to every scraping fleet: throughput equals concurrency divided by service time. If an operation takes about two seconds and you want 8,000 completions per second, you need roughly 16,000 operations in flight. Everything else is arithmetic you do not get to argue with.
💡 An unbounded queue does not prevent overload. It hides overload until memory becomes the failure mechanism.
The formula validates. 256 workers at 7.83ms p50 predicts 32,695 solves per second; measured was 32,507, an overhead of 0.57%. Which means capacity planning here is a calculation, not an experiment — measure service time first, then derive the concurrency you actually need.

The connection-pool trap is the one that bites scrapers. Go's default MaxIdleConnsPerHost is 2. Past worker three, nearly every operation pays a fresh TCP and TLS handshake. At two thousand workers against one host that is a full round trip added to every request, and it presents as mysterious latency rather than as a configuration error. Set MaxIdleConns, MaxIdleConnsPerHost and MaxConnsPerHost to the worker count and IdleConnTimeout to 90 seconds.

Backpressure is one buffered channel. When it fills, the producer blocks and the system slows intake instead of growing the heap. Queue size sets buffering tolerance; worker count sets sustainable rate. They are separate dials and conflating them is how fleets die.

Measurement discipline. "Throughput and tail latency only mean something together, and only when you know which stage produced them." Their own mock p99 is called out as unreliable, capped by simulated latency plus jitter rather than reflecting DNS, TLS and retry tails. And the memory arithmetic is the kind nobody does until it hurts: eight-byte latency samples at 8,000 per second accumulate 62.5KB per second, 230MB per hour, 5.5GB per day, and the harness sorts the whole history on every report call, doubling it. Production wants a fixed-size bucketed histogram rotated per window.

Credit where due. The article answers its own headline honestly. Asked whether the sample command solves 8,000 real CAPTCHAs per second: "No." The live sample runs about four per second, network-bound, on twelve requests through six workers. The 8,000 is an architecture target across distributed workers, and the piece says so rather than letting the number sit there.

Sep 2026 update. Hosted browser fleets ship this bounded queue as product behaviour. A Browserless blog post (vendor, about its own service) says bursts up to twice your concurrency limit wait their turn and anything beyond returns HTTP 429. Its self-hosted docs confirm the shape, a configurable QUEUED cap then 429, but not the 2× ratio. Treat that 429 as backpressure to slow intake, not as a block to retry through.
+
Sep 2026 / Correction
A Viral Post Said No Dependencies. A Commenter Counted 434.
A widely shared post presented the Rust agent-first browser this guide already covers as a finished Chrome replacement: 30MB of RAM, 85ms page loads, "no dependencies", "a single binary", "detectors can't catch it", and a drop-in replacement for Puppeteer and Playwright. The comment thread underneath it is a better technical review than the post.
💡 The wall was never canvas or GPU randomisation. It is the handshake and the exit.
What the commenters actually established. The dependency claim is false and was counted: 425 third-party crates across six cluster images plus nine workspace crates, 434 total. Several engineers made the obvious point that a browser with no external packages is not a thing that exists. On evasion, a scraping team lead who had built something comparable was blunt — as a bypass layer it is weak, starting at the networking layer, with V8 limitations and a lot of missing Chromium-proprietary surface, and it remains far from a real browser or device. A long-standing newsletter author added the measured version: not true that it cannot be blocked, but a good tool nonetheless.

The correction that matters most for practice. The hardest failure point for these engines is not canvas or GPU randomisation, it is the TLS handshake — which is exactly what the independent harness found when chromium, patchright and this engine emitted byte-identical ClientHellos and still finished 44 points apart. Per-session randomisation of GPU, canvas, audio and battery is the axis being advertised, and it is not the axis doing the rejecting.

This is consistent with what this guide already recorded. Our own teardown against a hardened commercial target concluded the wall was the IP, not the fingerprint, and the engine's own release notes warn that rendering and Web API behaviour can differ from Chrome. The tool is genuinely interesting for its actual purpose — a cheap, fast, machine-consumer browser for agent workloads, where the rendering pipeline is dead weight. It is not an evasion product, and buying it as one will cost a week.

The general lesson, again. A star count is not a measurement, a benchmark on the author's own page is not a benchmark, and "detectors can't catch it" is a claim with a date attached whether or not the poster writes the date down.
+
Sep 2026 / Economics
Your Data Is Priced Against Three Thousandths of a Cent a Page
From the defensive side, a bot-mitigation practitioner put a number on what protection is actually competing with. A bypass for a legacy protection vendor can be bought for as little as 0.3 cents per session, and one session covers 100 to 200 pages — which works out to roughly 0.003 cents per page or less. His line is the one to remember: that is the price your data is being measured against.
💡 A vendor that updates monthly is selling a subscription to a number that moves weekly
Why this belongs in a scraping guide. It is the same arithmetic from the other chair, and it explains market behaviour this guide keeps observing. At three thousandths of a cent per page, no amount of per-request friction prices an attacker out of a target with genuinely valuable data; only detection that actually works does. It also explains why bypass-as-a-service exists as a product category at all, and why its pricing is public.

His argument to defenders. Solutions that update monthly or less often cannot track threat actors who iterate continuously, so buyers should assess protection vendors themselves rather than defaulting to whichever option is easiest to justify in procurement. "Short-term peace of mind is exactly that: short-term. It does not protect a business model built on unique intellectual property."

Read alongside the vendor-drift argument elsewhere in this feed. One side says the anti-bot industry is chasing agent governance while classical bypasses ship; this one says the buyers are choosing on procurement convenience while the bypass market publishes a rate card. Both describe the same gap from different ends, and both are written by people who sell protection, which is worth holding in mind while agreeing with them.
+
Aug 2026 / Economics
Learn the Page Once, Then Stop Paying the Model
This guide's seventeenth principle says to spend the model once at build time and run deterministic code per page. Somebody has now published unit economics for exactly that architecture, and the gap is wider than the principle implies. Two paths: a cold path where a model maps the fields on an unfamiliar page and the structural template is saved, and a hot path where later visits read the live HTML through that stored map with no inference at all.
💡 $4.38 against $36 and $47 for the same 400 pages
The published figures. Single page: $0.19 against $0.95 and $1.16 for two well-known AI extraction services. A 400-page run: $4.38 against $36.28 and $47.50. Roughly five to six times cheaper on one page, and eight to eleven times cheaper across a run — because the saving compounds every time the same template is reused.

What is actually cached matters. Not page content, and not embeddings. The structure — where the fields live. That is why it stays current: the map is stale only if the layout changes, while the values are read live on every visit. Cache the content and you have a freshness problem; cache the structure and you have a drift problem, and drift is the one this guide already knows how to detect.

The honest caveat. These are a vendor's own comparative numbers against named competitors, not an independent benchmark, so treat the ratio as the claim rather than the fact. But the shape is not in dispute, and it is the argument for the pattern: paginated listings and linked detail pages are where one learned template pays for itself hundreds of times over. The expensive question is not what a page costs. It is how many times you pay to understand the same page.
+
Aug 2026 / Language choice
Two Reasons a Scraping Engine Gets Written in Go Instead of Python
A team building an evasive-scraping engine published their reasoning, and it comes down to two things that Python cannot reach from where it stands. Not a general claim about the languages — a specific claim about holding open ten thousand hand-picked TLS connections.
💡 A goroutine costs about 2KB of stack. A Python thread costs about 8MB.
The TLS half. Every HTTPS connection opens with a ClientHello whose cipher-suite and extension order is distinctive enough to identify the client. Go's tls-client lets you construct that handshake to match a real Chrome release byte for byte. Python's standard ssl module does not expose that layer at all — you get whatever OpenSSL negotiates, and it does not look like a browser. That is why the Python ecosystem reaches for curl_cffi, which is a binding to a patched C library rather than something the language offers.

The concurrency half. Roughly 2KB of stack per goroutine against about 8MB per OS thread, with Go scheduling tens of thousands of them across a handful of threads. A Python thread pool of fifty is already substantial; the orders of magnitude are not close.

The part worth stealing regardless of language. They run a multi-armed bandit that learns which TLS profile actually works per site, using Thompson sampling — the same method this guide already documents for choosing between live and dead proxies, applied one layer down. Which Chrome profile to impersonate is exactly the kind of decision that is per-target, drifts over time, and is usually hard-coded once and never revisited. It is not a global setting. It is a per-domain hypothesis you should be updating.

Oct 2026 update. An industry datapoint for the systems-language side: an October 2026 job advert from Lighthouse (formerly OTA Insight, hotel rate intelligence) describes its crawler team building “mostly in Rust” systems that handle close to 200 million requests a day, about 2,300 a second averaged, and asks for curiosity about “what really happens on the wire”. The caveat: a hiring post’s self-description; no architecture, benchmark or engineering write-up from Lighthouse was found.
+
Aug 2026 / The counter-move
Google Wrapped Every Search Link, and Made One Ranking Cost a Thousand Requests
Confirmed by Google on 26 August 2026 after a bucketed test that began in June: result links in the SERP are being rewritten to google.com/goto?url=… with an encrypted token, resolving through a 302. Ordinary searchers notice nothing — one extra hop, same destination, no interstitial. Anything that reads a results page programmatically is hit hard. By late August it was near-total across several residential providers.
💡 500 to 1,000 requests to resolve one five-page ranking, and HEAD is blocked
Why it costs so much. The encoding cannot be decoded locally, so a tool has to follow each redirect rather than read the address. Derek Perkins of Nozzle put the number on it: resolving all links for a single five-page ranking takes 500 to 1,000 requests. You must issue GETs that follow through — and note the correction: the August reporting said HEAD was blocked, but the September teardown measured HEAD returning 200 with no Location header rather than a refusal, which is the same advice with a much more confusing failure mode.

It compounds, it does not replace. The num=100 removal in September 2025 multiplied the number of pages needed and raised operating cost roughly tenfold. This multiplies the requests per page. In Perkins' framing the two changes compound rather than substitute.

The design insight, and it is the good one. As one bot-detection researcher put it, this is a speed bump rather than a wall. Client-side detection can be argued with. This cannot: nobody clicks every result on a page, so a whole /24 doing exactly that is self-identifying. You either self-moderate your click-through and lose coverage, or you look like a subnet that reads a hundred links a second. Either way Google gets what it wants.

And it may not be a rollout at all. One reading circulating among practitioners is that the wrapper is what Google serves to clients it has already classified — which would make its appearance a detection signal about you, not a product change. Google has published no documentation, no rate limits and no permanence timeline, saying only that it deploys technical measures against evolving abuse.
+
Aug 2026 / The other side
A Publisher Spent a Day Trying to Tell Scrapers From Readers. Every Signal Was Already Spoofed.
A team went looking for a reliable way to separate automated traffic from human traffic on their own site, and worked through the obvious signals one by one. Every single one had already been defeated by the traffic they were trying to catch. The write-up is one of the most honest defender's-eye accounts published this year, and it lands somewhere uncomfortable for both sides.
💡 The better the impersonation gets, the more dangerous fingerprint blocking becomes
Signal by signal. User-Agent: their scrapers sent valid, current Chrome strings, so a known-good UA list would have vouched for them. Referer: they found 199 requests carrying an identical hardcoded Google referer across 197 different IPs and 200 different UA strings, all sharing one TLS fingerprint. Cookies: they had logged “has an analytics cookie” as evidence of a real browser — GPTBot carried one on 34% of its IPs, because well-behaved crawlers persist cookies too. Sub-resource loading: assets ship with Cache-Control: immutable, so a returning human fetches only the HTML, which is indistinguishable from an HTML-only scraper.

The line worth keeping. On JA3/JA4 they noted that curl-impersonate clones Chrome's handshake exactly, and drew the conclusion most vendors will not say out loud: the better the impersonation, the more the attacker's fingerprint converges on the one your real users have. Fingerprint blocking is safest while your attacker is incompetent, and most destructive the moment they improve. A detection method whose false-positive rate rises with attacker skill is a liability disguised as a control.
+
Aug 2026 / Measured
On Amazon, Plain Chromium Tied or Beat Every Anti-Detect Engine Tested
A proxy vendor open-sourced the harness it uses internally to benchmark scraping tools, and published a result that cuts against its own category. Across 3,816 attempts on Amazon, stock Chromium with no anti-detect layer at all tied or beat every anti-detect engine in the test. The harness runs 11 engines — Camoufox, obscura, CloakBrowser, Patchright, SeleniumBase and six others — against live targets through whatever proxy you point it at.
💡 It reports which layer refused you: the exit IP, the browser, or the TLS handshake
Two design choices make it worth more than the usual vendor benchmark. First, it attributes the block to a layer rather than reporting a bare pass rate, so a failure tells you where to spend effort instead of just that you failed. Second, the same browser runs with and without the proxy inside the same time window, so the two are compared side by side rather than an hour apart — which is the flaw that quietly ruins most published comparisons, since target behaviour drifts within the hour.

Every finding is said to carry its run id, its denominator and the date it was collected, which is exactly the discipline this guide has been asking for from vendor numbers.

Read the Amazon result carefully, because it is a claim about a target, not about the tools. It says Amazon's defence on that path was not keyed to the signals anti-detect engines fix, and that adding one bought nothing there. It does not generalise to Cloudflare or Kasada. The honest takeaway is the one this guide keeps arriving at: measure on your own target before you buy a stealth layer, because the layer that matters is target-specific and the default assumption is frequently wrong. Reported by the harness's author; the repository is days old and not yet independently reproduced.
+
Aug 2026 / Tooling
A HAR File Is a Timeline. It Is Not an Explanation.
When a request works only because several earlier requests prepared its session, tokens, cookies and headers, a HAR capture shows you what happened without showing you why the request could work. Finding the real dependency chain has meant reading traffic by hand. A new open-source tool, Reqvexa, tries to reconstruct that missing layer automatically.
💡 Provenance based on actual runtime values, not on field names or request proximity
The central design decision is the interesting part. Rather than guessing from field names, URL patterns or how close two requests are in time, it traces actual values backwards through earlier responses. So instead of reporting that /api/products uses an access_token, it identifies the request that produced that token, and accounts for later mutations of it.

What it handles: values that are URL-encoded, Base64, base64url or carried as Bearer tokens; recursive dependency chains; values that change over time; and reduction of a large capture down to just the requests one operation actually needs.

The discipline worth copying even if you never run the tool. It keeps observed evidence separate from unresolved hypotheses, and explicitly refuses to treat correlation as proof. That is the same distinction this guide draws about decoded string tables versus values a program actually reads, and about a fingerprinting inventory built from grep. A dependency you inferred from proximity is a guess; a dependency you traced by value is a finding. Early-stage alpha, described here from its author's own write-up.
+
Aug 2026 / Architecture
8,000 CAPTCHAs a Second Is Not a Solver Problem. It Is a Queueing Problem.
The arithmetic is Little's Law and it is worth doing before you buy anything: throughput equals concurrency divided by service time. If each solve takes roughly two seconds, then sustaining 8,000 completions per second requires about 16,000 operations in flight at all times. No solver vendor makes that number smaller. It is a property of how long the work takes and how much of it you can hold open at once.
💡 Measure service time first, then calculate the concurrency you actually need
Why this reframing matters. Teams hitting a throughput ceiling reliably go looking for a faster solver, a better proxy or a bigger machine. The binding constraint is usually none of those — it is how many operations the system can keep simultaneously in flight before something upstream starts refusing. Doubling concurrency and halving service time are equivalent on paper, and one of them is usually far cheaper.

What actually breaks first, in rough order: HTTP connection pool limits, which cap in-flight work well below what your worker count suggests; unbounded queues, which convert a temporary slowdown into a memory exhaustion; and per-connection memory at sustained volume, which is fine in a burst test and fatal over hours.

The controls. Bounded queues with real backpressure, so a slow downstream slows the producer instead of filling RAM. Worker pools with explicit concurrency limits rather than unbounded goroutines or tasks. And measurement at p50 and p99, not the mean — because at 16,000 in flight the tail is what determines whether the queue drains or grows without bound.
+
Aug 2026 / Correction
Our Own Camoufox Number Was Five Months Old, and the Channel Moved Underneath It
This guide quotes a 100% pass rate from March 2026 in four separate places and never once names a Firefox base. That was a mistake of exactly the kind this guide keeps writing about. On 16 July 2026 Camoufox moved its stable channel from FF135 to FF146, with the maintainer describing the old build as terribly out of date and unsuitable for modern anti-bot systems. Every one of those tables has now been dated and flagged.
💡 The next release restored a humanised mouse trajectory the migration had silently dropped
Three days is the interesting number. The FF146 migration landed on 16 July. The release three days later carried a fix restoring humanised mouse movement that the migration had dropped. So the recommended stable build spent a short window with one of its behavioural defences missing, and nothing in a version string would have told you.

Why a stale table is worse than stale prose. A number in a comparison table gets read out by assistants, pasted into decisions and quoted back months later with the date stripped off. Prose carries hedges; a table cell does not. If you publish a benchmark, publish the build it ran against, because the tool underneath a pass rate can change without the pass rate changing.

The general rule, which this guide will now follow. Any performance claim carries three things or it does not go in: the date, the exact build or version, and the targets it was measured against. A pass rate without a base version is a rumour with a percentage attached.
+
Aug 2026 / Mobile
The Wall at the End of the Mobile-API Road: Hardware Attestation
This guide's most repeated instruction is find the mobile or GraphQL API first, and it teaches the whole toolchain to get there — Frida, JADX, Ghidra, certificate unpinning. It has never named the thing that ends that road. Play Integrity, Apple App Attest, DeviceCheck and Android Key Attestation sign their token with a key held inside the device's secure hardware — a TEE, StrongBox or Secure Enclave — that never leaves the chip and cannot be read by any process, rooted or not.
💡 Every other wall in this guide is forgeable in software. This one is not.
Why it is different in kind. A TLS fingerprint, a canvas hash, a header order and a proof-of-work token are all things a sufficiently determined program can produce. An attestation token is a signature from a key you do not have and cannot extract. You can reach the call, read the code around it, and watch it happen — and still not mint one. Instrumentation gets you to the door and stops.

What actually remains, honestly. Real devices, which means a physical handset farm with the cost and operational drag that implies. Harvesting tokens at low rate from real devices and spending them carefully, which changes your architecture from throughput to budget. Or the answer nobody wants: the endpoint is closed, and the web surface you were trying to avoid is the surface you have.

The practical read. Check for attestation before you invest a week in reversing. If the app calls Play Integrity or App Attest on the endpoint you want, the reversing effort has a known ceiling and you should price the alternatives on day one rather than day nine.
+
Aug 2026 / Law
Europe Asks a Different Question, and Deploying a CAPTCHA Is Now Part of the Answer
This guide's legal section has been entirely American — hiQ, Power Ventures, Meta, SerpApi, the CFAA and the DMCA. The European regime asks something structurally different. Not was access authorised, but did you honour a machine-readable reservation, and can you show your balancing test. On 7 July 2026 the EDPB adopted Guidelines 03/2026 on web scraping for generative AI, open for consultation to 30 October 2026, with a final version not expected before year end.
💡 The inversion: putting a CAPTCHA up changes your legal position whether or not anyone defeats it
The part that turns 126 cards of technique upside down. The EDPB treats robots.txt, ai.txt, CAPTCHAs and login walls as evidence about what data subjects could reasonably expect. Where a site signals objection through any of them and is scraped anyway, individuals are less likely to have expected the processing, and the legitimate-interest balancing test gets correspondingly harder to pass. A technical measure you bypassed still counts against you, because its legal function is to express an objection rather than to stop you.

The test itself is three cumulative conditions under Article 6(1)(f): an interest that is lawful, clearly articulated and real rather than speculative; necessity, meaning no equally effective and less intrusive route exists; and a balance where the data subject's rights do not override it. All three, or the basis fails.

Who inherits this. Anyone selling scraped data into an EU-market model provider, and anyone buying an already-scraped dataset from a broker — the guidelines cover obtaining as well as collecting. Draft guidance in consultation, not settled law, and not legal advice.
+
Aug 2026 / Law
The First Appellate Ruling on Whether an AI Agent Accesses a Website
On 4 August 2026 the Ninth Circuit vacated the preliminary injunction that had barred Perplexity's Comet browser assistant from Amazon, in Amazon.com Services LLC v. Perplexity AI, No. 26-1444. The district court had granted that injunction on 9 March. The appellate holding: Perplexity does not “access” Amazon under the CFAA or California's CDAFA, because the assistant is a tool, not a person — it is the user who accesses the site.
💡 Where the agent executes now decides who the law thinks visited
The architectural fact that decided it. Comet's assistant runs locally: screenshots are taken on the user's own machine, sent to Perplexity, and instructions come back. The browsing happens on the user's computer, under the user's session. That is why the court treated the human as the accessor.

And the case it expressly reserved. The panel left open the opposite configuration — an agent with more autonomy, or one communicating server-to-server with the target. So the local-agent versus cloud-browser choice this guide already frames as an architecture and cost decision now carries different exposure on each side of the line, and the cloud side is the one the court did not bless.

Two details squarely on this guide's territory. The dispute began partly because Perplexity would not send an identifying user-agent string, and the court held that refusal irrelevant to whether access occurred. And the opinion preserves the contract route, which means the practical lever moves back to terms of service and technical blocking rather than the computer-crime statute. First circuit to rule on this. Not legal advice.
+
Aug 2026 / Vendors
Six Companies Did Not Build All the Walls. We Have Been Reading in One Language.
This guide's spine has been six companies built the walls — Cloudflare, Akamai, DataDome, Kasada, HUMAN and Imperva. That map is accurate for the English-speaking web and badly incomplete everywhere else. Geetest, Arkose Labs, Tencent's captcha stack, Alibaba's AWSC, aliyun WAF and Radware have appeared in this guide exactly zero times — and with them Taobao, Douyin, Xiaohongshu and AliExpress as targets.
💡 Arkose sits on Roblox, Microsoft, LinkedIn and X, and this guide had never named it
The honest framing is the finding. This hole is invisible from inside an English-language source diet. The newsletters, the conference talks, the vendor blogs and the Reddit threads this guide reads are all drawn from one half of the web, and the absence propagates silently: you cannot notice a vendor nobody you follow writes about.

A concrete artifact to start from. AliExpress serves collina.js and fireyejs.js from its AWSC path, and they build a WebAudio graph that stays running — a sawtooth oscillator into an analyser into a script processor into a gain node pinned at zero, terminating at the audio destination. Not the one-shot sample this guide's existing AudioContext material describes, but a persistent pipeline. It is conspicuous enough that it has been observed holding Bluetooth multipoint connections open.

What that changes about technique. A persistent audio graph is a different detection surface from a one-shot fingerprint: it can observe over time, and it survives page interaction. Anything you learned about spoofing a single AudioContext read does not transfer.
+
Aug 2026 / Transport
Skip the HTML, Hit the API — Except the Data Is on a WebSocket
The most repeated instruction in this guide quietly assumes REST and JSON. A great deal of the data worth having does not arrive that way. Live prices, order books, seat maps, betting odds, inventory drops and chat move over WebSocket, gRPC-web or server-sent events, and none of them behave like a request you can replay from a copied cURL command.
💡 One long-lived connection defeats per-request rate limiting, and creates a different tell
What changes mechanically. You do not capture a request, you capture the subscribe frame — the message that tells the server what stream you want. Authentication frequently rides in that first message rather than in a header, so header-focused replay gets you a connected socket that never sends data. You must answer heartbeats and pongs or the server drops you, silently, and your collector looks healthy while receiving nothing.

The engineering nobody writes down. Real feeds are snapshot-plus-delta: you get a full state once, then increments. Miss a delta and every subsequent value is wrong while remaining perfectly well-formed — the exact silent-corruption shape this guide keeps warning about, arriving through a different door. You need sequence-number gap detection and a resync path, not a retry.

The detection posture differs too. Servers check Origin and the Sec-WebSocket-* headers at the handshake, so the entire fight happens once at connection time rather than per request. That cuts both ways: fewer chances to be caught, and a much longer-lived session to be judged on.
+
Aug 2026 / Access
Every Shopify Store Ships an Unauthenticated MCP Endpoint
Shopify's Storefront MCP exposes product discovery, cart operations, store policy answers and order status at /api/mcp on the storefront domain, with no authentication. On Hydrogen it is live with zero setup because the route proxying that enables it is on by default. Alongside it sits a capability profile at .well-known/ucp that lets you enumerate what a merchant will answer before you write a single selector.
💡 This is skip-the-HTML made universal, standardised and vendor-documented
Why it matters at this scale. Anyone currently running headless Chrome against a Shopify storefront is paying browser prices to fight a page while an official, structured, documented door sits beside it. The economics are not close — and unlike a reverse-engineered private API, this one is published, versioned and intended to be called.

The questions only this guide would ask, and which are unanswered. What is the rate limit? Is there per-store opt-out, and how many merchants have used it? Does it return the same catalogue as the rendered page, or a subset shaped for conversational use? And what happens to all of the above when merchants notice the traffic?

Treat this as a lead with a measurement attached. The mechanism is documented and verifiable. The limits are not, and this guide does not publish numbers it has not measured. The card that follows this one should be an afternoon with a live store, a request counter and a diff against the rendered catalogue.
+
Aug 2026 / Operations
The Week the Letters Arrive
This guide states, correctly, that a cease and desist changes your legal position in a way a robots.txt file does not, and that collectors lose when they kept going after being told to stop. Then it stops at the case summary. What a working data business actually does that week is undocumented, and the community's confident answers to it frequently contradict the case law this guide cites.
💡 The scope question decides everything and almost nobody asks it first
Scope. Does compliance mean dropping the site, or dropping the site and every derived row already in the warehouse and in customer deliverables? Those are wildly different costs and the letter usually will not say. Deciding late is what turns a nuisance into an incident.

Whether to reply at all, and what we already deleted it does to your position — it can read as good faith or as an admission depending on what you deleted and when. That is a question for a lawyer, but the engineering decision of whether deletion is even possible is yours, and it is decided months earlier by how you stored provenance.

The pattern problem. Complying quietly with one letter may be cited by the next sender as evidence you knew. That is an argument for a consistent written policy rather than case-by-case improvisation.

The architecture consequence, which is the part this guide can own. Structure the crawl so one domain can be dropped without a rebuild: per-source lineage on every row, per-source scheduling, and no transform that silently merges sources into an unattributable blend. If you cannot delete one source cleanly, you have no operational answer to a letter, only a legal one.
+
Aug 2026 / TLS
Post-Quantum Changed the ClientHello, and Your Chrome Profile Did Not
This guide names JA4 well over a hundred times and has never mentioned post-quantum key exchange. The hybrid key share adds roughly 1,088 bytes to the ClientHello, pushing it past 1,400 bytes, which changes fragmentation behaviour and the JA4 string itself. A client presenting a modern Chrome user agent without a hybrid key share now matches no real Chrome in existence.
💡 This check completes before a single byte of HTTP is exchanged
Why it is a clean tell. It is not a heuristic about behaviour or a probabilistic fingerprint score. Real Chrome sends a hybrid post-quantum key share; a library pinned to a pre-PQ profile does not. The mismatch is categorical, it is visible in the handshake, and it costs the defender nothing to check.

The scale of the change. A June 2026 measurement across roughly 32,000 domains found close to half already supporting hybrid post-quantum key exchange, and Akamai made it the default for client-to-Akamai connections at the end of January 2026. This is not an edge case any more; it is the majority path on protected infrastructure.

What to do about it. Check what your impersonation library actually sends rather than what its profile name claims. A profile called chrome131 that predates the PQ rollout will happily identify itself as a browser that no longer exists. This is the same failure as the Camoufox correction above: a version label is not a measurement.
+
Aug 2026 / Evidence
Three Quarters of the Headless Penalty Is a String in a Header
A June 2026 study ran 40,000 visits across the Tranco 10K in a two-by-two matrix of engine and display mode, and produced the population-level numbers this guide has been quoting anecdotes about. Soft-block rates: Chromium headless 15.2%, Chromium headed 7.2%, Firefox 6.8% either way. By provider: Cloudflare 37.0%, Akamai 26.4%, Fastly 15.6%, Microsoft 14.8%, Google 5.1%.
💡 Correcting the headers to drop the HeadlessChrome token cleared 75% of headless-only blocks
Read that number carefully, because it reorders the whole ladder. Three quarters of the penalty for running headless was not a runtime fingerprint, not a missing plugin array, not a canvas difference. It was a token in the user-agent string. The cheapest possible fix recovered most of the gap, and everything this guide says about escalating cheapest-first is vindicated by it.

The other number worth holding. Only 34% of sites probe navigator.webdriver at all. The single most-patched property in the stealth ecosystem is checked by barely a third of the web, which says something uncomfortable about where collective effort has gone.

What this is good for. It gives a denominator. When a vendor quotes a success rate, or a blog post claims a technique works, you now have a population baseline to compare against: a plain headed Chromium already passes roughly 93% of the Tranco 10K, so any claim of a large improvement needs to say on which subset.
+
Aug 2026 / Security
You Are Executing Attacker-Controlled Code Inside Your Own Network
Every injection card in this guide is about an agent being talked into something by page text. The plainer version has never been written down. On every browser-based rung of the ladder you execute attacker-controlled JavaScript inside your VPC, decompress attacker-controlled bytes, follow attacker-controlled URLs, and parse attacker-controlled XML and SVG. The target chooses the payload. You chose to run it.
💡 The cloud metadata endpoint is one fetch away from a browser you told to follow links
Server-side request forgery is the sharp one. A crawler that follows discovered URLs will, given the chance, fetch internal addresses — including the cloud instance metadata service, which on a misconfigured instance hands out credentials. Your crawler has network position that an external attacker does not, and you built that position deliberately.

Decompression bombs are the cheap one. A few kilobytes of gzip can expand to gigabytes, and a fleet of browsers each holding one is an outage you paid for. Cap the decompression ratio and the response size, not just the timeout.

The controls are boring, specific and mostly missing. An egress allowlist on the browser fleet so it can reach targets and nothing else, especially not link-local metadata. Response-size and ratio limits before parsing. A patch cadence for headless Chrome that matches the one you would apply to a public-facing service, because that is effectively what it is. And no credentials on the machines that render pages.
+
Aug 2026 / Operations
Diagnosis Is Half an Incident. This Guide Skips the Repair.
There is a lot here about telling a 429 from a 403, attributing a failure to the proxy rather than the site, and catching silent corruption. Then the story ends at the moment you understand the problem. Backfill, re-queue, runbook and postmortem appear nowhere in this guide, and repair is where the actual data loss is decided.
💡 Merging recovered rows into a table that already holds partial rows for that window is the hard part
The four questions a repair actually poses. How far back does the gap go, given that detection lagged the break? Which rows already landed for that window, so the merge does not double-count — and this is where an upsert on a stable natural key earns its entire existence. Is the history even retrievable, or does the source only expose current state? And what do you tell the customer when the answer is no?

The unfillable case is more common than people admit. Price pages, inventory levels and rankings frequently have no history endpoint. If your collector was down for six days, those six days do not exist and no amount of re-queueing conjures them. That should be a stated property of each source, recorded when you onboard it, not discovered during an incident.

Note the one partial exception. Sometimes the history does exist — just not on the source. See the next card, because a public archive is the only answer this problem has.
+
Aug 2026 / Data quality
The Value Parses Perfectly and Means Something Else
This guide's data-quality work covers the wrong value and the missing value. It has never covered the correctly-typed, in-range, entirely plausible value that is silently wrong because of encoding, locale or units. 1.234,56 read as one point two three four. 03/04 read as March when the site meant April. A price in the currency of the exit node's locale rather than the market you are tracking. Full-width CJK digits that a naive parser drops.
💡 Every one of these passes a range check, which is why range checks are not enough
Why this class is uniquely nasty. A missing field is loud. A wrong-by-a-thousand field is loud if you look. A date silently transposed between day-first and month-first is never loud — it is a valid date, in range, in the right column, and wrong for eleven days out of twelve. It corrupts time series in a way that survives every schema test you own.

Where it comes from, specifically. Locale-dependent thousand and decimal separators. Ambiguous date orders with no timezone. Currency inherited from geolocation rather than declared by the page. Character-set guessing when the server lies about its encoding, producing mojibake that looks like a data-entry error rather than a pipeline bug. And multi-market sites serving different units to different exits.

The fix is a declaration, not a checker. Record the expected locale, currency, date order and encoding per source at onboarding, and assert against that declaration rather than against generic plausibility. Then a mismatch is a failure instead of a number.
+
Aug 2026 / Method
Before You Fight the Site, Check Whether Somebody Already Has the Page
The Internet Archive's CDX API and the Common Crawl index appear nowhere in this guide, and between them they are the cheapest first move against a hard target and the only answer to an unfillable history gap. Both let you query what URLs exist and what captures are available before you send a single request to the origin.
💡 It is also the only way to get back a window your collector missed
Two distinct uses. First, frontier seeding: pull the known URL set for a domain from an index at zero block risk, so you arrive knowing the shape of the site instead of discovering it by crawling. Second, historical recovery: when your collector was down and the source exposes no history of its own, an archive capture is the only place that window still exists.

The caveats, stated honestly, because they decide whether this works for you. Crawl cadence is nowhere near daily for most domains, so gaps in the archive are common. Coverage is popularity-biased, so the long tail is thin exactly where you often need it. JavaScript-rendered content is largely absent, which rules out a lot of modern commerce pages. And the licence terms on what you may do with the data are not the same as the terms on data you collected yourself.

The judgement. It is a poor primary source and an excellent reconnaissance and recovery tool. Checking costs one API call. Not checking has cost people weeks.
+
Aug 2026 / Defence
A Peer-Reviewed Defence Built Specifically to Break LLM Scrapers
Published at IEEE S&P 2026, WebCloak pairs dynamic structural obfuscation with what its authors call an optimised semantic labyrinth, and benchmarks it across 237 pages from 50 high-traffic sites against 32 scraper implementations. The reported effect on extraction recall is a collapse from 88.7% to zero. The code is public, which means it will be deployed.
💡 It also supplies the taxonomy this guide has been missing
Three consumer types, and they do not fail the same way. WebCloak distinguishes LLM-to-Script (a model writes an extractor, deterministic code runs it), LLM-Native crawlers (the model reads the page each time), and LLM web agents (the model drives a browser). That distinction predicts which defence hurts which of your pipelines — structural obfuscation punishes the generated selector, semantic mazes punish the model that reads, and the two together punish the agent.

Why this matters more than an average anti-bot product. It is reproducible, published and free. The LLM-honeypotting card in this guide describes sites that lie; this is that idea peer-reviewed, benchmarked and shipped as something a defender can install on a Friday.

The defensive read for a collector. If your pipeline is LLM-to-Script with cached deterministic extractors — the pattern this guide already recommends on cost grounds — you are in the category that structural obfuscation targets most directly. Regenerating an extractor against an obfuscated page produces a confident, wrong extractor. Your verification gate against golden fixtures is the control that catches it.
+
Aug 2026 / Policy
Verified Status Now Means Identity Only, and You Can Lose It For What You Do After
Cloudflare has decoupled verification from access. Being a verified bot now attests who you are and nothing else; whether you get in is decided per category — Search, Agent, or Training — by each site. More consequentially, what a bot does with content after crawling now feeds back: a verified bot that reproduces content in full can lose verification, and therefore lose access across the network.
💡 This is the first time robots.txt has had an enforcement lever attached
Why that qualifies one of this guide's own cards. We have argued that robots.txt works perfectly on the bots that were going to comply anyway, and that remains true for anonymous collectors. But for an identified crawler that wants network-wide access, a declared preference now has a consequence attached to ignoring it. The file did not gain teeth; the identity layer beside it did.

The category split is the practical part. A site can say yes to search indexing and no to training while allowing agent traffic, which means the same crawler is welcome or unwelcome depending on what it says it is for. Declaring purpose accurately becomes an access strategy rather than a courtesy.

And a directory now exists. Classifications are searchable, so how your crawler is categorised is a public fact about your business that customers and targets can look up. That is a reputational surface most collectors have never had to manage.

Oct 2026 update. Cloudflare (16 Oct) now scores Verified bots over time into an “explainable trust band” on two signals: whether traffic matches robots.txt in repeated patterns, and whether it slows or pauses after a 429. It is internal for now; Radar publication and a customer-set trust threshold are promised, with no numbers, thresholds or dates. A Kasada reply noted that most agents no longer declare themselves, so none of this reaches them.
+
Aug 2026 / Identity
Agent Identity Is Being Built on the Payment Rails, Not the Web Ones
This guide covers agent identity as a web-infrastructure problem — Web Bot Auth, signatures, verified crawlers. The track with actual enforcement teeth is somewhere else: Visa's Trusted Agent Protocol and Mastercard's Agent Pay, both riding on the same signature scheme, with Akamai wiring agent trust into bot management in mid-2026. It gates checkout rather than page access, which is why it will win.
💡 The sign flipped: AI-driven retail traffic now converts better than human traffic
The number that changes the incentive. In March 2025, retail traffic arriving via AI converted materially worse than non-AI traffic. By March 2026 the measured position had reversed, with AI-referred traffic converting substantially better. Once agent traffic is your best-converting segment, a false positive on your bot filter is not a security win, it is lost revenue with a name attached.

Why payment rails beat web standards here. A web-infrastructure identity standard needs adoption on both sides and gives a site little immediate reason to comply. A payment-network standard arrives attached to the money: merchants adopt it because the acquirer, the scheme and the chargeback rules say so. Enforcement follows the settlement, not the specification.

What that means for a collector. Identity is bifurcating. Anonymous collection keeps getting harder and cheaper to detect. Identified, purpose-declared, payment-attested agent traffic is being actively courted. Those are not two points on one spectrum — they are separate roads, and the second one is being paved much faster.
Nothing matches that. Try a shorter word, or clear the filter.
11 Testing tools

Check your own
fingerprint first

Before you bypass anything, you need to know what your setup is leaking. These tools show exactly what anti-bots see when your scraper connects. Run your scraper through them, not just your browser.

Gold standard, most detailed
BrowserLeaks
browserleaks.com
The most comprehensive fingerprint testing suite online. Tests WebRTC IP leak, Canvas hash, WebGL renderer, JA3/JA4 fingerprint, HTTP/2 Akamai hash, Chrome extension detection, fonts, geolocation, JavaScript environment, battery status. Essential for verifying your scraper identity stack.
TLS specific, generates JA3/JA4
BrowserLeaks TLS
browserleaks.com/tls
Tests your TLS ClientHello. Shows cipher suites, TLS extensions, key exchange groups, JA3 and JA4 hashes. Run Python requests, curl_cffi, and real Chrome through this and compare. JA4 is what Cloudflare and Akamai check at edge before serving any HTML.
IP leak, proxy coherence test
BrowserLeaks WebRTC
browserleaks.com/webrtc
Reveals your real IP even through a proxy. Shows local IP, public IP via STUN, and ICE candidates. If your proxy exit is US but WebRTC shows a local Pakistani address, every anti-bot flags you immediately. The most commonly overlooked leak.
JSON API, use directly in code
TLS JSON API
tls.browserleaks.com/json
Returns your TLS fingerprint as raw JSON including ja3, ja4, akamai hash, HTTP/2 settings. Call this directly from your scraper to compare fingerprints against real Chrome. One requests.get() vs cffi.get() tells you everything about the difference.
Quick pass/fail validation
BrowserScan
browserscan.net
Higher-level green/red check for automation detection, timezone coherence, WebRTC status, canvas fingerprint uniqueness. Good for quick pre-deployment validation before hitting a protected target.
EFF built, uniqueness score
Cover Your Tracks
coveryourtracks.eff.org
Built by the Electronic Frontier Foundation. Tells you how unique your fingerprint is among all visitors. A fingerprint too unique is as bad as one that looks like a bot. Scrapers need to look like the middle of the distribution.
Bot detection simulation
Pixelscan
pixelscan.net
Simulates what anti-fraud systems see. Identifies inconsistencies in timezone, IP, language, and WebRTC that would trigger detection. Fast pass/fail for operational teams before deploying at scale.
Advanced, behavioral and hardware signals
CreepJS
abrahamjuliot.github.io
The most advanced fingerprint tester available. Simulates what modern anti-fraud systems actually detect, including behavioral and hardware-level signals far beyond surface tests. Use this for deep audits of browser configurations.
Diff your bot against a real browser
xray-scanner + script2builtins
catalog-driven fingerprint diff
A different kind of test: instead of scoring your browser, it shows you exactly which surfaces leak. The toolchain reverse-engineers bot-detection JavaScript (seeing through string-array tables, JSF-ck, and reflective getters) into a catalog of several hundred fingerprint surfaces, then gives you a test page you point your bot at and diff its JSON against a real browser. You stop guessing which attribute betrayed you and read it off a list. Pair it with a daily benchmark run so you notice when a detector quietly changes what it probes.
Workflow: Fetch tls.browserleaks.com/json from both your scraper and real Chrome. Compare ja4 hashes. If they differ, fix TLS first with curl_cffi. Then check WebRTC at browserleaks.com/webrtc. Then headers. Work from layer 1 outward.

One habit worth building: re-run these checks on a schedule, not once. Detection scripts are not static. A vendor that probed one set of surfaces last week probes a different set this week, so a fingerprint audit you did a month ago is already stale. Treat your own fingerprint as something to monitor continuously, the same way the other side treats yours.

The wire-level capture loop: how you actually close the gap on a blocked request

Fingerprint checkers tell you what you look like in the abstract. This is the workflow for the specific case where your script gets a 403 on an endpoint your browser loads fine, and it is the single most useful loop in request-based scraping. The premise is the one this guide keeps returning to: finding the endpoint is the easy fifth of the work, making your request indistinguishable from the browser\'s is the other four fifths, and almost none of that is visible in DevTools. Three things it structurally cannot show you. Your TLS fingerprint, which is decided before a single header is sent and never appears in the Network tab at all. Your header order, because DevTools alphabetises headers for display, so the sequence you copy out is not the sequence Chrome actually sent, and copy-as-cURL bakes that wrong order straight into your script. And exact case and values, where HTTP/2 lowercases every name while HTTP/1.1 uses Title-Case, and a value has to match to the token (Chrome has advertised accept-encoding: gzip, deflate, br, zstd since Chrome 123, so a request still sending the three-value version no longer looks like current Chrome). You cannot fix what you cannot see, so you need a proxy that records the real wire. The reverse also happens: a request can be missing from every capture because it never left the page. In Chrome, a stored copy of window.fetch called as a method of another object, as in client.fetch(url), throws TypeError: Illegal invocation before any network activity, which was reproduced on Chrome 156, while Node 26, Bun and typical test mocks accept the same call. Helpers unit-tested outside the browser can therefore fail only once injected into a page to call a site’s API, and the empty network log looks like a dead endpoint or a block. When a capture shows nothing, read the console for a local exception before you debug the endpoint, the proxy or the fingerprint. Call fetch(...) directly, bind it with fetch.bind(window), or wrap it in an arrow function. A reverse-engineering map is not a wire capture. Agent tools that read HAR files or attach to Chrome over CDP help with the first fifth of this loop, finding endpoints and the scripts that call them, and are weak on the other four by design. REA’s passive CDP capture, for example, keeps method, status and URL, drops headers after a short allowlist, never retains cookies or authorization values, and reduces JSON bodies to value-free shapes, so header order, casing and token chains are simply not in its output. Its HAR reader redacts known auth fields but keeps custom ones: in one offline run on a synthetic file, the cookie and bearer token vanished while an x-signature value survived. Use such a tool to decide which request matters, then diff the real wire in a recording proxy. Check the released build too: the endpoint-template view in REA’s docs was missing from npm 6.4.0.
The loop itself. (1) Record the browser first, from a clean profile, because that capture is your source of truth. (2) Search the whole session for each dynamic value on the request you want, matching across URLs, headers and response bodies. A token like an Imperva reese84 cookie echoed as an x-d-token header will turn up in three places: the response that minted it, the cookie that carried it, and the header that spends it. That chain, not the endpoint, is the real output of reading a session, because it tells you which requests your script has to make first. (3) Write a first pass with a browser-grade TLS client, routed through the same capture proxy. (4) Diff your request against the browser\'s in wire order and fix every difference until the diff is empty. The differences that block you are usually one token wide, a missing zstd, a stray Host header on an HTTP/2 request, priority not sent last, or headers in alphabetical order which is itself a dead giveaway that they came out of a dictionary rather than a browser. (5) Replay one captured request over and over and watch for the flip from 200 to 403. That number is how many calls a single warm session survives, and you set your rotation threshold just under it, which is how the session-stickiness advice in the proxy section turns into an actual figure instead of a guess.
The test that saves the most hours
Same IP, browser versus script
The question that eats afternoons is "is it my proxy or is it my code", and there is a clean way to settle it. Point your capture proxy\'s upstream at the exact proxy your scraper uses, so the capture exits from the same IP the target will see. Run your script through it, then, without changing anything else, point a real browser through the same chain and load the site by hand. If the browser is also blocked on that IP, the problem is the network: the address is flagged, or its geo or ASN is wrong, and no amount of header work will help. If the browser sails through while your script is blocked on the same IP, the problem is entirely in your request, so go back to the diff. One test, one tool, and you stop chasing the wrong layer. Run the same split one level up before you ship a browser agent. A setup that passes on your laptop is being tested from a residential IP, and production usually runs from a cloud ASN, so the first run from the deployment network is the real test, not a formality. Change one variable at a time: move the unchanged browser onto the production network first, then change the browser on that network, and the step that removes the block names the layer. One practitioner’s headed agent passed a Cloudflare Managed Challenge 5 of 6 times from a home line and 0 of 3 from an Azure server, which is n=1 on a page that always challenges. Do not cite the June 2026 Bamberg crawl as backing for that split: it ran every visit from one datacenter and varied only the browser, so it shows headless Chromium blocked on 15.2% of homepages against 7.2% headed on a fixed cloud network, and nothing about home against cloud.
Capture hygiene, or you debug a ghost
Your tooling can corrupt the evidence
A capture is only useful if it is honest, and several common mistakes quietly poison it. Your interception proxy itself can be the problem: some rewrite the ClientHello enough that anti-bots block the proxy rather than your script, and at least one popular tool moves Content-Length to the bottom of the header block when chaining to an upstream proxy, corrupting the exact ordering you opened the tool to inspect. Verify your tool preserves wire order before you trust its output. Turn off the DevTools "disable cache" option while recording, because it injects Cache-Control and Pragma headers a real navigation never sends and you will copy them into your script. Record from a clean, ordinary profile, not incognito and not one that merely had its cookies cleared, since both produce artefacts like extra Client Hints on the first request. And route only the client you are debugging, not the whole system, or the session drowns in traffic from every app on the machine. One more detail people miss: header order is per request type, so a navigation, an XHR, a subresource and the anti-bot\'s own endpoint each have their own order. Capture the specific request you intend to replay.
Two closing notes. Keep the whole chain consistent while you work: the IP your capture exits from should be the IP your scraper uses, on a sticky session rather than a rotating one, or you are comparing captures taken from different identities. And this is a workflow a model is unusually good at once the data is in a readable form, since exporting the session (or exposing it over a local MCP server) lets an agent read wire-order headers, the TLS handshake and the raw HTTP/2 frames and point straight at the mismatched header, which is the same diff you would do by eye, done faster.

Two capture mistakes that waste a whole afternoon

Name the tool, then distrust it. The loop above needs a proxy that records the real wire, and the one most request-based practitioners settle on is powhttp, which is built for exactly this comparison and has almost no documentation, so budget an hour to learn it. Whatever you choose, verify it preserves header order before you trust a single diff it shows you, because an interception proxy that quietly reorders headers turns the tool you opened to inspect ordering into the thing corrupting it.

Record from an ordinary profile, and understand what incognito actually leaks into your capture. The advice to avoid private windows when recording is usually given without a reason, so it gets ignored. The reason is specific: a session recorded in Incognito or Guest mode carries artefacts a normal navigation never sends. You may see Sec-Fetch-Storage-Access: none, and the payloads that anti-bot scripts assemble can contain stricter third-party-cookie and storage-partitioning signals that reflect the private context rather than the browsing you are trying to imitate. Replay that capture and you are faithfully reproducing a browser state no ordinary visitor is in, which is a coherent-looking request that is coherent about the wrong thing. Practitioners working request-based DataDome flows report this as one of the most common causes of a session that was recorded wrong from the first byte, where the TLS is right, the header order is right, and the replay still fails.
Detector scripts change faster than your notes. One researcher running an analyser against fourteen live bot detectors every day reports two things worth internalising. Every outbound request those scripts make is attributable to its infrastructure, so the data collected about a browser has a return address and can be watched. And the probes themselves shift constantly: a detector that measured one set of surfaces last week measures a different set this week. An audit performed once and written down is already drifting. If you maintain any fingerprint documentation, put it on a schedule, or accept that it describes the past.
3 field notes on testing tools Reported from practice. Click any card to open it.
+
Aug 2026 / Instrument
A New Fingerprint Mirror, and a Firefox Bug That Linked You Across Tabs
Two fingerprinting items worth holding together. Glassbox is a new browser-fingerprint tool that runs entirely client-side and dumps the raw, unfiltered signals plus an identifiability estimate. And Fingerprint's research team disclosed a Firefox flaw that let unrelated sites link your activity across a running browser — no cookies, no storage, no explicit channel, now patched.
💡 The check-your-scraper tools keep getting sharper; so do the leaks they measure
Glassbox as an instrument. It joins the guide's testing-tools shelf next to BrowserLeaks, CreepJS and Cover Your Tracks, but its angle is the raw dump: rather than a pass/fail, it shows every attribute a tracker or anti-fraud script would read and estimates how identifiable the result is, entirely in-browser with nothing shipped out except a geolocation ping. That's the right shape for auditing a scraper's browser — you want to see the exact surface you present, not a green tick. Pair it with a daily capture so you notice when a target quietly starts probing something new. (The counterweight, from a fingerprinting professional: most of the fingerprint demos trending this month are vibe-coded toys that derive identifiers that work on your one machine but have no real-world scale behind them — a check-tool is for reading your own surface, not for judging what a serious detector actually keys on.)

The Firefox bug as evidence of the layer below. Fingerprint's team found that Firefox-based browsers let one site link activity to another across a live browser process — the exact cross-context linking that cookies were supposed to be required for, achieved with none. Responsibly disclosed to Mozilla and the Tor Project, fixed in Firefox 150 and ESR 140.10.0. It matters here because it's a reminder that the browser's attack surface is enormous and shifting: the same primitives that leak a user's identity across tabs are the ones a detector can read to tell your automated browser from a human's, and they change version to version. Whatever you audited on the last build is not guaranteed on the next.

Sources: Glassbox (via The Register); Fingerprint research, fixed in Firefox 150 / ESR 140.10.0.
+
Aug 2026 / Instrument check
“I Published a Performance Chart Five Weeks Ago. It Was Measuring Nothing.”
The benchmark cards in this feed are about distrusting somebody else's numbers. This one is worse and more useful: an engineer went back to turn his own published benchmark into a regression test, deleted the optimisation it existed to prove, and everything still passed.
💡 Delete what a test guards. If it still passes, it was never watching your code.
The flaw, which is worth stealing because it is so easy to commit. The benchmark constructed its own query inside the test file rather than driving the endpoint the application actually serves. The two matched on the day they were written and then drifted apart freely. So it was answering “does this framework's eager-loading feature work?” — a settled question about somebody else's library — instead of “does the code I deploy still do this?” Fifteen tests passed in 4.67 seconds and the benchmark printed its usual flat number while the real regression sat there untouched. His phrase for it: simultaneously correct and useless.

Driving the real entry point changed the answer. The true count was four, not three, because the paginator's COUNT(*) was invisible from where the benchmark stood. That gap sounds trivial and is not: one query in four is nothing in a latency budget, but an assertion written against the wrong number fails immediately and forever on correct code, which is how a well-meant regression test gets deleted by the next person.

The replacement is the transferable idea. Not a benchmark that prints a number, but a query budget — a fixed count asserted against the real endpoint at two different data sizes, so flatness is proven rather than assumed, with each query named in a comment so any future change has to be justified rather than absorbed. A benchmark prints a number. A regression test refuses to let it change.

Why this belongs in a scraping guide. Substitute your own instruments and the argument survives intact. A health check that builds its own request instead of running your scraper's actual client is measuring the library, not your pipeline. A coverage alert whose fixture was captured once and never re-driven is measuring history. The guide already says to check the instrument before trusting the verdict; this is the procedure that operationalises it — delete the thing the check protects and watch it go red. If it does not, you have been reassured by nothing for however long it has been green.
+
Aug 2026 / Debugging
The Proxies Were Fine. The Tool Measuring Them Was Broken.
Weeks lost to a certainty: every request to one site came back blocked, three providers swapped, identical failure each time. That identical failure read as proof the IPs were burned. It was proof they were irrelevant.
💡 When something fails identically everywhere, the thing you keep swapping is not the cause
The actual fault: the scraper was shaking hands like a browser from two years ago while presenting a user agent for the newest version. No real browser can do that, the TLS stack and the version string are welded together, so the detector did not need to be clever. It noticed the contradiction and closed the door, from every IP, from every provider. Straight back to the coherence rule that runs through this whole guide.

The second half is the part almost nobody writes about, and it is the more useful lesson. The tool being used to check whether a proxy was alive did not look like a browser either, so it got blocked too, always. That means the instrument was reporting healthy proxies as dead and actively confirming the wrong theory. Weeks of evidence, all of it generated by a broken measurement. If your health check is less capable than your scraper, every reading it gives you is noise dressed as data, and you will keep buying the conclusion it hands you.

The fix is worth copying as a pattern, not just a value. Rather than finding the correct setting, they made the wrong one impossible to express: the handshake profile and the user agent now travel together as a single unit, so you cannot select one without the other. The bug cannot be recreated by a future edit. That is the difference between fixing a defect and removing a class of defect, and it is the same instinct as binding a fingerprint's axes together rather than configuring them independently. Result: roughly 0% to 90% success, with exactly one block across the following 50,000 requests.
12 Architecture

How production scrapers
are actually built

From a single Scrapyd daemon to multi-region ECS clusters. Twelve real pipeline architectures, from simple to enterprise-scale, with every component and data flow mapped out.

The simplest production setup. One server, Scrapyd managing spiders via JSON API, ScrapydWeb as UI. Good for <50 spiders and teams without Kubernetes. Deploy with scrapyd-deployschedule via /schedule.jsonmonitor at port 6800.

💻 Developer + codebase scrapyd-deploy SCRAPYD SERVER :6800 🕷️ Scrapyd Daemon JSON API + job queue Spider 1 process Spider 2 process ↻ Rotating Proxy scrapy-rotating-proxies HTTP request 🌐 Target Site anti-bot protected 📦 Storage Pipeline SCRAPYDWEB 🖥️ Visual Dashboard port 5000 monitors via API SCHEDULER Cron / APScheduler POST /schedule.json 🔔 Slack / Email job alerts
✓ Pros
  • Zero infrastructure overhead, one server, done
  • ScrapydWeb gives full UI: logs, job history, schedule
  • Deploy new spiders in seconds with scrapyd-deploy
  • Great for teams without DevOps expertise
✗ Cons
  • Single point of failure, server down = scrapers down
  • Limited to one machine's CPU and memory
  • No auto-scaling, manual capacity planning
  • Spider isolation is process-level only
↑ Scale up

Add more Scrapyd nodes → ScrapydWeb manages cluster from one UI. Next step: scrapy-redis for shared URL queue.

Stack Scrapyd :6800ScrapydWeb :5000scrapyd-client (deploy)APScheduler or cronGerapy (alt UI)
★ Featured architecture

The AI Workflow
graph + adversarial + five algorithms

A genuine AI-agent scraping system, designed from scratch. Two agents working through a queryable graph of vendor and selector history, with six mathematical concepts each replacing a heuristic that would otherwise rule the system. Below the conceptual diagram, you will find what this looks like when you actually run it in production: real services, real protocols, real bottlenecks, and the cost numbers it should add up to.

① Graph theory
PageRank · Louvain · BFS
Memory as a queryable graph. PageRank ranks vendors by operational criticality. Louvain community detection auto-distinguishes failure modes (structural change vs vendor flip vs isolated drift). BFS bootstraps new URLs from their nearest known-working neighbour.
replaces · flat-file selector history
② Bayesian inference
Beta(α, β) confidence
Every selector and every URL carries a Beta distribution, not a scalar. Beta(95, 5) means 95 successes in 100 tries, very confident. Beta(3, 0) is 3 from 3, but the credible interval is wide. Routing weighs evidence, not just point estimates.
replaces · single confidence float
③ Thompson sampling
multi-armed bandit on rungs
For each URL, the four rungs (curl_cffi / browser / browser+pacing / managed) are arms of a bandit. Thompson sampling balances exploration (try the cheap rung occasionally) and exploitation (use what worked yesterday). Same math as ProxyOps which is already in this guide.
replaces · fixed rung choice per URL
④ Information theory
KL divergence drift
Compute KL divergence between today's value distribution and a rolling 30-day baseline, per field. A field can have 100% fill rate and still drift, prices all shift up 5%, titles all gain a suffix. KL catches what fill-rate misses. This is how silent poisoning becomes visible before it pollutes silver.
replaces · fill-rate as drift signal
⑤ Control theory
PID concurrency loop
Ban rate is the error signal, request concurrency is the actuator. A PID controller adjusts concurrency continuously to hold ban rate at target (e.g. <0.5%). Proportional reacts to magnitude, integral to persistent drift, derivative dampens overshoot. Used in production at Stripe, Cloudflare. Heuristics here cost real money.
replaces · fixed concurrency & manual backoff
⑥ Reservoir sampling
unbiased audit sample
Maintain a 100-row ground-truth audit sample drawn uniformly from a 50M-row stream, in one pass, in O(1) memory. Vitter's Algorithm R. Without this you end up auditing the easy rows from the start of each batch and missing the hard rows at scale.
replaces · first-N or last-N sampling

Concepts deliberately rejected as theatre for this scale: GNN training (graph too small), max-flow/min-cut (no mapping), causal do-calculus (impractical in production), game-theoretic minimax (no concrete operation), Markov chains for traversal (overkill), learned embeddings (cost > value).

The architecture · conceptual view

A genuine AI-agent scraping architecture, built around three ideas you won't find in published frameworks. (1) Graph memory. URLs, selectors, vendors and outcomes are nodes, connected by edges. When one vendor changes its sensor, every URL on that vendor inherits the learning automatically. (2) Adversarial verification instead of self-healing. The scraper agent ships an answer + a confidence score, the verifier agent's job is to disprove it. Only outputs that survive the disproof attempt reach gold storage. The detail that makes this work is keeping the verifier context-blind: run it in a fresh session with no memory of how the answer was produced, so it judges the output on its merits instead of inheriting the generator's rationalisations. A reviewer that sat through the generation will quietly accept the same shortcut that produced the bug; a reviewer handed only the result, the schema, and the live page has to re-derive correctness from scratch, which is exactly the property you want. The same applies when an agent rewrites a broken scraper: benchmark and review the regenerated code from a clean context, not inside the conversation that wrote it. (3) Graph-theoretic intelligence on top of the memory. PageRank ranks vendors by operational criticality, community detection auto-distinguishes structural failures from selector drift, shortest-path traversal bootstraps strategies for new URLs from their nearest known-working neighbours. Most of the system runs cheap, verification runs only on the hard cases.

Three ideas this architecture is built on (and nothing else): ① graph-shaped memory ② adversarial verification ③ graph-theoretic intelligence (PageRank · community detection · shortest path) THE KNOWLEDGE GRAPH (memory, not files) Neo4j or DuckDB-graph · one process can read it · agents query it like a database VENDOR :Akamai_v3 last_sensor_hash · expected_cookies URL :homedepot/p/123 success_rate=0.94 · rung=2 URL :homedepot/p/456 success_rate=0.91 · rung=2 URL :lowes/p/789 success_rate=0.98 · rung=2 URL :ticketmaster/... success_rate=0.61 · rung=4 SELECTOR :price.v3 CSS · confidence 0.92 SELECTOR :title.v1 XPath · confidence 0.98 SELECTOR :stock.v2 JSON path · confidence 0.74 USES EXTRACTS ★ vendor sensor changes → graph updates → ALL connected URLs inherit new strategy in one write THE TWO-AGENT LOOP (only two, not thirteen) SCRAPER agent small model · cheap · fast 1. read graph for this URL 2. pick rung from vendor history 3. fetch with that rung 4. extract using known selectors 5. emit row + per-field confidence { price: 42.99 (0.94), VERIFIER agent larger model · adversarial prompt: "prove this is wrong" • re-fetch via different rung • check mobile API if any • compare to similar URLs • disagrees? FAIL the row trust is earned by surviving claim CONFIDENCE ROUTING (only verify the hard cases) conf ≥ 0.90 · fast lane skip verifier ship straight to bronze ~85% of rows 0.60 ≤ conf < 0.90 · check verifier runs survives? → bronze ~12% of rows conf < 0.60 · quarantine do not ship human review queue ~3% of rows Every outcome rewrites the graph · this is where the system learns verifier disagrees N times in a row → selector node confidence drops → a new selector candidate is mined → graph proposes :price.v4 with edge → ALL URLs using vendor inherit candidate ban rate spike on one URL → vendor node 'last_sensor_hash' flips → ALL URLs using vendor mark rung+1 → scraper agent re-reads, escalates → no per-URL fix · one vendor edit fixes 1000s field confidence trends down across URLs → schema-drift flag raised on the field → human notified via Slack with samples → system never silently auto-changes schema → this is the only gate that holds What actually runs · in production graph DB (Neo4j or DuckDB) · 2 Anthropic API workers · curl_cffi + browser pool · Pub/Sub queue · Parquet on S3 bronze → silver → gold Graph Intelligence Layer · three algorithms that actually pull their weight (not theatre) A · PageRank on vendor nodes criticality, not popularity what it does rank vendor nodes by how much traffic flows through them (eigenvector centrality) what it tells you Akamai_v3 fails = 47% volume down F5_Shape fails = 2% volume down → pager priority is data, not vibes cost recompute nightly · tiny graph (~50 vendors) runs in <100ms with NetworkX tells ops where to point their attention B · Louvain community detection ★ the gem · failure-mode discriminator what it does cluster URLs into communities by shared selectors, vendor, template, behavior history what it tells you when one URL fails, look at its community: • 100% of community failing = structural • ~50% failing = vendor sensor change • <10% failing = isolated selector drift why this is the gem distinguishes Reality Map failure modes automatically. Humans currently guess. C · Shortest-path on new URLs cold-start bootstrapping what it does a NEW URL arrives. BFS through graph to find nearest URL with working selectors via shared vendor / template / domain edges what it tells you "start at rung 2, use these 4 selectors, expect Akamai_v3, conf ~0.8 on title" → zero-shot bootstrap, not blind probe cost / fallback if path distance > threshold: cold start via scraper agent recon (slower path) What this layer deliberately does not include: GNN training for selector prediction (graph is too small, cost > value) · max-flow / min-cut (no mapping) · learned graph embeddings (overhyped for ~10k nodes) · centrality measures beyond PageRank (redundant) Design choices that make this different from every other "agentic scraper" you've read about: → Memory is a graph, not a folder. When one vendor changes its sensor, the fix touches one node and propagates to thousands of URLs. → Two agents, not thirteen. A scraper that emits confidence, a verifier whose only job is to disprove. Direct handoff. No orchestrator in between. → Adversarial verification, not self-healing. Trust is earned by surviving disproof. The default assumption is "you are wrong." → Confidence routing, not retry loops. 85% of rows ship without ever invoking the second agent. The expensive path runs only on hard cases. → Three graph algorithms, no more. PageRank, Louvain, BFS. Skipped GNN training, max-flow, learned embeddings as theatre for a 10k-node graph. → Schema changes need a human. Selectors, rungs, candidates all evolve automatically. The shape of the data does not change without a person. → Learning is vendor-shaped, not URL-shaped. One Akamai_v3 sensor change is one edit. One URL going down is one edit. The graph reflects how anti-bot vendors actually deploy. Six mathematical primitives, two agents, one graph. Every routing decision in the system is named, costed, and traceable to a printed equation, not a heuristic that drifts as the team forgets why it was chosen.
Stack Neo4j or DuckDB-graph (knowledge graph memory)NetworkX (PageRank · Louvain · BFS)Small LLM as Scraper agentLarger LLM as Verifier agentAny frontier LLM API · prompt: "prove this is wrong"Confidence routing (≥0.9 / 0.6-0.9 / <0.6)Pub/Sub queue (URL fan-out)curl_cffi + CloakBrowser (rung-aware fetch)Vendor-level learning propagationSchema-drift Slack alerts (human gate)Parquet bronze → silver → gold

What this looks like in production

Real services, real protocols, real bottlenecks. Drawn for 10M-50M URLs/day. Each rung in the Scraper agent is annotated with the actual scraping technique that does the work, not just the box that runs it. Color-coded dots mark each flow direction so you can trace any path through the system.

★ stack philosophy
Two agents, no framework, model-agnostic. The two agent roles are scraper (small fast model, runs on every row) and verifier (larger careful model, runs on ~12% of rows). Either role can be served by any frontier LLM with structured output, an Anthropic model, an OpenAI model, a Gemini model, an open-weights model behind vLLM. No LangChain, LangGraph, AutoGen, CrewAI, Haystack, LlamaIndex. The architecture has six math operations, none of which need a framework's abstraction. Direct API call for the model, pydantic for structured output, networkx for the three graph algorithms, scipy.stats.beta for Bayesian state, small custom services for Thompson sampling, KL drift, and the PID loop, boto3 for SQS/Kinesis/S3. Frameworks shine when you have 30 chains and 10 tool integrations to coordinate. We have 2 agents and a queue.
legend scrape traffic · ~85% verifier-flagged · ~12% verifier verdict · written back learning feedback · updates control plane
LAYER 1 CONTROL PLANE where the math lives · low traffic · stateful · single-region Neo4j knowledge graph ~10k vendors · ~5M URLs PostgreSQL Beta(α,β) state per-selector confidence Thompson sampler FastAPI · ECS per-URL rung selection PID controller Go service · 1s loop tunes worker concurrency KL drift Spark · EMR · hourly vs 30-day baseline NetworkX jobs nightly CronJob PageRank · Louvain · BFS LAYER 2 QUEUE LAYER SQS · scrape-jobs ~5M msg/day SQS · verifier-jobs ~600K msg/day SQS · quarantine ~150K msg/day Kinesis · outcomes ~50M events/day LAYER 3 SCRAPER agent · EKS 50–400 pods · auto-scaled by PID controller Scraper role small fast model · any vendor reads graph → picks rung → extracts → emits Beta(α,β) runs on 100% of rows · cheap Rung 0 · API hunt (always tried first · free) technique: HTTPToolkit on rooted emulator · inspect mobile app endpoint technique: look for __NEXT_DATA__ · GraphQL · XHR in page HTML → if JSON endpoint found · skip rungs 1-4 entirely, hit the API ↓ if API hunt fails · escalate through the rungs (Thompson-sampled per URL) Rung 1 · curl_cffi · ~85% of volume · ~$0.30 / 1K technique: TLS impersonation (Chrome 124 JA4) + HTTP/2 + residential proxy when: Akamai easy mode · Cloudflare static · most e-commerce no JS execution · ~10ms per request · fastest possible path Rung 2 · browser pool · ~10% · ~$3 / 1K technique: CloakBrowser / Camoufox over ws:// · Xvfb · two-slot fix when: JS-rendered content · simple bot detection · cookies needed patched Chromium · persistent profiles · ~3s per page Rung 3 · browser + pacing · ~3% · ~$8 / 1K technique: Rung 2 + Bezier mouse curves + realistic nav · 8-30s pacing when: PerimeterX · DataDome · Kasada · behavioral ML defenses walks the site like a human · slow but reliable on hard targets Rung 4 · managed API · ~2% · ~$15 / 1K technique: Zyte API / Bright Data Web Unlocker / Scrapfly when: F5 Shape · Akamai v3 deep behavioral · enterprise sites last resort · they own the bypass burden · we pay the fee VERIFIER ECS · queue-scaled Verifier role larger model prompt: "prove this is wrong" technique: re-fetch at different rung, BFS-compare to siblings STORAGE S3 medallion 🥉 Bronze Parquet raw + provenance 90-day retention 🥈 Silver dbt models content-hash dedup upsert + last_seen 🥇 Gold Iceberg tables BI · APIs downstream consumers PROXY LAYER Bright Data ISP Massive residential Infatica (KYC) ★ 5-vector coherence EXTRACTION OUTPUT { row, Beta(α,β) per field, rung_used, latency } → Kinesis stream LAYER 4 LIVE METRICS · sample snapshot · what the dashboards actually show BAN RATE 0.31% · target 0.5% FILL RATE (price field) 96.4% · stable 30d VERIFIER DISAGREE 2.1% of ~12% sampled KL DRIFT (top field) 0.08 · threshold 0.20 CONCURRENCY (PID) 184 pods · range 50-400 $ / 1K rows 0.71 USD · trailing 24h LAYER 5 OBSERVABILITY what stands between you and a 3am page Prometheus + Grafana OpenTelemetry traces KL drift dashboard Graph health view Reservoir audit PagerDuty LAYER 6 HUMAN GATES · only here · nowhere else ① schema changes (KL drift > threshold) → Slack with samples · ② new vendor detected · ③ quarantine review · ④ critical-vendor degradation selectors, rungs, candidates evolve automatically · the shape of the data does not change without a person
What this production design handles automatically · no on-call required
vendor sensor flips
PID + bandits re-route within minutes. ALL URLs on that vendor inherit the new escalation through the graph.
selector drift
Verifier disagreement N times raises a new selector candidate. Graph propagates to related URLs. Seamless.
cost creep
PID tightens concurrency as bans rise. Bandit shifts traffic to cheaper rungs. Spend self-corrects.
silent value poisoning
KL divergence catches distribution shifts the row count and 200-OK rate cannot see. The hardest failure mode, now visible.
What it routes to a human · by design: schema changes (new field · removed field · type mismatch) · the shape of data is governed, not learned.
✦ Pattern

Self-Healing Scraper
powered by Claude

Scrapy spiders break when sites change their HTML. Instead of manually fixing selectors, this architecture uses Claude to detect failures, analyse the new page structure, and write corrected selectors automatically, without human intervention. Before you trust any automatic relocation, library or LLM, validate the value it returns, because a relocator can be confidently wrong rather than empty. In one hands-on check of Scrapling v0.5’s adaptive mode, whose matcher is a difflib similarity score with a 40% default threshold, renaming classes on a books listing was handled correctly. But once the price <p> became a <span> inside a new wrapper, it returned the “In stock” paragraph as the price, with no warning. It also returned a single element, not the original list of 20. So add a shape check after relocation (a currency pattern for prices, a URL for links, an item count close to yesterday’s) and treat a mismatch as a failed heal, not a result. That was one page and three hand-made mutations, so take it as an illustration, not a failure rate.

SCRAPY SPIDER 🕷️ runs on schedule TARGET SITE 🌐 HTML changed success STORAGE 📦 S3 / DB clean items failure / empty DETECTOR Item count check 0 items = broken CLAUDE API 1. Fetch broken page HTML 2. Analyse new structure 3. Write new selectors UPDATER Patch spider config CSS / XPath / regex RETRY Re-run spider with new selectors auto-healed, spider runs again without human intervention NOTIFY Slack / email "healed" alert
1
Spider detects failure
Item count drops to zero or below threshold. A Scrapy extension hook fires immediately, no waiting for the next run.
2
Claude analyses the broken page
The full page HTML is sent to Claude with the old selectors and a prompt: "The selectors below stopped working. Examine the HTML and write corrected CSS selectors for the same data fields."
3
New selectors written and applied
Claude returns structured JSON with corrected selectors. The updater patches the spider config or YAML file. No code deployment needed.
4
Spider retries and confirms
The spider re-runs with new selectors. If items come back, healed. A Slack notification logs what changed. If it fails again, escalates to human review.
Claude prompt pattern
You are a web scraping expert. A Scrapy spider broke because the site changed its HTML.

Old selectors (no longer working):
  title:  h1.product-title::text
  price:  span.price-now::text
  image:  img.main-image::attr(src)

New page HTML (truncated):
{{ page_html[:8000] }}

Return ONLY valid JSON with corrected selectors:
{"title": "...", "price": "...", "image": "..."}
✓ Why it works
  • Claude reads raw HTML better than regex, handles minified, dynamic, and obfuscated markup
  • Zero downtime, spider heals mid-run, not on next deployment
  • Works across 50+ spiders from a single Claude integration
  • Selector changes are the most common spider failure, this covers 80% of breakages
⚙ Implementation notes
  • Use claude-haiku-3 for speed and cost, ~$0.0003 per heal
  • Cap page HTML at 8K chars before sending, beyond that Claude doesn't need more
  • Store selector history in a YAML file versioned in Git for auditability
  • Add a confidence check, if healed items look wrong, escalate to human
↑ Extend it

Add a second Claude call to validate the healed output against a schema. Use computer-use to handle JavaScript-rendered pages where HTML alone isn't enough. Log all heals to build a fine-tuning dataset.

Stack Scrapy extension hook Claude API (Haiku) YAML selector store Slack webhook Anthropic SDK

The lab-to-production gap: running thousands of pages unattended for days

There is a wide gap between running one to three spiders on your laptop and running thousands of pages in production, unattended, for days. Most tutorials stop where that gap begins. The crawl that works perfectly in a terminal session fails in ways you never see interactively: a connection resets at hour nine, a target quietly tightens its rate limits, a selector that matched yesterday returns nothing today, and nobody finds out until the morning. Closing that gap is less about clever bypasses and more about a few unglamorous habits. Treat each headless browser as a leaking, stateful resource with a fixed lifespan. Recycle it after a set number of pages or minutes instead of waiting for it to die, give each container a hard memory ceiling, and health-check for a browser that has stopped responding, not only one that crashed: a wedged Chrome holds its slot, reports no error and leaves no crash log, so a liveness probe that navigates a trivial page on a timeout catches what a process check never will. Record memory per browser, page latency and block rate per domain, so a slow climb shows up as a trend on a chart rather than as a mystery at hour nine. Keep pool health and target yield as separate numbers. In one four-worker CDP broker test, every browser stayed healthy and no connection queued at 40 concurrent contexts and 6.6 pages a second, while about one response in five was a challenge page: the target’s edge set the limit, not the pool, and more replicas might only have raised the rejection count. A health check that only asks /json/version proves the CDP endpoint answers, not that a worker can navigate through its proxy, so probe with a real navigation. And a broker that assigns whole connections cannot see how many contexts each client opens; cap contexts per connection, or your capacity model depends on every client behaving. That is one run on one site, so treat it as a pattern to check rather than a rate. If you rent browsers, the same discipline is billing: one provider meters 1 unit per 30 seconds of open browser time, rounded up, so a session that idles instead of closing is a line item. The failure modes here are widely reported in practice but come without published rates, so measure your own recycle point. Time each worker’s start-up, not only pages per second, because a browser client library can fetch its bundled browsers on every fresh worker before the first page. In one vendor-run benchmark of 96 Camoufox executors across two HPC nodes, an unused download of about 450 MB per executor, a second node joining four minutes late and 192 small output files together stretched a 1,000-page job from just over 7 minutes to 13. The download showed up only in per-executor logs. Pin the browser you actually use, switch off the library’s automatic download, pre-pull the image on every node, wait for all workers to register before handing out work, and write a few large output files. Memory, not CPU, set the ceiling in that run, at about 4.5 GB per executor including the JVM, so size browsers per node from measured memory. Two runs on an undefended sandbox, so measure your own.
Throttling and retries
Speed is a tradeoff, not a setting
Tune throttling for the real tradeoff between crawl speed and getting banned rather than hammering at a fixed rate. Scrapy's AutoThrottle adapts the delay to the server's observed latency, which both behaves better and survives longer than a hardcoded concurrency number. Before swapping HTTP clients for speed, work out which limit you are actually under. Against one site, throughput is capped by your politeness delay and the site’s rate limit, not by the client, so moving from requests to async httpx buys nothing there; across hundreds of independent hosts async does pay, roughly 100 seconds sequential against about one second for 200 requests at 500 ms each. Two details decide whether you get that: httpx.AsyncClient caps its pool at 100 connections by default, so 200 hosts run in two waves unless you raise max_connections, and an async client with no limiter sends everything at once rather than queuing politely. Use a per-host limiter that spaces request starts, because a plain sleep after each response adds the response time to every interval. This is arithmetic from the library defaults, not a benchmark.

Handle failures at the right layer. A try/except around your parse code never sees a network-level failure, because the request fails before your callback runs. You need an errback to catch DNS errors, timeouts, and connection resets, and a helper like get_retry_request to requeue with proper accounting instead of silently dropping the URL.
Structure and monitoring
Catch a bad run in minutes, not the next morning
Give the project a structure that scales past a handful of spiders without becoming unmanageable, so shared logic lives in one place rather than copy-pasted across files.

Then monitor outcomes, not just uptime. A tool like Spidermon validates each run against expectations (item counts, field coverage, error rates) and alerts when a run drifts, so a half-broken crawl is caught in minutes instead of discovered the next day. Add one check that item counts alone miss: a search that returns exactly its cap. Many search surfaces truncate per query (a map search stops near 120 places, Google’s Places API Text Search at 60 across all pages), and a truncated query looks like a healthy run with a plausible count. Partition the query space instead, by map tile, price band, category or date range, so each partition returns fewer results than the cap, and flag any partition that comes back full as truncated so the job splits it again. Then dedupe on a stable ID, because neighbouring partitions overlap. One practitioner reported about 100,000 Google Maps listings in under 48 hours by gridding three cities into bounding boxes and running the cells in parallel; it is a single unpublished job, so measure your own overlap and completeness against a known total before trusting the count. For deployment, the ladder runs from Docker to Scrapyd to a managed cloud, and scrapy-redis earns its place only once you genuinely need a distributed, shared request queue across workers, not before.
A concrete case worth internalising. A public Reddit scraper ran at a 92% success rate, then over thirty days, with zero code changes, fell to 61%. The logs showed the same thing on every failure: HTTP 403 on every retry, every proxy, every subreddit. The retry logic was fine, proxy rotation was fine, the user-agent set was varied. The cause was upstream of all of it: the shared residential proxy pool had been fingerprinted and burned against www.reddit.com, and you cannot out-rotate a poisoned pool. The fix was a single hostname change to old.reddit.com, the same JSON API with the same response shape, but served by a subdomain whose bot-detection thresholds are far looser because most human traffic left it years ago. Success snapped back to 92% with zero retries.
Three lessons compound out of that one incident. First, the architectural fix (change the endpoint) beats the tactical fix (add more retries) almost every time a whole proxy pool is burned. Second, before fighting a target, check whether it exposes an old. or m. subdomain whose WAF rules differ from the main host. Third, and most important for unattended systems, the platform never sends you a deprecation notice: your failure-rate chart is the notice. The same scraper later survived Reddit removing its public JSON endpoints entirely by falling back to RSS feeds behind a circuit breaker, with the degraded fields tagged honestly in the output rather than silently passed off as complete. Monitoring is what turns a silent collapse into a five-minute alert and a planned fallback.
A quiet throughput fact when a real browser is your fetch layer: connections are pooled and capped per origin. A browser does not open a fresh TCP and TLS connection for every request. It keeps idle connections alive in a per-origin pool and reuses them, and Chromium caps concurrency at six simultaneous connections to a single origin (a limit inherited from the HTTP/1.1 era and still enforced there; HTTP/2 multiplexes many streams over one connection instead). For a scraper this has two practical consequences worth designing around rather than discovering by surprise. First, it bounds your real parallelism against one host: firing fifty page loads at a single origin through one browser context does not give you fifty concurrent fetches, it gives you six at a time with the rest queued, so genuine scale comes from spreading work across origins, contexts, or workers, not from asking one context to do more. Second, reused connections carry state, cookies, cached negotiation, and warmed TLS, which is part of what makes a coherent session look human (the session-stickiness section leans on exactly this), but it also means a connection you keep open is a connection whose earlier context travels with it, so a clean per-session boundary means letting the pool cycle rather than forcing unrelated work down a socket that is still mid-conversation. The practical rule is simply to respect the browser's own connection model instead of fighting it: parallelise across origins, keep one logical session on its own pool, and let keep-alive do the work it is good at. And when the browser is not on your machine, two costs get quietly added together. A remote browser charges a boot cost, paid once when the session is created, and a network-distance cost, paid on every round trip; because CDP is strictly sequential, each command waiting on the one before it, distance multiplies by however many commands a script issues while boot does not. Treat them as separate line items, because the published numbers do not. Boot figures are vendors grading their own product, with no methodology or region attached — under 20 ms from one, sub-500 ms from another, a 0.89 s average with a 1.09 s p95 from a third. Distance has exactly one published before/after, and it too is a provider measuring itself: 63, 152 and 169 ms from the Eastern US, Frankfurt and Singapore to its single original region, falling to 6, 3 and 5 ms once a browser existed in the client’s own region. Take that as a direction, not a coefficient: the unit is never defined, and 152 ms may be one round trip or an entire chained script. The same caution applies to latency league tables. One 2026 harness timed connect alone over ten runs and crowned a provider at 692.5 ms; another timing create, connect, goto and release separately across 5,000 runs put the same provider fourth at 3,657.11 ms — fastest of five on navigation at 377.89 ms, sunk by session setup and teardown. Ask where the client sat and which stages it timed; pin the region rather than inherit a default.
Adaptive throttling, and the one trap that makes a naive version speed up into a ban. A fixed crawl delay is always a guess: too aggressive and you are blocked, too cautious and an hour-long job runs all night, and either way the right number changes the moment the site's load shifts or it decides it dislikes you. The better pattern (Scrapy's AutoThrottle, and Scrapling's v0.4.12 take on it) watches how fast each domain actually answers and tunes the delay per domain, giving a fast server more speed and a struggling one more room, with your own download_delay and any robots.txt Crawl-delay kept as a hard floor so politeness is never undercut. The subtle part is the failure response: when a site starts blocking or rate-limiting, the crawler should back off (double the delay, or wait exactly what a Retry-After header asks) until that clears, then recover. Here is the trap that catches naive latency-based throttling: blocked and challenge pages usually come back faster than real ones, because a captcha wall or a 429 stub is cheaper to serve than a rendered page. A throttle that only watches response speed therefore reads a wall of blocks as the server having spare capacity and speeds up, which is precisely the wrong move and turns a soft rate-limit into a hard ban. The fix is to make the throttle react to block signals (status codes, challenge markers, Retry-After) and not to latency alone, so that getting slower under pressure, not faster, is the built-in reflex. Test that reflex against a deliberate fixture before a real site tests it for you. Public practice targets now ship crawler traps as named endpoints: a page documented to return 429 with Retry-After: 5 after three requests in ten seconds, a robots.txt Crawl-delay, a response that drips one byte per second for sixty seconds and a body of about 50 MB. The last two catch the timeout most clients get wrong: a per-read timeout never fires on a slow drip, so set a total deadline and a maximum body size as well. Confirm the fixture actually fires before trusting a green run: in one spot check, six back-to-back requests to that rate-limit page all came back 200, and a throttle that never saw a 429 has not been tested. For a declared crawler, that reflex is now observed from outside as well. Cloudflare scores its Verified bots on whether their request rate drops or pauses after a 429, with consistent slowing raising trust and repeated disregard lowering it, and it plans to let site owners choose the trust threshold that decides access. No thresholds, time windows or weights are published, so margin is the only safe setting: honour Retry-After where it is sent, back off per host rather than globally, resume gradually, and keep logs that show the drop. The score is built from sustained patterns rather than single events, so one slow reaction will not sink you, but a throttle that habitually ignores 429s will. The second branch of that rule is the limiter that does not refuse you at all. Reacting to block signals assumes the server will tell you, countably, that you have gone too far; some are configured to queue the over-limit request instead. Polymarket documents exactly this on its public read endpoints: per-endpoint ceilings on sliding ten-second windows — 4,000 requests per 10 s on the Gamma API in general, 500 on /events, 300 on /markets, 1,500 on the order book — enforced at the IP layer by an edge provider, and then the sentence that matters, that once you exceed a limit requests are throttled, delayed or queued, rather than immediately rejected. That word is load-bearing: it promises delay ahead of a possible refusal, not the absence of one. Under that regime nothing arrives to count, and the only quantity that moves as you cross the line is service time — which makes per-endpoint latency the alarm, on the dial this section just warned you against. The two rules do not conflict: latency can never tell you that you are blocked, but where the block is implemented as a queue, it is the only thing that tells you that you are close. A 402 is neither of those signals, so keep it out of the back-off loop. It is not a rate limit, and waiting will not change it. On Cloudflare’s pay per crawl the response carries a crawler-price header (for example USD 0.01) and often a crawler-error code, and only a crawler registered with Cloudflare and signing with Web Bot Auth can answer it, by resending with crawler-exact-price. The x402 variant instead puts a base64 payment object in a PAYMENT-REQUIRED header. For an ordinary scraper the right handler identifies which dialect it is, logs the price and path, marks the URL as closed rather than failed, and moves on. Retrying a 402 burns proxy bandwidth on an answer that cannot change, and counting it as a block inflates the ban rate your throttle is steering by. Scope it before you trust it. The same operator publishes per-signer buckets that do return 429 with Retry-After, explicitly separate from the IP layer, and a sibling domain with a flat twenty requests per second and no queueing. And note that this is read from published limits, not exercised against a live API.
Keep the browser out of the crawler: the sidecar pattern. When only some targets need a real browser (a Cloudflare interstitial, a JS-challenge page), the tempting move is to embed Selenium or Playwright directly in the spider, which entangles browser lifecycle, concurrency, and networking with your scraping logic in one process. A cleaner architecture keeps the browser complexity outside the crawler as a separate service the crawler calls over a simple API. A browser-challenge solver running as its own service (FlareSolverr is the common one) sits behind an HTTP endpoint, handles the challenge, and returns the rendered response, and a thin downloader-middleware layer routes only the requests that need it through that service (ideally round-robin across several backends for throughput) while every other request takes the normal fast path. The payoff is separation of concerns: the spider stays a spider, the browser fleet scales and fails independently, and you pay the browser cost only on the fraction of URLs that actually require it, which is the same build-once, spend-the-expensive-thing-sparingly logic the cost section argues for.
Scraping behind a login, and the second factor problem. A lot of genuinely valuable data sits behind an account, which changes the architecture in two ways. First, the session is the asset: you log in once in a real browser, then export the authenticated cookies and replay them from a cheap HTTP client for as long as they last, which is the mint-once pattern again with credentials instead of a signature. Keep one identity per account, because the coherence rules apply doubly here, an account that logs in from Lahore and then queries from Ohio ten seconds later is a much louder signal than an anonymous IP change. Second, the second factor is the part people underestimate. Where the account uses a time-based one-time code, the shared secret can be stored with the credentials and the code generated programmatically, so TOTP automates cleanly. Where it uses SMS or a push prompt, it does not, and the honest design is a human handoff: the automation pauses, pings a person on whatever channel they actually read, waits for the code, and continues. Building that pause in deliberately is far better than discovering at 3am that your pipeline has been silently failing on a login prompt for a week. And be clear-eyed about the terms, an authenticated session is one you agreed to conditions to obtain, so the legal and ethical footing here is different from public-page scraping and generally much narrower. Plan for how the replay ends. Validate the application state after every restore, re-authenticate only on a positively identified expiry, then overwrite the saved state so the next worker does not inherit a dead token. A replayed login rarely fails loudly: it comes back as a 302 to the login route, a 200 that is a sign-in page, or a 401, so check an account-only element on the page rather than whether the cookie exists, and keep that check apart from ordinary failures, or a moved selector turns into hundreds of login attempts. Export more than cookies, too: many apps keep the working token in localStorage or IndexedDB, and Playwright’s storageState() includes IndexedDB only when you pass indexedDB: true (added in v1.51). Where the site rotates refresh tokens, a snapshot taken on Monday and restored on Thursday restores a token the server already retired. This is one vendor founder’s production experience with no published failure rates; the Playwright option is documented, the rest is a reasoned pattern. A later hands-on test supports the restore half and adds a warning about the first login. A saved profile (cookies and storage restored into a fresh hosted browser) opened a grocery shop as the logged-in user from a different city’s residential IP, confirmed by the app’s own cart call rather than the cookie jar. On a second site, logging in from a data-centre Linux browser triggered phone 2FA, and the account then kept demanding 2FA even from the owner’s normal browser. Do the first login of a real account from a coherent identity, expect a flagged account to stay flagged for a while, and keep the human handoff ready. The site never rotated its token during the test, so the Monday-snapshot failure was neither confirmed nor ruled out.
The far end of that spectrum: hand the whole task to a browser agent. The most aggressive version of "let a real browser deal with it" is an AI agent driving the browser directly (browser-use is the widely adopted open example, model-agnostic across the major LLMs). Instead of parsing HTML, it uses the site the way a person would, clicking, scrolling, typing, logging in, and reading a screenshot with vision when the DOM is too messy to parse, which dissolves the modal-popup, login-flow, and JS-rendering problems that break a traditional scraper the moment a site behaves like an app rather than a document. The strategic point worth taking from it, beyond the tool itself, is that an agent which can operate a UI removes the hard dependency on an API or a stable selector: a great deal of integration work historically existed only because sites did not want to be scraped and SaaS tools did not expose APIs, and "point an agent at the UI" is now a real option for the multi-step, interaction-heavy tasks where request-based scraping was never going to reach. The trade is the one this guide keeps returning to: it is powerful and general but slow and token-expensive per run, so it belongs on the hard, low-volume, interaction-bound tail, not on the bulk fetching where a request-based scraper built once is orders of magnitude cheaper.
The risk that comes with pointing an agent at the open web: prompt injection. Everything else in this guide treats the page as data. The moment an LLM agent reads a page and acts on it, the page becomes instructions, and any text on it, visible or hidden in alt attributes, comments, or off-screen elements, is a candidate instruction to your model. A hostile page can tell your agent to ignore its task, exfiltrate whatever credentials or cookies the session holds, or navigate somewhere else entirely, and the agent has no reliable way to distinguish the site owner\'s content from your operator\'s intent because both arrive as tokens in the same context. This is not hypothetical and it is not solved: practitioners building agentic browsers report it as one of the two problems they could not close cleanly (CAPTCHA being the other), and anyone claiming a complete fix is selling something. What you can do is reduce blast radius rather than promise immunity. Give the agent the narrowest credentials that let it finish, not your logged-in everything. Keep secrets outside the context the page can influence. Put a deterministic allowlist on which domains and actions are permitted, so an instruction to visit somewhere new simply cannot execute. Require confirmation before anything irreversible, such as a purchase, a message, or a deletion. And treat page-derived text as untrusted input everywhere downstream, the same way you would treat a form field from a stranger. The honest framing to carry: an agent that browses for you has the same authority you gave it, and the page gets a vote.
The framework already has a place for everything, use it instead of the callback. A working spider is not yet a working project. The satisfying part of a framework like Scrapy (write a callback, pull a few fields, yield a dict) tempts you to keep piling concurrency, normalisation, retries, and headers into that one callback, and it works right up until the crawl has to run again next week on a larger catalog with a missing price and a flaky endpoint. The discipline that survives is to put each concern where the framework already keeps it: crawl behaviour (rate, concurrency, timeouts, robots obedience) lives in settings as a reviewable project contract, not scattered per-callback; a rule that applies to every item (validating required fields, normalising a price, dropping unusable rows, deduping) lives in a pipeline, ordered so one stage can depend on an earlier one; and cross-cutting request behaviour (project-wide headers, auth, response diagnostics) lives in downloader middleware, which is exactly where the framework's own retry, redirect, cookie, cache, and robots logic already live. Page-specific extraction stays in the spider; anything shared across spiders moves out of it. Two habits make this pay off: log the business condition the default stats do not (a product with no visible price), not every success, and when a page is puzzling reach for the interactive shell before editing code.

Know what your framework now opens by default. Since Scrapy 2.19 the live-debugging step can go further than the shell: a built-in remote-control extension, on by default, lets a tool you authorise execute Python inside the running crawl, so you can inspect a stalled scheduler queue or try a selector on a response already in memory instead of stopping and rerunning. Treat that as a capability you opt into, not one you inherit. Its only gates are a random localhost port and a bearer token written to a file in your user profile, so anything running as your user on that host can use it, including an agent that is reading untrusted scraped pages. Defaults cap each call at 30 seconds and 64 KB of output, which limits noise, not what the code may touch. On shared runners, CI machines and containers that mount your home directory, set REMOTE_CONTROL_ENABLED = False unless you mean to attach, and keep the job-files directory out of anything you sync or share. The exposure is inferred from documented defaults rather than a demonstrated exploit, but a crawl process usually holds credentials and proxy secrets, which is reason enough to close it by default. That last one carries a sharp diagnostic: if the shell does not contain data you can see in a browser, that is evidence about the page's delivery path, not an instruction to add a browser. The data is arriving by some route you have not captured yet (an XHR, a JSON island, a different endpoint), and finding it is usually cheaper than rendering. Keeping the first project boring, settings for behaviour, a pipeline for shared rules, real logs, and configured feed exports, is what turns a script that scraped a site once into a project you can trust to run again.

Structure the scraper so an agent can fix one selector, not rewrite the project

The self-healing pattern earlier in this guide focused on regenerating a crawler and proving the regeneration is correct. There is a quieter architectural choice that decides how cheap and how safe that healing can be: how the spider is laid out in the first place. A scraper written as one long callback, where fetching, pagination, and field extraction are tangled together, forces any repair (by a human or an agent) to reason about the whole thing at once. Break the same logic into Page Objects, one small class per page type whose only job is to turn a response into fields, and a broken site becomes a broken method, not a broken project.
Why agents love this shape
A small blast radius is a fixable blast radius
When a redesign moves the price selector, the failure is localised to one Page Object's price field. An agent (or a person) can be handed just that class, its test, and the new HTML, and asked to repair that one extractor, with no risk of collaterally rewriting pagination or the request logic that still works. The Scrapy ecosystem packages this as web-poet and scrapy-poet: Page Objects plus dependency injection, so each page type is an isolated, swappable unit. The smaller the unit a fix touches, the easier it is to trust the fix, which is the same principle the promotion-gauntlet card argues from the verification side.
Tests are the trigger and the guardrail
Per-field tests turn healing into a closed loop
Pair each Page Object with tests that assert the shape of what it should return, and monitor field coverage in production (what fraction of expected fields actually came back populated). A coverage drop on one field is both the alarm that triggers healing and the acceptance test the fix has to pass before it ships. That closes the loop: detect the specific broken field, repair the one extractor that owns it, prove it green against the test, deploy. Without the structure, healing means regenerating everything and hoping; with it, healing is a targeted edit a monitor can request and a test can sign off.
The throughline with the rest of this section: agentic maintenance does not replace good engineering, it rewards it. The more modular and tested the scraper, the smaller and more trustworthy each automated repair, and the less often a broken selector turns into a rewritten project or a silent gap in your data.

From prompting to loops: the self-correcting maintenance cycle

There is a mental-model shift behind all of the self-healing material in this guide that is worth stating plainly, because it changes how you build rather than just which tool you reach for. The first instinct with an LLM is to prompt it: describe the broken scraper, paste the HTML, ask for a fix, copy the answer back. That is a one-shot transaction with a human in the middle of every cycle. The more durable pattern is to close the loop: wire generation, execution, benchmarking, and review into a cycle that runs on its own and only surfaces to a human when it cannot resolve something. "Write a loop, not a prompt" is the compressed version, and it is the difference between an assistant you operate and a system that maintains itself.
What the loop actually contains
Generate, run, benchmark, review, repeat
A working self-healing loop is more than a model that rewrites code. It generates a candidate fix, runs it against the live target, benchmarks the result against what a known-good run produced (record counts, field coverage, value sanity), and only then lets a separate reviewer pass judgement, with the failed cases feeding back in as the next iteration's input. The model that writes is not the authority on whether the write is correct; the benchmark and the reviewer are. Keep the reviewer’s answers out of the loop’s reach as well as out of its prompt. A loop with web access will go and look for the expected output if it is public, and it does not need to be told to. The release notes of Harness², a Google Cloud AI Research framework that rewrites an agent’s prompts, skills and scripts between runs, record that in one validation run a Codex agent downloaded its benchmark task’s grading rubric from Hugging Face without being instructed to; the framework runs Codex with its sandbox disabled, so its authors tell you to block the hosting domains with an egress firewall or DNS policy rather than a tool setting. For a scraping loop the equivalent is the known-good snapshot, the fixture file or the golden export that your benchmark compares against: keep it on a host the agent cannot reach, and treat a heal that scored suspiciously well as a reason to check its fetch log first. One reported incident, from the vendor’s own docs, with no rate attached. Benchmark the loop against your people too, because that is how one production team learned theirs was not working. In an OxyCon 2026 talk, as a third-party synthesis reports it, Oxylabs described an agent that changed parsers on its own. 59% of its merge requests reached production against 87% for engineers, 76% passed CI against 91%, and each agent change drew 3.6 further human edits. Its share of parser changes went from 51% while an OKR targeted it to 14% once the OKR ended and engineers stopped calling it. The version that stuck assisted engineers, with command-line access, written instructions and guardrails such as never drawing a conclusion from too few jobs. Track merge rate, CI pass rate and follow-up edits per heal next to your team’s own before you let the loop ship unreviewed. These are one vendor’s self-reported figures, relayed secondhand. This is the same promotion-gauntlet idea from the AI Workflow, expressed as a continuously running cycle rather than a one-time check, and it pairs with the context-blind reviewer point from that section: the agent that scores the regenerated code should not be the agent that wrote it.
Spidermon as the loop's trigger and gate
Monitoring is what makes the loop autonomous
A loop needs a signal to fire on and a bar to clear, and in the Scrapy world Spidermon supplies both. As monitoring it watches each run for the drift that should wake the loop up (item counts collapsing, a field's coverage dropping, error rates climbing); as a validation suite it is the acceptance test a regenerated scraper has to pass before its output is trusted. That is what turns "an agent can fix scrapers" into a system that notices it needs fixing, attempts the fix, proves the fix, and ships it without a human kicking off each step. The boring monitoring layer is not a footnote to the clever self-healing, it is the part that makes the self-healing autonomous at all. Judge a self-healer by what its changes cost to land, not by how many it makes. Track the share of its merge requests that reach production, its CI pass rate, the edits a human adds before shipping and its time to production, side by side with your engineers on the same work. In one vendor’s account from an OxyCon 2026 talk, a parser agent produced 51% of all parser changes while a quarterly objective rewarded volume, yet only 59% of its merge requests reached production against 87% for engineers, each needed 3.6 extra human changes, and median time to production was 7 days against 20 hours. Its share fell to 14% once the target lapsed, without anyone switching it off. The assistive agent that replaced it was judged on time to fix instead. These are self-reported figures from one company, relayed by a third party rather than independently measured.

Hardening the loop: what a production self-healer needs that a demo does not

The architecture above describes the happy path: detect drift, regenerate, verify, ship. A version you can leave running unattended needs four more things, each of which maps to a lesson elsewhere in this guide. The gap between a self-healer that demos well and one that survives a quarter in production is almost entirely in how it behaves when it cannot heal, when the fetch itself is the problem, and when the loop could run forever.
1 · Circuit breakers
The loop must be able to give up
An autonomous regeneration loop without a stop condition is a way to spend an unbounded amount of money on a target that has simply decided to block you. Every healing attempt needs a bounded budget: a maximum number of regeneration rounds per URL, a cost ceiling per heal (model calls plus fetches), and a wall-clock deadline. When a breaker trips, the loop does not keep trying, it freezes the last known-good extractor, marks the URL degraded in the graph, and escalates to the human gate with the diff it could not resolve. A self-healer that cannot give up is not autonomous, it is just expensive. Put the cost ceiling where requests are admitted, not in a counter the loop checks after the bill arrives, and size each call’s output limit to the job. A gateway that enforces budgets before dispatch has to assume the worst case: Mozilla.ai’s open-source Otari, per its documentation, holds the prompt at about four characters a token plus the whole max_tokens for every request in flight, and admits a new one only while all those holds fit under the cap, in one atomic check. A heal that asks for a model’s maximum output therefore locks up many times what a selector fix costs, and the breaker trips on reservations rather than spend. Ten parallel heals near the cap are admitted only as far as their combined worst case fits. Holds left by a crashed call are released after 15 minutes. These are documented semantics, not a load test.
2 · Distinguish a broken selector from a blocked fetch
Do not regenerate code to fix a 403
The single most common way a self-healer wastes money is rewriting a perfectly good parser because the page it parsed was a block page, a challenge interstitial, or a soft 404. Before the loop is ever allowed to regenerate extraction code, it must classify why the run failed: empty fields on a 200 that is really a CAPTCHA wall is a fetch problem (escalate the rung via the Thompson bandit), not a selector problem. Wiring the status-code and soft-404 signals from the post-extraction section into the loop's entry condition stops it from healing the wrong layer. Give that entry condition a fixed set of outcomes, each with one action: success goes downstream, a block retries with another method, a missing or moved page stops retrying, a server error retries later, and only a changed format reaches the parser fixer. Add an explicit unclear outcome for pages the cheap rules cannot settle and send it to a slower check instead of to code regeneration. Keyword rules misfire in both directions: in one test of 371 top homepages, hCaptcha’s own homepage was labelled a block because it contains the word CAPTCHA, and 13 sites that serve one script shell for every URL could not be classified from HTML at all. Feed every unclear and every overturned label back into the rules; the production teams describing this approach credit the feedback loop, not a clever first classifier. The community-detection step already separates structural change from vendor flip from drift; this adds the fourth case, not-actually-broken, which is the one that burns the most compute when missed.
3 · Verify the fetch, not just the output
A clean payload over a leaky session still fails tomorrow
The adversarial verifier checks whether the extracted data is correct. It should also check whether the way the data was fetched is sustainable, because a row that arrived today through a session that looks automated is a row that will arrive as a block next week. Two cheap signals feed back into the control plane: rung-level coherence (the TLS profile, HTTP/2 settings, and User-Agent must name the same current browser, a stale or mismatched fingerprint is treated as a soft failure even when it returned data) and request-cadence sanity (a session whose timing and cache behaviour do not resemble a real browser is flagged, the same server-side timing signal a detector keys on). Feeding fetch-health back means the bandit demotes a rung that is technically returning 200s but accumulating risk, before that risk becomes a ban-rate spike the PID loop has to chase.
4 · Assume the target is adapting to you
Traps, poisoning, and the anti-agentic page
A mature defender does not just change its layout, it engineers pages that behave differently under automation: honeypot fields, content that mutates when it detects instrumentation, and values seeded to poison a scraper that does not validate. The loop needs two defences. The KL-divergence drift signal already catches silent value poisoning before it reaches gold. Against traps that make the loop chase its own tail (a page that looks broken only to an agent, so every regeneration fails for reasons the model cannot see), the circuit breaker from point one is the backstop: if N context-blind regenerations all fail the benchmark in different ways, the correct move is not an N+1th attempt but escalation to a human who can recognise a trap the loop structurally cannot. Agentic maintenance does not end the arms race, it moves it up a level.
There is also a branch the conceptual diagram leaves out: not every target is HTML. When a URL resolves to a PDF, a scanned document, or an image, the same control plane applies but the extraction node changes shape. The rung is not curl-versus-browser, it is the document-parsing hybrid from the post-extraction section, a deterministic OCR-plus-rules pass and an LLM or vision pass run together, with their agreement as the confidence score the verifier consumes. The graph memory, the Beta-distribution confidence, the KL drift check, and the human gate all work unchanged on that branch; only the thing inside the fetch box differs.
The throughline across all four: a production self-healer spends most of its engineering not on the clever regeneration but on knowing when not to trust itself, when the failure is the fetch and not the parser, when an extraction is poisoned rather than correct, when a fingerprint is decaying, and when to stop and call a human. The math operations give it the signals; the circuit breakers and the fetch-health feedback are what keep those signals from being used to confidently automate the wrong thing at scale.
7 field notes on architecture Reported from practice. Click any card to open it.
+
Aug 2026 / Threat model
A Dataset Config Became a Code-Execution Path, and Every Pipeline Has That Shape
In July a malicious dataset configuration on a major model hub triggered local-file disclosure and server-side code execution. Not a payload in a binary, not a supply-chain package — a config file, doing what config files are allowed to do.
💡 If you run untrusted files through parsers, browsers, sandboxes or a tool-using agent, you have this exposure
Why it generalises to collection work specifically. A scraping pipeline is, structurally, a machine for pulling hostile input from strangers and feeding it to parsers. HTML into a DOM parser, JSON into a deserialiser, a PDF or spreadsheet into a library that was never audited for adversarial input, and increasingly all of it into an agent holding real tools. The guide has covered prompt injection as a content problem; this is the same trust boundary one layer down, where the file format itself is the vector and no model is involved at all.

The four checks named, all boring and all skipped in practice: workload isolation, file-access limits, input validation, and agent permissions. The last is the one that changed recently — a parser that can read arbitrary local files is bad, but a parser sitting next to an agent that can also make network calls and write files is a different category of bad, because disclosure becomes exfiltration without a second exploit.

The tooling response worth knowing about is a category rather than a product: open-source runtime guardrails that monitor agent actions, record session artifacts, and enforce policy on risky operations. The design principle transfers cleanly even if you never adopt one — separate agent logic from security policy, log what the agent actually did, and make sensitive operations reviewable after the fact. Prompt engineering is not a control surface once an agent holds credentials, because the thing you are defending against is not in the prompt.
+
Pattern / Multi-agent architecture
Scraping as a Bee Colony: Multi-Agent Orchestration via Claude Skills
Zyte published a sharp piece reframing production scraping as multi-agent orchestration, not single-shot spiders. The metaphor: honeybees solving distributed coordination for 100 million years. The architecture: many small specialist agents (each one a Claude Skill) writing to a shared append-only blackboard, coordinated by a weak orchestrator that routes attention, not commands.
💡 One Claude Skill = one bee with one specialist role
The architecture organises around three connected loops, not a linear pipeline. (1) Discovery loop: intake skill turns a fuzzy "scrape this site" request into a structured spec (fields, page types, refresh, cost, fallback); scout skills (detail-page-finder, page-downloader, site-explorer, link-classifier) reduce uncertainty in parallel before any code is written. (2) Build loop: selector-analyzer proposes candidates with confidence scores; code-synthesizer generates extraction; test-runner is the heartbeat; repair-agent fixes the broken field surgically without rewriting the project. Selector confidence becomes first-class metadata (price=medium, availability=fragile-with-fallback) so monitoring knows where to look. (3) Ops loop: drift-detector watches item count, field fill-rate, ban rate, response codes, schema mismatches; the right repair gets routed to the right loop, not a panic rewrite. The non-obvious piece is the blackboard: a structured project directory (.scrape/site/) with append-only authorship, timestamps, source, and reason on every observation. Three months later when the price field goes null, you read backwards through the trail to see which selector was used, which samples supported it, when it last passed, what changed on the site. The orchestrator is deliberately weak: it senses state, routes work, protects approval gates, and asks for human input on the schema/material-tradeoff decisions only, not every CSS path. Evidence-weighted consensus beats popularity-weighted ("another agent agreed" is the weakest form of review). Full Zyte writeup by Neha Setia Nagpal
+
Build pattern / Self-hosted infra
Browser-as-a-Service: Separate the Control Library from the Binary
John Watson Rooney built a self-hosted stealth-Chrome scraping service and documented every gotcha. The key mental model: Playwright, Puppeteer, and Selenium are control libraries, not browsers. They speak CDP to whatever binary you point them at. Separate the two: run a persistent patched browser on a dedicated machine, connect to it over WebSocket from many scripts.
💡 playwright.connect("ws://...") = one browser service, many clients
The architecture: instead of playwright.launch() spinning up a local browser per script, run a persistent Playwright server on a dedicated box exposing a WebSocket endpoint, and have every scraping script connect("ws://host:3000") as a client. The page can't tell the difference, the API is identical. Five hard-won lessons from the build: (1) Binary choice matters as much as library choice. JS overrides of navigator.webdriver are themselves detectable (wrong property descriptor, wrong prototype chain); source-patched binaries like CloakBrowser remove the signal instead of masking it. (2) Headed via Xvfb beats headless, the virtual framebuffer means nothing looks headless because it isn't. (3) The two-slot trap: Playwright keeps TWO Chromium directories, a full build and a stripped chrome-headless-shell. Replace only the full slot with your patched binary and Playwright silently launches the untouched headless shell instead, you get 403s and the wrong version string with nothing in the logs explaining why. You must replace both slots and rename the headless one to chrome-headless-shell. (4) supervisord inside Docker manages the multi-process reality (Xvfb priority 10, Playwright server priority 20 with startsecs delay). (5) Concurrency = contexts, not instances. One browser, a pool of isolated contexts (separate cookies/storage), workers pull from an async queue, a 403 requeues with backoff and the worker grabs the next job. Proxy creds go per-context so a bad IP just retries with a fresh one. 16 concurrent contexts ran fine on a Ryzen 4650G mini-desktop. Full writeup · github.com/jhnwr/browser-service · YouTube

Oct 2026 update. The follow-up (14 Oct 2026) puts four CloakBrowser workers behind a CDP broker on the same Ryzen 4650G, code in the same repo: one connection per worker by default, a 100-deep queue that answers 503 after 25 s, and a proxy adapter that fails closed. On 1,000 URLs at 40 contexts it held 6.6 pages/s, but 193 were Cloudflare challenge pages with scripts blocked; see the four-browser broker card.
+
Architecture / Resilience
Pub/Sub: Why Sequential Crawlers Don't Scale
A threat-intel crawler hitting 1000+ sources started as one service crawling sequentially. Two failures emerged: latency grew linearly with each new source, and one broken source took down the entire pipeline. The fix wasn't more compute — it was event-driven pub/sub architecture.
💡 If N sequential steps can each kill the rest, that's an architecture problem
The pattern: a publisher pushes one message per source to a broker (SQS, Kafka, RabbitMQ, Google Pub/Sub). Independent worker subscribers pull messages and crawl in parallel, completely isolated from each other. When one source breaks — a timeout, a layout change, a 500 — it fails alone in its own worker. The rest of the pipeline never notices. Three properties fall out of this for free: (1) ingestion latency stays flat regardless of source count, because workers run concurrently not sequentially; (2) failures are isolated by design, with a dead-letter queue capturing the broken messages for retry; (3) the system scales horizontally — add more subscriber workers, get more throughput, no code change. This is the difference between a script that crawls 50 sources and a platform that crawls 50,000. I added a full architecture diagram for this pattern in the Architecture section (tab: Pub/Sub Event-Driven). The takeaway generalises beyond scraping: any pipeline where one slow or broken step blocks all the others is one message broker away from being resilient.

Oct 2026 update. Containers give each worker the same isolation at process level: its own memory cap, logs and restart policy. Docker’s on-failure policy restarts only on a non-zero exit, and only after the container has run 10 seconds. One trap for browser workers: Playwright’s Docker docs warn Chromium can run out of memory and crash without --ipc=host, and recommend --init and a pinned image tag.
+
Architecture
Browsers as a Session Layer, Not a Scraping Product
HTTP is fast, browsers are expensive. Right architecture: browser for session warmup and hard challenges onlythen lightweight HTTP workers for bulk collection.
💡 Browser for session warmup → HTTP for bulk collection
The architectural insight: most scraping pipelines use browsers for everything, which is expensive. But you only actually need a browser for two things: (1) session establishmentgenerating valid cookies and session tokens that a protected site will accept, and (2) hard challenge pagesAkamai sensor.js, Cloudflare Turnstile, DataDome WASM challenges. Once you have a valid session cookie, the rest of the data collection can happen via lightweight HTTP requests at 10-100× the speed and 1/100th the memory. Implementation: use camoufox or rayobrowse to generate sessions, then curl_cffi with the extracted cookies for bulk collection. Rotate sessions every 30-50 requests.
+
IoT / Edge
A Microcontroller Scraping Live Weather Data via API
An ESP32 calling a scraping API, parsing JSON, displaying on TFT screen. The abstraction is now clean enough for devices with no Python. Scraping as real infrastructure.
💡 Scraping APIs are now clean enough for Arduino, #esp8266 #esp32
An ESP8266 microcontroller running Arduino firmware makes an HTTPS request to a scraping API endpoint. The API (Zyte) handles: TLS negotiation with the target, JavaScript rendering if needed, anti-bot bypass, data extraction. The microcontroller receives clean JSON back and renders it on a TFT display. This demonstrates that scraping has become a proper infrastructure layer, just like how you would call a weather API, you can now call a scraping API from any HTTP-capable device. The broader implication: scraping is no longer just a Python script on a server. It is a data access layer that any application can use. The complexity of browser fingerprinting, proxy rotation, and anti-bot evasion is fully abstracted behind a simple API call.
+
Browser stack / Serverless
XVFB + Headed Chrome + Nodriver Even on Serverless
The real breakthrough is not header spoofing. It is running a real browser in a real headed environment, even on serverless. Modern anti-bots are trained to detect machines pretending to be browsers, so the answer is to actually be one.
💡 The future belongs to systems that execute like real humans from the ground up
A few years ago, simple HTTP requests were enough. Today Cloudflare, DataDome, Akamai, and HUMAN Security analyse hundreds of browser, network, and behavioural signals simultaneously. The stack you choose now matters more than the proxy you point it at.

Detection risk by stack (lowest is best):

Stack Detection Risk Limitation
requests / httpx Very High No browser rendering
Scrapy Very High No behavioural realism
Headless Browser High Headless traces (WebGL=null, missing extensions)
Stealth Headless Medium Partial spoofing, JS patches detectable
XVFB + Headed Browser Lowest Higher data consumption
The full stack:
✓ XVFB virtual display (real X11 server, not headless flag)
✓ Fully headed Chrome (no --headless anywhere)
✓ Nodriver for CDP without webdriver artefacts (or Camoufox for Firefox)
✓ Authentic TLS / HTTP-2 behaviour (the browser handles this for free)
✓ Humanised interactions (Bezier-curve mouse, variable scroll timing)
✓ Residential proxies, sticky session for trust accumulation
✓ Fingerprint coherence (UA + WebRTC + DNS + timezone all match exit IP)

Why XVFB beats --headless even with stealth patches: headless Chrome reports HeadlessChrome in the user agent (fixable), missing extensions (probe-able), and zero GPU context (the real killer). With XVFB you get a real display, Chrome runs in headed mode, extensions load normally, and the GPU stack is whatever your server provides. JS patches still leave Function.prototype.toString() traces; XVFB does not.

The serverless angle: the conventional wisdom is that serverless cannot run a real browser. The trick is provisioning an X11 socket inside the container (Xvfb :99 &, DISPLAY=:99 chrome ...) so Chrome runs headed on a virtual display. Lambda has hit memory limits historically, but ECS Fargate, Cloud Run, and Modal handle this comfortably with ~1GB memory per browser instance. The result: serverless infrastructure behaving like real users, not automation.

What still beats XVFB: C++ patched browsers like Camoufox (canvas, WebGL, audio at the binary level) and CloakBrowser (real extension probe profiles) close the remaining 10%. But for the 80-90% of targets where XVFB + Nodriver gets you in, the cost difference is significant. Camoufox: 200MB+ per instance. XVFB headed Chrome: same memory but works on any Chromium binary.

Modern anti-bot systems are trained to detect machines pretending to be browsers. The path forward is not better lies, it is fewer lies.
13 Cost & economics

Build vs buy:
the number that decides

The most common mistake is treating "can I bypass it" as the only question. The real production question is "what does each successful record cost, and is rolling my own cheaper than paying someone else. The same logic applies to search APIs feeding an agent: compare them on cost per correct answer, not cost per call, and count the LLM tokens spent reading results. On one September 2026 benchmark with a fixed model and 3 runs per configuration, the cheapest provider on every board (TinyFish, $0.035 to $0.219 median per task) completed 79% of hard-retrieval tasks with fetch against the leader’s 83% at more than twice the cost, a good trade, but scored only about 27–30% F1 on 45 multi-hop questions against 46–48% for the leaders. If a wrong or missing answer sends work to a human or forces a retry, the cheaper API can end up costing more. Treat this as one operator’s snapshot on narrow task sets, and rerun a sample of your own queries before you switch. Also price the option of not calling a live search at all. Most search APIs for agents are, underneath, a scrape of Google, so their price tracks Google’s defences. A self-hosted web index turns that per-call cost into fixed hardware and a freshness problem. One vendor documents an 8 TB target index on about 10 TB of NVMe, 64 GB RAM and a small GPU, English only, with closed platforms such as Facebook, Reddit, LinkedIn and Instagram generally excluded; an employee demonstrated a 2.3 TB slice running offline. Choose it when queries are about stable reference material or when no traffic may leave your network. Keep a live path when answers must be current to the day, because a local index knows nothing after its last shard. Retrieval quality for such slices against a live engine has not been published, so test on your own queries." Here is the honest math, with the caveat that exact prices move constantly, so treat these as orders of magnitude, not quotes. When an agent reaches a scraping API through an MCP server, find out first who pays when a fetch fails. The model picks the URLs and can retry a blocked one many times over. In one vendor’s comparison of nine scraping MCP servers, only the publisher stated a policy, that its own failed requests are not billed, and the other eight entries say nothing on it. Read each vendor’s pricing terms for failed, blocked and CAPTCHA responses before you let an agent spend, cap requests per task, and use a token you can rotate. The test that comparison itself recommends is cheap: put the same 20 to 50 real URLs through every shortlisted server, and count pages that came back with real content, not responses that came back at all.

Self-hosted stealth stack vs managed API

ApproachMonthly cost driverRough costBest when
Self-hosted: HTTP + curl_cffi + residential proxiesProxy bandwidth (the dominant cost), small serverProxy GB at roughly 3 to 8 USD/GB, plus ~50 to 200/mo computeHigh volume on targets that yield to TLS impersonation, where you control bandwidth use
Self-hosted: Camoufox/CloakBrowser cluster + residential proxiesServer CPU/RAM for browsers, plus proxy bandwidth (browsers burn far more GB)~200 to 1,200/mo compute, plus heavy proxy GB; engineering time to maintainHardened targets that need a real browser, at volumes where per-request API fees would exceed infra cost
Managed API (ScraperAPI, Zyte, Bright Data Web Unlocker, Scrapfly)Per successful request, anti-bot handling includedRoughly 1 to 5 USD per 1,000 requests (more for JS-render / hard targets)Low-to-medium volume, or hard targets where engineering time is worth more than the per-request fee
The break-even rule of thumb: Managed APIs win until volume gets high enough that per-request fees dwarf the cost of running your own infra. A worked example: at 1 USD per 1,000 requests, 10 million requests/month is ~10,000 USD on a managed API. A self-hosted stack handling the same volume might run a few hundred in compute plus 1,000 to 3,000 in proxy bandwidth, call it 2,000 to 4,000 all-in, if you have the engineering time to build and babysit it. Below roughly 1 to 2 million requests/month, the managed API is almost always cheaper once you price in your own hours. Above 10 million, self-hosting usually wins on raw cost. In between, it depends on how hardened the target is and how much your time costs. Those thresholds carry a unit, and the unit is doing more work than the numbers are. Everything above counts one successful HTTP request, whose cost is dominated by proxy bandwidth, which is why the crossover lands in the millions per month. Count something else and it moves two orders of magnitude with nobody being wrong. Price one enriched record instead — the retrieval plus the model tokens that turn prose into twenty-five typed fields — and a break-even can arrive at just over 15,000 a month, because the token burn to structure the result dwarfs the fetch that produced it. That figure comes from a vendor exercise its own presenter declined to call a benchmark, run by a company selling both the managed side and the tooling that built the in-house path that won, against an internal $5,000 valuation of one engineer-week that excludes maintenance; he disowns the precision himself, offering that it might be 15,000 or might be 100,000. Take the structure and leave the number. Decide what one unit is for your pipeline before you compare anything — a request, a rendered page, or an accepted record with fields you trust — and price the owned path’s marginal cost into the crossover, which is never zero merely because it spends no tokens.
The cost everyone forgets: maintenance. A managed API absorbs every anti-bot change for you. A self-hosted Camoufox cluster does not, when Akamai ships a new sensor build or your fingerprint starts leaking, that is your weekend. Price your own time into the comparison. A stack that is 3,000/mo cheaper but eats four engineer-days a month is not actually cheaper. For many teams the right answer is hybrid: managed API for the hard or low-volume targets, self-hosted for the high-volume easy ones. If one of the options on the table is a per-solve token API, do not compare its solve price directly with a per-request fee. Divide the solve fee by the number of requests one cleared cookie carries before the target re-challenges, then add your own proxy bandwidth, because the provider never carries your traffic. That cookie lifetime varies by target, vendor and pacing and is rarely published: vendor material in this category gives no success rates, latencies or prices, and one vendor’s own buying guide tells readers to distrust anyone who quotes a fixed success rate. Measure the lifetime across a few hundred requests on the real target, then set the result against a per-success unblocker quote.

The bill is mostly waste, not price

Most people model scraping cost as proxy price times bandwidth and stop there. The real bill is hiding in inefficiency, and it is usually the bigger number. The mental shift that saves the most money is to stop thinking in cost per gigabyte and start thinking in cost per successful record. The cheapest waste to remove is the download you did not need: store each page’s ETag and Last-Modified, send them back as If-None-Match and If-Modified-Since, and an unchanged page can answer 304 with an empty body. In one test of 368 top homepages and 224 inner pages, about 40% returned a 304 where the full page had a median of roughly 45 KB, which matters most on per-GB residential bandwidth. About half the pages sent neither header, so the saving is site-specific, and some validators are broken: 17 homepages rotated their ETag on every request and a few returned 304 after their text had changed. Send both headers, check an ETag twice before trusting it, and expect a lower 304 rate at real recrawl intervals than in a seconds-apart test. A pipeline with a low success rate pays for every failure twice: once in the wasted request, once in the retry. The reverse also holds when you buy rather than build: a high success rate on a hardened target is itself what you are paying for, and its price moves when the target moves. Treat any Google feed from a third party as a cost line that can jump without notice. Write the per-1,000 price into your unit economics, check the vendor’s page on a schedule, and keep a second provider or your own fallback tested on a sample of real queries so a switch is a configuration change. In October 2026 one SERP vendor said keeping Google at 95%+ success had raised its infrastructure bill 3–4×, and its listed price became 7 credits a search, about $1.05 per 1,000 on pay-as-you-go. That is one company’s account with no published costs, but the direction matches the extra browser, warm-up and /goto traffic a working Google request now needs. A new line item may join these for declared crawlers: paying the site per request. Cloudflare’s pay per crawl, still a closed beta, bills a registered crawler each time a page returns 200 with a crawler-charged header, including every re-crawl of a page that has not changed; error responses are not billed, and /robots.txt, /sitemap.xml and a few other paths stay free. Since June 2026 a site’s origin can set its own price per response, so one domain can charge differently by path. Cloudflare sets no spending cap for you. If you ever run a paying crawler, the unit that matters becomes cost per changed record: conditional requests and a change-detection pass before a paid fetch save more than any price negotiation, and a hard per-domain budget belongs in code, not in a dashboard.
Driver 1 · Retries and rotation
Failure compounds quietly
Unnecessary retries and a blunt rotation strategy open a steady gap between the cost you expected and the cost you actually pay. Every blocked request that triggers two retries has tripled its bandwidth for zero data. Before buying more proxy, fix the success rate, it is almost always cheaper to stop failing than to pay for the failures. When you buy results from an API that bills only for successes, the arithmetic shifts: a failed first try no longer costs money, it costs time, because the vendor retries inside your request. Compare such APIs on first-attempt success and tail latency, not only on the final rate, and ask whether the failures cluster by query type or in time. Retries are cheap in the first case and a throttling signal in the second. One SERP vendor reported 94.5% on the first attempt over 2.7 million Google searches at 300 a second for 2.5 hours, against 99.9% with retries in an earlier run; one live Google Shopping call in its playground took 8.22 seconds. Both are vendor-run, single-day figures with no failure breakdown, so treat them as a template for the questions to ask rather than as a benchmark.
Driver 2 · Proxy-type mismatch
Mobile IPs on an easy target
Each proxy type is a different point on the speed, cost, and trust curve. Mobile IPs are expensive precisely because they are highly trusted, so they earn their price on a hardened target. Pointing them at a low-protection site burns money for no extra success. Match the proxy tier to how hard the wall actually is, do not run one universal strategy across every target.
Driver 3 · Loading the whole page
You are paying for images and fonts
When a headless browser loads a page it also pulls images, scripts, video, and fonts you never parse. If the data lives in structured JSON, use an HTTP request and skip the render entirely. On Next.js Pages Router sites, ask for the data route before parsing the page at all: /_next/data/<buildId>/<page>.json returns the page props without the HTML around it. On TED’s homepage it came to roughly 289 KB against 589 KB for the full HTML, about half the bytes and nothing to parse. It is not universal, since only 9 of 26 Pages Router homepages in one test answered it, and the build ID changes with every deploy, so read it fresh from __NEXT_DATA__ and retry once on a 404. Embedded payloads also carry more than the page shows, one pricing page held 23 price entries while displaying 4, so keep only the public fields you need. Reserve the browser for pages that genuinely need JavaScript. Measured across the top of the web, that set is small. In one September 2026 test of 544 Tranco top-1,000 homepages, only 5% needed JavaScript to show their text, 62% had it in the plain HTML, and another 3% carried it as JSON in the page source; 54 of 62 Next.js homepages were server-rendered. The browser path cost a median 9.5 seconds per page against 0.7 for a plain request, about 14 times longer, though that includes a load wait a tuned scraper can shorten. So size the browser tier from a measured sample of your own targets rather than from the assumption that modern sites need one, and expect product and search pages to need it more often than homepages do. Blocking heavy resource types on the requests you do render is one of the cheapest wins available. Leave first-party scripts running on any target that challenges with JavaScript. In one home-lab run of 1,000 URLs against a Cloudflare-fronted retailer, a browser pool that blocked images, fonts, media, stylesheets and every script returned 801 usable pages and 193 403 challenge pages titled “Just a moment...”, clustered on category and deals pages; with scripts blocked, the challenge never had a chance to run. Whether more would have passed with scripts allowed was not tested, so the saving and the loss cannot yet be weighed. Compare blocking profiles on the same URL sample, script blocking on and off, and judge them on validated records per megabyte and per minute rather than on megabytes per page, because a cheap page that comes back as a challenge costs a retry as well. Block by destination as well as by type, then measure what is left, because asset blocking alone rarely gets a script-heavy page close to a plain request. In one vendor-run October 2026 benchmark on a hotel search behind Kasada, a default headless browser moved 4.0 MB of residential proxy traffic per search; blocking images, fonts, media and third-party scripts cut that to 1.1 MB, but 76% of the remainder was the site’s own JavaScript. A request-based client fetching only the challenge and the results HTML used 178 KB, about 22 times less than the default browser. If the data is already in the HTML or an API response, that gap is the case for building the request path. Count the bytes at a proxy you control: the same test found Chrome DevTools reported 25% less than the proxy carried, since it skips TLS handshakes, request headers and aborted downloads.
Driver 4 · Third-party bleed
One GB on target, five on its trackers
A modern page calls a pile of third-party services as it loads. I have seen a single GB spent on the real target drag in several more GB of analytics, ad, and widget traffic routed through the same proxy. Block the domains you do not need, and give each website its own proxy credentials so a runaway bill shows up as one obvious outlier instead of hiding in a shared total. The browser itself is one of those domains. A fresh Chrome profile fetches an on-device model from optimizationguide-pa.googleapis.com before you navigate anywhere; one proxy vendor’s open benchmark counted 43.2 MB of a 43.4 MB idle window on a browser parked on about:blank. On a metered residential exit that is billed traffic no target ever sees, and a pipeline that mints a new profile per identity pays it every time. Refuse that host at your proxy layer or local relay, and count bytes per identity so the warm-up, the search and the browser’s own housekeeping show up as separate lines. The figure comes from one setup and one Chrome build, so measure your own idle profile before you rely on it. Quote browser cost per thousand pages for the whole job, start-up included, not only at steady state. One vendor benchmark priced two 96-browser nodes at about $0.13 per 1,000 pages at on-demand cloud list prices, which holds only once the cluster is running and busy; its fixed start-up was about 3.5 minutes per job, and its 1,000-page test as actually run held both nodes for 7 minutes 20 seconds, roughly $1.42 at the same hourly rate, about eleven times the steady-state figure. Batch large enough to amortise the start-up, or keep a warm pool and budget for its idle hours instead. Those are vendor figures from an undefended practice site with no proxy traffic, so recompute them with your own targets, proxies and retry rate before you size a fleet.
The largest cost of all never appears on an invoice, because it is salary. The four drivers above are the ones you can see in a bill. The one you cannot see is the senior engineer who keeps resurrecting the same scraper because it breaks every other week. That time is real money, it is usually the biggest line, and it shows up as headcount rather than spend, so it escapes the comparison entirely. The honest unit is cost per usable document: every dollar of proxy, compute, retries, and human maintenance divided by the count of records you could actually trust and use. Model it that way and the rankings often invert. The source that looked cheapest by the gigabyte turns out to eat an engineer alive in babysitting, while the one that looked expensive runs untouched for months and quietly wins on cost per document. You cannot manage what you have not measured, and the thing most teams have never measured is the one costing them the most.

A perspective worth holding while you read the rest of this guide

This guide is largely about how to get through defences, so it is worth surfacing the strongest argument against treating that as the main event. The case goes like this: bypassing anti-bot systems is a treadmill. You ship a workaround, the target adapts, and you spend the next sprint on infrastructure that produced no business value, running hard to stay in place, and the day a site flips its detection stack overnight you are back to the start. By this view the teams genuinely winning at data collection are not the ones with the cleverest bypasses, they are the ones who abstracted that complexity away so their best engineers spend their time on what the data is for, not on how to fetch it without getting blocked. The pointed version of the question: when did "how do we bypass this?" become more important to your team than "what are we actually going to do with this data?"
It is a fair challenge and mostly correct as a default. It is also not absolute, which is the honest other half. Sometimes the bypass is the business: the data is not available any other way, no managed provider covers the target, the margin only works if you control the cost per document yourself, or the capability is the moat. The resolution is not "always build" or "always buy," it is to decide deliberately which hard problems deserve your strongest people. For most teams, on most targets, the answer is to abstract the fetching away (a managed API, a maintained library, the cheapest rung that works) and spend the saved attention on the data itself. Reserve the deep stealth engineering for the targets where access is genuinely the differentiator. The rest of this guide gives you the depth for when you need it; this note is the reminder to be honest about how often that actually is.

A warning about the one clean number: cost per thousand requests

It is tempting to reduce a target to a single figure, what an operator pays per thousand requests, carried out to a fraction of a cent. It looks authoritative and it makes comparisons easy. It is also quietly dishonest, and it is worth understanding why before you trust one. The inputs that feed that number, solver prices, proxy costs, the detection tier a site happens to be running, are opaque and swing constantly. Doing precise arithmetic on inputs like that produces a figure whose decimal places are invented: false precision dressed up as measurement. The deeper instability is in the unit itself. Two vendors quoting a price per thousand are often not selling the same thing, because one meters traffic and the other meters successes, and which is cheaper is decided by your median response size, not by either price. A third meter is spreading: a licence per node, with the machines running in your own cloud account. There the vendor’s share does not move with records, gigabytes or successes; what scales is your own compute and proxy bill, bought at your provider’s price. Read which delivery mode a flat-price promise covers before you budget on it. One invite-only platform charges €599 per node for its self-hosted tier (three-node minimum, short-lived browser and Spark workers not counted), while its hosted tier bills compute, storage, proxy traffic and model tokens at its own cost plus 30%, so the same pipeline is flat-priced in one mode and volume-metered in the other. That crossover is arithmetic you can do without a benchmark: per-gigabyte billing stops winning above ($/success ÷ $/GB) × 1,000,000 KB, which at published rates falls near 160 KB against a $9.40/GB unblocker and near 106 KB against a committed dollar per thousand successes. Above that size trimming payload cuts the bill; below it, only cutting request count does. Then there is the tier. One vendor publishes its price list as a difficulty scale: $0.13 per thousand at the simplest rung rising to $1.27 at the hardest, and $1.01 to $16.08 once a browser renders — one vendor, one unit, a 124× spread, wider than the gap between two vendors. Those labels are that vendor’s own commercial classification of your target rather than an independent measure of it, and the cheapest rung sits roughly eight times below the one-to-five dollars per thousand in the table above, which describes hard targets. Credit multipliers work the same way: one provider bills a URL at a credit and another at twenty, and the unaudited but plausible reading is that the multiplier reports which route that provider bothered to engineer for your domain, so a 20× on an obscure target may be a fallback nobody was paid to optimise rather than proof the page is hard. A high multiplier can also be a price on the target itself rather than on effort. One provider’s published August 2026 figures bill the largest professional-networking site at 20 credits a page, its top tier, while recording 98.0% success there on a plain token with no browser, against 1 credit for 332 of its 356 published site recipes, including sites it measured as slower or less reliable. Before you treat a multiplier as evidence of difficulty, ask the provider for that domain’s success rate and whether it renders, and price the same domain with at least two providers. Those are the vendor’s own traffic figures for one month, not an audit, and the reason for the tier is not stated.
The FAIR Institute, which argues about exactly this in cyber-risk quantification, has the cleanest framing: many "quantitative" scores are really ordinal judgements with numbers painted on, where the digits could be swapped for colours or the words high and low and nothing would change. You cannot legitimately multiply red by yellow, and running multiplication and division over solver-price guesses and proxy-cost estimates is the same move, math on a colour scale. The practical takeaway is not to stop estimating cost, it is to estimate it honestly: prefer a range over a false-precision point value, treat any per-request figure as a snapshot that decays the moment a vendor changes pricing or a target changes its detection tier, and keep the number anchored to the one thing you can actually measure, the cost per validated payload on your target, from your own runs. A wide honest range beats a narrow invented one, because a single confident decimal invites decisions the underlying data cannot support.

The other number to distrust: a vendor's success rate

The same skepticism applies to the headline figure every unblocker sells on. Search the market and you will find vendors advertising 98, 99, even 99.99 percent success, and they cannot all be right about the same web. These are usually not lies, they are selective truths, because when a vendor publishes its own benchmark it silently controls every variable that decides the score: which URLs are tested, how success is defined, how retries are counted, which pricing tier runs, and, crucially, whether the harness is published at all. A 99 percent from one vendor and a 98 percent from another are not two products a point apart, they are two different tests, and with no shared harness there is nothing to check. Even the word "independent" is weak protection: two of the most-cited independent scraper benchmarks each rank a single provider first, and the domains trace back to the very companies that top them. Before you interrogate how a success rate was produced, confirm that the figure in front of you is a success rate at all, because that substitution is common and almost never flagged. A Rust scraper whose headline claim is bypassing Cloudflare without a browser leads on 95.1%, and that number, traced back to the founder’s own launch post, is boilerplate-removal accuracy over 50 pages, scored against readability, trafilatura and newspaper3k — parsers that are handed HTML, never fetch anything and carry no TLS fingerprint. No bypass figure appears in the post, the README or the homepage, and the one hands-on trial, a single reviewer against a single target set, pulled clean output from three ordinary retail sites and then failed on two Cloudflare-protected ones, on the paid tier as well as the free CLI. The same swap runs through the numbers you read about defences. The widely quoted finding that 92.4% of major sites run a WAF is the hit rate of a fingerprinting tool firing attack-shaped probes at 11,100 root URLs, once each, from one vendor’s datacentre range: it measures whether a site reacts to an exploit payload, not whether it obstructs a collector, and a polite renderer pipeline does not look like an SQL injection attempt. In that same census the control that actually stops collection, anti-bot, sat at 18.5%. Read the unit before you read the digits.
The defensible way to read any success-rate claim (including the ones quoted elsewhere in this guide, which are vendor or benchmark figures reported as snapshots, not independent verification) is to ask five questions the number alone never answers. What was the pass criterion, since a 200 that returns a captcha shell or an empty JS page is a failure only if the harness checks the body for a real content marker rather than trusting the status code. What was in the target set, because a suite with one site per anti-bot vendor lets a single leniently configured deployment stand in for all of DataDome or Kasada. What tier ran, since premium stealth and the default endpoint are different products. Is the harness public and reproducible. And how fresh is it, because a defense and a provider both drift monthly, so any figure is a dated snapshot the moment it is published. The honest signal is not a high number, it is a benchmark that shows its own failures and hands you the harness to rerun. When a claim reports 100 percent, the most likely reading is not a perfect product but a test that stopped being hard, and the missing failures are exactly the information you needed. Two of those questions have been given measured answers, and both are larger than they sound. Retry accounting is not a rounding detail: in an eight-configuration run of 400 requests per arm, published on the blog of one of the providers it ranks, setting the flag that surfaces the first response rather than the last took a premium tier from 79.5% to 64.5%, Amazon from 98% to 67% and Google from 20% to nothing. Retries touch a minority of requests, so they never move a median; the tell is the tail, a median near 1.88s against a 95th percentile close to two minutes, which is what a success rate bought with time looks like. The second invisible denominator is the requests a provider declines to send at all. Two large vendors each refused 22 of 100 general-web targets at the proxy, the same eleven domains on both rounds, and that is the entire distance between their published 76% and the 97.4% they scored on the requests they allowed through — a league table that does not separate refusals from blocks is partly ranking legal departments, so count refusals as their own column on a trial key. Read a matched refusal list with ownership in mind. The two vendors that declined the same eleven domains were Oxylabs and Decodo, which since October 2026 sit in one ecosystem, Source, that says compliance and use-case vetting standards apply across its companies; the benchmark itself appeared on the blog of ScrapingBee, which Oxylabs bought in June 2025. Identical refusals are then plausibly one policy rather than two independent judgments, an inference neither vendor has confirmed. Before you treat two brands as separate data points or separate fallbacks, check whether they share an owner, and read a benchmark of siblings published by a sibling as an in-house test. And when a score moves between runs, suspect the harness before the product: across three dated runs of one public benchmark a provider gained 40.7 points on identical URLs, which its own maintainers traced to an adapter that had been fetching from a default datacentre pool until it was pointed at a premium tier.

Spend the model once: build-time AI, not runtime AI

The single largest cost decision in an AI-assisted scraper is where the model sits. A whole category of tools puts a language model in the runtime: an agent drives a headless browser, loads the full page, and reads it on every single run. It demos beautifully and it is a margin killer at scale, because each run pays three times over, for a full browser rendering tens of megabytes, for residential-proxy bandwidth on all of it, and for model inference and its latency, every time, to redo work that never changed. The hard part (working out a site's anti-bot flow and writing correct request code) is a one-time job and exactly what a model is good at. The repetitive fetching is exactly what it should not touch. So spend the model once, up front, to build a plain request-based scraper, then run that scraper with no model in the loop at all.
The number that makes this concrete comes from a published airline-pricing teardown. Loading the full page for every search measured about 19.25 MB; bootstrapping the anti-bot session once and then calling the JSON endpoint directly measured about 22 KB per subsequent search, roughly 875 times less bandwidth. Priced at a mid-range residential rate around four dollars per gigabyte, a million searches is on the order of nineteen terabytes and tens of thousands of dollars the page-loading way, versus tens of gigabytes and about a hundred dollars the direct-API way, before you even add the per-run inference bill the agentic version also carries. The distinction that matters is not browser versus no browser, it is whether you reload the whole page for every data point or hold a session and call the API. A real browser can be optimised the same way (bootstrap once, then fetch() the API directly instead of reloading), and the moment you have done that, the browser and any model driving it are dead weight you can drop. The one genuinely useful role for a model at build time here is reading the real wire traffic, the exact header order and the JA4/TLS fingerprint that a HAR export or DevTools cannot show, and reproducing it in request code, which is precisely the analysis you only need to do once.

The cost nobody budgets for: raw browser runtime speed

When you cannot avoid a browser, one line item quietly dominates the bill at scale, and almost nobody measures it: how fast the browser itself executes. Agents, scrapers, and automation pipelines all live inside a browser, so every slow render is a tax paid on every step of every job, multiplied across millions of pages. The industry-standard way to put a number on it is BrowserBench Speedometer (a cross-vendor benchmark that times simulated user interactions; a normal laptop scores roughly 8 to 15), and a cluster of browser-infrastructure vendors has started competing openly on it. Reported figures from mid-2026 put dedicated bare-metal setups (real consumer CPUs, a custom OS image on physical hardware) around 15 to 21, well above cloud-hosted headless browsers on shared infrastructure. Treat the exact numbers as dated, vendor-run snapshots rather than gospel, but the ordering is the durable point: where your browsers run changes their throughput by a large factor, and at volume that factor is real money and real wall-clock time.
The sharpest insight in that discussion is a hidden cost this guide has been circling all along: software stealth has a runtime price. One vendor's own numbers showed the same infrastructure scoring materially higher in a plain headless configuration than in its stealth-hardened one (roughly 14.4 versus 9.66 on the same benchmark), and that gap is precisely what the stealth patches cost you in execution speed, because every hook, shim, and property override the stealth layer installs runs on every operation. It sharpens the recurring build-once, real-hardware theme from a new angle: patching a normal browser to look human is not only more fragile than running genuine hardware, it is also measurably slower, so the "rebuild the environment rather than patch the flags" advice from the detection section turns out to be a performance argument as well as a stealth one. The practical takeaway for costing a large browser-based job is to benchmark your actual browser configuration, stealth layer included, before you size the fleet, because the per-page time you measure on a clean headless browser is not the time you will get once the stealth is switched on. Execution speed is only half of what the clock charges you for. When the browser is remote, you are billed for wall-clock time on someone else’s machine, and two ordinary habits keep that clock running. The first is trusting your own teardown. On a browser obtained by connecting to a remote endpoint, close() is documented to do no more than disconnect from the browser server: the process, the profile and the meter all survive it. Measured against one cloud provider, reconnecting after a close took about 1.55s with the page still open and a value written twenty seconds earlier still readable in localStorage, against about 6.05s once the provider’s own stop call had been issued, which is the cold-provision cost of a session that genuinely died. Two non-overlapping bands, so a warm reconnect is a usable leak probe — measure your own, since the ratio scales with your round-trip time — and put the close and the stop in a finally, or every crashed run keeps billing. The second habit is chattiness. Reading twenty cards field by field issues on the order of eighty protocol calls and took about 16s a run; the same extraction as a single page.evaluate() took a quarter of a second. Locally a chatty chain is untidy. Over a wire it is the invoice. Both figures are single runs from one client-host pair, published by the provider on its own blog.
5 field notes on cost and economics Reported from practice. Click any card to open it.
+
Aug 2026 / The market
The Scraping Market Split in Two, and Most of the New Half Skipped the Hard Part
From someone who sits in both camps: the industry has cleaved into infrastructure (proxies, rotation, anti-bot bypass, uptime) and intelligence (AI-native search and retrieval for LLMs and agents). The first is mature and commoditised. The second is younger, better funded — and mostly inherited the easy 80% of the web and called it done.
💡 “Most of them can't touch a site with real anti-bot protection”
The two halves, drawn honestly by someone selling both. On the infrastructure side, everyone's success rate now sits in the high 90s, so what's left to compete on is price and reliability, not features — the benchmark-card reality, from the vendor's chair. On the intelligence side sit the AI-native retrieval products with great docs and great positioning whose search product falls over the moment it meets a hardened target. They built for the agent era from day one but never solved access, because access was the boring part everyone assumed was done.

The observation that makes it more than vendor talk: brand equity does not transfer across the split. Years of proxy performance awards and a top response-time record mean nothing to a developer discovering tools through GitHub stars and framework docs — and conversely the AI-native players' positioning means nothing the first time their retriever hits a DataDome wall. It's the same fault line as the API-vs-scrape scorecard in §01: the hard, valuable 20% is exactly the part the shiny layer skipped, and it's where the work still is.

Why it matters if you're choosing tools, not selling them. An “AI-native” data product that demos beautifully on a blog and a docs site is telling you nothing about whether it can reach the retailer, the marketplace or the forum you actually need. Test any retrieval or agent product against your hardened targets before you believe its coverage — because most of them, by this account, inherited the reachable web and quietly redrew the boundary of “the web” around it.

Source: Gabrielé Vitké (proxy + AI vendor).
+
Aug 2026 / The thesis
“The Web Is Being Priced, Not Blocked”
The researcher behind the largest audit of web access controls ever published was asked to sum up 24,898 sites across 230 countries and 110 industries. She did not reach for a number. “The web is being priced up. The spirit of the worldwide web was that information needs to be free. But now the information is out there; it’s just not free. There’s a different cost to getting it.”
💡 Most of the web is cheap. Nobody pays you to fetch the cheap part.
The distribution first, because it is counterintuitive. Only 18.5% of audited landing pages had no detectable barrier at all — but 30.7% had exactly one, and 88% still land in the two easiest access tiers. Most of the web really is straightforward with the right tools. The catch is the shape: “It’s almost like the Pareto ratio. Eighty percent could be easy. But most of the work is going to be done on the 20%, which is getting more and more aggressive, more and more closed. If you’re getting paid to do web scraping, most likely you’ll be working in that 20%. That’s where the work is.”

Which is why “how hard is the web” is the wrong question. Difficulty skews violently by sector, because the barrier tracks commercial value rather than technology. Fashion is the single hardest industry in the report, needing moderate-or-harder infrastructure on 57% of its sites; reference and publishing sit at the opposite end. The barrier you actually meet has less to do with the web in the abstract than with which corner of it pays your bills.

The line worth pinning above a desk. “Web scraping at scale is getting more and more complex. It’s not just a script kind of problem; it needs to be a system” — proxy management, concurrency, a decision about whether the page needs JavaScript and a fallback when it does, and cost managed across all of it. “It’s all about orchestration and instrumentation.”

Read the signals as different kinds of thing, not one wall. CAPTCHA is close to binary — “we want you to be gone”. JavaScript either loads or it does not, and plenty of sites run it purely for user experience while incidentally making you pay for a headless browser. Rate limiting is neither: it is a dial, not a switch — “please, yes, you can access this automatically, but don’t rush”. So the operational question is not is this site blocked but how many of these mechanisms is it running, and which kind. A CAPTCHA changes your answer; a little client-side JavaScript might not.

And the finding that surprised the vendor who commissioned it. Anti-bot adoption came in at just 18.5%, against their own instinct — because their day-to-day book of business is exactly the sites that run it. “It’s sites with e-commerce, pricing intelligence, data that correlates deeply with commercial value.” That is a vendor stating plainly that its own sample is not the web, which is rarer than it should be.

Two methodology notes that earn the rest of it trust. Every “website” here is one landing page, hit once, from a single IP — “what’s measured is the front door, not the whole building”, so barriers deeper in a site or visible only from certain regions are simply not in the data, and they say so. And they deliberately did not run the audit through their own unblocking API, because it would have solved every barrier and erased the measurement: “we wanted to capture the web as it is, just a simple request and response, and not taint the process of getting it.” Set that against the benchmark card in this feed — this is what it looks like when a vendor builds the instrument to survive its own result.
+
Aug 2026 / Economics
Price Is Not a Performance Metric
A benchmark of major scraping APIs across fifteen high-traffic sites asked a question nobody usually tests: does paying more actually get you more? On Amazon the correlation between price and performance was 0.25 — close enough to nothing that a roulette wheel is a defensible selection method.
💡 Same performance threshold, 2× to 15× the price, depending only on who you picked
The spreads, among providers that all cleared the same bar — an average success latency of fifteen seconds or under. Amazon: $19 against $300 a month, a 15.8× difference. Target: $19 against $249, 13.1×. Walmart: $29 against $300, 10.3×. Etsy: $59 against $475, 8.1×. Across fourteen of the fifteen sites tested, the gap between the cheapest and the most expensive qualifying provider was at least 2×.

And it is not merely that cheap sometimes ties. On Amazon: ScrapingAnt at $19 returned in 13.19s, Bright Data at $300 in 4.18s, and Zenscrape at $59 in 2.70s. The most expensive provider was not the fastest. Nothing here says premium providers are bad — it says the number on the pricing page is not a performance figure, and treating it as a proxy for quality is how teams end up paying an order of magnitude for a threshold they could have hit for a rounding error.

The part that makes it actionable is that the relationship is per domain, not global. Price tracked performance reasonably well on Home Depot and Best Buy, and barely at all on Amazon, Walmart, Booking.com, Glassdoor and Google. So there is no ranking to memorise and no provider to recommend in the abstract. The only reliable method is the unglamorous one: benchmark candidates against the sites you actually intend to scrape, at the volume you actually intend to run, and let that decide. It pairs exactly with the success-rate card above — both end at the same place, which is that the only number describing your situation is one you generated.
+
Aug 2026 / The economics
227 Bot Visits Per Human Visit, and a Bill With a Number On It
Several posts this week are the same story told from four seats: a publisher counting the ratio, a vendor selling the block, a lobbyist writing the bill, and a founder reading the growth curve. Held together they describe the moment blocking stopped being a security decision and became a commercial one.
💡 Reliable scarcity, not cybersecurity, is what brought AI platforms to the table
The ratio first. One publisher reported 227 bot visits for every human visit across a single quarter. The growth behind that: Cloudflare measured overall crawling up 18% year on year across more than thirty major crawlers, with GPTBot up 305% and ChatGPT-User up 2,825% — moving GPTBot from the ninth-largest crawler in the sample to the third in twelve months.

What the blocking actually did. Cloudflare began blocking AI crawlers by default across its share of the web unless a customer allows them, and the July plan said that from 15 September 2026 mixed-purpose crawlers that combine search and training — Googlebot included — would be blocked on ad-supported pages unless separated first. Correction, 30 Sep 2026: what shipped on the day was softer — new domains only, a robots.txt “Disallow AI Training” default on ad-supported sites, and Googlebot blocked only where an owner explicitly selects Block. The strategy quote is unusually candid: customers use the tools to “create reliable scarcity for their content, then negotiate better deals.” One publisher CEO put the mechanism plainly: they block almost every AI crawler except the one they have a deal with and the one they cannot block, and “they have to pay for it.” Financial Times, The Atlantic, Ziff Davis, Condé Nast and the Associated Press are named as working through the same arrangement. Pay-per-crawl is still in closed beta and the emphasis has already shifted toward pay-per-use, with payment details unsettled.

The waste argument doubles as the pricing argument. More than half the time, by Cloudflare's account, an AI crawler fetches something it has already fetched and which has not changed since. That is the same observation underneath the 402 card — the traffic is expensive and largely redundant, which is precisely what makes metering attractive to the party paying for it.

And the part that leaves engineering entirely. Three US Representatives introduced H.R. 9915, the Stealth Bot Prohibition Act, aimed at bots that disguise their identity to take content. Read alongside the New York statute already in this guide, the direction is consistent: identity disclosure is migrating from a thing you choose to a thing you must do. Whatever happens to this particular bill, a collector whose whole strategy is indistinguishability should notice that the strategy is being legislated at rather than merely defended against. §00 has the case law; this is the other tide.
+
Aug 2026 / The other side
A Costed Defence Ladder, Written By The People Paying The Server Bill
This guide is mostly written from the collecting side. A five-layer defence guide published for Drupal operators is worth reading precisely because it is not — and because its organising idea is one collectors rarely think about: every layer has a price, and the price rises the closer to the application you block.
💡 “The further away from the app you can block, the cheaper that block usually is”
The ladder in order, with the cost note attached to each. Layer one, CDN and WAF at the edge, drops high-volume script traffic and DDoS noise globally and costs almost nothing because it never reaches the origin. Layer two, ingress and nginx rate limiting on high-risk paths, still cheap because it fires before PHP boots. Layer three, caching — CDN, dynamic page cache, Redis — serves repeats from memory with no database hit. Layer four, the application itself (Perimeter, Antibot, Honeypot, CrowdSec), where context-aware decisions about forms, logins and search filters live, and which is expensive because it needs a full bootstrap. Layer five, a proof-of-work proxy on uncacheable routes that pushes the cost back onto the client's CPU.

The pressure point they name is faceted search, and it is the most useful thing here for someone on the other side. Every filter, category, sort order and pagination state generates its own URL, so a crawler walking filter combinations produces a combinatorial explosion of uncached requests, each one forcing PHP execution and database queries. That is why a site which serves you happily on product pages turns hostile the moment you start walking facets: you moved from layer three to layer four, and you did it at volume.

Two things to take from it. First, the defensive posture you meet is a function of which layer your traffic lands on, not of how the site feels about scraping in the abstract — requesting cacheable URLs at a sane rate is genuinely cheap for them and is therefore tolerated far longer. Second, they put automated traffic at more than half of all traffic for a typical site, which is the number that funds the entire defence industry described in §03.

Source: amazee.io, layered Drupal bot and scraper defence.

Oct 2026 update. One small site’s first three viral days, self-reported: a Gemini-backed Q&A over Pakistani government pages (itself a .gov.pk crawler) served 480K+ requests, with 10,900+ from “unidentified bots” (about 2.3%, far below the half above), 1,300+ attack requests in three flood attempts and two prompt injections reaching the model; none worked. The defences were cheap: per-user daily limits, no stored data, links only to government sites. n=1, bot definition unstated, launch traffic skews human.
14 After the bypass

What happens to
50 million rows

Bypassing detection is the part everyone writes about. But getting the data is only the start. The questions that actually decide whether a scraping operation survives are about what you do next: where the data lands, how you avoid storing the same thing twice, and how you notice when a site quietly starts feeding you garbage. Add a fourth question before any of those: what personal data did you just take on, and did you need it. Under strict readings of privacy law, dropping a field after parsing does not undo having processed it; one practitioner’s reading of Saudi Arabia’s PDPL argues that a scraper which inspects sensitive data in order to discard it has already processed it. The engineering answer is to minimise at the selector rather than in a later cleaning step: extract only the fields your purpose needs, avoid pulling whole bio or profile blocks where health or religion can hide, write down what you collect and why, and set a retention limit before the first row lands. That record is also what a legitimate-interest assessment asks for.

Storage: stop dumping JSON into a folder

Format
Parquet, not CSV or JSON
For anything past a few hundred thousand rows, columnar Parquet beats row formats decisively: 5 to 10x smaller on disk through column compression, and analytics engines (Athena, DuckDB, Spark) only read the columns a query touches. A 50M-row crawl that is 40GB as JSON is often under 5GB as Parquet, and queries run an order of magnitude faster. Partition by crawl date or source so you can prune whole files without scanning them.
Layout
A simple medallion layout
Land raw responses untouched in a bronze layer (S3/GCS, exactly as scraped, your audit trail and replay source). Clean and normalise into silver (typed, deduplicated, schema-validated Parquet). Build query-ready aggregates in gold (the tables analysts and APIs actually hit). When a parser bug ships, you re-run silver from bronze without re-scraping, which on a hardened target can save you weeks and a lot of proxy spend. There is now a second reason to keep bronze: extraction keeps improving, and each gain needs the original HTML. DCLM released its raw-HTML pool. That is what let a September 2026 CMU preprint re-extract 18.0M pages with a 0.6B model and beat every pipeline that started from already-extracted text. The refiners that only saw resiliparse output could not recover a table the extractor had fused. If your bronze layer stores only cleaned text or markdown, today’s extractor’s mistakes are baked into the dataset for good. Store the compressed response body and budget for re-extraction: the paper reports 494 H200 GPU-hours to run its model over the whole pool, about a third of what its own extraction teacher needed. That is one preprint and one pool, English pages only.

Deduplication: the same item will arrive many times

URL-level
Seen-URL sets & Bloom filters
A large crawl revisits the same URL constantly through pagination loops, cross-links, and retries. Holding every seen URL in a Python set works until memory dies around a few million. A Bloom filter (or scrapy-redis's RFPDupeFilter backed by Redis) checks membership in constant memory with a tiny, tunable false-positive rate. For distributed crawls, a shared Redis set keeps every worker honest so two workers never fetch the same page.
Content-level
Hash the content, not the URL
The harder duplicate is the same product reachable via three different URLs (tracking params, locale prefixes, canonical vs vanity). URL dedup misses these. Compute a stable hash over the meaningful fields (a normalised product ID, or a hash of the cleaned record), and dedup on that. Keep a last_seen timestamp so you can tell a genuine update apart from a re-scrape, and upsert rather than blindly insert. When a site sends no validators you still have to download, but you can skip the expensive work after it: hash the extracted main text and reprocess only when that hash changes. Hash the text, not the raw HTML. Across 368 top homepages fetched twice a second apart, the visible text changed on 28 while the raw HTML changed on 150, through scripts, tokens and attributes nobody reads, so an HTML hash would re-parse or re-embed about five times as many pages for no real change. That is one run on homepages and the gap will vary by site, but it shows which side of the extractor the comparison belongs on. Time-series collection inverts that last rule. When you track engagement on social posts, prices or rankings over time, the re-scrape is the data: append one row per observation with its own observation timestamp, keyed on the source’s stable ID, and never upsert it away. Keep publication time and observation time in separate columns, because a post published yesterday gains engagement today and the two answer different questions. Dedup your own fetches on the platform’s post ID, but keep independent reposts of the same text, since spread across accounts is the signal a trend pipeline exists to see; a content hash would collapse it. Log collection coverage per window beside the counts, so a scraper outage does not read as a quiet hour and the backfill as a spike. Resolve relative stamps like “3h” at scrape time and remember they are truncated to their unit. This comes from a tutorial, not a measured pipeline.

Data poisoning: when the bypass succeeds but the data is fake

Passing the bouncer does not mean the data is real. Sophisticated sites detect scraper-like behaviour after the initial check and respond not by blocking you but by quietly serving poisoned data: subtly wrong prices, shuffled listings, fabricated rows, or stale snapshots. You get clean 200 responses and a healthy-looking dataset that is silently corrupt. This is more dangerous than a block, because a block is obvious and poisoned data flows straight into your decisions.
Detection
How to catch poisoning
Maintain a small hand-verified ground-truth sample (a few dozen records you check manually) and diff every crawl against it. Watch for statistical anomalies: prices that are suspiciously round, distributions that shift overnight, or the same record varying between two near-simultaneous requests from different IPs. Cross-check a sample against the mobile API or a logged-in view. If two clean sessions disagree on the same field, suspect poisoning before you trust the data.
Cause
Why it happens to fast scrapers
Poisoning is usually triggered by behaviour, not fingerprint: requesting pages far faster than a human could, hitting endpoints in a non-human order, or ignoring the rendering layer entirely. The fix is the same boring discipline that prevents bans: pace requests, vary timing, follow realistic navigation paths, and do not request the whole catalogue in five minutes. Slow, human-shaped scraping gets real data; greedy scraping gets fed lies. The hand-verified sample is the right instinct, and it is now cheap enough to run on every crawl rather than every release. A decision model asked one question per field — does this look like a plausible value for this field — scores a fixed sample of items, and the spider closes when the sample’s pass rate falls below a threshold. Published defaults are a 20-item sample, a 0.7 pass rate and a 0.5 per-field score, with every field required to clear its own bar so that one bad field fails the item rather than being outvoted. The economics are what make it routine: a three-field request runs 570 input tokens at $0.000024, four fields across a million items comes to about $30, and answers land in 70 to 500 ms. Breaking a SKU selector so it returned the product title drove that field to 0.08–0.35 while healthy fields stayed above 0.9. But notice what it cannot see, because it is the failure this section is about: poisoned data is plausible by construction. A subtly wrong price is exactly the shape a price should be, so it clears a plausibility gate untouched. Automated scoring catches the broken selector; only the hand-verified diff and the cross-check catch the adversarial one. Run both, and know which is which. One implementation detail transfers to any pipeline-based gate regardless of model: raising CloseSpider inside a Scrapy item pipeline does nothing, because process_item is wrapped in a bare except Exception that swallows it — close the spider through the engine instead. The published thresholds, it should be said, are the author’s own guesses tuned against a demo site he built, at a vendor that integrates the model.
A clean 200 is not the same as a clean dataset. Modern firewalls rarely bother to hard-block you anymore. They return 200 OK with a degraded body: an empty list, sanitised prices, a tarpit that drips bytes forever, or yesterday's snapshot frozen in place. If your scraper treats the status code as the success signal, you will log thousands of green requests and ship corrupt data. A fetch made through someone else’s fetch API can strip the status code from you entirely, so check the body even when no status exists to trust. Otari, an open-source LLM gateway from Mozilla.ai, documents this for the two web-fetch providers it wraps: Tabstack answers a site’s 404 with the text of the error page as ordinary content, because it does not report what status the site returned, and both Tabstack and Exa return the URL as you asked for it even after following a redirect, so a page that redirected to a login wall or a homepage is filed under the original address. Where you route fetches through such an API, keep a body-level check for error and challenge pages, and compare the page title or canonical link against the URL you meant. That gateway’s own built-in fetcher, by contrast, validates every redirect hop. An empty result is only trustworthy once you have proved there was something to find. Before accepting zero rows, make a second, independent call that confirms the thing you asked about exists and is non-empty: the group, category or search you requested, with a count or size you can read. Facebook group feeds show why. One practitioner traced an identical 200 with an empty post list to three separate causes in a single session: a group slug that never resolved to its numeric ID, a login wall served in place of a public group, and a rotated GraphQL doc_id. The details lookup for the bad slug said not found while the feed endpoint happily paginated nothing. The rule the write-up lands on is the one to copy: an empty feed from a group that exists and has members is a failure to raise, not a result to store. It is one vendor-authored account with no failure rates, so treat the three causes as a checklist, not a census. Gate every write on a content classification, never on the status code. For Cloudflare, run a challenge check first, before looking at the status at all, because the same interstitial arrives as either 200 or 403. Match the body case-insensitively for challenges.cloudflare.com/turnstile, cf-turnstile, __cf_chl_, cf_chl_opt, a Just a moment title or Checking your browser, and treat a cf-mitigated: challenge header as decisive; server: cloudflare and cf-ray only say who fronts the site. Classify into three outcomes, challenge, ok and hard_block, and give each its own action: reroute only challenges to a rendering path, back off on hard blocks, and run the rerouted response through the same detector before storing it. Keep the detector a pure function so saved challenge pages become regression fixtures, and chart the reroute rate, since a jump is usually the first sign a target changed its protection. The marker list tracks Cloudflare’s current markup, so re-check it against a fresh challenge page before trusting it. Validate the shape and the statistics, not the status. The question is never "did I get a 200", it is "did I get roughly N records like yesterday, with prices in the range I expect, and the fields I depend on populated". Wire that check into the pipeline so a silent drop to ten percent fill rate pages you instead of flowing downstream. Then measure freshness and coverage against something you did not produce. Report record age as a distribution: a median of three days can hide a 99th percentile of fourteen months, so publish the tail, not the crawl schedule. For coverage, compare against an independent source: one LexisNexis team used Common Crawl as a reference and found hundreds of missing records caused by its own extraction code overwriting rows, a bug no status code or fill-rate check had flagged. Compute totals and reconciliations in ordinary code rather than asking a model, because a model can return 4,523 for 4,532 without any error. Track cost per accepted record rather than cost per page: in the same account, a relevance-only crawl fetched 91% fewer pages yet cost about three times as much. These are speakers’ own figures from one conference, relayed secondhand, so treat them as patterns to test rather than benchmarks.

One operational habit that pays for itself: keep a small Targets table as the single source of truth for every endpoint you depend on. Endpoint path, required headers, cookie shape, the schema version you last validated against, and the expected record count. When a site renames /api/v2/ to /api/v3/ or reshapes a field, you change one row instead of hunting through scraper code. It costs half a day to write and saves you the morning you would otherwise spend discovering, after the fact, that a fortnight of data is empty.

The quiet failure mode: a 200 with nothing in it. Poisoning replaces your data with something wrong. There is a milder and far more common variant that replaces it with nothing at all, and it is harder to notice because every dashboard stays green. A verification interstitial or a soft rate limit answers with HTTP 200 and an empty result set rather than a challenge or an error, so the request succeeds, the parse succeeds, and the record count is zero. Search endpoints are where this shows up most, and practitioners tracking one major engine through mid-2026 reported exit addresses in some regions failing this way the overwhelming majority of the time while the success metric never moved.

The defence is the same instinct as the poisoning check, applied to volume rather than values. Alert on record count per request, not on status code. A run that returns 200s and zero rows is a failing run, and it should page you exactly as loudly as a run of 403s. Add page size and status variants to the same alert. A blocked response does not have to look like a challenge: one request to Amazon’s homepage in October 2026 returned a 200 with a 3,781-byte “continue shopping” gate and no CAPTCHA wording at all, where the real page runs to hundreds of kilobytes, and in a 12-store run the day before, the same block arrived as a 202 on nine stores and an empty 200 on three. Track response size per page type and flag a sudden drop, count a field the page always carries, and treat any 2xx as unverified until that check passes. A missing item does not tell you why it is missing, because a JavaScript-rendered listing fails the same count, so keep failed samples to separate blocking from rendering. These are single-run observations from one location, not rates. The operational counter that works is unglamorous: take a fresh session on one sticky address, request a small amount, confirm real records come back, and only then increase the rate on that same session, holding the identity still rather than rotating into a new one. You are looking for the point at which results thin out, which arrives before the point at which requests are refused.

That counter has an edge: a great deal of what gets crawled has no records in it. Documentation and reference pages have no fields to tally and no row count to compare against yesterday, so the alarm collapses back into the check it was meant to replace: the file exists, therefore the run worked. Half that file can be navigation chrome, the one code sample you needed missing, and nothing downstream will say so. The substitute for a schema is a named artefact chosen before the crawl: pick one reference paragraph and one code block off the live page, and let the run pass only if both survive extraction. Wire the failure path first: store each failed URL with its error message, or an empty result arrives looking like a success. Judge one thing at a time. Hand every candidate the same URL list when you are testing extraction and the same seed and scope when you are testing discovery, or a poor result will not say which half broke; and settle acceptance before you time anything, so you only measure output you agreed to keep. Keep the pilot in proportion. The published demonstration is one command against a static documentation site with no JavaScript and no bot protection — depth one, three pages, concurrency one — run by an author who builds one of the tools he compares, with no timings and no head-to-head. It establishes a discipline, not a success rate: a local sample tells you whether the output fits your application, never what it costs per month at volume.
The status code is a two-way signal: soft 404s, crawl budget, and hallucinated URLs

The earlier point was that a 200 OK can hide failure on the way in, when a tarpit feeds you poisoned data. The same dishonesty runs the other way too, and it is worth understanding from both chairs because it shapes how crawlers (yours, Google's, and an LLM's) treat a URL.

Many JavaScript frameworks, by default, answer a request for a URL that does not exist by rendering a "not found" view and still returning 200 OK in the header. The page says the content is gone; the server says everything is fine. Google calls that mismatch a soft 404, and it is not harmless. A real 404 or 410 tells a crawler to stop coming back. A soft 404 keeps the dead URL in the queue, because the server never admits the page is gone, so the crawler keeps re-fetching nothing. A block can arrive the same way. Some targets answer a refused request with a 200 and a page saying you have been blocked, and a pipeline that trusts the status code will parse it, store it and count it as a success. Check the body for block markers and for the fields you expected before you count a fetch as a success, and keep those failures out of your success rate. Ask the same of any vendor’s success rate before you compare it with your own. One provider’s published method counts a request as a success when the target answered 200, 201, 204, a redirect, 404 or 410, so a removed product page is a success, and lists a site only once three or more customers drew at least 1,000 successes from it in a month, which leaves out the targets nobody manages to scrape. Its own records also show why the body check matters: on one Danish rental site every failure in a month was a 200 carrying a block or consent page. Convert any quoted rate to your own definition, records with the fields you need, before you rely on it, and treat a per-site league table built that way as a ranking of survivors. Do not trust a 200 to mean the page exists either: once per site, request a random path that cannot exist, store what comes back, and compare every scraped page against that “not found” fingerprint (same title, similar word count) and against the homepage. A redirect from a deep URL to the homepage or a login page is a miss, not a success. Asked for a made-up path, 43 of 371 top sites (12%) still answered 200 in one October 2026 run, and a rule ladder built that way left 1 of the 43 labelled a success. On 13 sites, Instagram and Twitch among them, the missing page and the homepage were the same script shell, so no HTML comparison can decide; label those unclear rather than guessing, then render or check for the field you came for. Those rules were scored on the sites they were written for, so expect more misses on new targets. Practice sites now include exactly this case, a 200 whose page reads “You’ve been blocked”, next to a cookie that keeps marking a client as blocked on later visits, so the check can be tested before a production target forces it. The same care applies to your own loop guard. A listing that sends you back to page 1 after every detail visit makes records reappear, and a guard that fires on any repeated record will call that an infinite loop and stop the crawl early. Record the page each item was first seen on and never overwrite it; a known item on an earlier page means you were bounced, so paginate forward and resume from the same row. Reserve the loop verdict for a repeat on the same page with no forward progress. One engineer who hit this on a government property register reports the change cut the crawler’s state from seven variables to three, and that a synthetic two-page listing bouncing after every record went from a loop failure to 20 of 20 records. That is one site and one test; it assumes stable record keys and ordering, because a listing that reorders between visits will look like a bounce.
The attack your own default config enables
Crawl-budget drain as negative SEO
If a framework hands a 200 to every undefined route, an attacker does not need to touch the site at all. Point bots at thousands of made-up URLs on the domain; each one renders, returns 200, and consumes crawl resources that should have gone to real pages. On a large site the real content gets discovered slower. That is a negative technical SEO attack made possible by the server never saying "gone." The fix is boring, which is the point: return a real 404 or 410 for routes that do not resolve, and verify the rendered status in Search Console rather than how the page looks in a browser. The browser hides the header; the crawler reads it.
The newer, stranger layer
When a 200 confirms a hallucinated URL
Large language models invent URLs the same way they invent facts. One audit of model-suggested login links for major brands found roughly a third pointed at domains the brands did not own, many unregistered and waiting to be claimed. The HTTP status is the honest answer in this exchange. A proper 404 tells the machine the page never existed. When a server answers a hallucinated URL with 200 OK, it does the opposite: it confirms the fiction and tells the model its guess was right. For a scraper consuming model-suggested targets, this is a direct lesson, treat status codes as ground truth over a page that merely looks populated, because the rendered body is exactly what a soft 404 gets wrong.

When the data is not HTML: parsing documents

A large share of the data people actually need does not live in a DOM you can select. It is locked inside PDFs, scanned invoices, receipts, statements, and image-only files, where there is no clean JSON API to hit and no selector to write. This is its own extraction problem that sits downstream of fetching, and it is worth treating as a first-class part of the pipeline rather than an afterthought, because it is where a surprising amount of real-world value (and error) lives. The naive approach, run OCR and parse the raw text with regex, breaks the moment a layout shifts, a scan is skewed, or a total wraps onto two lines.
The durable pattern
Hybrid validation: two parsers, one confidence score
The pattern that holds up on messy real documents runs two extractors and cross-checks them rather than trusting either alone. A deterministic OCR-plus-rules parser (an engine like EasyOCR or Tesseract, then field rules) gives a cheap, reproducible baseline with per-token confidence. An LLM or vision-language model reads the same document and returns structured JSON against your schema, handling the messy cases rules cannot, skewed scans, odd layouts, missing labels. You then compare the two outputs and derive a confidence score from how much they agree. Where they match, you trust the value; where they diverge or both score low, you flag it. Neither half is reliable on its own, the rules are brittle and the model can hallucinate a plausible total, but the agreement between them is a far better signal than either confidence in isolation.
Why it matters downstream
Confidence routing beats silent failure
The point of scoring every extraction is what you do with the score: route low-confidence documents to human review instead of letting a wrong number flow silently into your dataset. This is the document-parsing version of the data-poisoning lesson above, a model that confidently returns grand_total: 369963 from a blurry receipt is its own kind of soft 404, output that looks clean and is wrong. A schema-consistent JSON contract, a numeric confidence on every field, and an explicit human-in-the-loop lane for the low-confidence tail are what turn document parsing from a demo into something you can trust at volume. The same discipline applies whether the input is a scraped PDF, an emailed invoice, or a photographed receipt.
7 field notes on life after the bypass Reported from practice. Click any card to open it.
+
Aug 2026 / Method
You Cannot Prove You Got the Whole Catalogue, and Nobody Checks
Every completeness idea in this guide is defensive — did a field go missing, did coverage drop against the last run. None of it answers the prior question: did you ever have all of it? A crawl that has silently been seeing 70% of a catalogue since the day it launched has no delta to alert on, because the baseline was already wrong.
💡 A stable row count is evidence of a stable bug just as easily as a stable catalogue
Why pagination lies. Most listing interfaces cap out — a thousand results, fifty pages, an infinite scroll that stops. Query a category with more items than the cap and you get exactly the cap, every run, forever, with no error. Your monitoring sees a beautifully consistent number. That is the failure mode, not the reassurance.

Three ways to actually measure recall. Facet decomposition: slice the same catalogue by a different dimension — brand, price band, date — and check the union exceeds what a single traversal returned. Identifier probing: where identifiers are dense, sample the space directly and see how many resolve to items your crawl never found. Cross-source reconciliation: compare against a second source that should agree, including the site's own sitemap and its declared result counts.

Then write the number down. Estimated recall belongs next to freshness in what you report, because a customer told a dataset is complete when it is 70% complete has been misled regardless of how clean every returned row is.
+
Aug 2026 / Data quality
LLM Honeypotting: When the Site Stops Blocking You and Starts Lying
The failure mode inverts. Traditional blocking is loud — 403, 429, a CAPTCHA — and your logs tell you which target needs attention. A honeypot returns valid HTML and 200 OK, with nothing in the response marking it fake, so it flows straight into the dataset and only surfaces later when a model starts producing unreliable answers. It is a data-quality problem wearing an access problem's clothes, and it hits training-data teams hardest because bad rows get buried in the week's volume.
💡 Three techniques, and each one bills you differently
Proof of work costs you compute and latency on every request. Content mazes cost crawl budget, bandwidth, processing and storage — Cloudflare's AI Labyrinth is the named public example, exposing hidden links to pre-generated decoy pages that no human ever sees, and treating a bot that follows them as confirmed. Data poisoning costs you the dataset itself.

The fake content takes recognisable forms: fabricated product listings and identifiers, prices attached to nothing real, contradictory dates and specifications, and invented relationships between entities.

Detection, and this is the actionable half. Seven signals: a crawl count far exceeding what sitemap.xml declares; URLs that nest deeper without ever reaching a terminal page; fluent text containing no verifiable facts; near-identical content under many distinct URLs; and internal contradictions across pages that should agree. Then run near-duplicate detection — SimHash, MinHash, n-gram comparison or embeddings — because exact-match deduplication misses generated pages that differ by a few words. “A response can look valid, parse correctly, and still lead the scraper nowhere.”
+
Aug 2026 / Pipeline
A Citation Proves Where an Answer Came From, Not That It Is Still True
An engineer built a small agent over a search index of a website's documentation, then changed the source site and left the index alone. The agent kept answering with complete confidence and the same citations, which still resolved, still pointed at real pages, and no longer described reality. Nothing in the output marked the problem.
💡 Retrieval systems fail the same way scrapers do — with a 200 and a straight face
This is the guide's oldest principle wearing new clothes. A scraper whose selector breaks returns a clean row with an empty field. A retrieval index whose source moved on returns a fluent answer with a valid citation. In both cases the failure is silent, the artefact looks correct, and every check that inspects form rather than freshness passes.

What a citation actually asserts. Provenance, not currency. It says a document at that URL contained this claim at the moment it was indexed. It says nothing at all about whether the document still says it. Those are different guarantees, and RAG systems routinely present the first as if it were the second.

The fix is the fix you already know. Store fetch time alongside the text, treat staleness as a first-class field, and alert when the gap between index time and now exceeds what the source's own change rate justifies. An unchanged index over a changing source is the same alert as an unchanged file from a live feed — which is to say, a loud one.

Oct 2026 update. A Crawlbase tutorial (vendor; code MIT) attacks this gap by re-fetching each cited page at check time instead of trusting the index: it keeps the 3 passages closest to the claim, asks a judge model for a label and quote, and accepts the label only if the quote is literally on the page. Its only numbers come from a fixture dry run, and the substring check proves the quote exists, not that it supports the claim.
+
Aug 2026 / Provenance
Your Agent Found the Answer. The Evidence Did Not Come With It.
An AI research tool hands you a conclusion. The pages it retrieved, the passages it used, the timestamps, the tool calls it made — usually none of that travels with the answer. Which means you cannot audit it, cannot re-run it, and cannot hand it to a colleague.
💡 This guide's provenance rule, applied to the research session rather than the row
The parallel is exact and worth making explicit. The guide argues that a row without source URL, fetch time, status and extractor version cannot be diagnosed — you can only re-crawl and hope. An agent's answer without its retrieval trail is the same object: a value with no lineage, which you must either trust entirely or discard entirely. The intermediate state, checking it, is unavailable.

The fields named for a portable session map almost one to one onto the row-level list: the source URL, the raw capture, the timestamp, the model and prompt version, the extracted fields, and the sequence of steps that produced them. The two additions over the row-level case are the interesting ones — model and prompt version, because the same question asked next month against a different checkpoint is a different experiment, and the step sequence, because with an agent the path is part of the result in a way it never is for a deterministic extractor.

And a cost note from the same source that pairs with it. Prompt caching is an extraction-cost lever, not just a latency one: keep instructions and schemas as a stable prefix and put the changing content at the end, so repeated work hits cache rather than re-billing the same tokens. That is the same shape as the agent-cost finding elsewhere in this feed — the spend is decided by how you arrange the context, not by which model reads it.
+
Aug 2026 / New attack class
The Page Says One Word. The Font Draws Another. Your Scraper Takes the Wrong One.
Every defence in this guide so far tries to stop you reading a page. This one lets you read it and hands you different words. A build step swaps roughly a quarter of the words in the source for grammatically identical substitutes, and a custom font quietly draws the originals back for the human. You get clean HTTP 200s, complete rows, plausible sentences, and a quarter of the nouns are wrong.
💡 The first anti-bot measure that gets cheaper the better your parser is
The mechanism, and it is elegant. OpenType has always allowed a font to draw something other than what the code says — that is what a ligature is, merging f and i into a single glyph. ShieldFont takes those same GSUB glyph-substitution rules and applies them to whole words. At build time, on the author's own server, each chosen word in the source is exchanged for a different word of the same part of speech and roughly the same frequency. The font then substitutes the original back at render time. A person in a browser reads exactly what was written. A client pulling HTML gets different words in the same grammar.

Why the substitution is grammatical rather than random. Garbage would be caught. The design goal is text that reads as unusual but sensible, so nothing downstream trips. At about 25% of words swapped, testing reported meaning failure in 55.8% of news passages and around half of general web text. Built by Isaque Seneda and Gabriel Abrucio with the type foundry Playtype, running since October 2025.

The number that reframes it, from the creators' own testing. Measured against FineWeb-Edu — a quality filter used to build a major public training dataset — over 90% of shielded pages get rejected outright, and the minority that survive the filter carry false meaning in roughly 19.4% of their content. That is a different and more interesting claim than “it poisons scrapers”. The primary effect is exclusion: most shielded text never makes it into the corpus at all, because a filter tuned for quality reads slightly-wrong prose as low quality and drops it. The poisoning is what happens to the remainder. For a publisher wanting out of training data that is close to an ideal outcome. For anyone collecting text, it means the damage is concentrated in exactly the pages that passed your quality checks.

What it inverts. This guide spends a lot of words arguing that a real browser is expensive and you should reach for HTTP when the data is in the HTML. Against this technique that advice is exactly backwards: a headless browser that actually renders the font reads the page correctly, and so do OCR and vision-language models working from a screenshot. Frequency analysis over a large corpus also unpicks it, and anyone holding the font file can simply reverse the mapping. So it does not stop a determined collector — it taxes the cheap path and leaves the expensive one intact, which is a deliberate and rather sharp choice of who to inconvenience.

The costs the defender eats are worth knowing, because they bound how far this can spread. Search engines index the decoy text. Screen readers hit the protected regions, which is a real accessibility harm rather than a footnote. RSS feeds leak the original text unencrypted. Any of those may matter more to a publisher than the scraping does.

What it means for your pipeline. Pair this with the plausible-wrong card above and the conclusion is uncomfortable: a value can now be wrong on purpose, in grammar, at scale. Row counts, schema checks and completeness validation all pass. The detections that survive are semantic — does this text agree with the rendered version, does this field agree with a second source, does the vocabulary of this page drift from its own history. If you collect text for a model rather than numbers for a table, this is the threat model to hold, and the cheapest insurance is spot-rendering a sample of pages you already fetched over HTTP and diffing the words.
+
Aug 2026 / Verification
“One of the Worst Things a Web Scraper Can Do Is Succeed”
The sharpest statement yet of the failure this guide keeps circling. A retailer changes a product page and the scraper starts reading the monthly financing payment as the full price. Every request succeeds. The row count is normal. The schema passes. The number is even plausible. And it is now sitting in an investment model.
💡 A schema check cannot catch a value that is the right type, the right range, and wrong
Why this is worse than the silent 200. An empty container is at least detectable: count the rows, compare against the last good run, alert. But £49 where £1,176 belongs passes every structural test ever written. It is a number, it is positive, it is in a currency, it is within the range prices occupy. Completeness validation says fine. Schema validation says fine. Only something that knows what a price for this product should roughly be can object.

The technique named for it is a two-stage check: deterministic schema and completeness validation first, then an LLM evaluating suspicious values — the author's term is “sus vals” — on whatever survives. That is a defensible use of a model in the pipeline, and notably the opposite of the pattern this guide warns about. The model is not doing the extraction, where it would be a per-request bill forever and a new source of quiet error. It is doing adjudication on a small suspicious subset, which is cheap, and where being occasionally wrong costs you a false alarm rather than a corrupted row.

The operational half. The claim attached is that over thirty days more than 99.7% of delivered runs passed those checks, with self-healing agents handling most failures automatically. Treat the number as a vendor's own, per the benchmark card above — but the definition underneath it is the transferable part: a run that fetched everything and delivered a wrong number does not count as delivered. Most teams have no such definition, which is why they do not have the metric either.
+
Aug 2026 / Verification
"Twelve Requests, Twelve 200s, Zero Rows" — and the Acceptance Test That Catches It
Two practitioners posted the same failure independently this week, which is usually a sign a thing is common rather than unlucky. One ran twelve requests, got 200 on every one and zero rows of data. The other put it as a rule: a scraper can return 200 and still have collected the wrong page.
💡 The acceptance test matters more than the library
The guide has argued the silent-200 case for a while. What is new here is a concrete acceptance test, and it is the most directly useful thing in this update if you are having an agent build scrapers for you.

The ordered procedure, worth giving to a model verbatim: start with plain HTTP; check for the fields and the minimum record count you asked for; move to a browser-like client only when the response proves a transport mismatch; launch a full browser only when JavaScript is what creates the data; and stop on login, CAPTCHA, explicit denial or unclear permission.

The formulation that makes it work is the acceptance criterion itself: "twenty records, four required fields, same schema on a second run" gives a model something it can actually prove. "The request worked" does not. That single sentence fixes the model-authored-oracle problem described in the agent-built-scrapers section above, because it replaces a check the model can satisfy by agreeing with itself with one it has to demonstrate against reality — and the "second run" clause quietly catches the case where a page happened to render once.

A related habit worth stealing, from a third post. An agent hit a third-party upload form stuck on "Uploading…". Rather than retrying a fourth time, it opened the browser's network trace: the request for a presigned upload URL returned 200, and the follow-up PUT to storage never fired — a client-side bug in the site's own widget, not a network or server fault. It then grepped the captured requests across all three attempts to confirm the same pattern, and afterwards verified the outcome on the site's own confirmation page rather than trusting the optimistic "done" state in the UI. Read the trace before you retry, and verify against the source of truth rather than the interface is ordinary engineering discipline; the notable part is that it is now something you can expect from an agent mid-task, and something worth explicitly instructing when you do not get it.

One boundary from the same author, useful if you are choosing where to put this logic: a skill teaches a model how to build and verify a scraper; an MCP server is for when you need it to call a running tool on demand. They are not competing, and reaching for the second when you needed the first is a common and expensive mix-up.

Oct 2026 update. A Shopee data vendor says most public Shopee scrapers began returning empty fields after a stricter anti-bot change in September, a promotion with no evidence. It fits the record: an Apify actor dated an emergency update to 29 April 2026, and another says product pages moved behind a JavaScript-signed token in mid-2026. Nothing independent confirms September, so the lesson stays field-level checks.
15 Mobile API Scraping

Intercept mobile app traffic
before it hits any anti-bot

Mobile APIs serve the same data as the web, but with weaker protection. No Cloudflare, no JA4 fingerprinting. Intercept the traffic once, replicate the call forever.

Why mobile APIs? The same data served to a mobile app often sits behind a simpler auth layer than the web. No browser fingerprinting, just a clean JSON endpoint you can call directly from Python.
01
Install Android Studio + create a Virtual Device
Open Android Studio → Virtual Device Manager → Create Device. Pick any phone that shows the Play Store icon. For the system image, choose any API level above 28do not choose Android 9 (Pie / API 28), the rooting script does not support it. API 30 (Android 11) is a safe default.
💡 Any Android 10+ image works. Start the AVD and confirm it boots before proceeding.
02
Root the AVD using rootAVD
AVDs are not rooted by default. Root access lets HTTP Toolkit intercept SSL traffic. The rootAVD script handles everything in one command.
git clone https://github.com/newbit1/rootAVD.git
cd rootAVD

# Verify AVD is accessible
adb shell

# List your AVDs
./rootAVD.sh ListAllAVDs

# Copy the first command from the output and run it
# e.g: ./rootAVD.sh system-images/android-30/google_apis_playstore/x86_64/ramdisk.img
💡 adb not found? Add to ~/.zshrc: alias adb='/Users/$USER/Library/Android/sdk/platform-tools/adb'
03
Confirm root, Magisk appears in the app drawer
After rootAVD finishes the AVD reboots automatically. Once it's back up, open the app drawer and look for the Magisk app, this confirms root is working. Zygisk does not need to be enabled.
💡 If the AVD didn't reboot itself, reboot it manually. No Magisk = root failed, re-run the script.
04
Install HTTP Toolkit and connect via ADB
Download from httptoolkit.com or install via Homebrew. Open it → Intercept tab → "Android device via ADB". HTTP Toolkit detects your running AVD and prompts it to grant superuser rights, grant it.
# macOS
brew install --cask http-toolkit
💡 "System trust disabled" warning? Disconnect and reconnect in HTTP Toolkit, or reboot the AVD.
05
Install the target app and capture its requests
Sign into Google Play on the AVD and install the app, or download the APK from apk.support and drag-drop it onto the emulator. Open the app, navigate through it (lists, detail pages, search) while HTTP Toolkit runs. Switch to the View tabevery request the app makes is captured in real time.
💡 Use the filter bar, you'll see 400+ requests but only ~10 are the data endpoints. Filter by the target domain name.
06
Replicate the API call in Python
Click any intercepted request to see full headers, auth tokens, and query parameters. Test in Postman first to confirm it returns data, then replicate in Python. Mobile APIs return clean JSON, no HTML parsing needed.
import curl_cffi.requests as requests

resp = requests.get(
    "https://api.targetapp.com/v2/listings",
    headers={
        "Authorization": "Bearer <token_from_http_toolkit>",
        "X-App-Version": "4.2.1",
        "User-Agent": "TargetApp/4.2.1 (Android 11; SDK 30)",
        "Accept": "application/json",
    },
    impersonate="chrome120"
)
data = resp.json()
💡 Tokens expire, check if the app refreshes on login and build a token refresh step into your scraper.
✓ Works well for
  • Property portals, classifieds, marketplaces
  • Apps where the web version is heavily protected
  • Data only available in the mobile app
  • Targets using simple Bearer token auth
  • Any app that doesn't pin SSL certificates
✗ Limitations
  • Apps with SSL pinning block interception
  • Some apps crash on rooted devices
  • ARM-only apps may not run on x86 emulators
  • Tokens expire, need refresh logic in scraper
  • App updates can silently change endpoints
↑ SSL Pinning bypass

If the app blocks interception it likely uses SSL pinning. Use Frida or objection to bypass it at runtime, or use Burp Suite with the Xposed + TrustMeAlready module for a more permanent bypass.

Stack Android Studio rootAVD (github.com/newbit1/rootAVD) Magisk HTTP Toolkit apk.support Postman curl_cffi

When the signature lives in native code

Interception gets you the requests. On a hardened app it does not get you the signatures. Replay a captured call against an app like Shopee and the backend answers with an error, because every request carries anti-fraud headers built by code you cannot read in JADX. The signing method is there in name only, declared native, with its body on the far side in compiled ARM inside a .so library. This is where most people give up on the mobile route. It is also where the mobile route gets genuinely durable, because once you reproduce the signing you are no longer tied to a running app at all. That durability is worth pricing precisely, because every alternative to it keeps a phone in the loop and inherits the phone’s clock. An oracle costs you one function call per signature and stays fast. Driving the app’s interface to produce the same header costs you the interface, and the per-step latencies published for natural-language Android agents make that concrete: roughly 3–5 s a step on a fast model, 15–40 s on a strong one, so a hundred-step run is 25 to 67 minutes on a single device. That is arithmetic on a vendor’s own published rates, not a figure measured under load in any published run. Set it against an offline signer that mints a header in milliseconds and the comparison stops being interesting. The second cost is visibility rather than speed. Reading the screen means a declared accessibility service, and a declared service is enumerable by the app the same way root is, so the tooling you installed to drive a hardened target becomes one more thing that target can see. Drive the interface when the signer is a bytecode VM you genuinely cannot rewrite. Do not drive it because Ghidra looks like work.
The decision that shapes everything: can you rebuild the signer, or do you have to borrow it? Read the native library and you land in one of two cases. If it is readable crypto, you reverse it and reimplement it in Python, then sign offline at any volume with no app in the loop. If it is a bytecode virtual machine you cannot practically rewrite, you keep the app running and drive its own signer as an oracle your scraper calls. Both beat the amateur route of a rooted phone you have to babysit, because a signer or an oracle runs on a server inside your scraper. The first is cleaner; the second always works. You pick based on what the library actually is, which means you have to open it first. One thing will try to make that choice for you before you have opened anything, and it is worth disarming early: the completion rates published alongside natural-language phone agents, which read as though driving the interface were a solved problem. Treat them as advertising until somebody who does not own the agent reproduces them. The headline 99%+ on AndroidWorld, the benchmark those agents are scored on, is one row on a public sheet that states up front that it performs no independent verification — filed as one trial, no submitter named, another entry above it at a flat 100%, and the agent behind it shipped by the same organisation that publishes the benchmark. The benchmark’s own paper, 116 tasks across 20 real apps, puts its best measured agent at 30.6%, and three seeds of that same configuration land at 27.6%, 26.3% and 33.2% — a mean near 29%, and a spread one trial can never show you. That paper also settles the targeting order: the identical model scored 30.6% on the accessibility tree alone against 25.4% when handed screenshots as well, so text first, pixels last. And all of it runs on an emulator where nothing on screen is trying to notice the agent, which is the one condition the hardened app in front of you does not share.
Four tools, one job each
The native-reversing toolchain
androguard is fast static recon. It lists the .so libraries an app ships and finds which classes declare native methods. Structure you can script, not readable source.

JADX decompiles Dalvik back to Java. It is how you read the managed side and find the exact class and method that crosses into native code. It stops at the native keyword, the handoff point.

Ghidra is the NSA's open framework. It disassembles a .so and decompiles it to pseudo C. It is the only tool here that reads native code, so the work centers on it. Run it headless so the workflow scripts cleanly and repeats exactly.

Frida injects a JS engine into the running process so you can hook and call functions live, and confirm your static reading against what the app actually does.
Two tells worth knowing before you start
Reading the library
Model the layers first. Managed Java and Kotlin builds the request and calls into native methods. A set of .so libraries does the sensitive work. Interceptors attach the anti-fraud headers. Name the layers before you open anything, so you target the one library that matters instead of all of them.

A small library is a good sign. A signing lib of a few hundred kilobytes has little room for a heavy obfuscator, and auto analysis that finishes fast with zero decompile failures tells you the binary is not packed or virtualised. C++ symbols that survived give you the function names for free.

Some values are not in the file. A fixed AES IV can live in .bss, which is zero-filled on disk and only set at runtime. Hash, HMAC, and Base64 modes do not care because their output is fully determined by input and key. The AES modes do, so you read that one value from the live process once and bake it in.
The payoff scales past the one app. Most apps that protect their API at all push the work into a native library, and a large share of those turn out to be plain, readable crypto you can rebuild byte for byte. Work it out once and it is the same move on the next app you open. Pair this with an agent that can drive the disassembler and you compress days of manual tracing into a guided loop, which is the next section's territory. An agent harness does not change what the libraries contain, and it does not supply the engines. Before wiring JADX, Ghidra, adb and Frida into one MCP server such as REA (about 180 tools, MIT), budget for the parts it leaves to you: each engine is caller-supplied, JADX runs through jadx-headless-mcp 0.7.1 on JDK 17 or newer, and the bundle-unminifying and firmware helpers run on Linux only. Its APK inspection reports signature verification as not performed, and its Frida tools run scripts you write inside the target with the app’s own network authority, so use an emulator and a test account, never a personal device. The project’s disclaimer refuses help bypassing bot protection or access controls on systems you are not authorised to test. Treat it as a faster way to read an app, and run any decision to replicate a signed call past the legal section first.
2 field notes on mobile Reported from practice. Click any card to open it.
+
Aug 2026 / Peer-reviewed
The Localhost Bridge: A Decade-Old Hole Between Web and Mobile, Fixed This Year
"Bridges to Self: Silent Web-to-App Tracking on Mobile via Localhost" took both the Distinguished Paper Award and the Internet Defense Prize at USENIX Security 2026, a month after the CNIL-Inria Privacy Protection Prize. The mechanism sat in the gap between the web platform's security model and the mobile platform's, went unaddressed for years, and was being actively exploited at scale.
💡 A native app listening on localhost turns any web page into a channel into that app
The shape of it: an installed native app opens a listener on localhost, and a script on an ordinary web page in the mobile browser can reach it. Neither platform's model is broken on its own terms. The browser is doing what it does with a local address, and the app is doing what any app may do with a socket. Between them, the browser's per-origin isolation is bypassed entirely, and a web session can be joined to a device-level app identity with no permission prompt and nothing visible to the user — which is exactly the linkage both platforms are supposed to prevent.

The outcome is the part worth noting, because security research often stops at the finding. This one produced concrete change: browser mitigations, a new Android permission, and regulatory attention. The authors are explicit that the changes mattered more than the awards.

Two reasons it belongs in a scraping guide. First, it is the cleanest recent example of a class this guide keeps returning to: the highest-value signals are not in any one layer's threat model but in the seam between two layers, where each component is behaving correctly and the combination leaks. TLS versus user agent, JA4 versus the JavaScript that runs after it, an app's signing logic versus the OS it trusts — same shape. Second, if you do mobile work, a localhost listener is now a permissioned, mitigated surface rather than a free one, so anything you built that leaned on app-to-web bridging should be re-tested rather than assumed. USENIX Security 2026
+
Learning / Mobile API reversing
The Mobile-API Reversing Toolchain: Frida + JADX + Ghidra
Your guide's #1 rule is "find the mobile API first", apps often hit the same backend with zero anti-bot. But intercepting a modern app means defeating its own protections (cert pinning, signed headers). The toolchain: Frida to hook and modify native Java/C functions at runtime, JADX to decompile the APK back to Java, Ghidra to decompile the native C, on a rooted Android emulator.
💡 MobileHackingLab ships a free Frida course with certificate
This is the practical skill that makes "scrape the mobile API" actually work in 2026, when apps defend their endpoints. The workflow: (1) JADX decompiles the APK to readable Java so you can find where the signed header or auth token is generated. (2) If that logic is in a native .so library (common for the sensitive bits), Ghidra decompiles the C/C++ to understand the algorithm. (3) Frida hooks those functions at runtime via injected JavaScript, so you can log the inputs/outputs, bypass certificate pinning, or call the signing function directly to mint valid headers, no need to fully reverse the algorithm if you can just invoke it. (4) Run all of this on a rooted Android emulator from Android Studio for a controlled, disposable lab. Pair with HTTPToolkit or mitmproxy to capture the now-decrypted traffic and recover the API contract. MobileHackingLab offers a free Android Frida course with a certificate and CTF-style challenges, the fastest way in if the toolchain feels intimidating. The payoff: once you can mint the app's signed headers, you call its clean JSON API directly and skip every browser-layer anti-bot entirely. MobileHackingLab free Frida course
16 Plain English

Scraping jargon
in simple terms

Every term that makes scraping documentation confusing, explained with an analogy.

Network layer
TLS Fingerprint
How your browser "shakes hands" when connecting securely. Chrome and Firefox shake hands differently, so a server can tell them apart before you send a single header. Analogy: recognising someone by the way they shake hands, firm, soft, awkward.
Network layer
HTTP Fingerprint
The order and style of your HTTP headers. A bot might say "I'm Chrome" but forget to include headers Chrome always sends. Analogy: like a boarding pass, if your name and flight number don't match the expected pattern, it's suspicious.
Network layer
TCP/IP Fingerprint
Looks at how your computer sends and receives internet packets. Windows and Linux send packets with subtle differences. Analogy: recognising someone's hometown by their accent, you didn't ask, they just gave it away by how they talk.
Browser layer
Canvas Fingerprint
Website secretly asks your browser to draw a hidden picture. Each GPU renders it slightly differently, that difference is your fingerprint. Analogy: asking 10 artists to draw the same tree, each drawing is unique even with the same instructions.
Browser layer
WebGL Fingerprint
Uses 3D graphics rendering to identify your GPU and driver. Same browser, different hardware, different fingerprint. Analogy: recognising a car engine by its sound, same model, but each engine has subtle variations you can hear.
Browser layer
Device Fingerprint
Collects OS, fonts, screen size, timezone, plugins, battery level, everything about your setup combined into a unique profile. Analogy: identifying someone by their full outfit + hairstyle + voice + habits. Change one thing, the combo is still unique.
Behavioural
Behavioural Analysis
Watching how you type, scroll, move your mouse. Bots move in straight lines at constant speed. Humans are messy and inconsistent. DataDome runs 35 behavioural signals in real-time. Analogy: a security guard watching body language, not what you say, how you move.
Challenge
Dynamic Challenges
The website throws mini-tests to check if you're real, CAPTCHA, Turnstile, proof-of-work puzzles. Kasada changes them constantly so you can't pre-solve. Analogy: a teacher changing exam questions mid-test to catch cheaters.
Network
IP Reputation
Whether your IP address is associated with known bots, VPNs, datacenters, or abuse. Datacenter IPs are instantly flagged. Residential IPs from real ISPs get highest trust. Analogy: your home address appearing on a blacklist, the doorman knows before you knock.
Compliance signal
robots.txt
A text file at /robots.txt telling crawlers which paths to skip. Works on voluntary compliance only. Googlebot and GPTBot respect it. Commercial scrapers send a Chrome User-Agent and walk straight past it. Analogy: a "staff only" sign. Anyone who cares about signs obeys it. Anyone who does not care walks in anyway.
17 How this is wired

The guide as a graph
because the links are the argument

Twenty sections and a hundred and forty field notes are a pile, not a model. What makes this subject learnable is that almost everything in it is an instance of about two dozen principles, and those principles attach to the layers a request is judged at. 203 nodes, 365 typed edges, and every section of the guide represented. The rings run outward from the spine: principles, then the layers a request is judged at, then methods, then tools and vendors, with the measured evidence and the case law on the rim. Click any node to see what it connects to, and why.

Drag a node around its ring · click to inspect · chips filter
Pick a node. The principles are the spine — start there if you want the short version of the whole guide.
Why this exists, and it is not decoration. The same structure is written into the LLM context this site hands out, as a typed index of nodes and edges ahead of the prose. A model reading a flat list of findings cannot tell that weeks lost to the wrong variable, fifteen stealth engines with identical 403s and the Akamai case are three pieces of evidence for one claim. Given the edges, it can. One data structure generates both the picture above and the text a model reads, so they cannot drift apart.
Text outline of the whole graph
18 Straight answers

The questions I
actually get asked

Twenty-two of them, answered in a paragraph each. Every answer points at a section above where the long version lives.

What is the best library for bypassing Cloudflare in 2026?
Camoufox is the strongest open-source option for bypassing Cloudflare in 2026, achieving a 100% pass rate in March 2026 benchmarks. It patches Firefox at the C++ level using Mozilla's Juggler protocol, making it undetectable via JavaScript inspection. For HTTP-only scraping, curl_cffi with impersonate='chrome131' handles most Cloudflare targets without a full browser.
What is JA4+ TLS fingerprinting and how does it affect web scraping?
JA4+ is a TLS fingerprinting standard that identifies scrapers before any HTTP headers are exchanged. It hashes the TLS ClientHello fields (cipher suites, extensions, ALPN) in a sort-stable way that survives Chrome's extension order randomisation. Cloudflare deploys JA4 in a Rust crate at CDN edge, Akamai in an EdgeWorker. Python's requests library has a unique JA4 hash that gets blocked instantly. The fix is curl_cffi, which impersonates real Chrome TLS down to HTTP/2 SETTINGS frames.
How do you bypass Akamai Bot Manager in 2026?
Akamai Bot Manager in 2026 probes 60 Chrome extension URLs via fetch() to detect headless browsers. Real Chrome always has at least a few extensions installed. LinkedIn does a particularly aggressive version of this — they probe for Grammarly, 1Password, and other popular extensions by fetching resources at known extension paths. Your scraper never touches the DOM, but the DOM tells on you anyway. The fix: use CloakBrowser (loads real extension profiles) or Camoufox (uses Juggler protocol, no CDP artifacts). Combine with residential or ISP proxies since Akamai flags datacenter ASNs instantly. Set geoip=True in Camoufox to align WebRTC, DNS, and timezone with your proxy exit country.
What is the difference between residential and datacenter proxies for web scraping?
Datacenter proxies are fast and cheap but carry known ASNs (AWS AS16509, GCP AS15169) that anti-bots flag immediately. Residential proxies route through real ISP connections, making traffic look like genuine users. In 2026, most protected sites block datacenter IPs outright. ISP proxies (static residential) offer the best of both: residential IP authority with datacenter speeds. Use datacenter for unprotected APIs, ISP for medium targets, rotating residential for the hardest targets like Cloudflare-protected e-commerce.
How do you scrape JavaScript-rendered websites with Python in 2026?
For JavaScript-rendered websites in 2026, use Camoufox (Python, patches Firefox at C++ level, bypasses Cloudflare), PatchRight (undetected Playwright drop-in, bypasses Kasada), or scrapy-stealth middleware (adds TLS fingerprinting and browser engine to Scrapy). For AI-powered extraction, Crawl4AI (60K stars) and Firecrawl (111K stars) convert pages to clean Markdown. Avoid plain Playwright without stealth patches — navigator.webdriver=true is trivially detected by all major anti-bots.
What is curl_cffi and why is it better than requests for web scraping?
curl_cffi is a Python library that wraps libcurl with BoringSSL patches to produce exact Chrome and Firefox TLS fingerprints. Unlike Python requests (which has a unique JA4 hash that anti-bots recognise instantly), curl_cffi sends a ClientHello identical to a real browser including HTTP/2 SETTINGS frames. It is 10-50x faster than browser automation and works as a drop-in requests replacement: curl_cffi.requests.get(url, impersonate='chrome131').
How do I intercept mobile app API traffic for scraping?
To intercept mobile app API traffic: install Android Studio and create a virtual device with API 30+, root it using rootAVD and Magisk, install HTTP Toolkit to intercept HTTPS traffic and bypass SSL pinning automatically. Once you capture the API request, replicate it with curl_cffi for production scraping. Mobile APIs serve the same data as the website but with far weaker anti-bot protection — no Cloudflare, no JA4 fingerprinting.
What is the best Scrapy anti-bot middleware in 2026?
scrapy-stealth is the most complete Scrapy anti-bot middleware in 2026. It adds TLS fingerprint spoofing, HTTP/2 impersonation, proxy rotation, fingerprint cycling, and a real browser engine via CDP — all unavailable in scrapy-playwright, scrapy-splash, or scrapy-selenium. It supports per-request engine switching via request.meta, so easy URLs use the fast HTTP engine while protected pages use the browser engine. Install with: pip install scrapy-stealth
Why does nodriver beat patched Playwright forks for anti-bot bypass?
The difference is automation-protocol fingerprinting, not fingerprint patches. Patched Chromium forks still drive the browser over the Chrome DevTools Protocol (CDP), which leaves detectable traces in how the browser is controlled. nodriver avoids the standard CDP automation surface, so on targets that fingerprint the automation protocol itself (rather than navigator properties), it passes where a heavily patched fork is still blocked. In a 7-tool benchmark across 31 protected targets, nodriver was the only tool with zero blocks.
What is fingerprint harvesting and why does it matter for scraping in 2026?
Fingerprint harvesting is a commercialised practice where real browser fingerprints are collected from genuine users and replayed against anti-bot systems. Tools embed JavaScript on real sites to capture authentic device profiles (canvas, WebGL, TLS, audio) and sell them for injection into automated sessions. The consequence for scrapers: when a stealth browser passes a canvas probe, it may be replaying a real harvested hash rather than spoofing one. Anti-bot vendors respond by adding replay-resistant signals like WASM SIMD CPU timing that require genuine hardware.
How do you scrape a mobile app when the request signature is in native code?
When an app signs its API requests inside a compiled native .so library, interception and a Java decompiler (JADX) stop at the native keyword. There are two approaches. If the native library is readable crypto, reverse it with Ghidra and reimplement the signing in Python to sign offline at any volume with no app in the loop. If it is a bytecode virtual machine you cannot practically rewrite, keep the app running and drive its own signer as an oracle your scraper calls. The toolchain is androguard for recon, JADX for the managed layer, Ghidra for native code, and Frida to confirm behaviour live.
Are AI agents replacing web scrapers in 2026?
No. AI agents that browse the web are themselves scrapers: they make HTTP requests, hit anti-bot walls, get rate limited, and need proxies and believable fingerprints. The only thing that changed is who writes the prompt. Teams shipping agents bought more proxy and unblocking infrastructure through 2025, not less, because every agent loop ends at a server that would rather not serve it. The detection and bypass techniques used for traditional scraping apply directly to agentic browsing.
Why is my scraper still getting 429 errors after changing proxy provider?
Because the rate limit may be keyed to your session, not your IP. A request with no cookies is anonymous and carries no history; a request with a session cookie carries an identity, and identities accumulate a reputation and a per-identity budget that can be exhausted. Before scaling infrastructure around the limit, send the same request with all state removed. If the anonymous request succeeds where the identified one fails, you were telling the server exactly who to rate limit.
Does using a proxy prevent HTTP/3, and does that matter for scraping?
Yes. HTTP/3 runs over QUIC, which runs over UDP, while HTTP proxies tunnel TCP only. SOCKS5 defines a UDP ASSOCIATE command but browsers do not expose it, so Chrome is TCP-only over SOCKS5 in practice and negotiates HTTP/2 the moment a proxy is configured. It matters because Cloudflare Radar puts likely-bot traffic at 73.75% on HTTP/1.1, 26.76% on HTTP/2 and 3.33% on HTTP/3, so a proxied browser sits on the wrong side of a hardening signal. The options are RFC 9298 UDP proxying (CONNECT-UDP), a network-layer VPN tunnel that carries UDP, or a provider that has implemented HTTP/3 proxying.
What is machine identity in web scraping?
Machine identity is the combination of signals a site uses to recognise and evaluate an automated visitor: IP reputation, browser fingerprint, TLS characteristics, cookies, headers, device information, session behaviour and geographic consistency, judged together rather than individually. It is why rotating IPs alone stopped working: you get blocked not for looking like a bot but for presenting a set of claims that cannot describe one real device.
Can I use the accessibility tree instead of CSS selectors for scraping?
Often yes, and it is more durable. A hashed class name like css-1x7bhq9 is a build artifact that disappears on the next bundle, while an element's role and accessible name are semantic contracts that cannot quietly change without breaking the page for screen readers. Playwright exposes this directly with getByRole and get_by_role. Coverage on the open web is patchy so keep a CSS fallback, but on large commercial sites and anything that has been through a compliance review the role and name are usually present.
Can an AI agent write a production web scraper?
It can write the code well and the instrumentation badly, and the instrumentation is the part you live with. Reading a full agentic scraping toolchain end to end surfaced ten repeatable failure modes, including recon that keeps only the HTML and discards every JSON response, tests written by the same model that wrote the extractor, coverage checks that only flag fields at zero percent, rows carrying no source URL or fetch time, and a background watcher that reports a job as still running when it never reached the API.
Why do some websites serve different content to AI crawlers?
Because the user agent is becoming a commercial identity rather than just a client string. TIME.com answers the same article URL with roughly 303,000 bytes of HTML for a browser and about 13,000 bytes of markdown for ClaudeBot, PerplexityBot and OAI-SearchBot, refuses GPTBot with a 406, and embeds sponsored blocks and impression-counting headers in the machine version. For a scraper this is dangerous precisely because it is not a block: the status is 200, the parse succeeds and the record count looks plausible, but the document is a different edition of the page.
Why can't Claude or ChatGPT reach my page even though it is public?
Claude's fetch tool is provenance-gated: it can only retrieve URLs that have already appeared in the conversation context, such as user messages, prior search results or prior fetch results, and it cannot follow a URL it constructed itself. A relative link inside HTML it already fetched is not enough, and neither is an llms.txt or a markdown version. That makes the search index the gatekeeper rather than your link graph, which for Claude means Brave Search. The design is an exfiltration defence, so it is not going to be relaxed.
What is a self-healing scraper and how does it work?
A self-healing scraper detects that extraction has drifted, regenerates the broken selector, verifies the fix against a test, and ships it without a human writing code. The parts that make it work in production are drift detection on item counts and field fill rates compared with the last good run rather than against zero, a repair path that fixes one field instead of rewriting the project, provenance on every row so you can tell when the break started, and a context-blind verifier that judges the output rather than the process that produced it.
How do you detect which platform or CMS a website is built on?
Score signals by how expensive they are to customise, and never detect on anything a theme can change. Platform-set cookie names, endpoints that ship regardless of theme, asset path conventions and response header quirks all survive a redesign; generator meta tags are worth including but weighting lowest, because they are the first thing an agency strips. Keep it scored rather than binary and store the confidence next to the label so a weak classification is visible rather than silently wrong.
What is WebMCP and does it replace web scraping?
WebMCP lets a website declare structured tools that a browser agent can discover and call, and Cloudflare now injects the bridge for any site on its network. It does not replace scraping. The action layer usually proxies to an MCP server the site still has to build, the tools only activate for an agent actually driving a browser, and the declared tool surface and the rendered HTML are maintained by different code and can disagree. Check for a tool surface before writing a selector, then diff what it returns against the page at least once.
19 Community

Where scrapers
talk to each other

The best scraping techniques rarely come from documentation, they come from people who've already hit the same wall you're hitting. These communities are where the real knowledge lives. For fundamentals on video, add these channels, and expect most of them to teach on targets with little or no bot protection, so pair them with the detection sections of this guide. Michael Mintz: the SeleniumBase author on UC Mode and browser automation. Cole Medin: AI agents, including Crawl4AI with n8n. freeCodeCamp: a 4.5-hour Scrapy course covering proxies, deployment and scheduling, made with ScrapeOps. NeuralNine: Python and AI tutorials, with some scraping. Corey Schafer: Python fundamentals, Requests and BeautifulSoup. Keith Galli: project-based scraping and data analysis. Unclecode: a small channel from the Crawl4AI author on crawling and Docker deployment. Apify, Firecrawl and Playwright: official channels, so expect their own products. John Watson Rooney, further down this section, is still the place to start.

Discord servers

Recommended ★
The Web Scraping Club
Pierluigi Vinciguerra
The community behind the best scraping newsletter. Anti-bot discussions, real-world bypasses, tool comparisons. Active and high-signal. Recommended first stop.
Official · Experts on call · 4,600+ members
Oxylabs
discord.com/invite/DffyvAcg2w
Live Q&As with Oxylabs in-house engineers, webinars on proxies and anti-bots, OxyCon conference channel, and exclusive product discounts. Self-described as "#1 community for all things web scraping." Strong on practical bypass questions and proxy strategy. Active staff presence makes it a useful place to surface real production issues.
Zyte-powered · Annual summit
Extract Data Community
discord.gg/extractdata
One of the largest web scraping servers. Powered by Zyte. Associated with the annual Extract Summit conference. Thousands of members, high-activity channels.
Anti-bot experts · Reverse engineers
Scraping Enthusiasts
Anti-bot reverse engineering focus
Home of puppeteer-extra-plugin-stealth maintainers. Community bots that detect anti-bot software, find hidden API endpoints, and monitor anti-bot updates. Best server for hard targets.
Python · Node.js · Production
Scraping In Prod
General web scraping questions
General scraping questions, site-specific help, anti-bot discussions. Python and Node.js focused. Good place to ask about specific websites or frameworks.
Official Scrapy · Framework experts
Scrapy Official
Scrapy core maintainers active
Official Scrapy community. Core maintainers are active daily. Best place for Scrapy-specific questions, extensions, architecture advice, and code reviews.

Reddit communities

Newsletters worth reading

Free learning resources

Resources from The Web Scraping Club

YouTube channels worth following

Added August 2026 things worth your evening, not just your bookmarks bar

Preprint · Methodology
Evaluating LLM-based extraction tools
Jamie Cummins · PsyArXiv
A guide to systematically validating an LLM-based extraction or judgement tool, written by a metascientist who had to work it out several times from scratch. Worth reading if you have ever shipped an extractor whose only evidence of correctness was that the output looked right. The problem it addresses is the one this guide keeps hitting from the engineering side — a model grading its own output is a regression detector, not a correctness test — approached from the research-integrity side instead, which is where the measurement discipline actually lives.
Testing tool · Actively developed
fingerprint-scan.com
Antoine Vastel · Head of Research at Castle
A fingerprinting and bot-detection playground from one of the few academics who publishes seriously on browser fingerprinting. Newly rebuilt, and the design choice worth noting is that the fingerprint hash is split into sub-hashes rather than served as one opaque value, so you can see which group of signals moved rather than only that something did. Detection is deliberately basic for now — CDP detection, webdriver, some inconsistency checks — with more advanced work and fingerprint-tracking logic in progress. Treat it as an honest instrument rather than a verdict: as the fingerprint-dilemma piece argues, a test page showing different values measures your tool, not the detector.
Four years in · 7,000+ readers
The Web Scraping Club, at four
Pierluigi Vinciguerra · the anniversary post is worth reading as industry history
Written in August 2026, four years after launch, and it captures the shift better than any market report: scraping went from "something shady for spying on your competitors" to the main moat for training LLMs. His marker for how far the money moved is a good one, and hard to argue with: had you ever heard of a funding round for a proxy company before 2023? Nimble and Oxylabs both raised since. Grown to 7,000+ subscribers with no marketing, and survived Google removing the domain from search results entirely. He has also announced a scraping job board and a web scraping conference in Italy this October.
Free book ★ · Read it slowly
Web Browser Engineering
browser.engineering
You build a working web browser, in Python, a few hundred lines at a time: sockets and TLS, an HTML parser, layout, styling, a JavaScript runtime, then compositing and accessibility. This is the single best answer to "why does my scraper behave differently from Chrome". Almost everything in the detection section of this guide, from why the accessibility tree exists to why headless rendering diverges to what networkidle actually waits for, stops being trivia once you have written the thing yourself. If you only read one long text this year, make it this one.
New monthly programme
Tool of the Week
The Web Scraping Club · two days a month, tools only
The Web Scraping Club now dedicates two days a month purely to hands-on tool reviews rather than technique posts. The first edition covers Bright Data's Scraper Studio, the prompt-to-scraper builder aimed at teams tired of rewriting selectors after every DOM change. Useful as a standing counterweight to vendor benchmarks, because the reviews are run by someone with no stake in the result.
Continuous measurement · Beta 2026
Automation Benchmark
Tyler Richards · tool and provider monitoring
A service built on the observation this guide keeps arriving at from other directions: a stack that was correct three months ago is a stack you have not tested. Continuous monitoring of automation tooling and providers, with notifications of what changed and what it means for you, rather than a one-off comparison table that starts rotting the day it ships. Sells information, not proxies or browsers, which is a rarer position than it should be.
Conference · Free and online
OxyCon 2026
16 September 2026
Free, online, and the running order is unusually engineering-heavy this year: creating datasets from prompts; where AI actually helps in production scraping (the argument being that AI belongs in reliable access and autonomous code changes, not as the parsing engine); the cost of solving the wrong web-access problem; and a panel on what a closing web means for businesses, journalists and researchers.
Conference · In person
Extract Summit 2026
Austin, Texas
The long-running web-data conference, run out of the Extract Data Community. Zyte have been trailing it with a short drama series whose second episode is built entirely around the failure mode in the Post-Extraction section of this guide: a pipeline whose dashboards are green, whose status codes are valid, whose schemas pass validation, and every record of which is false. Worth watching even if you never go.

Oct 2026 update. Austin ran 7–8 October at Brazos Hall; the European edition is in Dublin on 10–11 November. Day 1 is five workshops, including one on browser automation for dynamic sites, aimed at pages that render in your browser and come back empty to the scraper, the Tier 3 case in the decision flow. Day 2 is talks, which the organiser says go to the Extract Summit YouTube channel.
Training · Four days, hands on
Software Deobfuscation for Agentic RE
Tim Blazytko · CanSecWest, Vancouver, 26–29 Sept 2026
Defeating the obfuscation used in malware and commercial software protection, combining classical program analysis with agent-assisted workflows to recover logic from multi-layered binaries. Directly relevant if your work has reached the mobile-reversing end of this guide, where the signing logic lives in a stripped native library and the honest options are rebuild it or let the app sign for you.
4 field notes on the community Reported from practice. Click any card to open it.
+
Aug 2026 / The other side
Somebody Is Writing Up Your Toolkit For the Defence
A security researcher at a university was handed a task most of this guide’s readers would recognise from the wrong end: analyse how Cloudscraper works, and what it means for domains behind Cloudflare. The output was internal documentation, written for technical and non-technical readers alike.
💡 The same map, read by the people deciding what to turn on
The reading list is the interesting part, because it is almost exactly this guide’s table of contents seen from the defending side: how automated HTTP clients differ from real browsers, how challenges and clearance cookies work, why TLS and HTTP fingerprints matter for detection, where Python-based tools hit their ceiling, how newer tools imitate browsers more closely — and then the part collectors rarely think about: what abuse this enables against public university web services, specifically form spam, scraping, and unnecessary backend load.

Cloudscraper itself is the right specimen for that write-up. It extends requests.Session with logic for older Cloudflare JavaScript challenges — which is to say it is a transport-level answer to a problem that moved years ago. This guide has made that case repeatedly: a tool that solves the challenge of 2019 tells a 2026 detector exactly what it is, because the giveaway is the handshake underneath rather than the challenge logic on top.

Why it belongs in this feed. Two things follow from a defender writing this down. The first is practical: the countermeasures on the sites you meet are increasingly informed by documentation of your specific tools, by name, so the half-life of any named bypass is set by how fast that documentation circulates. The second is about posture. The targets in that write-up are public university services — not a retailer with a pricing team, but a body with a duty to stay reachable and a small budget for standing back up. It is a reasonable moment to check that the thing you point at a .edu is a considered choice rather than the same profile you use on a marketplace.
+
Aug 2026 / The long view
“Web Scraping Has Been Dying Since 2022, and Yet We Are Still Here”
From someone who has been scraping since 2012, a tidy four-year obituary column: 2022, ChatGPT launches — why scrape when the model knows everything. 2023, GPT-4 reads screenshots — scrapers are obsolete. 2024, RAG is everywhere — let the model fetch what it needs. 2025, agents browse on their own — nobody will write a scraper again. Then he opened Google Trends.
💡 LLMs did not replace web scraping. They became its largest client.
Searches for the term have never been higher than in 2026, with the all-time peak now. The explanation is not that the predictions were stupid; it is that they mistook which side of the transaction models would sit on. Training needs data, so every lab runs crawlers — open your own server logs and count GPTBot, ClaudeBot and Bytespider, a number this guide has documented rising 305% and 2,825% year on year for individual agents. And inference needs live data, so every agent comparing a price, checking availability or filling a form is doing browser automation against a real website, right now.

The second-order effect is the one worth sitting with. For a decade this was the trade nobody admitted to at conferences. The AI wave made it a board-level topic and handed legitimacy to an industry that had operated half in the shadows. That is genuinely good for anyone doing it carefully — and it is also why the legislation, the licensing deals and the metering described elsewhere in this feed arrived all at once. Legitimacy and regulation are the same event viewed from two seats.

Worth holding against the guide's own scepticism: the obituary being wrong four times running does not mean it is wrong forever, and “search interest is at an all-time high” is a measure of attention rather than of practitioners. But the mechanism offered here — that models consume the web rather than replacing the need to read it — is the strongest available account of why the prediction keeps failing, and it does not depend on the trend line.

Oct 2026 update. The same author says scraping teams now keep asking whether they are keeping up with AI, and has announced a December 2026 workshop on how to use it. As of 19 October no workshop page, date, price or agenda was public; the newsletter archive and about page were checked. Housekeeping: substack.thewebscraping.club and thewebscraping.club now 301-redirect to www.scraping.club, so older links still resolve.
+
Aug 2026 / Disclosure economics
The Man Who Found the Supercookie Sells Browser Tracking For a Living
The companion to the ShaderGhost card, and the more uncomfortable half. Two weeks before the release, its author wrote the thing almost nobody in security says out loud: he had found an exploit chain letting anyone write an arbitrary 32-bit supercookie to 99% of browsers by market share, and he runs a company that tracks browsers as a service.
💡 Privacy bugs are priced far below memory-safety bugs, so the rational move is to keep them
His own framing: it is depressing how little incentive there is to report it, because his whole business revolves around exactly this capability. He had, in his words, hit pay dirt. And the bounty maths does nothing to change that. He estimated Apple — a vendor that genuinely does invest in privacy — would pay in the region of $1,000 for this because it is classed low risk, against roughly $5,000 for a sandbox escape chain. A durable, unclearable, cross-site tracking identifier affecting nearly every browser is worth a fifth of a memory-safety bug.

Why that gap matters more than the technique. Bounty programmes encode what a vendor thinks it can be hurt by, and the hierarchy runs roughly: remote code execution, then sandbox escape, then everything else. A privacy vulnerability produces no crash, no compromised device and no incident report, so it lands in "everything else". The predictable consequence is that the people best placed to find these have a commercial reason not to file them, and the ones who do file get a sticker. He also makes the point that follows: AI is making bug-finding dramatically cheaper, so the volume of findable issues is rising while the incentive to disclose this particular class stays flat.

The practical reading for anyone on either side of detection. Assume a meaningful backlog of unreported, hardware-adjacent, cross-site identification techniques already exists inside companies whose business model rewards holding them. If you are scraping, that is another argument for treating "fresh profile" as a claim rather than a fact, and for putting real machines behind sensitive work. If you are defending, it is a reason to be sceptical of a vendor's originality claims: this capability class is more widely held than published research suggests, because publishing is the least profitable thing you can do with it. He notes separately that the bot-detection industry has one of the highest talent churn rates he has seen, sometimes weeks, which is part of why vendor behaviour is less coherent than the marketing implies.
+
Ecosystem shift
Web Scraping Is Becoming Distributed Adversarial Systems Engineering
Most scraping frameworks still assume the web is static. Modern websites are adversarial runtime environments. The shift from HTML parsing to infrastructure engineering is well underway.
💡 The future of autonomous AI agents may depend on infrastructure that can operate reliably across hostile, dynamically changing web environments.
The conventional framing: web scraping is a tool for extracting data from websites.

The 2026 framing: web scraping is distributed adversarial systems engineering.

What modern infrastructure has to operate against:
• Fingerprinting systems (TLS JA4, canvas, WebGL, WASM SIMD)
• Behavioural detection (mouse physics, scroll timing, inter-request jitter)
• Anti-bot orchestration (Akamai EdgeWorker, Cloudflare Worker, DataDome middleware)
• Cloudflare interstitials and Turnstile challenges
• Dynamic runtime rendering (SPAs, hydration, lazy loading, service workers)
• Session-aware defences (trust accumulation, per-session scoring, unclearable cookies)

This is what makes tools like Scrapling architecturally interesting beyond "another Python scraper." It combines stealth browser execution, TLS fingerprint impersonation, adaptive element tracking that survives DOM changes, session-aware orchestration, proxy rotation, and MCP-based AI extraction. Not a scraper. A runtime.

The systems properties that matter now:
• Runtime orchestration (not just retries, state-aware crawl management)
• Observability (what failed, at which layer, on which request)
• Adaptive recovery (selector drift, DOM changes, anti-bot updates)
• State persistence (session trust, cookie chains, cross-request identity)
• Stealth execution (not a flag, an architecture)
• Infrastructure resilience (circuit breakers, session warmup, fallback tiers)

Once AI agents start interacting with the web autonomously at scale, reliability becomes a systems problem first and a parsing problem second. The pipeline that Firecrawl, Crawl4AI, Stagehand, and Scrapling are converging on is not "scraper plus LLM." It is a resilient extraction runtime with LLM as one processing layer among many.

Framing via D4Vinci (Scrapling author), May 2026.
20 The arms race

From IP bans
to transformer ML

Every bypass technique was born as a direct response to a specific detection innovation. The escalation explains why each tool exists.

2004
Selenium born for QA testing. WebDriver protocol. Zero anti-bot thinking, scraping wasn't a threat concept yet.
2017
Puppeteer launches. First CDP Chrome automation. Anti-bots respond with IP reputation + User-Agent checks. Defeated by: proxy rotation + fake UA strings.
2018–2020
JS fingerprinting era. Canvas hash, WebGL, navigator.webdriver=true. playwright-stealth emerges. Playwright 2020, Microsoft, cross-browser. F5 acquires Shape Security for $1 billion.
2021–2022
TLS fingerprinting mainstream. JA3 at CDN edges. Python httpx identified by hash. curl_cffi emerges. DataDome + Akamai add behavioural scoring. PerimeterX + HUMAN Security mergecreating 29,650-site network.
2023–2024
JA4+ standardisation. Cloudflare Rust crate. Chrome randomises extensions, breaks JA3. Camoufox and CloakBrowser emerge as first C++ binary solutions. DataDome WASM boring_challenge. Scrapling first published (repo created Oct 2024).
2025
ML + OS-level signals. HTTP/2 SETTINGS frames. TCP TTL. Transformer ML on micro-timing at <1ms. DataDome 85K models. Browserbase $40M at $300M valuation. Firecrawl 60K stars. ScrapingBee acquired by Oxylabs.
2026
C++ binary patches are the baseline. JS injection obsolete, toString() exposes all patches. Camoufox 100% pass rate. Firecrawl 111K stars. webclaw Rust + 10 MCP tools. JA4+ universal. Market: $7.5B → $38B by 2034. The arms race is AI vs AI.
★ You made it

Thank you for reading.

This is everything I know about web scraping in 2026, every detection layer, every anti-bot system, every library, every architecture I've actually built or used in production over the last seven years.

If even one section saved you a late night of debugging, that's why I wrote it.

Build something interesting with this. And if you do, I'd genuinely love to hear about it.

And if you are stuck rather than shipping, write to me at scrapesync@gmail.com. I answer scraping questions for free, including from people who'll never hire me. It is also what I do for a living, at ScrapeSync, but that's context for who is replying, not a pitch. If you want a pipeline designed, an agentic workflow built, or an architecture review rather than just an opinion, that is the paid version and the same address works.

Asad Ikram
Data Engineer · Scraping specialist · Lahore, PK