<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[The Web Scraping Club]]></title><description><![CDATA[News, solutions and interviews about web scraping.
In this substack you will find weekly content about:
- Web Scraping techniques
- Interviews with key people in the industry
- Anti bot infos and counter measures
- Real world examples and code]]></description><link>https://substack.thewebscraping.club</link><image><url>https://substackcdn.com/image/fetch/$s_!gJt2!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F1e343ec9-7946-4440-8c00-57209a1d99a1_1024x1024.png</url><title>The Web Scraping Club</title><link>https://substack.thewebscraping.club</link></image><generator>Substack</generator><lastBuildDate>Sat, 25 Jul 2026 08:17:54 GMT</lastBuildDate><atom:link href="https://substack.thewebscraping.club/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[The Web Scraping Club SRL]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[pier@thewebscraping.club]]></webMaster><itunes:owner><itunes:email><![CDATA[pier@thewebscraping.club]]></itunes:email><itunes:name><![CDATA[Pierluigi Vinciguerra]]></itunes:name></itunes:owner><itunes:author><![CDATA[Pierluigi Vinciguerra]]></itunes:author><googleplay:owner><![CDATA[pier@thewebscraping.club]]></googleplay:owner><googleplay:email><![CDATA[pier@thewebscraping.club]]></googleplay:email><googleplay:author><![CDATA[Pierluigi Vinciguerra]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[THE LAB #111: The OSS browser automation stealth benchmark]]></title><description><![CDATA[Fifteen tools against two live bot detectors, and only four passed the strict one.]]></description><link>https://substack.thewebscraping.club/p/open-source-browser-automation-stealth-benchmark</link><guid isPermaLink="false">https://substack.thewebscraping.club/p/open-source-browser-automation-stealth-benchmark</guid><dc:creator><![CDATA[Pierluigi Vinciguerra]]></dc:creator><pubDate>Thu, 23 Jul 2026 03:00:05 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/155e990f-2e5f-4108-ad1c-7c47fc836d2f_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Every few months, I see a new &#8220;undetectable&#8221; browser automation library pop up on GitHub. The README always promises the same things: no <code>navigator.webdriver</code>, patched fingerprints, Cloudflare bypasses. These claims are easy to make but hard to verify. If you choose the wrong tool, you might not notice at first. Your scraper keeps running, pages load, but the data slowly turns into soft-blocked junk that looks fine until you actually check it.</p><blockquote><p>Give your AI a web data layer &#8211; <strong><a href="https://visit.decodo.com/rE0PQB">Decodo&#8217;s Web Scraping API</a></strong> turns any site into clean, structured data your models can actually use.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://visit.decodo.com/rE0PQB" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aRfI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 424w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 848w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 1272w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aRfI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png" width="724" height="362" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:512,&quot;width&quot;:1024,&quot;resizeWidth&quot;:724,&quot;bytes&quot;:716985,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://visit.decodo.com/rE0PQB&quot;,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/204450518?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!aRfI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 424w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 848w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 1272w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"></figcaption></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://visit.decodo.com/rE0PQB&quot;,&quot;text&quot;:&quot;Activate starter plan&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://visit.decodo.com/rE0PQB"><span>Activate starter plan</span></a></p></blockquote><blockquote><div><hr></div></blockquote><p>I wanted a number I could actually trust, not just another marketing claim, and started to write this piece. Then I saw the great work on the Tyler Richards website (<a href="https://richards.foo/tools/stealth-benchmark">richards.foo/tools/stealth-benchmark</a>) and took some inspiration from it.  My idea was to take the open source Python automation stack, point each tool at the same signals a bot detector checks in JavaScript, and score them all the same way. In the past, I have written about anti-detect browsers and fingerprint test pages, but those were about commercial GUI browsers. This time, I focused on the libraries you actually <code>pip install</code> and use in code.</p><p>The whole test harness is <a href="https://github.com/TheWebScrapingClub/thelab">in our GitHub repository reserved for paying users, inside the folder </a><strong><a href="https://github.com/TheWebScrapingClub/thelab">111.</a></strong><a href="https://github.com/TheWebScrapingClub/thelab">STEALTH_BENCHMARK</a>. You can run it yourself and get your own results.</p><h2>The tools I put on the bench</h2><p>I grouped the fifteen tools into families, so the scores are easier to compare.</p><p>My two baselines are plain <a href="https://playwright.dev/python/">Playwright</a> and plain <a href="https://www.selenium.dev/documentation/">Selenium</a>, with no stealth and no patches. These are the control group, and they are supposed to get caught. If a &#8220;stealth&#8221; tool scores the same as vanilla Playwright, that tells you a lot.</p><p>The Playwright stealth patches are <a href="https://github.com/Kaliiiiiiiiii-Vinyzu/patchright-python">patchright</a>, <a href="https://github.com/rebrowser/rebrowser-patches">rebrowser-playwright</a>, and <a href="https://github.com/QIN2DIM/undetected-playwright">undetected-playwright</a>. These keep the Playwright API and try to remove specific automation leaks underneath it.</p><p>The CDP-native group is <a href="https://github.com/autoscrape-labs/pydoll">pydoll</a>, <a href="https://github.com/ultrafunkamsterdam/nodriver">nodriver</a>, and <a href="https://github.com/cdpdriver/zendriver">zendriver</a>. They drop WebDriver entirely and talk to Chrome over the DevTools Protocol. nodriver is the official successor to undetected-chromedriver, written by the same author, and zendriver is a maintained fork of nodriver.</p><div><hr></div><blockquote><p><em>Undetectable browsers are getting impressively capable, but even the stealthiest one stalls when its connection lets it down. anyIP&#8217;s residential and mobile proxies pair real-user IPs with 99.9% uptime - so the network is never the weak link</em></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://anyip.io/?utm_source=thewebscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=gold_2026_4&amp;utm_content=banner_undetectable_browsers" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gs8k!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10e0c226-5c6e-45e7-ae8b-b34d38e570f3_4585x2292.png 424w, https://substackcdn.com/image/fetch/$s_!gs8k!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10e0c226-5c6e-45e7-ae8b-b34d38e570f3_4585x2292.png 848w, https://substackcdn.com/image/fetch/$s_!gs8k!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10e0c226-5c6e-45e7-ae8b-b34d38e570f3_4585x2292.png 1272w, https://substackcdn.com/image/fetch/$s_!gs8k!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10e0c226-5c6e-45e7-ae8b-b34d38e570f3_4585x2292.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gs8k!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10e0c226-5c6e-45e7-ae8b-b34d38e570f3_4585x2292.png" width="726" height="363" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/10e0c226-5c6e-45e7-ae8b-b34d38e570f3_4585x2292.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:728,&quot;width&quot;:1456,&quot;resizeWidth&quot;:726,&quot;bytes&quot;:962001,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://anyip.io/?utm_source=thewebscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=gold_2026_4&amp;utm_content=banner_undetectable_browsers&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/208113514?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10e0c226-5c6e-45e7-ae8b-b34d38e570f3_4585x2292.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gs8k!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10e0c226-5c6e-45e7-ae8b-b34d38e570f3_4585x2292.png 424w, https://substackcdn.com/image/fetch/$s_!gs8k!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10e0c226-5c6e-45e7-ae8b-b34d38e570f3_4585x2292.png 848w, https://substackcdn.com/image/fetch/$s_!gs8k!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10e0c226-5c6e-45e7-ae8b-b34d38e570f3_4585x2292.png 1272w, https://substackcdn.com/image/fetch/$s_!gs8k!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10e0c226-5c6e-45e7-ae8b-b34d38e570f3_4585x2292.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://anyip.io/?utm_source=thewebscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=gold_2026_4&amp;utm_content=banner_undetectable_browsers&quot;,&quot;text&quot;:&quot;Build with anyIP &#8594;&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://anyip.io/?utm_source=thewebscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=gold_2026_4&amp;utm_content=banner_undetectable_browsers"><span>Build with anyIP &#8594;</span></a></p><p></p></blockquote><div><hr></div><p>Then the browsers that rebuild the fingerprint instead of patching flags on top of a normal Chrome. <a href="https://github.com/daijro/camoufox">camoufox</a> and <a href="https://github.com/feder-cr/invisible_playwright">invisible_playwright</a> are patched Firefox builds that rewrite the fingerprint at the C++ level. <a href="https://github.com/CloakHQ/CloakBrowser">CloakBrowser</a> does the same to Chromium, shipping its own patched binary with a drop-in Playwright API. <a href="https://github.com/rayobyte-data/rayobrowse">RayoBrowse</a> is a self-hosted stealth Chromium from Rayobyte that runs in a Docker container and serves a fingerprint picked from a database of real devices. <a href="https://github.com/h4ckf0r0day/obscura">Obscura</a> is the odd one out, a headless browser engine written in Rust that embeds V8 and ships its own browser rather than driving Chrome at all. And <a href="https://github.com/D4Vinci/Scrapling">scrapling</a> through its StealthyFetcher wraps a stealth Chromium, while <a href="https://github.com/ultrafunkamsterdam/undetected_chromedriver">undetected-chromedriver</a> is patched Selenium.</p><h2>What the detector actually sees</h2><p>A bot detector is not just one thing, so before testing, you have to decide which layer you are actually targeting. In practice, a real detector looks at four main areas: the JavaScript fingerprint surface, the transport (TLS and HTTP/2), request timing, and the proxy exit. For this benchmark, I focused only on the JavaScript fingerprint surface and ignored the rest on purpose.</p><p>This is not about being lazy. Scoping it this way is what keeps the results honest. Transport fingerprinting mostly depends on the browser&#8217;s own network stack, which is the same whether you use Chromium or Firefox, no matter which Python library you use. Timing and behavior are a different topic, more about mouse movement and dwell time. Proxy reputation is about your IP, not your tool. So the real question I can answer by swapping libraries is simple: when a detector&#8217;s JavaScript runs inside the page, what does it actually find?</p><p>The detector looks for a fixed list of tells. Is <code>navigator.webdriver</code> true. Does the user agent say <code>HeadlessChrome</code>. Is the WebGL renderer a software rasterizer like SwiftShader instead of a real GPU. Are there leftover automation globals on <code>window</code>, like the old chromedriver <code>cdc_</code> variables or a framework&#8217;s init scripts. Does the <code>chrome</code> object have the right properties. Do the user agent, <code>navigator.platform</code>, and the Client Hints all agree on the OS. The knowledge base on richards.foo lists hundreds of these, but I pulled the highest severity ones into my own probe.</p><h2>How I set up the test</h2><p>At the core, I use a single HTML page called <code>probe.html</code>, served from a small local server. Each tool loads this page, the page reads its own fingerprint in JavaScript, and writes the result back into the DOM as base64 JSON. I use base64 so I do not run into any HTML-escaping issues when I pull the rendered page from each driver.</p><p>To keep the harness manageable, I wrap every tool, no matter how different its API, in a single method:</p><pre><code><code>def render(url, wait_selector=None, timeout=30000, settle_ms=6000, screenshot_path=None) -&gt; str:
    # launch in the tool's stealth config, goto(url), wait, return final HTML, close</code></code></pre><div><hr></div><blockquote><p><em>The new anti-bot solution - Toughest walls, lowest price. Claim your free 10 000 requests with coupon code WSCLUB</em></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://scrapeunblocker.com/?utm_source=wsclub&amp;utm_medium=email&amp;utm_campaign=free-10k" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0jGM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaf4debc-e56d-4592-992d-02cbb3112a17_2400x1200.png 424w, https://substackcdn.com/image/fetch/$s_!0jGM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaf4debc-e56d-4592-992d-02cbb3112a17_2400x1200.png 848w, https://substackcdn.com/image/fetch/$s_!0jGM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaf4debc-e56d-4592-992d-02cbb3112a17_2400x1200.png 1272w, https://substackcdn.com/image/fetch/$s_!0jGM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaf4debc-e56d-4592-992d-02cbb3112a17_2400x1200.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0jGM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaf4debc-e56d-4592-992d-02cbb3112a17_2400x1200.png" width="724" height="362" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/baf4debc-e56d-4592-992d-02cbb3112a17_2400x1200.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:728,&quot;width&quot;:1456,&quot;resizeWidth&quot;:724,&quot;bytes&quot;:493234,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://scrapeunblocker.com/?utm_source=wsclub&amp;utm_medium=email&amp;utm_campaign=free-10k&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/208113514?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaf4debc-e56d-4592-992d-02cbb3112a17_2400x1200.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0jGM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaf4debc-e56d-4592-992d-02cbb3112a17_2400x1200.png 424w, https://substackcdn.com/image/fetch/$s_!0jGM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaf4debc-e56d-4592-992d-02cbb3112a17_2400x1200.png 848w, https://substackcdn.com/image/fetch/$s_!0jGM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaf4debc-e56d-4592-992d-02cbb3112a17_2400x1200.png 1272w, https://substackcdn.com/image/fetch/$s_!0jGM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaf4debc-e56d-4592-992d-02cbb3112a17_2400x1200.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://scrapeunblocker.com/?utm_source=wsclub&amp;utm_medium=email&amp;utm_campaign=free-10k&quot;,&quot;text&quot;:&quot;Claim your credits&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://scrapeunblocker.com/?utm_source=wsclub&amp;utm_medium=email&amp;utm_campaign=free-10k"><span>Claim your credits</span></a></p></blockquote><div><hr></div><p>Whether it is Playwright sync, Selenium&#8217;s <code>execute_script</code>, pydoll and nodriver&#8217;s async CDP calls, scrapling&#8217;s <code>StealthyFetcher.fetch</code>, CloakBrowser&#8217;s <code>launch()</code>, RayoBrowse over a Docker CDP socket, or Obscura over its Rust CDP server, they all get wrapped by <code>render(url) -&gt; html</code>. I use the same method for the probe and the two oracle pages, so I never have to special-case a driver. Here is the full vanilla Playwright adapter, and it does not get simpler than this:</p><pre><code><code> from playwright.sync_api import sync_playwright

def render(url, wait_selector=None, timeout=30000, settle_ms=6000, screenshot_path=None):
    with sync_playwright() as p:
        browser = p.chromium.launch(channel="chrome", headless=True)
        page = browser.new_page()
        try:
            page.goto(url, wait_until="load", timeout=timeout)
            if wait_selector:
                page.wait_for_selector(wait_selector, timeout=timeout)
            else:
                page.wait_for_timeout(settle_ms)
            if screenshot_path:
                page.screenshot(path=screenshot_path, full_page=True)
            return page.content()
        finally:
            browser.close()</code></code></pre><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.youtube.com/@thewebscrapingclub&quot;,&quot;text&quot;:&quot;Check the TWSC YouTube Channel&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.youtube.com/@thewebscrapingclub"><span>Check the TWSC YouTube Channel</span></a></p><div><hr></div><p>For every Chrome-based tool, I used <code>channel="chrome"</code> to drive the real installed Chrome, not Playwright&#8217;s bundled Chromium. This choice is important. Playwright&#8217;s bundled Chromium in headless mode reports a software WebGL renderer (SwiftShader), which is just a headless artifact, not something the tool did. When I point the same tools at real Chrome, the WebGL renderer shows the actual Apple M2 GPU. By using real Chrome everywhere, the tests I measure reflect the tool&#8217;s stealth, not which Chromium binary happened to ship. The engine-level browsers like Camoufox, CloakBrowser, and Obscura ship their own binary, so they use that instead.</p><p>I ran everything in headless mode on purpose, since that is both harder and more common for scraping. Headed mode hides some of these tells automatically, and I mention that where it is relevant.</p><p>I do not just trust my own score, so I use two outside referees. My probe gives a 0 to 100 score by subtracting a weight for each tell the tool leaks. Then I check two open bot-detection pages for an independent verdict: <a href="https://www.browserscan.net/bot-detection">BrowserScan</a> and <a href="https://deviceandbrowserinfo.com/are_you_a_bot">deviceandbrowserinfo</a>. deviceandbrowserinfo is more useful because it returns a JSON breakdown, an <code>isBot</code> flag, and about twenty individual detections like <code>hasWebdriverTrue</code>, <code>isHeadlessChrome</code>, and <code>isAutomatedWithCDP</code>. I tuned the weights in my probe so the ordering matches those verdicts, and that is the calibration step. The runner also saves a screenshot of the deviceandbrowserinfo verdict for every tool, so the human or bot call is on record, not just my parser&#8217;s output.</p><h2>Running it, and what came back</h2><p>Fifteen tools, three targets each, and here is the table the reporter produced.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!u4lc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c313fb4-a001-4efb-9e81-359426621665_813x794.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!u4lc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c313fb4-a001-4efb-9e81-359426621665_813x794.png 424w, https://substackcdn.com/image/fetch/$s_!u4lc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c313fb4-a001-4efb-9e81-359426621665_813x794.png 848w, https://substackcdn.com/image/fetch/$s_!u4lc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c313fb4-a001-4efb-9e81-359426621665_813x794.png 1272w, https://substackcdn.com/image/fetch/$s_!u4lc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c313fb4-a001-4efb-9e81-359426621665_813x794.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!u4lc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c313fb4-a001-4efb-9e81-359426621665_813x794.png" width="813" height="794" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5c313fb4-a001-4efb-9e81-359426621665_813x794.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:794,&quot;width&quot;:813,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:100053,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/208113514?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c313fb4-a001-4efb-9e81-359426621665_813x794.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!u4lc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c313fb4-a001-4efb-9e81-359426621665_813x794.png 424w, https://substackcdn.com/image/fetch/$s_!u4lc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c313fb4-a001-4efb-9e81-359426621665_813x794.png 848w, https://substackcdn.com/image/fetch/$s_!u4lc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c313fb4-a001-4efb-9e81-359426621665_813x794.png 1272w, https://substackcdn.com/image/fetch/$s_!u4lc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c313fb4-a001-4efb-9e81-359426621665_813x794.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>My probe score and the deviceandbrowserinfo verdict matched on 12 out of 13 tools I could get a verdict for, so that is a 92 percent match. I saved the heatmap of every tell by every tool at <code>111.STEALTH_BENCHMARK/results/heatmap.png</code>, and you can see the tiers at a glance.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!apLu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcba13293-6327-47b8-9627-921bacd963bb_2142x1335.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!apLu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcba13293-6327-47b8-9627-921bacd963bb_2142x1335.png 424w, https://substackcdn.com/image/fetch/$s_!apLu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcba13293-6327-47b8-9627-921bacd963bb_2142x1335.png 848w, https://substackcdn.com/image/fetch/$s_!apLu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcba13293-6327-47b8-9627-921bacd963bb_2142x1335.png 1272w, https://substackcdn.com/image/fetch/$s_!apLu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcba13293-6327-47b8-9627-921bacd963bb_2142x1335.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!apLu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcba13293-6327-47b8-9627-921bacd963bb_2142x1335.png" width="1456" height="907" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cba13293-6327-47b8-9627-921bacd963bb_2142x1335.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:907,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:105958,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/208113514?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcba13293-6327-47b8-9627-921bacd963bb_2142x1335.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!apLu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcba13293-6327-47b8-9627-921bacd963bb_2142x1335.png 424w, https://substackcdn.com/image/fetch/$s_!apLu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcba13293-6327-47b8-9627-921bacd963bb_2142x1335.png 848w, https://substackcdn.com/image/fetch/$s_!apLu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcba13293-6327-47b8-9627-921bacd963bb_2142x1335.png 1272w, https://substackcdn.com/image/fetch/$s_!apLu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcba13293-6327-47b8-9627-921bacd963bb_2142x1335.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Four tools passed the strict referee: camoufox, CloakBrowser, RayoBrowse, and scrapling. Three of them get there the same way, by rebuilding the browser itself instead of patching automation flags onto a normal Chrome, so there is no <code>HeadlessChrome</code> in the user agent and nothing that reads as CDP automation. scrapling is the fourth, and it passes for a reason worth its own section below, because it took a version bump to get there. Everything that still leaves <code>navigator.webdriver</code> true sits at the bottom of the table, and everything in the middle is one user agent string away from a clean pass.</p>
      <p>
          <a href="https://substack.thewebscraping.club/p/open-source-browser-automation-stealth-benchmark">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[The Engineer's Guide to Residential Proxies: From IP Fundamentals to Exit Node Sourcing]]></title><description><![CDATA[Everything an engineer needs to know about residential proxies, ranging from how they work, their types, and where those IPs actually come from]]></description><link>https://substack.thewebscraping.club/p/the-engineers-guide-residential-proxies</link><guid isPermaLink="false">https://substack.thewebscraping.club/p/the-engineers-guide-residential-proxies</guid><dc:creator><![CDATA[Federico Trotta]]></dc:creator><pubDate>Sun, 19 Jul 2026 08:32:59 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d1f6fc98-d194-4113-b550-c482ad11ec89_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Let&#8217;s consider a common scraping scenario. You&#8217;ve been working on a new scraping project for days on a target website that is a heavily protected e-commerce. You launch the scraper to test it, but your first run gets blocked within a few minutes. This is where you try using a datacenter proxy pool because, you know, they are cheap. But the scraper is blocked again.</p><p>Then you get access to a residential proxy pool, and the scraper works.</p><p>If you&#8217;ve been there, you probably moved on without asking why. The adage is simple: if it works, don&#8217;t touch it!</p><p>But that &#8220;why&#8221; matters more than you think because residential proxies aren&#8217;t a kind of black magic. They&#8217;re a specific technical architecture on top of which your scrapers work. And as with any architectural choice, you have to make decisions on purpose and based on specific parameters.</p><p>In this article, you&#8217;ll get a complete technical picture of residential proxies, starting from how IP addresses are classified, how the proxy infrastructure routes your traffic, what residential proxies are, and their common types. In the end, you&#8217;ll also discover the ethics behind residential IP sourcing and how to choose ethical providers with a practical decision framework.</p><p>Let&#8217;s get into it!</p><div><hr></div><blockquote><p>Give your AI a web data layer &#8211; <strong><a href="https://visit.decodo.com/rE0PQB">Decodo&#8217;s Web Scraping API</a></strong> turns any site into clean, structured data your models can actually use.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://visit.decodo.com/rE0PQB" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aRfI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 424w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 848w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 1272w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aRfI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png" width="556" height="278" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:512,&quot;width&quot;:1024,&quot;resizeWidth&quot;:556,&quot;bytes&quot;:716985,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://visit.decodo.com/rE0PQB&quot;,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/204450518?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!aRfI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 424w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 848w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 1272w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"></figcaption></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://visit.decodo.com/rE0PQB&quot;,&quot;text&quot;:&quot;Activate starter plan&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://visit.decodo.com/rE0PQB"><span>Activate starter plan</span></a></p><div><hr></div></blockquote><h2>What Is an IP Address?</h2><p>An IP address (Internet Protocol address) is a numerical label assigned to every device connected to a network that uses the Internet Protocol for communication. On the practical side, the IP protocol serves two core functions:</p><ul><li><p>Host or network interface identification.</p></li><li><p>Location addressing.</p></li></ul><p>The current dominant versions are IPv4 and IPv6:</p><ul><li><p>IPv4 addresses are 32-bit integers, expressed in dotted-decimal notation as four octets ranging from 0 to 255 (for example, <em>203.0.113.47</em>), yielding a theoretical address space of ~4.3 billion unique addresses.</p></li><li><p>Due to exhaustion of this space, IPv6 was introduced, using 128-bit addresses expressed in colon-separated hexadecimal groups (for instance, <em>2001:0db8:85a3:0000:0000:8a2e:0370:7334</em>), offering a space of ~3.4 &#215; 10&#179;&#8312; addresses.</p></li></ul><p>IP addresses aren&#8217;t handed out randomly by providers: there&#8217;s a strict hierarchy that they must respect. Specifically, a global authority called <a href="https://iana.org/">IANA</a> divides the total pool of IP addresses into large blocks and distributes them to regional bodies known as RIRs (Regional Internet Registries). These RIRs then assign smaller blocks to ISPs (your home internet provider) and cloud providers (like AWS or Google Cloud), who finally assign individual IPs to end users and servers.</p><p>Why does this matter for scraping? Because <strong>this entire chain is publicly documented,</strong> and anyone can look up who owns a given IP address using tools like <a href="https://who.is/">WHOIS</a> (a directory of IP ownership records). This means a target website can instantly tell whether your request is coming from a residential IP, a datacenter IP, or a known proxy or VPN provider.</p><p>This is precisely why residential proxies are so valuable in web scraping: their IPs are indistinguishable from those of regular home users, making them much harder for anti-bot systems to flag.</p><div><hr></div><blockquote><p><strong><a href="https://byteful.com?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE">Start your scraping journey with Byteful</a></strong>: 10GB New Customer Trial | Use TWSC for 15% OFF | $1.75/GB Residential Data | ISP Proxies in 15+ Countries</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://byteful.com/?promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EOo9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 424w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 848w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1272w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EOo9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png" width="630" height="69.35779816513761" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:120,&quot;width&quot;:1090,&quot;resizeWidth&quot;:630,&quot;bytes&quot;:133037,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://byteful.com/?promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/194398731?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!EOo9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 424w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 848w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1272w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption"></figcaption></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://byteful.com?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE&quot;,&quot;text&quot;:&quot;Claim your 10GB here&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://byteful.com?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE"><span>Claim your 10GB here</span></a></p></blockquote><div><hr></div><h2>What Is a Proxy Server?</h2><p>A proxy server is an intermediary host that sits between your client and the target web server, forwarding requests and responses on your behalf:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-h-9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b51d07-75f3-4686-9b58-d0fe8fc03f8f_813x239.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-h-9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b51d07-75f3-4686-9b58-d0fe8fc03f8f_813x239.png 424w, https://substackcdn.com/image/fetch/$s_!-h-9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b51d07-75f3-4686-9b58-d0fe8fc03f8f_813x239.png 848w, https://substackcdn.com/image/fetch/$s_!-h-9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b51d07-75f3-4686-9b58-d0fe8fc03f8f_813x239.png 1272w, https://substackcdn.com/image/fetch/$s_!-h-9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b51d07-75f3-4686-9b58-d0fe8fc03f8f_813x239.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-h-9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b51d07-75f3-4686-9b58-d0fe8fc03f8f_813x239.png" width="813" height="239" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/72b51d07-75f3-4686-9b58-d0fe8fc03f8f_813x239.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:239,&quot;width&quot;:813,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:78922,&quot;alt&quot;:&quot;Proxy servers&#8217; architecture by Federico Trotta&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/203665222?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b51d07-75f3-4686-9b58-d0fe8fc03f8f_813x239.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Proxy servers&#8217; architecture by Federico Trotta" title="Proxy servers&#8217; architecture by Federico Trotta" srcset="https://substackcdn.com/image/fetch/$s_!-h-9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b51d07-75f3-4686-9b58-d0fe8fc03f8f_813x239.png 424w, https://substackcdn.com/image/fetch/$s_!-h-9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b51d07-75f3-4686-9b58-d0fe8fc03f8f_813x239.png 848w, https://substackcdn.com/image/fetch/$s_!-h-9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b51d07-75f3-4686-9b58-d0fe8fc03f8f_813x239.png 1272w, https://substackcdn.com/image/fetch/$s_!-h-9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b51d07-75f3-4686-9b58-d0fe8fc03f8f_813x239.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Proxy servers&#8217; architecture</figcaption></figure></div><p>Without a proxy, the TCP connection to the target server is established directly from your machine. With a proxy, your client opens a connection to the proxy, which opens a separate connection to the target, and the target sees the proxy&#8217;s IP address as the request origin. This way, your real IP never appears in the target&#8217;s access logs or network layer.</p><p>This IP substitution is the fundamental value proposition of a proxy in a scraping context. Every request your scraper sends carries a source IP and, as covered above, target servers actively use that IP to make trust decisions. By routing through a proxy, you <strong>externalize your network identity.</strong> This way, the anti-bots on target servers evaluate the reputation, geolocation, and classification of the proxy&#8217;s IP, not yours.</p><p>At the network level, a proxy is simply a process listening on a TCP port, capable of accepting inbound connections and initiating outbound ones. It maintains two separate TCP connections for each proxied request: one with your client and one with the target. These connections are independent: they can use different protocols, different keep-alive settings, and the proxy can pool or reuse the outbound connection across multiple client requests. </p><p>The proxy may also perform additional functions depending on its implementation. For example, it can manage authentication, logging, <a href="https://substack.thewebscraping.club/p/use-ipv6-scraping-nyxproxy">IP rotation (cycling through a pool of IPs across requests)</a>, and header manipulation.</p><h2>Proxy Protocols: HTTP/HTTPS vs. SOCKS5</h2><p>When routing traffic through a proxy, the protocol determines how the proxy handles your traffic at the application and transport layers. This has real implications for scraping purposes. The main protocols are the following:</p><ul><li><p><strong>HTTP/HTTPS</strong>: Proxies that use these protocols operate at the application layer (OSI Layer 7). When your scraping client sends a request through an HTTP proxy, it constructs a full HTTP request and sends it to the proxy server, which forwards it to the target on your behalf. For plain HTTP traffic, the proxy reads the request in full. For HTTPS traffic over an HTTP proxy, the client issues a <em>CONNECT</em> method request, instructing the proxy to establish a raw TCP tunnel to the destination. Once the tunnel is open, the TLS handshake occurs directly between your client and the target server. This means that the proxy becomes a blind pipe and cannot decrypt the payload. This is critical to understand: an HTTP proxy handling HTTPS via <em>CONNECT</em> does not perform SSL termination (unless it&#8217;s explicitly a man-in-the-middle proxy with a custom CA cert injected).</p></li><li><p><strong>SOCKS5</strong>: Is a lower-level protocol operating at the transport layer (OSI Layer 5). Unlike HTTP proxies, SOCKS5 is protocol-agnostic. This means that it simply relays raw TCP (and optionally UDP) streams without any awareness of the application-layer protocol being tunneled. After a brief handshake, the proxy establishes a TCP connection to the destination and shuttles bytes in both directions without inspection. This makes SOCKS5 suitable for any TCP-based protocol, not just web traffic.</p></li></ul><p>One operationally significant detail to consider is DNS resolution. With SOCKS5, the domain name is forwarded to the proxy for resolution, meaning DNS queries originate from the proxy&#8217;s network, not yours. This prevents <a href="https://substack.thewebscraping.club/p/the-stealth-stack-web-scraping#:~:text=following%20main%20mechanisms%3A-,DNS%20leaks,-%3A%20They%20happen">DNS leaks</a>, which can expose your real network location even when your traffic routes through a proxy. HTTP proxies also resolve DNS on the proxy side for <em>CONNECT</em>-tunneled requests, but behavior can vary by implementation.</p><p>Now, here&#8217;s a question for you: for scraping purposes, which protocol is the best? Well, for most scraping use cases, either protocol is functionally equivalent from the target server&#8217;s perspective, since both can tunnel TLS. SOCKS5 is generally preferred for its flexibility and slightly lower overhead, but HTTP proxies are often easier to drop into existing HTTP client configurations without additional dependencies.</p><div><hr></div><blockquote><p><span>Tired of stitching together proxies, browsers, and parsers just to scrape one page? </span><strong><a href="https://gaffa.dev/?utm_source=web_scraping_club&amp;utm_medium=email&amp;utm_campaign=sponsorship&amp;utm_content=homepage"><span>Gaffa</span></a></strong><span> handles the whole stack for you, with the control to fine-tune every step when you need it.</span></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://gaffa.dev/?utm_source=web_scraping_club&amp;utm_medium=email&amp;utm_campaign=sponsorship&amp;utm_content=homepage" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!v6_W!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F061555da-affe-4456-aa1f-c74eff73a1ce_2400x400.png 424w, https://substackcdn.com/image/fetch/$s_!v6_W!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F061555da-affe-4456-aa1f-c74eff73a1ce_2400x400.png 848w, https://substackcdn.com/image/fetch/$s_!v6_W!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F061555da-affe-4456-aa1f-c74eff73a1ce_2400x400.png 1272w, https://substackcdn.com/image/fetch/$s_!v6_W!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F061555da-affe-4456-aa1f-c74eff73a1ce_2400x400.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!v6_W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F061555da-affe-4456-aa1f-c74eff73a1ce_2400x400.png" width="664" height="110.81868131868131" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/061555da-affe-4456-aa1f-c74eff73a1ce_2400x400.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:243,&quot;width&quot;:1456,&quot;resizeWidth&quot;:664,&quot;bytes&quot;:43266,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://gaffa.dev/?utm_source=web_scraping_club&amp;utm_medium=email&amp;utm_campaign=sponsorship&amp;utm_content=homepage&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/203665222?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F061555da-affe-4456-aa1f-c74eff73a1ce_2400x400.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!v6_W!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F061555da-affe-4456-aa1f-c74eff73a1ce_2400x400.png 424w, https://substackcdn.com/image/fetch/$s_!v6_W!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F061555da-affe-4456-aa1f-c74eff73a1ce_2400x400.png 848w, https://substackcdn.com/image/fetch/$s_!v6_W!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F061555da-affe-4456-aa1f-c74eff73a1ce_2400x400.png 1272w, https://substackcdn.com/image/fetch/$s_!v6_W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F061555da-affe-4456-aa1f-c74eff73a1ce_2400x400.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://gaffa.dev/?utm_source=web_scraping_club&amp;utm_medium=email&amp;utm_campaign=sponsorship&amp;utm_content=homepage&quot;,&quot;text&quot;:&quot;Start With Gaffa&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://gaffa.dev/?utm_source=web_scraping_club&amp;utm_medium=email&amp;utm_campaign=sponsorship&amp;utm_content=homepage"><span>Start With Gaffa</span></a></p></blockquote><div><hr></div><h2>What Are Residential Proxies?</h2><p>A residential proxy is a proxy server whose outbound IP address is assigned by a consumer Internet Service Provider (ISP<strong>)</strong> to a real, physical household device like a home router, laptop, smartphone, smart TV, or similar. The word &#8220;residential&#8221; refers directly to the ISP classification of the IP: these are addresses allocated to residential customers, and they appear as such in public IP intelligence databases.</p><h3>How Residential Proxies Work</h3><p>The operational model of a residential proxy network involves three distinct parties: the proxy provider (the company offering proxy servers on the market), the exit node (the residential device), and the scraping client (you).</p><p>The exit node is a real consumer device whose owner has installed software that, in exchange for some compensation, allows the provider to route third-party traffic through their internet connection. This software runs as a background process and registers the device with the provider&#8217;s orchestration infrastructure. The device&#8217;s residential IP is thus made available in the provider&#8217;s pool.</p><p>The provider&#8217;s infrastructure is the backbone that ties everything together. It consists of:</p><ul><li><p><strong>A</strong> <strong>gateway layer</strong>: A set of proxy endpoints that your scraping client connects to. These endpoints are hosted on standard data center infrastructure and serve as the entry point into the residential network.</p></li><li><p><strong>An</strong> <strong>orchestration/routing layer</strong>: The internal system responsible for selecting which residential exit node to use for a given outbound request, based on criteria like target geolocation, session persistence requirements, and node availability.</p></li><li><p><strong>The exit node pool</strong>: The fleet of residential devices registered with the provider, each maintaining a persistent connection back to the orchestration layer.</p></li></ul><p>Thanks to this infrastructure, the scraping requests flow like that:</p><ol><li><p>Your scraping client connects to the provider&#8217;s gateway endpoint and authenticates.</p></li><li><p>The gateway receives your request and passes it to the orchestration layer, which selects an available residential exit node from the pool based on your session&#8217;s parameters.</p></li><li><p>The orchestration layer forwards the request to the selected exit node over the persistent internal tunnel.</p></li><li><p>The exit node&#8217;s software initiates the actual outbound TCP connection to the target server from the residential device&#8217;s IP.</p></li><li><p>The target server receives a request from what appears to be a residential IP, processes it, and returns the response.</p></li><li><p>The response travels back through the same path in reverse: exit node &#8594; orchestration layer &#8594; gateway &#8594; your client.</p></li></ol><p>This is why, from the target server&#8217;s perspective, the only visible connection is the one from the residential exit node. Your client&#8217;s IP, the provider&#8217;s gateway IP, and the internal routing infrastructure are entirely invisible.</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.youtube.com/@thewebscrapingclub&quot;,&quot;text&quot;:&quot;Check the TWSC YouTube Channel&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.youtube.com/@thewebscrapingclub"><span>Check the TWSC YouTube Channel</span></a></p><div><hr></div><h2>Types of Residential Proxies</h2><p>Residential proxies are segmented into distinct types based on two primary axes: IP persistence (how long you hold the same IP across requests) and targeting granularity (how precisely you can select the exit node&#8217;s location).</p><p>This section reports and describes the common residential proxy categories that are based on those two axes.</p><h3>Rotating Residential Proxies</h3><p>This is the most common type. With rotating proxies, the provider&#8217;s orchestration layer assigns a new exit node&#8212;and, therefore, a new IP&#8212;for each outbound request, or at a configurable interval. From your client&#8217;s perspective, you connect to the same gateway endpoint every time, but each request exits from a different residential IP. This is the default behavior on most residential proxy platforms and is the correct choice for large-scale scraping where you need to distribute requests across a wide IP footprint to avoid per-IP rate limits. Belos is a schema that shows, at a high level, how this process works:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!AS8D!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8695816-0df5-400e-95fd-f25925a6f017_957x428.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!AS8D!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8695816-0df5-400e-95fd-f25925a6f017_957x428.png 424w, https://substackcdn.com/image/fetch/$s_!AS8D!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8695816-0df5-400e-95fd-f25925a6f017_957x428.png 848w, https://substackcdn.com/image/fetch/$s_!AS8D!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8695816-0df5-400e-95fd-f25925a6f017_957x428.png 1272w, https://substackcdn.com/image/fetch/$s_!AS8D!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8695816-0df5-400e-95fd-f25925a6f017_957x428.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!AS8D!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8695816-0df5-400e-95fd-f25925a6f017_957x428.png" width="957" height="428" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d8695816-0df5-400e-95fd-f25925a6f017_957x428.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:428,&quot;width&quot;:957,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:133881,&quot;alt&quot;:&quot;Rotating residential proxies schema by Federico Trotta&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/203665222?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8695816-0df5-400e-95fd-f25925a6f017_957x428.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Rotating residential proxies schema by Federico Trotta" title="Rotating residential proxies schema by Federico Trotta" srcset="https://substackcdn.com/image/fetch/$s_!AS8D!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8695816-0df5-400e-95fd-f25925a6f017_957x428.png 424w, https://substackcdn.com/image/fetch/$s_!AS8D!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8695816-0df5-400e-95fd-f25925a6f017_957x428.png 848w, https://substackcdn.com/image/fetch/$s_!AS8D!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8695816-0df5-400e-95fd-f25925a6f017_957x428.png 1272w, https://substackcdn.com/image/fetch/$s_!AS8D!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8695816-0df5-400e-95fd-f25925a6f017_957x428.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Rotating residential proxies schema</figcaption></figure></div><p>The rotation can be triggered in two ways, depending on the provider:</p><ul><li><p><strong>Per-request rotation</strong>: A new IP is selected for every single HTTP request.</p></li><li><p><strong>Timed rotation</strong>: The same IP is held for a configurable duration before rotating.</p></li></ul><p>In the context of web scraping, the primary tradeoff is that rotating proxies make stateful interactions difficult. If a target website tracks session state via server-side session IDs or cookies tied to an IP, switching IPs mid-session will break the session. So, for purely stateless scraping cases like fetching product pages, search results, and public data, rotation is ideal.</p><h3>Sticky Residential Proxies</h3><p>Sticky proxies allow you to hold the same residential IP for an extended, configurable period<strong>.</strong> This typically ranges from a few minutes up to 30 minutes, depending on the provider, and is bounded by the exit node&#8217;s availability online. The session is maintained by passing a session identifier in your proxy credentials, which instructs the orchestration layer to pin your requests to the same exit node for the duration of the session.</p><p>This type is essential for scraping workflows that require statefulness, like multi-step form submissions, login-based scraping, checkout flows, or any target that correlates sequential requests by IP and invalidates sessions on IP change. The risk is that if the exit node goes offline mid-session (the residential device disconnects from the network, which happens regularly on consumer connections), the session is broken, and the provider will typically reassign you to a new IP. This requires your scraper to handle session recovery; otherwise, your pipelines will break.</p><h3>Static Residential Proxies</h3><p>Static residential proxies (often known as ISP proxies) occupy a middle ground. These IPs are registered to an ISP (giving them a residential-looking ASN and WHOIS classification) but are hosted on stable data center infrastructure<strong>,</strong> rather than actual consumer devices. The provider procures these IPs directly from ISPs and hosts them on servers they control.</p><p>The key advantage over standard rotating residential proxies is stability and speed. Since the IP is hosted on a server rather than a consumer device, it doesn&#8217;t go offline unpredictably, latency is lower, and bandwidth is not constrained by a household connection. You get a fixed, persistent residential-classified IP that behaves like a data center proxy in terms of reliability.</p><p>The tradeoffs are:</p><ul><li><p><strong>Cost</strong>: These are among the most expensive proxy types.</p></li><li><p><strong>Increasing detectability</strong>: As anti-bot vendors are building databases of known static residential/ISP proxy IP ranges, this kind of proxy works as a cat-and-mouse race to escape anti-bots.</p></li></ul><h3>Mobile Proxies</h3><p>Mobile proxies are a specialized subtype where the exit node is a mobile device connected via a cellular network, and the IP belongs to a mobile carrier&#8217;s ASN. These are distinct from standard residential proxies because mobile IPs carry a specific characteristic: mobile carriers use large-scale carrier-grade NAT (CGNAT), meaning a single public IP is shared among potentially thousands of concurrent mobile users.</p><p>This has a profound implication for anti-bot systems: blocking a mobile carrier IP risks blocking thousands of legitimate users simultaneously, making mobile IPs extremely difficult for targets to block without significant collateral damage. As a result, mobile proxies are the hardest proxy type to block and command the highest price per GB in the market. They are most valuable for targets with aggressive anti-bot systems, typically heavily protected e-commerce sites, social platforms, and ad verification use cases.</p><p>The tradeoff is latency and bandwidth variability<strong>.</strong> The exit node is on a cellular connection, which is subject to signal quality, network congestion, and higher round-trip times compared to a wired residential or data center connection.</p><h2>Residential Proxies Types: A Summary Comparison</h2><p>The table below summarizes the subtypes of residential proxies, each with its own specific characteristics:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LmCH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc65955-ab76-407b-af19-a3d555b7e468_1273x672.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LmCH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc65955-ab76-407b-af19-a3d555b7e468_1273x672.png 424w, https://substackcdn.com/image/fetch/$s_!LmCH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc65955-ab76-407b-af19-a3d555b7e468_1273x672.png 848w, https://substackcdn.com/image/fetch/$s_!LmCH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc65955-ab76-407b-af19-a3d555b7e468_1273x672.png 1272w, https://substackcdn.com/image/fetch/$s_!LmCH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc65955-ab76-407b-af19-a3d555b7e468_1273x672.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LmCH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc65955-ab76-407b-af19-a3d555b7e468_1273x672.png" width="1273" height="672" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1cc65955-ab76-407b-af19-a3d555b7e468_1273x672.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:672,&quot;width&quot;:1273,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:93154,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/203665222?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc65955-ab76-407b-af19-a3d555b7e468_1273x672.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!LmCH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc65955-ab76-407b-af19-a3d555b7e468_1273x672.png 424w, https://substackcdn.com/image/fetch/$s_!LmCH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc65955-ab76-407b-af19-a3d555b7e468_1273x672.png 848w, https://substackcdn.com/image/fetch/$s_!LmCH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc65955-ab76-407b-af19-a3d555b7e468_1273x672.png 1272w, https://substackcdn.com/image/fetch/$s_!LmCH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc65955-ab76-407b-af19-a3d555b7e468_1273x672.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/p/consulting&quot;,&quot;text&quot;:&quot;Need help with your scraping project?&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://substack.thewebscraping.club/p/consulting"><span>Need help with your scraping project?</span></a></p><div><hr></div><h2>How Residential Proxies Are Sourced and Why It Matters</h2><p>The technical sophistication of residential proxy networks is surely interesting for techies. But what&#8217;s also interesting is where the exit nodes actually come from. This is because understanding the sourcing model is not just an ethical exercise. It has direct operational implications for scraping professionals because the legality and reputational standing of the proxies you use can affect your liability exposure and the long-term reliability of your infrastructure. It also has implications for companies working in highly regulated markets, where specific certifications matter more than anything else. <a href="https://substack.thewebscraping.club/p/when-the-fbi-knocks-on-your-proxy">We&#8217;ve seen recently how bad things can go from one day to another if your proxy provider is not fully compliant on both IP sourcing side and customer onboarding</a>.</p><p>As introduced previously, providers build their residential IP pools by recruiting real consumer devices to act as exit nodes. The mechanisms for doing this fall into a spectrum that is described in the subparagraphs below.</p><h3>SDK Monetization</h3><p>The most widespread IP sourcing method is SDK embedding. In this model, a residential proxy provider operates a B2B program where mobile app developers and software vendors integrate the provider&#8217;s SDK into their applications. In exchange, the developer receives a revenue share or a flat fee per active device. When an end user installs the app, the SDK runs in the background and enrolls the device&#8217;s internet connection into the proxy pool.</p><p>The provider&#8217;s SDK typically does the following:</p><ul><li><p>Establishes a persistent outbound connection to the provider&#8217;s orchestration infrastructure.</p></li><li><p>Monitors device state to only proxy traffic when the device is charging and connected to Wi-Fi (to avoid draining battery or mobile data, which would generate user complaints).</p></li><li><p>Throttles bandwidth consumption to stay below the threshold of user noticeability.</p></li><li><p>Handles NAT traversal to make the device reachable for outbound proxy sessions.</p></li></ul><p>The apps embedding these SDKs span a wide range. Just to name a few:</p><ul><li><p>Free VPN clients.</p></li><li><p>File utility apps.</p></li><li><p>Weather apps.</p></li><li><p>Games.</p></li></ul><p>So, the SDK is not the app&#8217;s core function: it&#8217;s the monetization layer that the developer has grafted on.</p><h3>Opt-In Peer Networks</h3><p>Some providers operate a peer model, where users knowingly and explicitly enroll their devices in exchange for a direct benefit. This typically is free access to a premium VPN or bandwidth credits. The consent flow clearly explains that the user&#8217;s connection will be used to route third-party traffic.</p><p>This is the most ethically defensible sourcing model, as the value exchange is explicit and the user retains full awareness of what their device is being used for.</p><h2>Ethical Considerations</h2><p>The ethical landscape of residential proxy sourcing is genuinely complex, and it deserves some treatment to help you discern the right proxy provider to choose.</p><h3>The Consent Problem</h3><p>The foundational ethical issue is informed consent. When a residential device is used as an exit node, its owner&#8217;s internet connection carries traffic for a third party. This means that their IP address becomes associated with requests they did not initiate, to targets they may never have interacted with. If a scraping client uses that exit node to send requests that the target considers abusive, the device owner&#8217;s IP is the one that appears in server logs. The potential consequences for an unknowing participant range from having their IP blacklisted by services they use personally to, in extreme cases, being flagged by automated fraud or abuse detection systems.</p><p>This is why true informed consent in this context is very important. Also, for deep ethical reasons, informed consent would require a clear, plain-language explanation that the user&#8217;s internet connection will relay unknown third-party traffic, that their IP will be the visible origin of that traffic, and that this may affect their IP&#8217;s reputation.</p><h3>Bandwidth and Resource Consumption</h3><p>Enrolling a device in a proxy pool consumes the device owner&#8217;s bandwidth. In many markets, this resource is metered and has a real financial cost. In mobile-heavy markets where Wi-Fi and cellular data are tightly managed household resources, this is a non-trivial extraction of value from the device owner. This is another reason why proxy providers offer rewards to IP owners.</p><h3>The Downstream Use Problem</h3><p>Even when sourcing is fully and clearly consensual, the ethical chain does not end at the exit node: it extends to what the proxy traffic is used for. This is because a residential proxy provider cannot fully control what its clients scrape. This means that the same infrastructure used by a legitimate price monitoring service can be used for credential stuffing attacks, ad fraud, scalping bots, or scraping protected personal data.</p><p>For this reason, responsible providers implement terms of service enforcement, traffic monitoring, and client vetting. As a scraping professional, you are a link in this chain and carry some responsibility for the downstream ethics of the traffic you generate.</p><h3>Legal Exposure</h3><p>The legal status of residential proxy usage varies by jurisdiction and use case. In the United States, the Computer Fraud and Abuse Act (CFAA) has been interpreted broadly to potentially cover unauthorized access to computer systems. And whether bypassing a website&#8217;s IP-based blocks via residential proxies constitutes &#8220;unauthorized access&#8221; has been the subject of significant litigation.</p><p>In the European Union, the GDPR introduces additional complexity when scraped data includes personal information. So, note that using residential proxies does not insulate you from these legal frameworks. It only changes the network-layer attribution of the requests.</p><h2>What to Look For in a Residential Proxy Provider</h2><p>If you&#8217;re new to the scraping industry and need to get residential proxies, you may be overwhelmed by the offers that are on the proxy market. There are a lot of providers out there, and choosing the best one for you may be hard. </p><p>If you want to evaluate providers not only based on budget, the suggestion for you is to primarily make ethical considerations. If you don&#8217;t know where to start, indicators of a responsible residential IP sourcing model include the following:</p><ul><li><p>Published transparency reports detailing how exit nodes are recruited.</p></li><li><p>Clear opt-in documentation and publicly accessible SDK disclosure pages.</p></li><li><p>Active terms of service enforcement against prohibited use cases.</p></li><li><p>Direct compensation models for exit node participants.</p></li><li><p>Compliance certifications<strong>.</strong> Some of the most ethical providers have pursued SOC 2 or ISO 27001 certifications, which indicate a structured approach to operational governance.</p></li></ul><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Web Scraping Club is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>Conclusion</h2><p>Residential proxies are easy-to-use tools, but may be hard to understand. The gap between &#8220;it works&#8221; and &#8220;I know why it works&#8221; is where most engineers live, and it&#8217;s fine until it isn&#8217;t.</p><p>Understanding the full stack changes how you make decisions for your scraping projects. Why? Because it changes how you pick a provider, how you evaluate proxy types for a given target, and how you think about the ethical and legal weight of the infrastructure you&#8217;re routing traffic through.</p><p>So, now that you know how this infrastructure actually works, let us know: does it change how you&#8217;d pick proxies for your next scraping project?</p><div><hr></div><p><em>Did you like this article? Share it with someone who might find it useful and get a discount on paid plans.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/p/the-engineers-guide-residential-proxies?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://substack.thewebscraping.club/p/the-engineers-guide-residential-proxies?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div>]]></content:encoded></item><item><title><![CDATA[THE LAB #110: Why Shopee is so hard to scrape]]></title><description><![CDATA[Usually, when I dig into an app&#8217;s request signing, I like to end with something practical you can use, like a script, an offline signer, or at least a way to forge a header.]]></description><link>https://substack.thewebscraping.club/p/why-shopee-is-so-hard-to-scrape</link><guid isPermaLink="false">https://substack.thewebscraping.club/p/why-shopee-is-so-hard-to-scrape</guid><dc:creator><![CDATA[Pierluigi Vinciguerra]]></dc:creator><pubDate>Thu, 16 Jul 2026 21:53:52 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b596c526-9ef3-4cbc-ad6d-c93e73d4ac25_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Usually, when I dig into an app&#8217;s request signing, I like to end with something practical you can use, like a script, an offline signer, or at least a way to forge a header. This time, though, things turned out differently, and I think that&#8217;s actually more interesting. I tried to tackle Shopee from every angle I know: the native library inside the app, the JSON API behind the website, you name it. Every route hit a different wall. So here&#8217;s my map of those walls, why they hold up, and, to be honest, why I couldn&#8217;t get product data out of any of them.</p><blockquote><p>Give your AI a web data layer &#8211; <strong><a href="https://visit.decodo.com/rE0PQB">Decodo&#8217;s Web Scraping API</a></strong> turns any site into clean, structured data your models can actually use.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://visit.decodo.com/rE0PQB" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aRfI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 424w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 848w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 1272w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aRfI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png" width="556" height="278" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:512,&quot;width&quot;:1024,&quot;resizeWidth&quot;:556,&quot;bytes&quot;:716985,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://visit.decodo.com/rE0PQB&quot;,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/204450518?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!aRfI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 424w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 848w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 1272w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"></figcaption></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://visit.decodo.com/rE0PQB&quot;,&quot;text&quot;:&quot;Activate starter plan&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://visit.decodo.com/rE0PQB"><span>Activate starter plan</span></a></p></blockquote><blockquote><div><hr></div></blockquote><p>I&#8217;m picking up where I left off in <a href="https://substack.thewebscraping.club/p/reversing-shopee-app-ghidra">a previous investigation</a>, so I won&#8217;t repeat all the details here. Back then, I pulled <code>libshopeeaegis.so</code> (one of Shopee&#8217;s native crypto libraries) into Ghidra and rebuilt it in Python, byte for byte. That was a straightforward win: just an integer switch over standard AES, SHA, and HMAC, once you get past the intentionally confusing symbol names. But as I mentioned at the end of that article, Aegis is just a general-purpose toolbox, not the actual library that signs API requests. The real signer is a different beast, and that&#8217;s what I&#8217;m looking at now.</p><h2>What actually gates the API</h2><p>Capture any Shopee API request, strip the anti-fraud headers, replay it, and the backend answers with an HTTP error and code <code>90309999</code>. That code is the whole story of this article. It is the anti-fraud rejection, and by the end you will have seen it come back from the app, from a plain HTTP client, and from two different real browsers.</p><p>Two headers carry the protection. On the Java side, two interceptors in okhttp, the HTTP client library the Android app is built on, attach them: <code>com.shopee.app.network.antifraud.b</code> and <code>.d</code>, which name themselves <code>SecurityNewSapInterceptor</code> and <code>SecurityNewSapPostInterceptor</code>. The first header is <code>af-ac-enc-sz-token</code>, a constant for the session:</p><pre><code><code>af-ac-enc-sz-token: ghHLT8yNv0Xd8t9C2/t6Sw==|uJEC5q...JfDCw4ImZS5+gWyOO5Dr...8bd|rsGw3AWMR7Ryh7Jo|08|1</code></code></pre><div><hr></div><blockquote><p><em>The right foundation to keep a smooth scraping workflow. anyIP helps teams collect web data more consistently with high-quality residential and mobile IPs worldwide.</em></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://anyip.io/?utm_source=thewebscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=gold_2026_3&amp;utm_content=banner_scraping" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ro3G!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1943b734-365d-40a8-b4c3-04a3296bde99_574x287.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ro3G!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1943b734-365d-40a8-b4c3-04a3296bde99_574x287.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ro3G!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1943b734-365d-40a8-b4c3-04a3296bde99_574x287.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ro3G!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1943b734-365d-40a8-b4c3-04a3296bde99_574x287.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ro3G!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1943b734-365d-40a8-b4c3-04a3296bde99_574x287.jpeg" width="574" height="287" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1943b734-365d-40a8-b4c3-04a3296bde99_574x287.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:287,&quot;width&quot;:574,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:54790,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:&quot;https://anyip.io/?utm_source=thewebscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=gold_2026_3&amp;utm_content=banner_scraping&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/207213212?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1943b734-365d-40a8-b4c3-04a3296bde99_574x287.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ro3G!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1943b734-365d-40a8-b4c3-04a3296bde99_574x287.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ro3G!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1943b734-365d-40a8-b4c3-04a3296bde99_574x287.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ro3G!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1943b734-365d-40a8-b4c3-04a3296bde99_574x287.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ro3G!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1943b734-365d-40a8-b4c3-04a3296bde99_574x287.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://anyip.io/?utm_source=thewebscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=gold_2026_3&amp;utm_content=banner_scraping&quot;,&quot;text&quot;:&quot;Explore anyIP Proxies &#8594;&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://anyip.io/?utm_source=thewebscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=gold_2026_3&amp;utm_content=banner_scraping"><span>Explore anyIP Proxies &#8594;</span></a></p></blockquote><div><hr></div><p>This token is a pipe-delimited string: an encrypted device fingerprint, a 16-byte wrapped key, an 81-byte ciphertext, a 12-byte nonce, and two version markers. It&#8217;s computed once per session and then reused. The second header, <code>x-sap-ri</code>, comes with a set of signatures keyed by short hex strings, and these change every request since they&#8217;re computed over the request itself. Both headers are generated by <code>libshpssdk.so</code>, a hefty 4.7 MB native library. The tricky part is the per-request signature, since that&#8217;s the bit you can&#8217;t just precompute and reuse.</p><h2>Finding the signer, and deciding not to port it</h2><p><code>libshpssdk.so</code> exports exactly one symbol, <code>JNI_OnLoad</code>. Everything else is hidden, which tells you the native methods are registered at runtime with <code>RegisterNatives</code>, the Java Native Interface (JNI) call an app uses to bind native functions to Java methods, rather than exported by name. So we catch the registration as it happens. On arm64, <code>RegisterNatives</code> is a slot in the JNIEnv function table at byte offset <code>0x6b8</code>. We hook that slot spawn-gated, before the app&#8217;s own code runs, and read out every method the library registers. The agent is in <code>/shopee_sap_oracle/agent_oracle.js</code>.</p><p>We are after one JNI signature. A request signer takes the request and returns headers, which in JNI terms is two byte arrays in and a string out, <code>([B[B)Ljava/lang/String;</code>. One method matches. It is <code>vuwuuwvw</code>, registered at <code>libshpssdk+0xc7ebc</code>, on the obfuscated class <code>com.shopee.shpssdk.wvvvuwwu</code>, a name we recovered from the <code>jclass</code> argument of the registration call through <code>java.lang.Class.getName()</code>.</p><p>When I opened that address in Ghidra, I found the function itself barely does anything. It just marshals its arguments and passes them to another function, along with a 440-byte blob and a table of function pointers. That&#8217;s the classic shape of a bytecode interpreter: the blob is the program, the pointer table is the opcode handlers, and the real logic lives in the bytecode, not in any native function I can easily read. This is where my earlier approach hits a wall. Last time, I could just read the primitive bodies and match them to AES and SHA. Here, there are no such primitives. To reimplement the signer, I&#8217;d have to reverse and interpret the whole virtual machine (VM), a project that would take weeks and is on the same level as breaking DroidGuard, Google&#8217;s own anti-abuse VM. So instead of porting it, I decided to just call it directly.</p><h2>The one thing that works: the oracle</h2><p>The signer is a Java method on a class we already resolved, so we drive the app&#8217;s own code. The agent exposes one RPC (remote procedure call) export that takes a URL and a body, turns them into the two byte arrays the method expects, and returns whatever the signer returns. The driver (<code>/shopee_sap_oracle/frida_oracle.py</code>) spawns the app, waits for the signer to register, and signs requests we choose. Re-running it against the current app, the signer registers in three seconds and answers:</p><pre><code><code>[SIGN] url=https://mall.shopee.ph/api/v4/native/homepage body={"test":1}
   3c7b6059 = LR4te3nzTKb0RcbDqGs1bsXb0pB=
   71356940 = uMaRB6hpxBCq1BwZ2bFqGXjGFow=
   a0e0dcb3 = WFiutLJdM2C0t5Lr4yaoPr9CLn4=
   eaab58ec = 3BJOkkGhS53kj19RA2QqVLfC1m8sZFLSYm8W8S+Kl3yHU+vuzvb+...   (~2 KB)
   x-sap-ri = d9e7576a26efc4c9896e6716017c0e63cf0cae2c4c560ed7dd03</code></code></pre><div><hr></div><blockquote><p><strong><a href="https://byteful.com?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE">Start your scraping journey with Byteful</a></strong>: 10GB New Customer Trial | Use TWSC for 15% OFF | $1.75/GB Residential Data | ISP Proxies in 15+ Countries</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://byteful.com/?promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EOo9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 424w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 848w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1272w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EOo9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png" width="630" height="69.35779816513761" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:120,&quot;width&quot;:1090,&quot;resizeWidth&quot;:630,&quot;bytes&quot;:133037,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://byteful.com/?promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/194398731?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!EOo9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 424w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 848w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1272w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://byteful.com?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE&quot;,&quot;text&quot;:&quot;Claim your 10GB here&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://byteful.com?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE"><span>Claim your 10GB here</span></a></p></blockquote><div><hr></div><p>That&#8217;s the win, and I want to be clear about what it actually means. I can now produce the exact headers the app would attach for any request I choose, without needing to understand how the signer works under the hood. But before I trust these headers, it&#8217;s worth looking closely at the output, because it gives you a sense of how tough it&#8217;ll be to reuse them. The structural checks I&#8217;m about to show run on the captured values using <code>/shopee_sap_oracle/analyze_headers.py</code>. No device needed.</p><p>The short values look like base64 of a 20-byte digest, which is the length of HMAC-SHA1, so the easy reading is that they are keyed hashes and that is where a first pass leaves them. The easy reading is wrong. Decode seven of them as standard base64, and five come back with non-zero bits in the final padded position, which a conforming base64 encoder never produces from raw bytes:</p><pre><code><code>  6m6r94xGLKAjTMYNMepdMiLQpSB=  len=20  NON-canonical (low2=1)
  wLJO02yz2I+FAZETVwC0aTCflyB=  len=20  NON-canonical (low2=1)
  1iwMbNiaPma2XLT7ToRs4jgiasE=  len=20  canonical
  47WhCSbrTx1r5FdFFlgtpHPN0OO=  len=20  NON-canonical (low2=2)
  LzCPR2/LYpvqP/z1HBXtuzj+8dQ=  len=20  canonical
  vmPAmagMmOlffOGIWF8MwJEBuNT=  len=20  NON-canonical (low2=3)</code></code></pre><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.youtube.com/@thewebscrapingclub&quot;,&quot;text&quot;:&quot;Check the TWSC YouTube Channel&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.youtube.com/@thewebscrapingclub"><span>Check the TWSC YouTube Channel</span></a></p><div><hr></div><p>So these aren&#8217;t just plain base64-encoded digests. Either the alphabet is shuffled or there&#8217;s some transformation after encoding, and that&#8217;s exactly the kind of detail that trips up anyone trying to reproduce the signature with HMAC-SHA1 and a standard encoder. The keys themselves are interesting too: within a session, no two requests share a key, and those eight-hex identifiers aren&#8217;t the actual header names you see on the wire. Plus, endpoints with a body return four entries, while GETs without a body return three. There&#8217;s a whole mapping layer from the signer&#8217;s per-request keys to the real header names, and I never managed to reverse that part.</p><p>Then there is <code>x-sap-ri</code> itself. It is 26 bytes: a leading counter, a three-byte session ID, an eight-byte middle field, a <code>01</code> marker at byte twelve, and a thirteen-byte tail. The counter is the interesting part, because it is coarser than it looks. In the fresh capture, both requests came back with counter <code>d9</code> and session <code>e7576a</code> in front, and only the middle and tail differed:</p><pre><code><code>d9 e7576a 26efc4c9896e6716 01 7c0e63cf0cae2c4c560ed7dd03
d9 e7576a b4c3b40323380413 01 d56d3f5c7eaef45395425b9720</code></code></pre><p>So the leading byte isn&#8217;t just a simple counter that increments with each request. What actually separates one request from another is hidden in the middle and tail fields, and the token is tightly bound to the specific request through those parts. That binding is the first reason why a replay fails, and it&#8217;s where the real obstacles begin.</p><h2>App wall one: the replay is out of sequence</h2><p>The obvious next step was to sign a real product request using the oracle and try replaying it from outside the app. I picked a phone case from Shopee&#8217;s public site (<code>shop_id=20396998, item_id=5420033304</code>), had the oracle sign its product detail URL, attached the session&#8217;s <code>af-ac-enc-sz-token</code> and a cookie I grabbed with a one-shot header hook, and replayed the request with <code>curl-cffi</code>. The driver for this is <code>/shopee_sap_oracle/replay.py</code>. Here&#8217;s what Shopee sent back:</p><pre><code><code>[*] oracle signed 4 headers: ['5cfcca02', 'c382fc01', 'cf1e6149', 'x-sap-ri']
==== REPLAY RESULT ====
HTTP 403
{"5":false,"2":false,"0":2,"3":90309999,"error":90309999, ...}</code></code></pre><p>The status code changes depending on the endpoint, but the error code is always the same. Having a valid signature is necessary, but it&#8217;s not enough. This replay was always going to fail for two reasons: first, the <code>x-sap-ri</code> binding ties the token to the device&#8217;s own request sequence, so any injected, out-of-sequence request gets rejected, even with a fresh signature. Second, I put the signer&#8217;s per-request-keyed values under those same eight-hex keys, but I never actually confirmed that&#8217;s how the app names them on the wire. For a clean external replay, you&#8217;d need both of those right, and I could only be sure about one.</p><h2>App wall two: the RASP cancels everything</h2><p>The way to remove both doubts is to stop replaying and make the request from inside the app, where the sequence, the device state, the TLS, and the real header names are already coherent. The app builds its API calls on okhttp, so we went looking for its <code>OkHttpClient</code> at runtime with <code>Java.choose</code>. The probe is in <code>/shopee_sap_oracle/crash_trace.js</code>. There are more than a dozen live instances, and you spot the API client by its interceptors. The antifraud layer is not among the application interceptors where we first looked. It sits lower, among the network interceptors, which run after okhttp has set up the real connection, so it signs the request that is actually about to go on the wire.</p><p>As always, the code that will be used for the python reimplementation we&#8217;re showing now can be found&nbsp;<a href="https://github.com/TheWebScrapingClub/thelab">in our GitHub repository reserved for paying users, inside the folder&nbsp;</a><strong><a href="https://github.com/TheWebScrapingClub/thelab">110.</a></strong><a href="https://github.com/TheWebScrapingClub/thelab">SHOPEE-PART2</a><strong><a href="https://github.com/TheWebScrapingClub/thelab">.</a></strong></p>
      <p>
          <a href="https://substack.thewebscraping.club/p/why-shopee-is-so-hard-to-scrape">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Browser Fingerprinting Deep Dive]]></title><description><![CDATA[How Detection Works and How Scrapers Adapt]]></description><link>https://substack.thewebscraping.club/p/browser-fingerprinting-deep-dive</link><guid isPermaLink="false">https://substack.thewebscraping.club/p/browser-fingerprinting-deep-dive</guid><dc:creator><![CDATA[Dennis Kriukiov]]></dc:creator><pubDate>Tue, 14 Jul 2026 21:02:17 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/45ca223d-4bc5-4b3b-86ee-5867ae5e23e8_700x250.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>This is a guest post written by the Infatica team. For an independent benchmark of proxy prices, <a href="https://proxyprice.thewebscraping.club/">visit our Proxy Pricing Benchmark tool</a>.</em></p><div><hr></div><p>If you&#8217;ve run a scraper at any real scale recently, you&#8217;ve felt the shift. The block didn&#8217;t come from too many requests per minute, and rotating to a fresh IP didn&#8217;t fix it. The page just quietly refused to load the data you came for. That&#8217;s fingerprinting doing its job, usually before your first byte of useful content arrives.</p><p>We keep hitting the same pattern. A pipeline that ran fine for months starts returning empty pages or CAPTCHAs, the proxy pool looks healthy, the rate limits are nowhere near tripped, and nothing in the logs explains it. What changed is not how many requests you send. It is how much the target now knows about the client sending them.</p><h2>What browser fingerprinting is, and why it matters for scraping</h2><p>Fingerprinting identifies and tracks a client from the unique combination of characteristics its browser and device expose, with no cookies, no login, and no explicit identifier required. It doesn&#8217;t ask &#8220;who is this user?&#8221; It asks &#8220;does this look like the same client as before, and does this client look real?&#8221;</p><p>The signals are individually boring. Browser version, OS, screen resolution, installed fonts, timezone, locale, how the device renders graphics. None of them identify you alone. Put them together and you get a profile distinctive enough to survive across sessions, IP changes, and incognito mode. For the defense side that&#8217;s a bargain, because one collection step feeds fraud detection, bot detection, rate limiting, and access control at once.</p><h2>From IP-based blocking to identity-based detection</h2><p>The traditional model was simple. Send requests, rotate IPs, stay under the rate limit. As long as each request looked like it came from a different address, you stayed under the radar.</p><p>Fingerprinting breaks that model cleanly. Rotate all the IPs you want, and if your browser fingerprint stays identical across them, you&#8217;ve just told the anti-bot that one client is hiding behind a dozen addresses. No real user does that. It is more suspicious than a single sticky IP would have been. The rotation you relied on becomes the tell.</p><h2>How fingerprinting works under the hood</h2><p>Modern systems don&#8217;t lean on one identifier. They build a layered profile across browser properties, device capabilities, rendering behavior, and network characteristics, then score how coherent the whole thing looks. The more consistent and ordinary the profile, the more trust it earns. That last part is the one most scrapers get backwards, and we&#8217;ll come back to it.</p><h3>Passive signals</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9oS7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb80faccf-372c-414b-9d2e-15641f688bc1_700x275.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9oS7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb80faccf-372c-414b-9d2e-15641f688bc1_700x275.png 424w, https://substackcdn.com/image/fetch/$s_!9oS7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb80faccf-372c-414b-9d2e-15641f688bc1_700x275.png 848w, https://substackcdn.com/image/fetch/$s_!9oS7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb80faccf-372c-414b-9d2e-15641f688bc1_700x275.png 1272w, https://substackcdn.com/image/fetch/$s_!9oS7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb80faccf-372c-414b-9d2e-15641f688bc1_700x275.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9oS7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb80faccf-372c-414b-9d2e-15641f688bc1_700x275.png" width="700" height="275" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b80faccf-372c-414b-9d2e-15641f688bc1_700x275.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:275,&quot;width&quot;:700,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:6531,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/204182941?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb80faccf-372c-414b-9d2e-15641f688bc1_700x275.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9oS7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb80faccf-372c-414b-9d2e-15641f688bc1_700x275.png 424w, https://substackcdn.com/image/fetch/$s_!9oS7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb80faccf-372c-414b-9d2e-15641f688bc1_700x275.png 848w, https://substackcdn.com/image/fetch/$s_!9oS7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb80faccf-372c-414b-9d2e-15641f688bc1_700x275.png 1272w, https://substackcdn.com/image/fetch/$s_!9oS7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb80faccf-372c-414b-9d2e-15641f688bc1_700x275.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>These come for free, straight from the HTTP request or a basic JavaScript query, with no heavy lifting required:</p><ul><li><p>User-Agent string (browser, version, OS)</p></li><li><p>Accept headers (language, encoding support)</p></li><li><p>Screen resolution and color depth</p></li><li><p>Timezone and locale</p></li><li><p>Installed fonts and plugins</p></li></ul><h3>Active signals</h3><p>Active fingerprinting runs JavaScript to pull things that are much harder to fake, because they depend on how the hardware actually behaves:</p><ul><li><p>Canvas fingerprinting renders hidden graphics. Tiny differences in font rasterization, anti-aliasing, and GPU instructions produce a distinctive output.</p></li><li><p>WebGL fingerprinting probes the GPU and surfaces details about the graphics hardware and driver stack.</p></li><li><p>AudioContext fingerprinting reads the audio processing pipeline, which varies slightly across systems and adds more entropy.</p></li></ul><p>These matter because they measure behavior, not claims. A User-Agent is just a string you can set to anything. A Canvas hash is the device answering for itself. Spoof the User-Agent but leave the Canvas output screaming &#8220;headless Chromium on a Linux server,&#8221; and the two halves of your story don&#8217;t match.</p><h3>Network-level signals</h3><p>Detection doesn&#8217;t stop at the browser. The network layer sanity-checks the rest of the profile:</p><ul><li><p>IP address and ASN (who owns the network)</p></li><li><p>Geolocation consistency, checking IP against timezone against language</p></li><li><p>TLS fingerprinting (JA3, and increasingly JA4), the shape of your TLS handshake, which most HTTP clients get wrong in a recognizable way</p></li><li><p>HTTP/2 fingerprinting, the frame and settings ordering that Akamai in particular leans on</p></li><li><p>Connection reuse and timing patterns</p></li></ul><p>A browser claiming to be mobile Safari in Spain, connecting from a datacenter ASN with a Python-default TLS handshake, has three independent signals contradicting each other. Any one is forgivable. Together they&#8217;re a verdict.</p><h2>What makes a fingerprint suspicious</h2><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FXl_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6162bddd-cb96-46f5-9cce-73fe375dd4e3_700x225.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FXl_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6162bddd-cb96-46f5-9cce-73fe375dd4e3_700x225.png 424w, https://substackcdn.com/image/fetch/$s_!FXl_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6162bddd-cb96-46f5-9cce-73fe375dd4e3_700x225.png 848w, https://substackcdn.com/image/fetch/$s_!FXl_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6162bddd-cb96-46f5-9cce-73fe375dd4e3_700x225.png 1272w, https://substackcdn.com/image/fetch/$s_!FXl_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6162bddd-cb96-46f5-9cce-73fe375dd4e3_700x225.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FXl_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6162bddd-cb96-46f5-9cce-73fe375dd4e3_700x225.png" width="700" height="225" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6162bddd-cb96-46f5-9cce-73fe375dd4e3_700x225.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:225,&quot;width&quot;:700,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:6368,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/204182941?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6162bddd-cb96-46f5-9cce-73fe375dd4e3_700x225.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!FXl_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6162bddd-cb96-46f5-9cce-73fe375dd4e3_700x225.png 424w, https://substackcdn.com/image/fetch/$s_!FXl_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6162bddd-cb96-46f5-9cce-73fe375dd4e3_700x225.png 848w, https://substackcdn.com/image/fetch/$s_!FXl_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6162bddd-cb96-46f5-9cce-73fe375dd4e3_700x225.png 1272w, https://substackcdn.com/image/fetch/$s_!FXl_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6162bddd-cb96-46f5-9cce-73fe375dd4e3_700x225.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Most fingerprints aren&#8217;t obviously fake. They&#8217;re built from real values: common browsers, standard resolutions, familiar operating systems. What anti-bot systems hunt for is how those real values fit together.</p><p>The first giveaway is a mismatch between attributes. Real devices follow predictable patterns. Certain browsers cluster on certain operating systems, resolutions cluster around popular hardware, fonts and rendering align with the platform. A macOS Safari profile serving Windows fonts, or a mobile User-Agent reporting a 2560-wide desktop viewport, breaks that pattern. Each discrepancy is explainable on its own. Stacked together, they aren&#8217;t.</p><p>Then there&#8217;s rarity. A perfectly unique fingerprint feels like good privacy and is terrible for blending in. Real users fall into large, overlapping groups of near-identical configurations. A profile built from attributes that almost never co-occur is both easier to track and quicker to flag. This is the over-randomization trap, and it&#8217;s the most common self-inflicted wound we see. People randomize every parameter and end up with a device that has never existed on Earth.</p><p>Consistency over time counts just as much. Real users are stable, same device, same browser, broadly the same setup from one request to the next. When one fingerprint shows up on an IP in one region and then again on an IP in another, or when key attributes drift between sessions, it reads as synthetic. Several IPs sharing a single identity is a pattern normal traffic just doesn&#8217;t produce.</p><p>Automation leaves its own tells. Even a carefully configured environment leaks something: a missing or stubbed API, a <code>navigator.webdriver</code> flag left on, slightly different JS execution paths, rendering quirks that don&#8217;t match a real build. No single tell is fatal. Detectors collect the small ones until the picture tips toward automation.</p><p>Last comes behavior. A flawless fingerprint still falls apart under robotic behavior. Pages loaded instantly with no intermediate navigation, clicks landing with pixel precision, sessions that fire one request and vanish, all of it undercuts the identity. Online, your identity is as much how you move as how you look.</p><h2>Why fingerprinting breaks the old playbook</h2><p>For years the routine was simple. Rotate IPs, randomize headers, respect rate limits. It worked because defenses were built around request-level signals, mostly where traffic came from and how fast it arrived. Fingerprinting moves the decision from the isolated request to a persistent identity, and most of the old tricks stop paying off.</p><p>Stateless requests became stateful identities. The old model judged each request on its own. Block one IP, grab another, keep going. Fingerprinting adds continuity. Change the IP and the browser environment can still look identical, or inconsistent in ways that link the sessions together. Rotating IPs no longer resets who you are.</p><p>Header randomization stopped working too. Rotating User-Agents and tweaking Accept-Language used to add diversity. Fingerprinting doesn&#8217;t read those values in isolation. It checks whether they agree with each other. Spin up a new User-Agent on every request while your rendering and JS execution stay constant, and you don&#8217;t look like many users. You look like one unstable client lying about itself. Tools like Apify&#8217;s fingerprint-suite and BrowserForge exist because building a coherent set of headers and fingerprint values is harder than randomizing them.</p><p>Datacenter-only setups draw scrutiny. Datacenter proxies are fast, cheap, and easy to rotate, but once the network is part of the identity that stops being free. A datacenter ASN has a different shape than residential traffic, and pairing one with a fingerprint that claims to be a consumer laptop is a contradiction. It won&#8217;t always block you, but it raises the baseline scrutiny, and stacked with any other inconsistency it pushes you over the line. <a href="https://infatica.io/residential-proxies/?utm_source=thewebscrapingclub&amp;utm_medium=guest_post&amp;utm_campaign=fingerprinting_deep_dive&amp;utm_content=residential_proxies_inline">This is why residential and mobile proxies </a>have become table stakes on harder targets, even though they only fix the network half of the problem.</p><p>Detection also moved earlier. Older systems often gave you partial access before a rate limit kicked in. Now the fingerprinting script usually runs the moment the page loads, sometimes before any real content is served. Fail the check and you never reach the data. You get a CAPTCHA, a soft block, or a clean-looking empty response.</p><h2>How to build a setup that survives fingerprinting</h2><p>The goal isn&#8217;t to be invisible. It&#8217;s to be unremarkable, an identity where every layer tells the same ordinary story.</p><h3>Run a real browser engine</h3><p>Lightweight HTTP clients can&#8217;t fake Canvas, WebGL, or AudioContext, because those depend on a real rendering pipeline. So most serious setups drive an actual browser with Playwright, Puppeteer, or Selenium.</p><p>Vanilla automation still leaks, which is where the patched ecosystem comes in. playwright-stealth and puppeteer-extra-plugin-stealth paper over the best-known tells. undetected-chromedriver and its async successor nodriver harden Selenium-style automation, and patchright is a drop-in patched Playwright aimed at the same goal. When you need full managed profiles, anti-detect browsers like Camoufox (open-source, Firefox-based, with fingerprint injection built in), Kameleo, GoLogin, Multilogin, AdsPower, and Dolphin Anty handle the consistency for you. None of these is a magic bullet, since anti-bots study them too, but they clear the low-level signals that plain Playwright fails on day one.</p><h3>Keep each fingerprint internally consistent</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bWRn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36c912a5-d9a8-4d1b-bbbb-07942d7f2cf6_700x250.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bWRn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36c912a5-d9a8-4d1b-bbbb-07942d7f2cf6_700x250.png 424w, https://substackcdn.com/image/fetch/$s_!bWRn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36c912a5-d9a8-4d1b-bbbb-07942d7f2cf6_700x250.png 848w, https://substackcdn.com/image/fetch/$s_!bWRn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36c912a5-d9a8-4d1b-bbbb-07942d7f2cf6_700x250.png 1272w, https://substackcdn.com/image/fetch/$s_!bWRn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36c912a5-d9a8-4d1b-bbbb-07942d7f2cf6_700x250.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bWRn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36c912a5-d9a8-4d1b-bbbb-07942d7f2cf6_700x250.png" width="700" height="250" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/36c912a5-d9a8-4d1b-bbbb-07942d7f2cf6_700x250.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:250,&quot;width&quot;:700,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:9143,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/204182941?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36c912a5-d9a8-4d1b-bbbb-07942d7f2cf6_700x250.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!bWRn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36c912a5-d9a8-4d1b-bbbb-07942d7f2cf6_700x250.png 424w, https://substackcdn.com/image/fetch/$s_!bWRn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36c912a5-d9a8-4d1b-bbbb-07942d7f2cf6_700x250.png 848w, https://substackcdn.com/image/fetch/$s_!bWRn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36c912a5-d9a8-4d1b-bbbb-07942d7f2cf6_700x250.png 1272w, https://substackcdn.com/image/fetch/$s_!bWRn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36c912a5-d9a8-4d1b-bbbb-07942d7f2cf6_700x250.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This is where over-randomization kills more scrapers than under-randomization ever has. Don&#8217;t change every parameter on every request. Give each session one stable, coherent identity, where the User-Agent matches the OS, the resolution matches the device class, and fonts, language, and timezone all tell the same story. If you generate fingerprints programmatically, lean on something built for coherence, like BrowserForge or fingerprint-suite, rather than rolling your own random values.</p><h3>Align the fingerprint with the network</h3><p>A fingerprint is never judged alone. Claim to be a mobile user in Spain while connecting from a datacenter in another region, and the whole session weakens. Treat proxies as part of the identity layer, not just a source of IPs:</p><ul><li><p>Residential or mobile proxies to match real-user network conditions</p></li><li><p>Geo-targeted IPs so the IP, timezone, and language reinforce each other</p></li><li><p>Session-based (sticky) routing for continuity across a session</p></li></ul><p>And don&#8217;t forget the handshake. If your browser fingerprint is flawless but your TLS fingerprint is a Python default, JA3/JA4 will give you away. curl_cffi, curl-impersonate, and tls-client exist to make that layer match the browser you&#8217;re pretending to be.</p><h3>Treat sessions as isolated identities</h3><p>Each session should behave like its own user, with its own fingerprint, its own IP, and its own cookies and local storage. Don&#8217;t reuse one fingerprint across several IPs at the same time, and don&#8217;t let session state bleed between identities. In practice that means pairing one fingerprint to one IP for the life of a session, then rotating both together, the way a real device and its connection move as a unit.</p><h3>Make the behavior match the identity</h3><p>A perfect fingerprint can&#8217;t save robotic behavior. Real users don&#8217;t teleport onto deep pages, strip the data, and disappear. They land somewhere shallow, navigate, pause, scroll. Adding delays that reflect reading time, and routing through intermediate pages instead of hammering endpoints directly, does more for credibility than another round of fingerprint tuning.</p><h3>Know when to stop building it yourself</h3><p>Maintaining all of this is real, ongoing work, and the targets keep moving. Browser hardening, fingerprint coherence, proxy alignment, session isolation, behavioral pacing, every layer needs upkeep. At some point the maintenance cost outweighs the control you get from it, and pushing these layers down into infrastructure starts to make sense.</p><p>That&#8217;s the niche a managed scraper API fills. Instead of hand-coordinating proxies, browser environments, and anti-detection logic, you hand off the identity layer and keep your code about the data. <a href="https://infatica.io/scraper-api/?utm_source=thewebscrapingclub&amp;utm_medium=guest_post&amp;utm_campaign=fingerprinting_deep_dive&amp;utm_content=scraper_api_inline">Infatica&#8217;s Scraper API</a> is built for that hand-off, with fingerprinting, session management, and request routing handled underneath, so what you maintain is extraction logic rather than evasion logic. It isn&#8217;t the right call for every project, but when anti-detection upkeep starts eating your roadmap, it&#8217;s a trade worth weighing.</p><h2>Where this actually bites</h2><p>Fingerprinting decides what data you can reach, how trustworthy it is, and whether your pipeline survives at scale. Getting past IP filtering is step one. Holding a believable identity for the whole session is the real game, and it&#8217;s where most of the projects we see actually break.</p><p>Price intelligence and travel feel it first. Travel platforms are among the most aggressive adopters. Prices move by location, device, and browsing pattern, so a setup that trips the defenses sees prices real users never see, or different ones every session. A hotel-pricing comparison is only as reliable as the identities behind the requests.</p><p>Search results are another. Search engines read the context of a query, not only the query itself. Weak fingerprinting earns an instant CAPTCHA. To capture what real users in a given region actually see, the requests have to look like they came from real users in that region, on consistent devices.</p><p>E-commerce monitoring runs into the same wall, and we&#8217;ve watched it happen most often on the luxury retailers sitting behind Akamai. Retailers tune availability, pricing, and promotions by user profile, and they use fingerprinting to tell shoppers from bots. Get flagged and you see hidden or reordered listings, throttling, or temporary bans, small distortions that quietly poison competitor-pricing analysis over time.</p><p>Ad verification and market research depend on seeing exactly what a real user sees: ad placement, localized copy, device-specific variants. Fingerprinting interferes by serving fallback content, blocking certain placements, or masking geo-targeted campaigns. That&#8217;s fatal when you&#8217;re validating campaigns across regions.</p><p>Account-based platforms lean hardest on fingerprinting to spot unusual access. These setups tend to fail not on a single request but because the session itself stops looking trustworthy. A sudden device-profile shift, an IP-location jump, or off behavior can each trigger a security check or kill the session.</p><h2>Conclusion</h2><p>Fingerprinting changed the question. It used to be &#8220;where is this request coming from?&#8221; Now it&#8217;s &#8220;does this client hold together as a believable identity, request after request?&#8221; Browser attributes, network signals, and behavior all feed that judgment, and the old reflex of rotating IPs and randomizing headers actively works against you when the layers don&#8217;t agree.</p><p>The setups that hold up aren&#8217;t the ones that randomize the hardest. They&#8217;re the ones that look the most ordinary: one coherent fingerprint, matched to one plausible network, behaving like a real person, held steady for the length of a session. The closer your setup sits to how real users actually browse, the more reliable your data collection becomes, and the more boring, in the best way.</p>]]></content:encoded></item><item><title><![CDATA[How CAPTCHAs Are Evolving to Fight AI-Powered Bots]]></title><description><![CDATA[Is it game over for CAPTCHA solvers?]]></description><link>https://substack.thewebscraping.club/p/how-captchas-are-evolving</link><guid isPermaLink="false">https://substack.thewebscraping.club/p/how-captchas-are-evolving</guid><dc:creator><![CDATA[Antonello Zanini]]></dc:creator><pubDate>Sun, 12 Jul 2026 04:00:09 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/0de69b65-f569-4e56-89bb-12fda53cdc02_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You must have noticed that CAPTCHAs are getting crazier and crazier. What used to be a simple &#8220;What text do you see in this distorted image?&#8221; has turned into a sequence of layered, multi-step, highly interactive verification tasks.</p><p>Fortunately, not all providers have gone in that direction (think of the simple one-click Cloudflare Turnstile). However, many others are designing more intricate mechanisms to counter AI-based automation.</p><p><em>Is this the end of CAPTCHA solvers? And does this impact your scraping script? And how should we interpret these changes overall?</em> Let me introduce you to the new generation of CAPTCHA systems to address all of these questions!</p><div><hr></div><blockquote><p><span>Give your AI a web data layer &#8211; </span><strong><a href="https://visit.decodo.com/rE0PQB"><span>Decodo's Web Scraping API</span></a></strong><span> turns any site into clean, structured data your models can actually use.</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://visit.decodo.com/rE0PQB" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aRfI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 424w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 848w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 1272w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aRfI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png" width="556" height="278" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:512,&quot;width&quot;:1024,&quot;resizeWidth&quot;:556,&quot;bytes&quot;:716985,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://visit.decodo.com/rE0PQB&quot;,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/204450518?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!aRfI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 424w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 848w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 1272w, https://substackcdn.com/image/fetch/$s_!aRfI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef41f8-3e73-4fa1-a516-a17d90e53e20_1024x512.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://visit.decodo.com/rE0PQB&quot;,&quot;text&quot;:&quot;Activate starter plan&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://visit.decodo.com/rE0PQB"><span>Activate starter plan</span></a></p></blockquote><div><hr></div><p></p><h2>Why CAPTCHAs Have Become More Sophisticated</h2><p>Before the rise of modern AI, CAPTCHAs could already be solved automatically using traditional machine learning techniques and browser automation. The approach was based on three steps:</p><ol><li><p>Detect the challenge.</p></li><li><p>Recognize its content.</p></li><li><p>Simulate the required user interaction.</p></li></ol><p>For example, <a href="https://arxiv.org/abs/2409.08831">image classification models can identify the correct images in reCAPTCHA v2 </a>(&#8221;Select all images containing hydrants&#8221;):</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FqMY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac6c662a-5512-437f-ba42-bbeea16f824e_472x600.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FqMY!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac6c662a-5512-437f-ba42-bbeea16f824e_472x600.gif 424w, https://substackcdn.com/image/fetch/$s_!FqMY!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac6c662a-5512-437f-ba42-bbeea16f824e_472x600.gif 848w, https://substackcdn.com/image/fetch/$s_!FqMY!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac6c662a-5512-437f-ba42-bbeea16f824e_472x600.gif 1272w, https://substackcdn.com/image/fetch/$s_!FqMY!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac6c662a-5512-437f-ba42-bbeea16f824e_472x600.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FqMY!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac6c662a-5512-437f-ba42-bbeea16f824e_472x600.gif" width="472" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ac6c662a-5512-437f-ba42-bbeea16f824e_472x600.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:472,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Letting an ML model solve a reCAPTCHA v2 challenge&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Letting an ML model solve a reCAPTCHA v2 challenge" title="Letting an ML model solve a reCAPTCHA v2 challenge" srcset="https://substackcdn.com/image/fetch/$s_!FqMY!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac6c662a-5512-437f-ba42-bbeea16f824e_472x600.gif 424w, https://substackcdn.com/image/fetch/$s_!FqMY!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac6c662a-5512-437f-ba42-bbeea16f824e_472x600.gif 848w, https://substackcdn.com/image/fetch/$s_!FqMY!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac6c662a-5512-437f-ba42-bbeea16f824e_472x600.gif 1272w, https://substackcdn.com/image/fetch/$s_!FqMY!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac6c662a-5512-437f-ba42-bbeea16f824e_472x600.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Letting an ML model solve a reCAPTCHA v2 challenge</figcaption></figure></div><p>(For more details on reCAPTCHA solving, see our previous articles: <a href="https://substack.thewebscraping.club/p/bypassing-recaptcha-v3">Part I</a> and <a href="https://substack.thewebscraping.club/p/bypassing-recaptchas-with-open-source">Part II</a>.)</p><p>Similarly, OCR models could extract text from distorted image-based CAPTCHAs.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!F8gB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F949d6fa4-0d24-4b1d-937c-0b152786d860_702x150.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!F8gB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F949d6fa4-0d24-4b1d-937c-0b152786d860_702x150.png 424w, https://substackcdn.com/image/fetch/$s_!F8gB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F949d6fa4-0d24-4b1d-937c-0b152786d860_702x150.png 848w, https://substackcdn.com/image/fetch/$s_!F8gB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F949d6fa4-0d24-4b1d-937c-0b152786d860_702x150.png 1272w, https://substackcdn.com/image/fetch/$s_!F8gB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F949d6fa4-0d24-4b1d-937c-0b152786d860_702x150.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!F8gB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F949d6fa4-0d24-4b1d-937c-0b152786d860_702x150.png" width="702" height="150" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/949d6fa4-0d24-4b1d-937c-0b152786d860_702x150.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:150,&quot;width&quot;:702,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Example of a text-based CAPTCHA&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Example of a text-based CAPTCHA" title="Example of a text-based CAPTCHA" srcset="https://substackcdn.com/image/fetch/$s_!F8gB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F949d6fa4-0d24-4b1d-937c-0b152786d860_702x150.png 424w, https://substackcdn.com/image/fetch/$s_!F8gB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F949d6fa4-0d24-4b1d-937c-0b152786d860_702x150.png 848w, https://substackcdn.com/image/fetch/$s_!F8gB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F949d6fa4-0d24-4b1d-937c-0b152786d860_702x150.png 1272w, https://substackcdn.com/image/fetch/$s_!F8gB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F949d6fa4-0d24-4b1d-937c-0b152786d860_702x150.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Example of a text-based CAPTCHA</figcaption></figure></div><p>Combined with browser automation, these models were often accurate enough to solve many CAPTCHA challenges without human intervention.</p><p>A good example was <em><a href="https://github.com/Vinyzu/Botright">Botright</a></em>, an open-source browser automation library that integrated machine learning models to solve CAPTCHAs. The project has since been abandoned (its last commit was over two years ago), but it demonstrated how effective this approach could be.</p><p>I&#8217;ve personally tested similar techniques. While they were far from 100% reliable, they were still capable of passing many CAPTCHA challenges.</p><p>CAPTCHA providers have long been aware of these techniques. As automated solvers became more capable, they responded by making CAPTCHAs steadily more intricate. This led to the introduction of puzzle-based challenges, such as those used by AWS WAF CAPTCHA, hCaptcha, and other providers.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jOdq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac56b05-a077-4169-af09-aefcf0a42886_341x462.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jOdq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac56b05-a077-4169-af09-aefcf0a42886_341x462.png 424w, https://substackcdn.com/image/fetch/$s_!jOdq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac56b05-a077-4169-af09-aefcf0a42886_341x462.png 848w, https://substackcdn.com/image/fetch/$s_!jOdq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac56b05-a077-4169-af09-aefcf0a42886_341x462.png 1272w, https://substackcdn.com/image/fetch/$s_!jOdq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac56b05-a077-4169-af09-aefcf0a42886_341x462.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jOdq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac56b05-a077-4169-af09-aefcf0a42886_341x462.png" width="341" height="462" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2ac56b05-a077-4169-af09-aefcf0a42886_341x462.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:462,&quot;width&quot;:341,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;An example of an AWS WAF puzzle CAPTCHA&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="An example of an AWS WAF puzzle CAPTCHA" title="An example of an AWS WAF puzzle CAPTCHA" srcset="https://substackcdn.com/image/fetch/$s_!jOdq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac56b05-a077-4169-af09-aefcf0a42886_341x462.png 424w, https://substackcdn.com/image/fetch/$s_!jOdq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac56b05-a077-4169-af09-aefcf0a42886_341x462.png 848w, https://substackcdn.com/image/fetch/$s_!jOdq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac56b05-a077-4169-af09-aefcf0a42886_341x462.png 1272w, https://substackcdn.com/image/fetch/$s_!jOdq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac56b05-a077-4169-af09-aefcf0a42886_341x462.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">An example of an AWS WAF puzzle CAPTCHA</figcaption></figure></div><p>Yet, the rapid advancement of multimodal AI models has raised the bar even further&#8230;</p><p>Instead of training specialized ML models for each CAPTCHA type, LLMs can understand images, reason about what they see, and let the controlled browser interact with the page much like a human would. That has forced CAPTCHA providers to develop more sophisticated techniques to distinguish humans from bots. </p><p>This is where we stand today!</p><h2>Are CAPTCHAs Dead Because of AI?</h2><p>The short answer is: <strong>No!</strong></p><p>While multimodal AI models have fundamentally changed how CAPTCHA providers think about distinguishing human users from automated bots, traditional CAPTCHAs are far from obsolete. There are at least three reasons why:</p><ol><li><p><strong>Not every bot uses AI</strong>: Many scraping bots still rely on traditional browser automation or simple scripts. They don&#8217;t have access to AI tools capable of understanding and solving CAPTCHA challenges.</p></li><li><p><strong>LLMs are not deterministic</strong>: AI models can solve the same CAPTCHA challenge most of the time, but fail spectacularly on others. No wonder you can find examples on Reddit and X of AI-powered browsers repeatedly failing at CAPTCHA challenges.</p></li><li><p><strong>Solving the challenge is only part of the verification process</strong>: CAPTCHA systems usually evaluate much more than just the answer to the test. They also analyze behavioral signals such as mouse movements, cursor acceleration, click timing, scrolling behavior, touch interactions, and many other indicators. That&#8217;s why one-click, press-and-hold, and slider CAPTCHAs exist. An AI model may correctly identify the solution, yet still exhibit interaction patterns that look &#8220;robotic&#8221; rather than &#8220;human,&#8221; causing the verification to fail.</p></li></ol><p>That said, AI is undeniably making life harder for CAPTCHA providers. <a href="https://www.usenix.org/conference/usenixsecurity25/presentation/teoh">Numerous research papers</a> have shown that, given sufficient training data and realistic browser automation techniques (such as human-like mouse movements, scrolling, and timing), AI systems can successfully solve many traditional CAPTCHA schemes.</p><p>The real question is no longer whether AI can solve older CAPTCHAs, as it clearly can. The more interesting question is: <em><a href="https://substack.thewebscraping.club/p/bypassing-hcaptcha-llm">Can it reliably solve the new generation of AI-resistant CAPTCHAs?</a></em></p><h2>New Approaches to AI-Resistant CAPTCHAs</h2><p>More and more CAPTCHA providers are experimenting with advanced challenges specifically engineered to resist AI-powered solvers.</p><p>In this section, I&#8217;ll focus on three approaches to newer CAPTCHAs that I&#8217;ve personally seen challenge even some of the most popular CAPTCHA-solving solutions.</p><h3>hCaptcha&#8217;s New Puzzle-Based Challenges</h3><p>hCaptcha has always been one of the more difficult CAPTCHA providers to bypass, thanks to its creative human verification tests.</p><p>As AI capabilities have advanced, hCaptcha has responded by introducing elaborate puzzles purpose-built to thwart automated solving:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8q6C!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70c24751-745c-42df-bfec-b519bd1e351b_1225x1506.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8q6C!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70c24751-745c-42df-bfec-b519bd1e351b_1225x1506.png 424w, https://substackcdn.com/image/fetch/$s_!8q6C!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70c24751-745c-42df-bfec-b519bd1e351b_1225x1506.png 848w, https://substackcdn.com/image/fetch/$s_!8q6C!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70c24751-745c-42df-bfec-b519bd1e351b_1225x1506.png 1272w, https://substackcdn.com/image/fetch/$s_!8q6C!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70c24751-745c-42df-bfec-b519bd1e351b_1225x1506.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8q6C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70c24751-745c-42df-bfec-b519bd1e351b_1225x1506.png" width="1225" height="1506" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/70c24751-745c-42df-bfec-b519bd1e351b_1225x1506.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1506,&quot;width&quot;:1225,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Examples of recent hCaptcha puzzle-based challenges&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Examples of recent hCaptcha puzzle-based challenges" title="Examples of recent hCaptcha puzzle-based challenges" srcset="https://substackcdn.com/image/fetch/$s_!8q6C!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70c24751-745c-42df-bfec-b519bd1e351b_1225x1506.png 424w, https://substackcdn.com/image/fetch/$s_!8q6C!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70c24751-745c-42df-bfec-b519bd1e351b_1225x1506.png 848w, https://substackcdn.com/image/fetch/$s_!8q6C!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70c24751-745c-42df-bfec-b519bd1e351b_1225x1506.png 1272w, https://substackcdn.com/image/fetch/$s_!8q6C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70c24751-745c-42df-bfec-b519bd1e351b_1225x1506.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Examples of recent hCaptcha puzzle-based challenges</figcaption></figure></div><p>Notice how many of these challenges require much more than simply clicking on the correct images. They can involve drag-and-drop interactions, spatial reasoning, and specific knowledge.</p><div><hr></div><blockquote><p><em>Instead of fighting every CAPTCHA, focus on avoiding them. <strong>anyIP</strong> gives you access to high-quality residential and mobile proxies that help your traffic blend in more naturally.</em></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://anyip.io/?utm_source=thewebscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=gold_2026_2&amp;utm_content=banner_captcha" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sgdX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb21eff47-47c6-4800-be91-cb5baecdb729_4585x2292.png 424w, https://substackcdn.com/image/fetch/$s_!sgdX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb21eff47-47c6-4800-be91-cb5baecdb729_4585x2292.png 848w, https://substackcdn.com/image/fetch/$s_!sgdX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb21eff47-47c6-4800-be91-cb5baecdb729_4585x2292.png 1272w, https://substackcdn.com/image/fetch/$s_!sgdX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb21eff47-47c6-4800-be91-cb5baecdb729_4585x2292.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sgdX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb21eff47-47c6-4800-be91-cb5baecdb729_4585x2292.png" width="590" height="295" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b21eff47-47c6-4800-be91-cb5baecdb729_4585x2292.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:728,&quot;width&quot;:1456,&quot;resizeWidth&quot;:590,&quot;bytes&quot;:913094,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://anyip.io/?utm_source=thewebscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=gold_2026_2&amp;utm_content=banner_captcha&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/204450518?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb21eff47-47c6-4800-be91-cb5baecdb729_4585x2292.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sgdX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb21eff47-47c6-4800-be91-cb5baecdb729_4585x2292.png 424w, https://substackcdn.com/image/fetch/$s_!sgdX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb21eff47-47c6-4800-be91-cb5baecdb729_4585x2292.png 848w, https://substackcdn.com/image/fetch/$s_!sgdX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb21eff47-47c6-4800-be91-cb5baecdb729_4585x2292.png 1272w, https://substackcdn.com/image/fetch/$s_!sgdX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb21eff47-47c6-4800-be91-cb5baecdb729_4585x2292.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://anyip.io/?utm_source=thewebscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=gold_2026_2&amp;utm_content=banner_captcha&quot;,&quot;text&quot;:&quot;Start with AnyIP&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://anyip.io/?utm_source=thewebscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=gold_2026_2&amp;utm_content=banner_captcha"><span>Start with AnyIP</span></a></p></blockquote><div><hr></div><h3>Microsoft&#8217;s Multi-Option CAPTCHAs</h3><p>Explore the Microsoft forums and Reddit, and you&#8217;ll find plenty of users complaining about Microsoft&#8217;s newer human verification challenges (<a href="https://learn.microsoft.com/en-in/answers/questions/2289029/insane-captcha-6-times-x-10-tasks-and-doesnt-pass">example 1</a>, <a href="https://www.reddit.com/r/LinusTechTips/comments/1omalh3/hey_microsoft_this_captcha_is_one_of_the_worst/">example 2</a>, <a href="https://learn.microsoft.com/en-in/answers/questions/2289029/insane-captcha-6-times-x-10-tasks-and-doesnt-pass">example 3</a>). Here are a few examples:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FmoW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff8790bf-92fe-4646-9f60-a8f84b7d73fa_1868x1779.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FmoW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff8790bf-92fe-4646-9f60-a8f84b7d73fa_1868x1779.png 424w, https://substackcdn.com/image/fetch/$s_!FmoW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff8790bf-92fe-4646-9f60-a8f84b7d73fa_1868x1779.png 848w, https://substackcdn.com/image/fetch/$s_!FmoW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff8790bf-92fe-4646-9f60-a8f84b7d73fa_1868x1779.png 1272w, https://substackcdn.com/image/fetch/$s_!FmoW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff8790bf-92fe-4646-9f60-a8f84b7d73fa_1868x1779.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FmoW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff8790bf-92fe-4646-9f60-a8f84b7d73fa_1868x1779.png" width="1456" height="1387" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ff8790bf-92fe-4646-9f60-a8f84b7d73fa_1868x1779.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1387,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Microsoft&#8217;s new multi-option CAPTCHAs&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Microsoft&#8217;s new multi-option CAPTCHAs" title="Microsoft&#8217;s new multi-option CAPTCHAs" srcset="https://substackcdn.com/image/fetch/$s_!FmoW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff8790bf-92fe-4646-9f60-a8f84b7d73fa_1868x1779.png 424w, https://substackcdn.com/image/fetch/$s_!FmoW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff8790bf-92fe-4646-9f60-a8f84b7d73fa_1868x1779.png 848w, https://substackcdn.com/image/fetch/$s_!FmoW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff8790bf-92fe-4646-9f60-a8f84b7d73fa_1868x1779.png 1272w, https://substackcdn.com/image/fetch/$s_!FmoW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff8790bf-92fe-4646-9f60-a8f84b7d73fa_1868x1779.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Microsoft&#8217;s new multi-option CAPTCHAs</figcaption></figure></div><p>These challenges can be somewhat ambiguous. They tend to present multiple plausible answers, require you to search through numerous options, and may involve several consecutive steps before the verification is complete. As a result, they take considerably longer to solve and can be frustrating for legitimate users (especially if a mistake forces them to restart the entire challenge).</p><h3>Temu&#8217;s Proprietary CAPTCHAs</h3><p>Some platforms are moving away from standard CAPTCHA providers and building their own verification systems in-house. A notable example is Temu, which has recently introduced a restrictive, custom CAPTCHA mechanism:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!R1hb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94eb01ed-bc62-4d70-8cb0-67d57d1487d1_815x1092.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!R1hb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94eb01ed-bc62-4d70-8cb0-67d57d1487d1_815x1092.png 424w, https://substackcdn.com/image/fetch/$s_!R1hb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94eb01ed-bc62-4d70-8cb0-67d57d1487d1_815x1092.png 848w, https://substackcdn.com/image/fetch/$s_!R1hb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94eb01ed-bc62-4d70-8cb0-67d57d1487d1_815x1092.png 1272w, https://substackcdn.com/image/fetch/$s_!R1hb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94eb01ed-bc62-4d70-8cb0-67d57d1487d1_815x1092.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!R1hb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94eb01ed-bc62-4d70-8cb0-67d57d1487d1_815x1092.png" width="815" height="1092" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/94eb01ed-bc62-4d70-8cb0-67d57d1487d1_815x1092.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1092,&quot;width&quot;:815,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Examples of Temu&#8217;s CAPTCHAs&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Examples of Temu&#8217;s CAPTCHAs" title="Examples of Temu&#8217;s CAPTCHAs" srcset="https://substackcdn.com/image/fetch/$s_!R1hb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94eb01ed-bc62-4d70-8cb0-67d57d1487d1_815x1092.png 424w, https://substackcdn.com/image/fetch/$s_!R1hb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94eb01ed-bc62-4d70-8cb0-67d57d1487d1_815x1092.png 848w, https://substackcdn.com/image/fetch/$s_!R1hb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94eb01ed-bc62-4d70-8cb0-67d57d1487d1_815x1092.png 1272w, https://substackcdn.com/image/fetch/$s_!R1hb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94eb01ed-bc62-4d70-8cb0-67d57d1487d1_815x1092.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Examples of Temu&#8217;s CAPTCHAs</figcaption></figure></div><p>These challenges go beyond simple image selection and require very specific actions. For instance, they can involve multi-step interactions such as drag-and-drop tasks or slider-based tests that must be completed in a precise way.</p><p>What they generally have in common is the heavy use of AI-generated images. This creates an interesting dynamic: <em>AI is being used to produce the very content to stop AI-powered bots.</em></p><h3>The Future of CAPTCHA: What Comes Next</h3><p>While these new CAPTCHA challenges are definitely more difficult for AI to solve, it&#8217;s still unclear whether AI will evolve quickly enough to keep up in the ongoing arms race between <a href="https://substack.thewebscraping.club/p/the-evolving-career-scrapers">scraping developers</a> and CAPTCHA-protected websites.</p><p>What&#8217;s clear is that providers will continue experimenting&#8230;</p><h3>Hand-Gesture CAPTCHAs</h3><p>Hand-gesture CAPTCHAs represent a shift from traditional image-based verification toward real-world interaction signals.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Mibw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa8be0fa-37a3-4ced-b703-2bb1927ef246_1200x896.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Mibw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa8be0fa-37a3-4ced-b703-2bb1927ef246_1200x896.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Mibw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa8be0fa-37a3-4ced-b703-2bb1927ef246_1200x896.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Mibw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa8be0fa-37a3-4ced-b703-2bb1927ef246_1200x896.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Mibw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa8be0fa-37a3-4ced-b703-2bb1927ef246_1200x896.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Mibw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa8be0fa-37a3-4ced-b703-2bb1927ef246_1200x896.jpeg" width="1200" height="896" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/aa8be0fa-37a3-4ced-b703-2bb1927ef246_1200x896.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:896,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;New Google reCAPTCHAs based on hand gestures&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="New Google reCAPTCHAs based on hand gestures" title="New Google reCAPTCHAs based on hand gestures" srcset="https://substackcdn.com/image/fetch/$s_!Mibw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa8be0fa-37a3-4ced-b703-2bb1927ef246_1200x896.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Mibw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa8be0fa-37a3-4ced-b703-2bb1927ef246_1200x896.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Mibw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa8be0fa-37a3-4ced-b703-2bb1927ef246_1200x896.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Mibw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa8be0fa-37a3-4ced-b703-2bb1927ef246_1200x896.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">New Google reCAPTCHAs based on hand gestures</figcaption></figure></div><p>Google has recently begun testing a new reCAPTCHA system that asks users to prove they&#8217;re human by performing simple hand gestures in front of their device camera.</p><p>When enabled, the browser requests camera access and records a short video of the user&#8217;s hand movement. The system then analyzes the footage by extracting around 21 hand-landmark points (measurements of finger and joint positions) to identify specific gestures.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9k5a!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F628af754-1621-4429-99fb-e0cae7390f32_640x426.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9k5a!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F628af754-1621-4429-99fb-e0cae7390f32_640x426.jpeg 424w, https://substackcdn.com/image/fetch/$s_!9k5a!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F628af754-1621-4429-99fb-e0cae7390f32_640x426.jpeg 848w, https://substackcdn.com/image/fetch/$s_!9k5a!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F628af754-1621-4429-99fb-e0cae7390f32_640x426.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!9k5a!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F628af754-1621-4429-99fb-e0cae7390f32_640x426.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9k5a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F628af754-1621-4429-99fb-e0cae7390f32_640x426.jpeg" width="640" height="426" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/628af754-1621-4429-99fb-e0cae7390f32_640x426.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:426,&quot;width&quot;:640,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Making simple hand gestures in front of a camera&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Making simple hand gestures in front of a camera" title="Making simple hand gestures in front of a camera" srcset="https://substackcdn.com/image/fetch/$s_!9k5a!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F628af754-1621-4429-99fb-e0cae7390f32_640x426.jpeg 424w, https://substackcdn.com/image/fetch/$s_!9k5a!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F628af754-1621-4429-99fb-e0cae7390f32_640x426.jpeg 848w, https://substackcdn.com/image/fetch/$s_!9k5a!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F628af754-1621-4429-99fb-e0cae7390f32_640x426.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!9k5a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F628af754-1621-4429-99fb-e0cae7390f32_640x426.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Making simple hand gestures in front of a camera</figcaption></figure></div><p>This is an attempt to improve &#8220;liveness detection,&#8221; making it harder for automated systems to pass as real users. Still, it&#8217;s not impossible that AI-driven tools could eventually simulate these gestures using AI-generated videos via camera injection.</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.youtube.com/@thewebscrapingclub&quot;,&quot;text&quot;:&quot;Check the TWSC YouTube Channel&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.youtube.com/@thewebscrapingclub"><span>Check the TWSC YouTube Channel</span></a></p><div><hr></div><h3>Stronger CAPTCHAs, Worse User Experience?</h3><p>Newer CAPTCHAs are significantly harder for AI to pass, but they also introduce a new layer of friction for us, the everyday users. That&#8217;s the main trade-off in the current wave of anti-AI verification systems.</p><p>As models become more capable, CAPTCHAs are forced to evolve in parallel, often becoming more demanding not only for bots but also for legitimate users.</p><p>As a result, many users have been complaining on Reddit and X about frustration, increasingly time-consuming challenges, and <a href="https://www.reddit.com/r/degoogle/comments/1ubocbe/google_is_testing_a_new_recaptcha_system_that/">privacy concerns</a> (such as having to use a camera just to complete a CAPTCHA).</p><p>This has led to growing criticism that could eventually push providers to relax some of these verification mechanisms, either by making them easier or less frequent (which would be a win-win scenario for web scraping).</p><p>It&#8217;s quite clear that the real objective is finding a balance between effectively blocking bots and avoiding unnecessary friction for legitimate users.</p><h3>Are These New CAPTCHAs Effective Against AI?</h3><p>These newer CAPTCHAs aren&#8217;t becoming more elaborate simply for the sake of increasing difficulty. Providers are well aware that the probability of AI reliably passing these checks at scale remains low, though not zero.</p><p>The idea is to design challenges that are intricate enough to deter automation, while still incorporating sufficient interaction signals and behavioral data to assess whether the user is actually human.</p><p>In essence, beneath the surface, the core principle hasn&#8217;t fundamentally changed. What we are seeing is an evolution in execution, rather than a reinvention of the underlying idea.</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/p/consulting&quot;,&quot;text&quot;:&quot;Need help with your scraping project?&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://substack.thewebscraping.club/p/consulting"><span>Need help with your scraping project?</span></a></p><div><hr></div><h3>Avoiding CAPTCHAs Remains More Effective Than Solving Them</h3><p>The final question is: &#8220;Should these new CAPTCHAs scare us, web scraping experts/enthusiasts?&#8221; In a sense, they might, but ultimately this is not really a revolution&#8230;</p><p>The truth is that in web scraping, the most effective strategy is rarely about solving CAPTCHAs. Instead, it&#8217;s about avoiding them altogether.</p><p>CAPTCHA systems are just one layer of a <a href="https://substack.thewebscraping.club/p/anti-detect-anti-bot-matrix">broader anti-bot ecosystem</a>, and triggering them is a signal that the traffic is already considered low-trust or detectable.</p><p>For this reason, the focus should be on prevention rather than resolution. Best practices involve:</p><ol><li><p>Routing traffic through high-quality <a href="https://substack.thewebscraping.club/p/what-is-a-residential-proxy">residential proxy networks</a> with strong reputation IPs.</p></li><li><p>Using stealth browser environments such as <a href="https://substack.thewebscraping.club/p/kameleo-docker-exploring-the-docker">Kameleo Docker</a>, <a href="https://substack.thewebscraping.club/p/is-camoufox-still-effective-scraping">Camoufox</a>, or <a href="https://substack.thewebscraping.club/p/rayobrowse-browser-scraping">Rayobrowse</a>.</p></li><li><p>Managing and rotating browser sessions in automated scraping workflows.</p></li></ol><p>The end goal is to consistently emulate real, human-like browsing behavior to minimize the chances of detection. If human verification tests still appear, CAPTCHA solvers will remain the go-to option, as they must evolve to keep up with new challenges in order to stay in business.</p><div><hr></div><p>The Web Scraping Club is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p><div><hr></div><h2>Conclusion</h2><p>In this post, you&#8217;ve understood why CAPTCHAs have become more sophisticated and the role AI has played in this evolution. You&#8217;ve also seen what providers like hCaptcha, platforms like Microsoft, and sites like Temu are doing to defend against AI-powered automated scrapers and bots, as well as what the future of CAPTCHAs might look like.</p><p>I hope you now have a clearer understanding of the current and future CAPTCHA landscape. If you have any questions, feel free to leave them in the comments below. Until next time!</p><div><hr></div><p><em>Did you like this article? Share it with someone who might find it useful and get a discount on paid plans.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/p/how-captchas-are-evolving?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://substack.thewebscraping.club/p/how-captchas-are-evolving?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Web Scraping Club is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[When the FBI Knocks on Your Proxy Provider's Door ]]></title><description><![CDATA[My Thoughts on the NetNut Takedown and What It Means for Us in Web Scraping]]></description><link>https://substack.thewebscraping.club/p/when-the-fbi-knocks-on-your-proxy</link><guid isPermaLink="false">https://substack.thewebscraping.club/p/when-the-fbi-knocks-on-your-proxy</guid><dc:creator><![CDATA[Pierluigi Vinciguerra]]></dc:creator><pubDate>Thu, 09 Jul 2026 20:46:33 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/309ad261-46b9-4c83-9e4f-48437b24fc91_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you tried to log in to <strong>netnut.io</strong> recently, you probably noticed something unusual. Instead of the usual dashboard, there was an FBI seizure banner staring back at you.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yj5F!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1d3f70a-a0aa-4c98-98ea-263d6f38013e_2578x1506.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yj5F!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1d3f70a-a0aa-4c98-98ea-263d6f38013e_2578x1506.png 424w, https://substackcdn.com/image/fetch/$s_!yj5F!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1d3f70a-a0aa-4c98-98ea-263d6f38013e_2578x1506.png 848w, https://substackcdn.com/image/fetch/$s_!yj5F!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1d3f70a-a0aa-4c98-98ea-263d6f38013e_2578x1506.png 1272w, https://substackcdn.com/image/fetch/$s_!yj5F!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1d3f70a-a0aa-4c98-98ea-263d6f38013e_2578x1506.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yj5F!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1d3f70a-a0aa-4c98-98ea-263d6f38013e_2578x1506.png" width="1456" height="851" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c1d3f70a-a0aa-4c98-98ea-263d6f38013e_2578x1506.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:851,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2185706,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/206054175?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1d3f70a-a0aa-4c98-98ea-263d6f38013e_2578x1506.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!yj5F!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1d3f70a-a0aa-4c98-98ea-263d6f38013e_2578x1506.png 424w, https://substackcdn.com/image/fetch/$s_!yj5F!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1d3f70a-a0aa-4c98-98ea-263d6f38013e_2578x1506.png 848w, https://substackcdn.com/image/fetch/$s_!yj5F!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1d3f70a-a0aa-4c98-98ea-263d6f38013e_2578x1506.png 1272w, https://substackcdn.com/image/fetch/$s_!yj5F!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1d3f70a-a0aa-4c98-98ea-263d6f38013e_2578x1506.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>On July 2nd, the FBI, together with several industry partners, seized hundreds of domains tied to NetNut. For those who don&#8217;t know, NetNut is a large residential proxy service run by Alarum Technologies, a company that&#8217;s even listed on NASDAQ. The seized domains included <strong>netnut.com</strong>, <strong>proxyjet.io</strong>, and <strong>divinetworks.com</strong>. </p><p>This didn&#8217;t come out of nowhere. Let me walk you through how we got here, because the timeline is important to understand what happened.</p><h3>How the takedown happened</h3><p>It all started on June 19th, when <a href="https://krebsonsecurity.com/2026/06/popa-botnet-linked-to-publicly-traded-israeli-firm/">three security firms published reports</a> showing that NetNut&#8217;s residential proxy network was actually powering a botnet called Popa. The software was running on everyday devices like smart TVs and streaming boxes. Qurium, one of the security firms, stumbled onto this while looking into some denial-of-service attacks and traced the control servers back to pirated streaming apps. Then Brian Krebs made the story public, and about two weeks later, the authorities stepped in.</p><p>The takedown was a team effort: Google, the FBI, Lumen Technologies, The Shadowserver Foundation, and others all played a part. This is where things get interesting from a technical perspective, because taking down a proxy network is nothing like just unplugging a server.</p><p>Residential proxy networks are built to be decentralized. The exit nodes are actually millions of consumer devices scattered all over the world. Google estimated that NetNut had at least 2 million devices in its network. Obviously, you can&#8217;t just go and seize someone&#8217;s smart TV. What you can do is target the things those devices rely on to function as part of a commercial proxy service.</p><p>First, there&#8217;s the command and control layer. Google shut down the accounts and services that NetNut was using to control the malware. What I found interesting is that this C2 infrastructure was actually running on Google&#8217;s own services. This technique, called &#8216;living off trusted services,&#8217; lets malicious traffic blend in with normal cloud traffic. Without command and control, there&#8217;s no way to route customer traffic through those devices.</p><p>Next is the enrollment pipeline. Google Play Protect began warning users and disabling apps that included NetNut SDKs, and it will keep blocking any new installs. If a proxy network can&#8217;t add new devices, it starts to shrink over time, since devices get reset, replaced, or just go offline.</p><p>Then there&#8217;s the commercial front door. By seizing the main domains, the FBI basically shut down NetNut&#8217;s storefront and made it impossible to bring in new customers at scale.</p><p>All of this together, according to Google, cut down the number of available devices by millions and seriously hurt NetNut&#8217;s business.</p><h3>What makes this case different</h3><p>I&#8217;ve seen proxy networks get taken down before, but NetNut is different. This wasn&#8217;t some anonymous group hiding on the dark web. NetNut was run by a company listed on NASDAQ, with investor relations, quarterly reports, and even a legal team that put out a statement saying they were aware of the seizure and cooperating with investigators.</p><p>It must be said that Alarum has rejected the accusations, disputing their accuracy. On the commercial and official side, NetNut has always presented itself exclusively as a legitimate proxy provider for enterprise data collection, without ever declaring any kinship with the clandestine Popa botnet. In its communications following the seizure, the company stated that neither Alarum nor NetNut had been formally contacted by the FBI or any other governmental authority in connection with these matters, while temporarily suspending data traffic through the affected network services in order to investigate the incident and assess the infrastructure involved. Markets, however, delivered their own verdict quickly: Alarum&#8217;s stock crashed by more than 70% at the end of last week, bringing its market cap down roughly 96% from its 2024 peak, and the company itself warned that prolonged disruptions are expected to have a material adverse effect on its operations and financial results.</p><p>It&#8217;s important to be clear about which part of NetNut&#8217;s network is involved here, because NetNut wasn&#8217;t just selling one type of proxy. They offered rotating residential proxies, static ISP proxies, mobile proxies, and regular datacenter proxies. Datacenter IPs are the least controversial, since they come from cloud and hosting providers with clear ownership. The takedown focused on the residential side, where NetNut actually used two very different sourcing models.</p><p>On one side there are the ISP proxies, sourced through DiviNetworks,  the company NetNut originally grew out of. This is an above-board arrangement: as Spur documented in its research on how proxy providers co-opt entire networks, DiviNetworks recruits ISP partners with a straightforward revenue-stream pitch, terminates a GRE tunnel on the partner&#8217;s edge router, installs software on a VM inside the partner network, and uses policy-based routing to steer a slice of traffic through that VM and back out through NetNut&#8217;s infrastructure. From the partner&#8217;s perspective, a small amount of traffic is diverted; from NetNut&#8217;s perspective, selected egress from the partner&#8217;s public IP space becomes a commercial proxy product. The ISPs sign an agreement and get paid. You can argue about whether their subscribers know, but at least the contractual chain is in daylight.</p><p>The other side is the residential proxy pool, and this is where the Popa botnet comes into play. Researchers found that NetNut built this network by distributing SDKs for devices like smart TVs and streaming boxes. Synthient looked at over 20 real Popa publishers and didn&#8217;t see any of them asking users for consent. This is the part of the network that led to the takedown. When you have two million devices enrolled without real consent, that&#8217;s not a legitimate proxy network; it&#8217;s a botnet with a billing system, at least from a law enforcement point of view.</p><p>But the fact that divinetworks.com was seized along with the other domains tells me that investigators aren&#8217;t making a clear distinction between the different parts of the business. Even providers who think their sourcing is clean should take note.</p><h3>This isn&#8217;t just a one-off event; it&#8217;s part of a bigger pattern</h3><p>If this all sounds familiar, it&#8217;s because we&#8217;ve seen it before, and not long ago. The NetNut takedown follows <a href="https://substack.thewebscraping.club/p/google-vs-ipidea-takedown">right after Google&#8217;s disruption of the IPIDEA proxy network back in January 2026</a>. At the time, IPIDEA was one of the biggest residential proxy operations out there, and NetNut&#8217;s main competitor.</p><p>Google&#8217;s post-mortem on IPIDEA included what I think is the key point here: when their own botnet starts to fall apart, proxy operators just buy capacity from competitors and become resellers. The proxy industry is tightly connected, and Google is pretty confident that a lot of popular residential proxy brands were actually just reselling NetNut&#8217;s botnet.</p><p>If you buy residential proxies from a brand you haven&#8217;t really checked out, pay attention. The IP pool behind your provider&#8217;s logo might be NetNut&#8217;s, or IPIDEA&#8217;s, or whoever is next in line.</p><p>The timing here is no accident. Bot traffic is exploding, especially with AI agents and LLM-based crawlers on the rise. In all my years doing this work, I&#8217;ve never seen web scraping infrastructure get this much attention from regulators and platforms. We&#8217;re in a real bind: we need more scraping and automation than ever, which means more proxies, but at the same time, there&#8217;s a real push for transparency about how these networks are built and used, so we can tell the good actors from the bad.</p><p>The lesson from NetNut is that just being transparent about where your IPs come from isn&#8217;t enough. You also need to know who is actually using your network. Alarum said they did KYC and due diligence on NetNut&#8217;s customers, but Spur found that NetNut didn&#8217;t really require corporate verification or real know-your-customer checks before selling proxy access. Down the line, resellers and white labelers usually didn&#8217;t do any KYC at all. Anyone who knew where to look could buy access with just a burner email and a few dollars in crypto. The result? In just one week in June 2026, Google saw 316 different threat groups using NetNut exit nodes, including cybercriminals and espionage actors.</p><h3>What this means if you were a customer</h3><p>Let&#8217;s be honest: losing a proxy provider isn&#8217;t really a technical problem. If you&#8217;re running a serious scraping setup, switching providers just means changing your endpoint. And if you&#8217;ve set up any kind of automatic fallback system, which you really should have, your pipelines probably won&#8217;t even notice the downtime.</p><p>The real headache is legal and compliance risk, especially if you&#8217;re working in a highly regulated industry.</p><p>Take financial markets, for example: I&#8217;ve worked in this space myself. Web scraping is a key part of what&#8217;s called alternative data: things like scraped prices, app usage stats, satellite images, and transaction data. Investment funds use these to get an edge on listed companies, alongside the usual financial statements and market data.</p><p>Anyone who has tried to sell web-scraped data to an investment manager knows what comes next: the due diligence questionnaire. I&#8217;ve been through this process, and it&#8217;s not a formality. The standard DDQ used in the industry, published by FISD&#8217;s Alternative Data Council, dedicates an entire appendix to web scraping and explicitly asks vendors whether they use proxies, whether those proxies are anonymizing, whether the proxy providers include information sufficient for scraped websites to contact the company, whether data is collected from pages behind logins or CAPTCHAs, and whether the vendor or its service providers have received cease and desist orders, subpoenas, or been subject to investigations in the past five years.</p><p>Now imagine you&#8217;re a data vendor facing that last question, and your proxy provider&#8217;s homepage is showing an FBI seizure banner. Telling a hedge fund that your infrastructure supplier is under federal investigation is not going to help you close any deals. In this business, your compliance is only as strong as the weakest link in your supply chain. Proxies are definitely part of that chain, whether your legal team knows it or not.</p><h3>How I Think About Choosing Proxy Providers After NetNut</h3><p>The reality is that residential and mobile proxies have always been the most controversial part of the proxy world. Unlike datacenter IPs or ISP deals, residential proxies need real people to agree to run a proxy on their devices in exchange for something, maybe money if they&#8217;re in a bandwidth-sharing program, or a free app, since developers get paid by proxy providers for every install.</p><p>Most people don&#8217;t realize how widespread this is. <a href="https://spur.us/blog/how-proxy-providers-co-opt-entire-networks">Spur scanned over 6,000 LG and Samsung smart TV apps and found that more than 2,000 of them had residential proxy SDKs inside</a>. That means about a third of smart TV apps are quietly acting as proxy infrastructure. The real difference between legitimate and abusive setups comes down to consent and disclosure. Some providers, like <a href="https://get.brightdata.com/twsc">Bright Data</a>, Massive, and <a href="https://oxylabs.go2cloud.org/aff_c?offer_id=7&amp;aff_id=665">Oxylabs</a>, at least spell out the arrangement in their terms of service, as Spur pointed out. For example, you can check Bright Data's peer program directly on <a href="http://bright-sdk.com/">bright-sdk.com</a>, where you can find the certifications and compliance that they obtained.</p><p>Some providers have shown they&#8217;re willing to take a hit to keep their networks clean. For example, <strong><a href="https://brightdata.com/blog/leadership/why-we-willingly-killed-10-percent-of-our-network">Bright Data has publicly shared that they dropped SDK partners who wouldn&#8217;t digitally sign their apps or who served ads leading to shady websites</a></strong>. These partners made up over 10% of their residential peer network, so it wasn&#8217;t a small decision. On the You might be skeptical about companies promoting their own good behavior, but I think this is the right direction: sourcing based on real standards, not just growth at any cost. They&#8217;re also founding members of the <strong><a href="https://responsibledatacollection.org/">Alliance for Responsible Data Collection</a>, </strong>together with other players like <strong><a href="https://www.sequentum.com/">Sequentum</a> </strong>and <strong><a href="https://commoncrawl.org/">Common Crawl</a></strong>.</p><p>So here&#8217;s what I take away from all this. After what happened with IPIDEA in January and NetNut in July, picking a proxy provider based only on price isn&#8217;t just about quality anymore; it&#8217;s about risk. You need to ask the same questions the security researchers asked NetNut: How do you source your IPs, and can you prove consent? Who can actually buy access to your network, and what does your KYC process look like, even for your resellers? And what happens, both in contracts and technically, when abuse is found?</p><p>If your provider can&#8217;t answer these questions, someone else will, maybe with a seizure banner.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Web Scraping Club is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Session Stickiness at Scale - Insights from DataImpulse]]></title><description><![CDATA[Building a Proxy Rotation Layer That Mimics Real Users]]></description><link>https://substack.thewebscraping.club/p/session-stickiness-at-scale-insights</link><guid isPermaLink="false">https://substack.thewebscraping.club/p/session-stickiness-at-scale-insights</guid><dc:creator><![CDATA[Olia Liudko]]></dc:creator><pubDate>Tue, 07 Jul 2026 03:01:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!XNg7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5a58a9f-44c5-4200-b15d-aba98ee9fe68_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>This is a guest post written by the DataImpulse team. For an independent benchmark of proxy prices, <a href="https://proxyprice.thewebscraping.club/">visit our Proxy Pricing Benchmark tool</a>.</em></p><div><hr></div><p><span>You buy proxies from a provider with the biggest IP pool you can find. You rotate IPs for every request. You randomize User-Agent strings. You connect to proxy servers from various regions. You add delays between requests. You expect to smoothly scrape the necessary data in no time, as you have made everything so your scraper does not leave any traces behind and is impossible to get caught. The next thing you see usually happens around request 150-300 - this is blocks. CAPTCHAs, error 403, redirects to honeypot pages serving you wrong data. The whole pipeline crumples down.</span></p><p><span>If this situation is painfully familiar, you have probably let session stickiness out of sight while building a proxy rotation layer - a common mistake. This article explains in detail how to rotate proxies to mimic real users so your scraper remains a reliable data source even at scale.</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XNg7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5a58a9f-44c5-4200-b15d-aba98ee9fe68_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XNg7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5a58a9f-44c5-4200-b15d-aba98ee9fe68_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!XNg7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5a58a9f-44c5-4200-b15d-aba98ee9fe68_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!XNg7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5a58a9f-44c5-4200-b15d-aba98ee9fe68_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!XNg7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5a58a9f-44c5-4200-b15d-aba98ee9fe68_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XNg7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5a58a9f-44c5-4200-b15d-aba98ee9fe68_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e5a58a9f-44c5-4200-b15d-aba98ee9fe68_1672x941.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1162929,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/204178385?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5a58a9f-44c5-4200-b15d-aba98ee9fe68_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!XNg7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5a58a9f-44c5-4200-b15d-aba98ee9fe68_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!XNg7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5a58a9f-44c5-4200-b15d-aba98ee9fe68_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!XNg7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5a58a9f-44c5-4200-b15d-aba98ee9fe68_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!XNg7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5a58a9f-44c5-4200-b15d-aba98ee9fe68_1672x941.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2><span>Why careless IP rotation may speed up bot detection</span></h2><p><span>When you realize a website has blocked you out, the first instinct is to add more IPs - maybe buy proxies that perform better and are more adapted for dealing with detection systems. You try to add more geo-diversity. In other words, you use the same approach as before, just scale it. Sometimes it helps a bit; however, sometimes, blocks are back even faster.</span></p><p><span>The mistake here is that you are looking for a problem in the wrong place. It is not the lack of IP diversity, but the lack of session consistency.</span></p><p><span>Modern anti-bot systems like Cloudflare, Akamai Bot Manager, DataDome, or PerimeterX do not rely on the IP address as a primary method of bot detection. Instead, they consider a lot of signals and model session behavior. In such circumstances, your rotating efforts, if not strategic, can only make things worse and produce behavioral signals that are bot-like, not user-like. More IPs are not a fix for that problem. Modeling behavioral signals of genuine users is.</span></p><h2><span>The anatomy of bot detection</span></h2><p style="text-align: justify;"><span>In order to build a truly stealthy scraper with a rotation layer that gets the job done, it is important to understand first how detection happens - what exactly can give you away.</span></p><p><span>So, there are four layers of different signals that anti-bot systems analyze simultaneously and make a decision whether to let you be or deny access.</span></p><ul><li><p><span>Layer 1 - Network</span></p></li></ul><p><span>IP reputation, ASN classification, type of IP (residential, mobile, or datacenter), and provider fingerprints belong to that layer. Also, if a proxy or VPN IP is known, it would also be shown here.</span></p><p><span>This layer is the one developers usually optimize the most for. Ironically, this is the least important layer for security systems. That is why you fail when following scraping tutorials that offer to simply set as big an IP pool as possible and rotate addresses for every request. It is not because you have done something wrong or your proxies are bad, but because this approach does not work for scraping at scale.</span></p><ul><li><p><span>Layer 2 - TLS/HTTP fingerprints</span></p></li></ul><p><span>HTTP clients&#8217; fingerprints are nearly as unique as an IP address. TLS handshake leaves quite a trace with its cipher suite order, list of supported extensions, and elliptic curves. HTTP/2 creates another signal with header prioritization, window size, and frame ordering. The combination of TLS and HTTP/2 fingerprints is often enough for security systems to figure out whether it&#8217;s human-generated or bot-generated traffic, even without considering signals from other layers.</span></p><p><span>Widely used scraping libraries, for example, curl, requests, httpx, aiohttp  produce distinct, detectable fingerprints. Python is a popular choice for building scrapers now; still, if you use its requests library, a calibrated WAF would spot it from TLS handshake alone, User-Agent headers be forgotten, whatever they say.</span></p><ul><li><p><span> Layer 3 - Behavioral</span></p></li></ul><p><span>Requests cadence, referrer chain integrity, cookie handling, navigation patterns, and timing distributions are evaluated here. Even the delays between requests, if they are too precise and equal, can fuel suspicions, as humans do not make requests with a machine&#8217;s precision. Real users&#8217; referrers have logic to them. Human users&#8217; cookies are consistent and show that they came from a search engine page. They do not disappear mid-session or mid-navigation.</span></p><ul><li><p><span>Layer 4 - Session Graph</span></p></li></ul><p><span>This layer evaluates whether the sequence of cookies, tokens, page flow, and state transitions within the session makes sense for a human user. That is a layer most scrapes fail to pass, as not so many developers pay attention to coherence. Cause even if stand-alone signals look normal, it is important that they make sense when evaluated together. For example, a user visits an e-commerce website. Their session token would show that. Their viewport size would stay the same throughout the visit. They will go between pages with different time intervals, reflecting reading and decision time.</span></p><p><span>Coherence is the key that opens or closes a door for your scraper. And one proxy rotation at the wrong time can reveal your scraper&#8217;s true identity.</span></p><h2><span>Session stickiness: a true solution</span></h2><p><span>In the context of a proxy architecture, session stickiness is a condition when all requests that belong to the same logical session are routed via the same IP address. At the very least, it must be the same subnet, geo location, and ASN cluster.</span></p><p><span>Now, let&#8217;s define what a session may mean, cause in terms of scraping, there are several options possible.</span></p><ul><li><p><span>Authentication session</span></p></li></ul><p><span>The most obvious case. You log in, receive a session token, and proceed to the data. If your IP changes somewhere between the login request and the first data request after authentication, to the target server, it looks like a user logged in from Paris and then suddenly that same user is asking for data from Seoul. Session invalidation or re-verification is guaranteed.</span></p><ul><li><p><span>Stateful pagination</span></p></li></ul><p><span>This type is subtler and catches even sophisticated scrapers off guard. The thing is that many modern applications track your pagination state on a server&#8217;s side - in other words, a server &#8220;remembers&#8221; a page you are currently at and ties it to your IP. If the IP address changes between pages 3 and 4, the server loses this track. It may either block you or return incorrect data.</span></p><ul><li><p><span>Multi-step forms</span></p></li></ul><p><span>Cases when you need to simulate checkouts, survey scrapers, or lead generation cases are the most session-sensitive. The state that was established at the beginning is carried forward across the rest of the steps. A mid-process IP change is a red flag in such a scenario; detection systems will react immediately.</span></p><p><span>To sum up, it is not random IPs you need, but sessions and a rotation strategy that considers session coherence.</span></p><h2><span>Building the rotation layer</span></h2><p><span>Time to get to work. Remember: rotating session objects, not IP addresses.</span></p><h3><span>The session object model</span></h3><p><span>A session object, in simple words, is a set of signals that make a set of requests look like they originated from a real human user.</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;8a857197-6886-4ede-acef-8093a63ecbcc&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">@dataclass
class ProxySession:
    session_id: str
    proxy_endpoint: str          # host:port or provider session token
    cookies: dict = field(default_factory=dict)
    headers_profile: dict = field(default_factory=dict)
    geo: str = ""                # e.g. "pl,us,en"
    asn: str = ""                # e.g. "12345"
    created_at: float = field(default_factory=time.time)
    last_used: float = field(default_factory=time.time)
    request_count: int = 0
    flagged: bool = False

    def is_expired(self, ttl_seconds: int = 600, max_requests: int = 200) -&gt; bool:
        age = time.time() - self.created_at
        return self.flagged or age &gt; ttl_seconds or self.request_count &gt;= max_requests

</code></pre></div><p><span>The headers_profile is not just a User-Agent string. It is a set of HTTP headers that are used for each and every request of one session: User-Agent, Accept-Language, Accept-Encoding, Accept, sec-ch-ua, sec-ch-ua-platform, sec-ch-ua-mobile. They must stay the same for every request. If one request is coming as if from Chrome browser version 108, and another one from version 124, detection systems will notice a mismatch. Even without knowing exactly that you are a scraper, such a mismatch is enough to block you.</span></p><h3><span>Sticky session assignment</span></h3><p><span>Any scraper must use the same session object within one session - this is a rule. So the session assignment must not be random, but based on that rule to prevent any mix-ups.</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;3cf937ef-7a7a-42c8-9a78-f67e1c439f88&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">import hashlib
import threading
from typing import Dict

class SessionPool:
    def __init__(self, proxy_provider):
        self.provider = proxy_provider
        self._sessions: Dict[str, ProxySession] = {}
        self._lock = threading.Lock()

    def _make_key(self, job_id: str, target_domain: str) -&gt; str:
        raw = f"{job_id}:{target_domain}"
        return hashlib.md5(raw.encode()).hexdigest()[:12]

    def get_or_create(self, job_id: str, target_domain: str) -&gt; ProxySession:
        key = self._make_key(job_id, target_domain)
        with self._lock:
            session = self._sessions.get(key)
            if session is None or session.is_expired():
                session = self.provider.new_session(geo=self._infer_geo(target_domain))
                self._sessions[key] = session
            session.last_used = time.time()
            session.request_count += 1
            return session

    def _infer_geo(self, domain: str) -&gt; str:
        # Simplified &#8212; in production, map TLDs/domains to geo requirements
        tld_map = {".pl": "PL", ".de": "DE", ".co.uk": "GB", ".fr": "FR"}
        for tld, geo in tld_map.items():
            if domain.endswith(tld):
                return geo
        return "US"  # Default
</code></pre></div><p><span>Notice that the session key is determined by job_id + target_domain, so the scraper sticks to the same session until it expires. The session expiration is also based on 2 rules - TTL (sessions older than 10 minutes automatically get refreshed) and request count (after 200 requests, the session gets refreshed to prevent statistical fingerprinting from long-lived sessions).</span></p><h3><span>Geo-tied rotation</span></h3><p><span>Another life hack is to rotate IPs within the same geo or ASN cluster even when a session has expired, and you are spinning to the new one. Even between sessions, sudden changes (like jumping from Tokyo residential IP to Toronto residential IP) are noticeable and suspicious. When that happens, it looks more like a VPN switch, not a user returning.</span></p><p><span>Result-oriented proxy providers offer sticky sessions. In such a case, a session token has a location backed into it, and the provider guarantees that the traffic will originate as if from that location during a session&#8217;s lifetime.</span></p><h3><span>Header profile coherence</span></h3><p><span>Not only must IP remain the same throughout the session. If you rely on the same address, but the header profile changes from request to request, it is like a &#8220;Hello, I&#8217;m a scraper&#8221; sign for detection systems.</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;d0fd10ba-049f-4f61-a916-3047fad3c8a6&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">HEADER_PROFILES = [
    {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
        "Accept-Language": "en-US,en;q=0.9",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
        "Accept-Encoding": "gzip, deflate, br",
        "sec-ch-ua": '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": '"Windows"',
    },
    # Add more profiles reflecting real browser market share distributions
]
</code></pre></div><p><span>One session - one profile. No changes mid-session.</span></p><h3><span>Retry logic</span></h3><p><span>Retry logic is another thing that can blow your cover. When a request fails due to some reason and the target server instantly receives another one from the same origin, it most certainly means bots are in the game, as humans do not act with such light speed. An even less obvious trap is retrying with a different session object when your current session simply needs a brief backoff.</span></p><p><span>The better approach is to retry while relying on the same session object and a slight delay. If you receive a clear block signal, like 407, 429 errors with specific headers, or CAPTCHA, switch to the new session.</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;079799a9-54dd-48c6-a0a7-2ac6fc112533&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def fetch_with_retry(
    url: str,
    pool: "SessionPool",
    job_id: str,
    target_domain: str,
    max_retries: int = 3,
) -&gt; requests.Response:
    failures: list[str] = []

    for attempt in range(max_retries):
        session = pool.get_or_create(job_id, target_domain)
        try:
            response = make_request(url, session)
            if response.status_code in (407, 429) or is_captcha(response):
                session.flagged = True
                failures.append(
                    f"attempt {attempt}: session {session.session_id} "
                    f"blocked (status={response.status_code})"
                )
                if attempt &lt; max_retries - 1:
                    base = min(2 ** attempt * 2, 30)
                    time.sleep(base * (0.8 + 0.4 * random.random()))
                continue
            return response
        except (requests.Timeout, requests.ConnectionError) as e:
            failures.append(f"attempt {attempt}: {type(e).__name__}: {e}")
            if attempt &lt; max_retries - 1:
                base = min(2 ** attempt * 2, 30)
                time.sleep(base * (0.8 + 0.4 * random.random()))

    raise MaxRetriesExceeded("All attempts failed:\n  " + "\n  ".join(failures))
</code></pre></div><p><span>There is an important difference: TransientError (which includes network hiccup and timeout) retries with the current session. BlockedError marks the session as flagged, which in turn triggers is_expired() and forces a new session creation.</span></p><h2><span>Scaling concurrent sessions</span></h2><p><span>At a small scale, the session pool model works just fine. However, if your scraper runs on 50+ machines simultaneously, you need all of them not to create new sessions each, but to stick to the one.</span></p><p><span>A Redis-backed session pool is a solution. Basically, Redis is a shared notepad that lives on a server and that every machine can read and write to, so that there is no chaos in sessions. Store each ProxySession object (serialized as JSON) with a TTL key:</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;6a6de4fb-90af-4210-b5fa-1aa72d5a0d01&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">import redis
import json

r = redis.Redis(host='localhost', port=6379, db=0)

def store_session(session: ProxySession, ttl: int = 600):
    key = f"session:{session.session_id}"
    r.setex(key, ttl, json.dumps(session.__dict__))

def load_session(session_id: str) -&gt; Optional[ProxySession]:
    data = r.get(f"session:{session_id}")
    if data:
        return ProxySession(**json.loads(data))
    return None
</code></pre></div><p><span>If a proxy provider offers sticky sessions, your Redis only needs to track the session token, not the exit node state. A provider handling IP consistency and you dealing with cookies and headers is the preferred architecture for effective scaling because building and maintaining a distributed session pool is good 2-4 weeks of work. You need developers to do it - and add ongoing maintenance as websites update their detection systems.</span></p><h2><span>Testing rotation layer</span></h2><p><span>To be sure everything works fine, verify your rotation layer. Send 10 requests to see whether all of them really go via the same IP address.</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;64feb32a-56fd-4b85-85a2-e1cb6929b223&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">import requests

def verify_session_stickiness(session_token: str, n: int = 10):
    proxy = f"http://user__sessid.{session_token}:pass@gw.dataimpulse.com:823"
    seen_ips = set()

    for i in range(n):
        r = requests.get("https://httpbin.org/ip", proxies={"https": proxy})
        ip = r.json()["origin"]
        seen_ips.add(ip)
        print(f"Request {i+1}: {ip}")

    print(f"\nUnique IPs: {len(seen_ips)}")
    assert len(seen_ips) == 1, f"FAIL: Session leaked across {len(seen_ips)} IPs"
    print("PASS: Session sticky across all requests")

verify_session_stickiness("your_session_token_here")
</code></pre></div><p><span>To verify headers, use https://httpbin.org/headers and compare headers across requests. Any differences in User-Agent, Accept-Language, or sec-ch-ua between requests of the same session are a problem.</span></p><p><span>If you want a deeper test, use bot.sannysoft.com or pixelscan.net. Those sites show what detection systems see - WebDriver flags, inconsistent browser APIs, JA3 anomalies, timezone/language mismatches. You see what exactly is wrong and at which layer. This is an easier, faster, and cheaper way than constant tries and errors.</span></p><h2><span>Final thoughts</span></h2><p><span>Scrapers that remain undetected are not the ones with the largest IP pools. They are the ones that mimic real users&#8217; behavior the most persuasively - when the IP, the TLS fingerprint, the header profile, the cookie state, and the navigation flow all together create traffic patterns that are typical for a genuine human visitor.</span></p><p><span>IP rotation is necessary, but it is a tactic, while session architecture is a strategy. When you adopt that approach, you feel the difference - in lower block rates and higher data accuracy, in how much less firefighting your team goes through every time security systems update.  Scalability is another benefit.</span></p><p><span>Also, there is an undeniable tendency for detection systems to develop, and so must your scrapers do. Rotation that is based on sessions, not single IPs, allows for that development.</span></p><p><span>If you want to skip the &#8220;build infrastructure from scratch&#8221; part, </span><a href="https://dataimpulse.com/"><span>DataImpulse</span></a><span> is an ethical proxy provider that offers both rotating and sticky sessions. You can set the duration of a session manually - up to 120 minutes is possible. DataImpulse (dataimpulse.com) provides 90M+ of residential, mobile, and datacenter enterprise-grade addresses in 195 locations with advanced targeting options (extra costs) to help maintain session coherence. Web scraping, SERP tracking, price monitoring, and other sensitive scenarios are among its use cases. Prices start from $1/GB - for non-expiring traffic and 24/7 human support.</span></p>]]></content:encoded></item><item><title><![CDATA[From Local Scripts to Cloud Power: Scaling Your Scrapers with Oxylabs Headless Browser]]></title><description><![CDATA[A deep dive into why "headless" doesn't just mean "invisible", and why your architecture depends on it.]]></description><link>https://substack.thewebscraping.club/p/oxylabs-headless-browser</link><guid isPermaLink="false">https://substack.thewebscraping.club/p/oxylabs-headless-browser</guid><dc:creator><![CDATA[Federico Trotta]]></dc:creator><pubDate>Mon, 06 Jul 2026 03:00:50 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/c760f6b1-4f2b-464d-a81f-5cdc5ed93846_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Scraping data from the web is a craft that requires learning to use different tools. This is because you may start working on a target website with a scraping framework, realize there are issues you didn&#8217;t consider, and end up using other solutions. </p><p>Among the many tools a scraping engineer should master are headless browsers. But if you want to be completely honest, in the scraping industry there&#8217;s some confusion about the concepts of &#8220;headless browsers&#8221; and &#8220;browser automation tools&#8221;.</p><div><hr></div><blockquote><p><em>For your scraping needs, having a reliable proxy provider like <strong>Decodo</strong> on your side improves the chances of success. </em></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://visit.decodo.com/WyQ3mA" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qJZE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png" width="521" height="293.0625" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fec072bc-2ac2-49bf-8084-43858298808f_800x450.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:450,&quot;width&quot;:800,&quot;resizeWidth&quot;:521,&quot;bytes&quot;:554497,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://visit.decodo.com/WyQ3mA&quot;,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/187315628?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!qJZE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://visit.decodo.com/WyQ3mA&quot;,&quot;text&quot;:&quot;Try Decodo Now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://visit.decodo.com/WyQ3mA"><span>Try Decodo Now</span></a></p><div><hr></div></blockquote><p>In this article, you&#8217;ll get your ideas clear on headless browsers and browser automation tools. You&#8217;ll also discover the <strong><a href="https://oxylabs.go2cloud.org/aff_c?offer_id=7&amp;aff_id=665&amp;url_id=214">Oxylabs Headless Browser</a></strong> and learn how to use it in practice against common anti-bots. In particular, you&#8217;ll see the difference in servers&#8217; responses when hitting the same target URLs with and without the Oxylabs solution.</p><p>Let&#8217;s get into it!</p><h2>What is a Headless Browser?</h2><p>A headless browser is a fully functional web browser that operates without a graphical user interface (GUI). To understand what this means in practice, it helps to discuss what a browser actually does under the hood.</p><p>When you open a browser like Chrome and navigate to a URL, the browser performs a precise sequence of operations:</p><ul><li><p>It sends an HTTP request to a server.</p></li><li><p>It receives an HTML document.</p></li><li><p>It parses that <a href="https://substack.thewebscraping.club/p/scraping-with-llms-gpt-vision">HTML into a DOM (Document Object Model</a><strong><a href="https://substack.thewebscraping.club/p/scraping-with-llms-gpt-vision">)</a></strong><a href="https://substack.thewebscraping.club/p/scraping-with-llms-gpt-vision"> tree</a>.</p></li><li><p>It fetches and applies CSS stylesheets to compute the visual layout, downloads and executes JavaScript files, and processes any asynchronous network requests (XHR/Fetch API calls) triggered by that JavaScript.</p></li><li><p>It renders everything into visible pixels on your screen.</p></li></ul><p>Below is an image that graphically shows the whole process:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8kZp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff24c0d1e-a88d-4c62-bc89-e371a157093e_1145x328.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8kZp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff24c0d1e-a88d-4c62-bc89-e371a157093e_1145x328.png 424w, https://substackcdn.com/image/fetch/$s_!8kZp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff24c0d1e-a88d-4c62-bc89-e371a157093e_1145x328.png 848w, https://substackcdn.com/image/fetch/$s_!8kZp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff24c0d1e-a88d-4c62-bc89-e371a157093e_1145x328.png 1272w, https://substackcdn.com/image/fetch/$s_!8kZp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff24c0d1e-a88d-4c62-bc89-e371a157093e_1145x328.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8kZp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff24c0d1e-a88d-4c62-bc89-e371a157093e_1145x328.png" width="1145" height="328" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f24c0d1e-a88d-4c62-bc89-e371a157093e_1145x328.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:328,&quot;width&quot;:1145,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:352227,&quot;alt&quot;:&quot;What happens under the hood when browsers perform actions by Federico Trotta&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/202688828?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff24c0d1e-a88d-4c62-bc89-e371a157093e_1145x328.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="What happens under the hood when browsers perform actions by Federico Trotta" title="What happens under the hood when browsers perform actions by Federico Trotta" srcset="https://substackcdn.com/image/fetch/$s_!8kZp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff24c0d1e-a88d-4c62-bc89-e371a157093e_1145x328.png 424w, https://substackcdn.com/image/fetch/$s_!8kZp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff24c0d1e-a88d-4c62-bc89-e371a157093e_1145x328.png 848w, https://substackcdn.com/image/fetch/$s_!8kZp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff24c0d1e-a88d-4c62-bc89-e371a157093e_1145x328.png 1272w, https://substackcdn.com/image/fetch/$s_!8kZp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff24c0d1e-a88d-4c62-bc89-e371a157093e_1145x328.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">What happens under the hood when browsers perform actions</figcaption></figure></div><p>A headless browser performs every single one of those steps, except painting pixels to a physical display. It still builds the DOM, executes JavaScript, processes AJAX calls, and maintains a fully accurate representation of the page state in memory. But it does it without needing a graphical rendering environment.</p><p>It&#8217;s also important to understand the difference between a headless browser and a basic HTTP client. Tools like <a href="https://requests.readthedocs.io/en/latest/">Python&#8217;s </a><em><a href="https://requests.readthedocs.io/en/latest/">requests</a></em> library or <em><a href="https://curl.se/">curl</a></em> are pure HTTP clients. This means that they send a request and return whatever raw response the server provides. They have no concept of a DOM, no JavaScript engine, and no rendering pipeline. </p><p>The most widely used headless browsers are the following:</p><ul><li><p><strong><a href="https://phantomjs.org/">PhantomJS</a></strong>: One of the first dedicated headless browsers, built on an older version of WebKit. It was widely used before 2017 but has since been officially abandoned and is no longer maintained. You may still encounter it in legacy codebases, but it&#8217;s better not to use it for new projects.</p></li><li><p><strong><a href="https://developer.chrome.com/docs/chromium/headless">Headless Chrome/Chromium</a></strong>: Google officially introduced headless mode in Chrome 59 (2017), effectively making PhantomJS obsolete overnight. It exposes the <a href="https://chromedevtools.github.io/devtools-protocol/">Chrome DevTools Protocol (CDP)</a> as its programmatic interface, which automation frameworks connect to to send instructions and receive data.</p></li><li><p><strong><a href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Headless Firefox</a></strong>: Mozilla introduced headless support in Firefox 55. Powered by the <a href="https://firefox-source-docs.mozilla.org/overview/gecko.html">Gecko rendering engine</a> and the <a href="https://spidermonkey.dev/">SpiderMonkey JavaScript engine</a>, it&#8217;s a strong alternative to Chromium, particularly useful when you need to scrape or test behavior tied to the Gecko engine specifically.</p></li><li><p><strong><a href="https://webkit.org/">WebKit</a></strong>: WebKit is the browser engine that powers Apple&#8217;s Safari. Playwright bundles a version of WebKit that can be run in headless mode on non-Apple platforms, giving you the ability to simulate Safari&#8217;s rendering behavior on Linux or Windows servers (something that would be otherwise impossible).</p></li><li><p><strong><a href="https://splash.readthedocs.io/en/stable/">Splash</a></strong>: A lightweight, scriptable headless browser built specifically for web scraping, developed by the Scrapy team. It is written in Python and <a href="https://www.lua.org/">Lua</a>, exposes an HTTP API, and integrates natively with the <a href="https://www.scrapy.org/">Scrapy framework</a>, making it a beginner-friendly option for Python-based scraping pipelines.</p></li></ul><div><hr></div><blockquote><p>Your scraping workflows deserve a proxy infrastructure that just works. With <strong>Swiftproxy</strong> on your side, consistency is built-in.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://www.swiftproxy.net/?ref=webscrapingclub" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!g3qW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49af1519-bfa5-4c03-afea-f45743bd057b_2880x360.png 424w, https://substackcdn.com/image/fetch/$s_!g3qW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49af1519-bfa5-4c03-afea-f45743bd057b_2880x360.png 848w, https://substackcdn.com/image/fetch/$s_!g3qW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49af1519-bfa5-4c03-afea-f45743bd057b_2880x360.png 1272w, https://substackcdn.com/image/fetch/$s_!g3qW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49af1519-bfa5-4c03-afea-f45743bd057b_2880x360.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!g3qW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49af1519-bfa5-4c03-afea-f45743bd057b_2880x360.png" width="670" height="83.75" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/49af1519-bfa5-4c03-afea-f45743bd057b_2880x360.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:182,&quot;width&quot;:1456,&quot;resizeWidth&quot;:670,&quot;bytes&quot;:445760,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://www.swiftproxy.net/?ref=webscrapingclub&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/193806031?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49af1519-bfa5-4c03-afea-f45743bd057b_2880x360.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!g3qW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49af1519-bfa5-4c03-afea-f45743bd057b_2880x360.png 424w, https://substackcdn.com/image/fetch/$s_!g3qW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49af1519-bfa5-4c03-afea-f45743bd057b_2880x360.png 848w, https://substackcdn.com/image/fetch/$s_!g3qW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49af1519-bfa5-4c03-afea-f45743bd057b_2880x360.png 1272w, https://substackcdn.com/image/fetch/$s_!g3qW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49af1519-bfa5-4c03-afea-f45743bd057b_2880x360.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption"></figcaption></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.swiftproxy.net/?ref=webscrapingclub&quot;,&quot;text&quot;:&quot;Try Swiftproxy today&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.swiftproxy.net/?ref=webscrapingclub"><span>Try Swiftproxy today</span></a></p></blockquote><div><hr></div><h2>Headless Browsers vs. Automation Frameworks: A Critical Distinction</h2><p>At this point, it&#8217;s essential to draw a clear architectural boundary between two categories that are frequently, but incorrectly, used interchangeably: headless browsers and browser automation frameworks.</p><p>As said in the previous paragraph, a <strong>headless browser is the actual browser engine itself</strong>. So, it&#8217;s the software responsible for all the core browser operations described above.</p><p>A <strong>browser automation framework</strong>, on the other hand, <strong>is a programming library or tool that sits on top of a headless (or full) browser and provides a developer-friendly API to control it programmatically</strong>. The framework itself does not render pages, execute JavaScript, or manage network traffic: it delegates all of that to the underlying browser engine. What it provides is an abstraction layer to navigate to URLs, click elements, fill in forms, wait for conditions, intercept network requests, take screenshots, and to perform any similar activity.</p><p>Think of it this way: the headless browser is the engine of a car, and the automation framework is the steering wheel, pedals, and dashboard. You need both to drive, but they are fundamentally different components with different responsibilities.</p><p>This distinction also has a practical implication. If a website employs bot detection based on browser fingerprinting, it&#8217;s the underlying browser engine&#8217;s characteristics that matter, not the automation framework on top of it. This means that switching from Puppeteer to Playwright doesn&#8217;t change your browser fingerprint if both are driving the same Chromium engine with the same configuration.</p><p>The list below reports the programming libraries and tools used to control headless (or full) browsers programmatically:</p><ul><li><p><strong><a href="https://pptr.dev/">Puppeteer</a></strong>: <a href="https://nodejs.org/en">A Node.js</a> library developed and maintained by Google that provides a high-level API to control Headless Chrome/Chromium over CDP. It was the first major modern automation framework to replace PhantomJS and remains widely used in the JavaScript/Node.js ecosystem. </p></li><li><p><strong><a href="https://playwright.dev/">Playwright</a></strong>: Developed by Microsoft, Playwright is a more modern and feature-rich automation framework that supports Chromium, Firefox, and WebKit under a single unified API. It has become the preferred choice for many scraping engineers due to its superior <a href="https://substack.thewebscraping.club/i/162123239/manage-concurrency-with-asyncio-and-aiohttp">handling of async operations</a>, automatic waiting strategies, built-in network interception, and robust multi-browser support. On the side of programming languages, it&#8217;s available for Node.js, Python, Java, and .NET.</p></li><li><p><strong><a href="https://app.notion.com/p/TWSC-Oxylabs-headless-browser-365945fea5028086ad6ac71eba2df177?pvs=21">Selenium</a></strong>: The oldest and most widely adopted browser automation framework in existence, originally released in 2004. Unlike Puppeteer and Playwright, Selenium communicates with browsers via the <a href="https://app.notion.com/p/TWSC-Oxylabs-headless-browser-365945fea5028086ad6ac71eba2df177?pvs=21">W3C WebDriver protocol</a>. This means it supports a broader range of browsers and is the de facto standard in enterprise testing environments. Selenium supports multiple languages, including Python, Java, C#, Ruby, and JavaScript, which gives it an exceptionally large community and an extensive ecosystem of documentation and tooling. For engineers working in Python, Selenium with a headless Chrome or Firefox driver is still a very common scraping approach. Its main trade-off is that it can be slightly slower and less ergonomic for modern async-heavy scraping scenarios.</p></li></ul><h2>Why Do Engineers Need a Headless Browser?</h2><p>When you start defining how to scrape a website, your first instinct is often to reach for a lightweight HTTP client. And, in many cases, that is the right call. Simple, server-rendered websites that return complete HTML in their initial response are perfectly scrapable with tools like <em><a href="https://requests.readthedocs.io/en/latest/">requests</a></em><a href="https://requests.readthedocs.io/en/latest/"> </a>coupled with <em><a href="https://beautiful-soup-4.readthedocs.io/en/latest/">BeautifulSoup</a></em>. </p><p>However, as you begin working with modern websites, you will quickly encounter scenarios where an HTTP client falls flat. Understanding why this happens (and when a headless browser becomes necessary) is one of the most fundamental skills you need to master for your career.</p><p>This section discusses the most common scenarios when you need to use a headless browser for scraping target websites.</p><h3>JavaScript-Rendered and SPA Content</h3><p>The most common reason to turn to headless browsers is client-side JavaScript rendering. A large proportion of modern websites, in fact, are built as Single Page Applications (SPAs) using frameworks like React, Vue.js, Angular, or Next.js. In these architectures, the server does not send a fully populated HTML page. Instead, it sends a minimal HTML shell along with a bundle of JavaScript files. The browser then executes that JavaScript, which makes additional API calls to fetch the actual data and dynamically constructs the DOM. </p><p>If you scrape the raw HTML with an HTTP client, you will receive that empty shell and find none of the content you are looking for. A headless browser, instead, loads the page as a real user&#8217;s browser would, waits for the JavaScript to finish executing, and exposes the fully populated DOM for you to extract data from.</p><h3>AJAX and Asynchronous Data Loading</h3><p>Closely related to SPAs is the broader pattern of asynchronous data fetching. Even on websites that are not full SPAs, it&#8217;s extremely common for certain page sections to be populated via AJAX requests that fire after the initial page load. These requests hit internal or external APIs, and the responses are injected into the page dynamically. </p><p>With a headless browser and a tool like Playwright, you can intercept these network requests directly. Also, this is a powerful web scraping technique. Rather than scraping the rendered HTML, you capture the raw API response (usually as JSON), which gives you clean and structured data to work with.</p><h3>Infinite Scroll and Pagination</h3><p>Many websites like social media platforms, job boards, and e-commerce product listings implement <a href="https://substack.thewebscraping.club/p/handling-infinite-scrolling-python-js">infinite scroll</a>. This means that new content is loaded dynamically as the user scrolls down the page. On the practical side, this means that there is no &#8220;Page 2&#8221; URL to follow. The content doesn&#8217;t exist in the DOM until a scroll event triggers a new batch of data to load. </p><p>In this scenario, a headless browser allows you to simulate scroll events, wait for new content to be injected, and continue extracting data until a stopping condition is met. This is fundamentally impossible to replicate with a plain HTTP client.</p><h3>User Interaction Simulation</h3><p>Some content is only accessible after specific user interactions, like clicking on a button, selecting a value from a dropdown menu, submitting a search form, switching between tabs, and similar. A headless browser gives you a full interaction API that allows you to click on elements, type into input fields, select options, trigger keyboard shortcuts, and simulate virtually any user action. </p><p>This is particularly important when <a href="https://substack.thewebscraping.club/p/scraping-booking-playwright">scraping search-driven websites like travel booking sites</a>, real estate portals, and any similar websites where results are generated after the user performs actions.</p><h3>Bot Detection and Browser Fingerprinting Evasion</h3><p>Many high-value websites deploy anti-bot systems that analyze incoming requests to determine whether they originate from a real browser or an automated script. These systems are <a href="https://substack.thewebscraping.club/p/machine-learning-for-detecting-bots">trained using machine learning models to check for dozens of signals</a>. A headless browser provides a much more convincing browser environment than a raw HTTP client. </p><p>In particular, with additional configurations and stealth patches, you can significantly reduce the likelihood of your scraper being detected and blocked. Of course, this is an arms race: anti-bot technology continues to evolve, and a headless browser alone is not a silver bullet.</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.youtube.com/@thewebscrapingclub&quot;,&quot;text&quot;:&quot;Check the TWSC YouTube Channel&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.youtube.com/@thewebscrapingclub"><span>Check the TWSC YouTube Channel</span></a></p><div><hr></div><h2>What Is Oxylabs Headless Browser?</h2><p></p><p><a href="https://oxylabs.go2cloud.org/aff_c?offer_id=7&amp;aff_id=665&amp;url_id=214">Oxylabs Headless Browser</a> is a <strong>cloud-based browser automation solution</strong> that allows engineers to run and control remote browser sessions without the overhead of managing browser infrastructure locally or on their own servers. Rather than spinning up a local Chromium or Firefox instance on your own machine or cloud VM, <a href="https://oxylabs.io/products/headless-browser">Oxylabs Headless Browser</a> offloads all of that complexity to dedicated remote servers maintained by Oxylabs. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://oxylabs.go2cloud.org/aff_c?offer_id=7&amp;aff_id=665&amp;url_id=214" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QELq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc98d44ab-8eee-4984-b9f5-cd54c2ab4401_2400x1256.jpeg 424w, https://substackcdn.com/image/fetch/$s_!QELq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc98d44ab-8eee-4984-b9f5-cd54c2ab4401_2400x1256.jpeg 848w, https://substackcdn.com/image/fetch/$s_!QELq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc98d44ab-8eee-4984-b9f5-cd54c2ab4401_2400x1256.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!QELq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc98d44ab-8eee-4984-b9f5-cd54c2ab4401_2400x1256.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QELq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc98d44ab-8eee-4984-b9f5-cd54c2ab4401_2400x1256.jpeg" width="697" height="364.7760989010989" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c98d44ab-8eee-4984-b9f5-cd54c2ab4401_2400x1256.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:762,&quot;width&quot;:1456,&quot;resizeWidth&quot;:697,&quot;bytes&quot;:812366,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:&quot;https://oxylabs.go2cloud.org/aff_c?offer_id=7&amp;aff_id=665&amp;url_id=214&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/202688828?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc98d44ab-8eee-4984-b9f5-cd54c2ab4401_2400x1256.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QELq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc98d44ab-8eee-4984-b9f5-cd54c2ab4401_2400x1256.jpeg 424w, https://substackcdn.com/image/fetch/$s_!QELq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc98d44ab-8eee-4984-b9f5-cd54c2ab4401_2400x1256.jpeg 848w, https://substackcdn.com/image/fetch/$s_!QELq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc98d44ab-8eee-4984-b9f5-cd54c2ab4401_2400x1256.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!QELq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc98d44ab-8eee-4984-b9f5-cd54c2ab4401_2400x1256.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The result is a production-ready and fully managed browsing environment that you connect to via a standard WebSocket (WSS) connection, and control using any automation framework that supports the Chrome DevTools Protocol (CDP) exactly as you would a local browser instance.</p><p>The image below shows, at a high level, the architectural difference between traditional browser automation and using Oxylabs&#8217; solution:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!i_yr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feae7e0eb-20c8-4f67-b278-dbc294526bc8_995x518.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!i_yr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feae7e0eb-20c8-4f67-b278-dbc294526bc8_995x518.png 424w, https://substackcdn.com/image/fetch/$s_!i_yr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feae7e0eb-20c8-4f67-b278-dbc294526bc8_995x518.png 848w, https://substackcdn.com/image/fetch/$s_!i_yr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feae7e0eb-20c8-4f67-b278-dbc294526bc8_995x518.png 1272w, https://substackcdn.com/image/fetch/$s_!i_yr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feae7e0eb-20c8-4f67-b278-dbc294526bc8_995x518.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!i_yr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feae7e0eb-20c8-4f67-b278-dbc294526bc8_995x518.png" width="995" height="518" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eae7e0eb-20c8-4f67-b278-dbc294526bc8_995x518.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:518,&quot;width&quot;:995,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:368152,&quot;alt&quot;:&quot;Browser automation: local VS cloud-based via Oxylabs servers by Federico Trotta&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/202688828?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feae7e0eb-20c8-4f67-b278-dbc294526bc8_995x518.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Browser automation: local VS cloud-based via Oxylabs servers by Federico Trotta" title="Browser automation: local VS cloud-based via Oxylabs servers by Federico Trotta" srcset="https://substackcdn.com/image/fetch/$s_!i_yr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feae7e0eb-20c8-4f67-b278-dbc294526bc8_995x518.png 424w, https://substackcdn.com/image/fetch/$s_!i_yr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feae7e0eb-20c8-4f67-b278-dbc294526bc8_995x518.png 848w, https://substackcdn.com/image/fetch/$s_!i_yr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feae7e0eb-20c8-4f67-b278-dbc294526bc8_995x518.png 1272w, https://substackcdn.com/image/fetch/$s_!i_yr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feae7e0eb-20c8-4f67-b278-dbc294526bc8_995x518.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Browser automation: local VS cloud-based via Oxylabs servers</figcaption></figure></div><p>The currently available browser instance is a <a href="https://developers.oxylabs.io/scraping-solutions/headless-browser/chrome">Chrome-based environment (</a><em><a href="https://developers.oxylabs.io/scraping-solutions/headless-browser/chrome">ubc.oxylabs.io</a></em><a href="https://developers.oxylabs.io/scraping-solutions/headless-browser/chrome">)</a>. This runs high-performance Chromium instances with advanced stealth configurations on dedicated servers with integrated <a href="https://substack.thewebscraping.club/p/differences-residential-mobile-proxies">residential proxies</a>.</p><h3>Key Features</h3><p>What differentiates Oxylabs Headless Browser from a self-managed setup is the set of capabilities that are baked directly into the infrastructure. Rather than requiring you to implement them yourself, it provides you with the following:</p><ul><li><p><strong>Integrated residential proxies</strong>: Every browser session is automatically routed through Oxylabs&#8217; residential proxy network. This eliminates the need to manage a separate proxy integration and ensures that outgoing requests carry legitimate residential IP addresses, reducing the risk of IP-level blocks.</p></li><li><p><strong>Automatic CAPTCHA solving</strong>: The platform detects and <a href="https://substack.thewebscraping.club/p/bypassing-recaptchas-with-open-source">solves CAPTCHAs</a> by default on page load. For CAPTCHAs that appear mid-session, it exposes an event-based API via the <em>window</em> object that your script can subscribe to in order to pause and resume automation around the solving process.</p></li><li><p><strong>Geolocation targeting</strong>: You can pin your browser session to a specific country, state, or city by appending query parameters directly to the WebSocket connection URL. This is essential for scraping geo-targeted content, verifying localized pricing, or accessing region-restricted pages without maintaining a geographically distributed proxy infrastructure yourself.</p></li><li><p><strong>Device type emulation</strong>: The <em>p_device</em> parameter allows you to emulate desktop, mobile, or tablet environments. This gives you access to device-specific content, responsive layouts, and mobile-only UI elements or CAPTCHA variants.</p></li><li><p><strong>Session inspection</strong>: This is a debugging feature that uses VNC (Virtual Network Computing) to give you real-time visual access to a running browser session. This is invaluable when diagnosing issues that are not apparent from logs alone. For example, for unexpected page rendering, broken interaction flows, or timing-related problems in your automation script.</p></li><li><p><strong>MCP integration</strong>: Oxylabs Headless Browser supports connection via the <a href="https://developers.oxylabs.io/products/headless-browser/ai-workflows/integration-with-mcp">Model Context Protocol (MCP)</a>, enabling AI systems such as Claude Desktop or Cursor to drive browser automation sessions autonomously, which opens the door to AI-driven scraping and web navigation workflows.</p></li></ul><h3>How Oxylabs Headless Compares to Self-Managed Headless Browsers</h3><p>The practical significance of Oxylabs Headless Browser is best understood by contrasting it with the self-managed alternative. When you run Playwright or Puppeteer against a local Chromium instance, you are responsible for the entire stack:</p><ul><li><p>Installing and maintaining the browser binary.</p></li><li><p>Managing proxy rotation and authentication.</p></li><li><p>Implementing CAPTCHA-solving integrations.</p></li><li><p>Applying stealth patches to avoid fingerprinting.</p></li><li><p>Handling concurrency limits.</p></li><li><p>Ensuring your infrastructure scales with your scraping workload.</p></li></ul><p>Oxylabs Headless Browser, instead, abstracts all of that away behind a single WSS endpoint. In practice, this means that your code remains identical to what you would write against a local browser. The connection string is the only thing that changes. </p><p>This makes Oxylabs&#8217; solution particularly suited for teams that need to scale browser-based scraping quickly without investing in dedicated infrastructure engineering.</p><p>As a final note, it&#8217;s worth considering that billing is based on traffic consumed (per GB). This means traffic optimization becomes an important cost management practice when operating at scale.</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/p/consulting&quot;,&quot;text&quot;:&quot;Need help with your scraping project?&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://substack.thewebscraping.club/p/consulting"><span>Need help with your scraping project?</span></a></p><div><hr></div><h2>Testing Common Anti-bots The Hard Way: Step-by-step Tutorial on How to Hit Them Headless Mode</h2><p>In this section, you will learn how to hit common anti-bots in headless mode using Playwright. The idea is to compare the results obtained in headless mode with the ones obtained using Oxylabs Headless Browser.</p><p>The target websites with their anti-bots are:</p><ul><li><p>Ssense &#8594; Cloudflare</p></li></ul><ul><li><p>Gucci &#8594; Akamai</p></li><li><p>Loccitane &#8594; DataDome</p></li><li><p>Empire-cat&#8594; Kasada</p><p></p></li></ul><p>Let&#8217;s get into the tutorial!</p><h3>Prerequisites and Requirements</h3><p>To replicate this tutorial, you need at least <a href="https://www.python.org/downloads/">Python 3.8</a> installed on your machine.</p><p>Create a new folder for your project:</p><pre><code><code>mkdir test-antibots</code></code></pre><p>At the end of this step, the <em>test-antibots/</em> folder will have the following structure:</p><pre><code><code>test-antibots/
   |
   |-- tester.py
   |
   |__ venv/</code></code></pre><p>Where:</p><ul><li><p><em><a href="http://tester.py">tester.py</a></em> is the Python file that contains the scraping logic to test the antibots.</p></li><li><p><em>venv/</em> is the folder that contains the <a href="https://docs.python.org/3/library/venv.html">virtual environment</a>.</p></li></ul><p>All the files can also be downloaded <a href="https://github.com/TheWebScrapingClub/TheScrapingClubFree/tree/main/Oxylabs%20Browser">from our GitHub repository, available to anyone</a>.</p><p>In the activated virtual environment, install the needed libraries:</p><pre><code><code>pip install playwright chromium</code></code></pre><p>Very well. You are ready to code your tester!</p><h3>Step #1: Code The Scraper to Test the Anti-bots</h3><p>To test the antibots, write the following in <em>tester.py</em>:</p><pre><code><code>from playwright.sync_api import sync_playwright

# Target pages with their anti-bots
targets = [
    ("&lt;https://www.ssense.com/en-us/men/designers/acne-studios&gt;", "Cloudflare"),
    ("&lt;https://www.gucci.com/us/en/pr/women/shoes-for-women/sandals-for-women/flat-sandals-for-women/womens-slide-sandal-with-horsebit-p-834427AAE046273&gt;", "Akamai"),
    ("&lt;https://loccitane.com/en-us/olio-jeunesse-divine-30ml-27DH030I22.html&gt;", "DataDome"),
    ("&lt;https://www.empire-cat.com/equipment/used/catalog/aa/j1s03815&gt;", "Kasada")
]

results = []

# List of titles or substrings that indicate common blocks/challenges
BLOCK_INDICATORS = [
    "just a moment", "access denied", "attention required", 
    "cloudflare", "ddos protection", "captcha", "verify you are human"
]

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    context = browser.new_context()

    for url, antibot in targets:
        page = context.new_page()
        try:
            # We capture the response to check the status code
            response = page.goto(url, wait_until="domcontentloaded")
            page.wait_for_timeout(5000)

            title = page.title() or ""
            status_code = response.status if response else 0
            
            # Validation Logic
            is_blocked = False
            error_reason = ""

            # 1. Check HTTP Status (403 Forbidden is a classic block)
            if status_code &gt;= 400:
                is_blocked = True
                error_reason = f"HTTP {status_code}"
            
            # 2. Check for "Waiting Room" or "Block Page" titles
            elif any(indicator in title.lower() for indicator in BLOCK_INDICATORS):
                is_blocked = True
                error_reason = "Challenge/Block Page detected"
            
            # 3. Check for empty titles (Common with Kasada/DataDome blocks)
            elif not title or title.strip() == "":
                is_blocked = True
                error_reason = "Empty/N/A Title (Hidden Block)"

            if is_blocked:
                results.append({"url": url, "antibot": antibot, "title": title, "status": "fail", "error": error_reason})
                print(f"&#10060; [{antibot}] Blocked: {error_reason}")
            else:
                results.append({"url": url, "antibot": antibot, "title": title, "status": "ok"})
                print(f"&#9989; [{antibot}] Success: {title}")

        except Exception as e:
            error_msg = str(e).split('\n')[0] # Keep it brief
            print(f"&#10060; [{antibot}] Exception: {error_msg}")
            results.append({"url": url, "antibot": antibot, "title": None, "status": "fail", "error": error_msg})

        finally:
            page.close()

    browser.close()

# Print summary table
passed = sum(1 for r in results if r.get("status") == "ok")
failed = len(results) - passed

print("\n" + "=" * 105)
print(f"{'STATUS':&lt;10} {'ANTIBOT':&lt;12} {'RESULT/REASON':&lt;30} {'URL'}")
print("=" * 105)
for r in results:
    status = "&#9989; PASS" if r.get("status") == "ok" else "&#10060; FAIL"
    antibot = r.get("antibot", "N/A")
    # Show the reason if it failed, otherwise show the page title
    display_info = r.get("error") if r.get("status") == "fail" else r.get("title", "N/A")
    print(f"{status:&lt;10} {antibot:&lt;12} {str(display_info)[:28]:&lt;30} {r['url']}")
print("=" * 105)
print(f"&#9989; Passes: {passed}  |  &#10060; Fails: {failed}  |  Total: {len(results)}")
print("=" * 105)</code></code></pre><p>This snippet does the following:</p><ul><li><p>Maps a list of target URLs, each associated with their anti bot.</p></li><li><p>Uses Playwright to launch a Chromium browser in &#8220;headless&#8221; mode.</p></li><li><p>For each target URL, the script:</p><ul><li><p>Opens a new browser page.</p></li><li><p>Attempts to navigate to the site and waits for the initial content to load.</p></li><li><p>Pauses for 5 seconds to see if the page successfully renders the correct content or if it gets blocked/redirected by the site&#8217;s security.</p></li><li><p>Attempts to extract the page title as proof of a successful load.</p></li></ul></li><li><p>Avoids the common trap of assuming a successful page load implies successful data access by checking the HTTP status code, title semantics, and structural integrity (line &#8220;N/A&#8221; titles)-</p></li><li><p>Prints summarized results.</p></li></ul><p>Perfect. The code is ready for launch.</p><h3>Step #2: Launch The Tester And Retrieve Results</h3><p>After launching the scraper, the results are the following:</p><pre><code><code>STATUS     ANTIBOT      RESULT/REASON                  URL
==========================================================================================
&#10060; FAIL     Cloudflare   HTTP 403                       &lt;https://www.ssense.com/en-us/&gt;...
&#10060; FAIL     Akamai       Page.goto: net::ERR_HTTP2_PR   &lt;https://www.gucci.com/us/en/pr/&gt;...
&#10060; FAIL     DataDome     Empty/N/A Title (Hidden Bloc   &lt;https://loccitane.com/en-us/olio-&gt;...
&#10060; FAIL     Kasada       HTTP 429                       &lt;https://www.empire-cat.com/equi&gt;...
===========================================================================================
&#9989; Passes: 0  |  &#10060; Fails: 4  |  Total: 4
===========================================================================================</code></code></pre><p>As you can see, the anti-bots successfully blocked 4 requests on 4:</p><ul><li><p>Cloudflare returned a 403 (forbidden).</p></li><li><p>Akamai returns a <em>Page.goto: net::ERR_HTTP2_PR</em> error, which is often related to fingerprinting.</p></li><li><p>DataDome reported an empty result, which suggests it may have activated a captcha.</p></li><li><p>Kasada returns a 429 error. Generally speaking, a 429 error is a &#8220;too many requests&#8221; error, so the server blocks your scraper because you&#8217;re hitting it too fast. But specifically for Kasada, this often indicates a block due to fingerprinting.</p></li></ul><p>Terrific! You made it to the end of this tutorial. Now it&#8217;s time to see what happens when implementing the Oxylabs solution.</p><h2>Testing Common Anti-bots With Oxylabs Headless Browser: Step-by-step Tutorial</h2><p>In this tutorial section, you will learn how to hit the same websites as in the previous paragraph using Oxylabs Headless Browser.</p><h3>Prerequisites and requirements</h3><p>Other than the requirements previously met, you need an active <a href="https://dashboard.oxylabs.io/en/?utm_source=documentation&amp;utm_medium=organic&amp;utm_campaign=doc_refferal&amp;groupid=17788288593040">Oxylabs account</a> in order to create your username and password for using its headless browser. Also, make sure to match all the <a href="https://developers.oxylabs.io/products/headless-browser#quick-start-examples">requirements needed by the Oxylabs Headless Browser</a>.</p><p>In the <em>test-antibots/</em> folder, create a new Python file that will contain the scraping logic using the Oxylabs product:</p><pre><code><code>test-antibots/
   |
   |-- tester.py
   |
   |-- tester-oxylabs.py
   |
   |__ venv/</code></code></pre><p>Good. You are ready to proceed with the actual tutorial.</p><h3>Step #1: Code The Scraper Using Oxylabs Headless Browser</h3><p>Write the following code in <em>tester-oxylabs.py</em>:</p><pre><code><code>import json
import random
import string
import os
from playwright.sync_api import sync_playwright

# Define params for connecting to Oxylabs servers
username = "&lt;your-oxylabs-username&gt;"
password = "&lt;your-oxylabs-password&gt;"
endpoint = "ubc.oxylabs.io"

# Create folder for screenshots
if not os.path.exists("screenshots"):
    os.makedirs("screenshots")

def get_session():
    return ''.join(random.choices(string.ascii_lowercase + string.digits, k=10))

# Define Chrome arguments
chrome_args = json.dumps([
    "--disable-blink-features=AutomationControlled", 
    "--enable-features=NetworkServiceInProcess"
])

# Define params for Oxylabs Headless Browser
params = f"p_device=mobile&amp;p_cc=US&amp;p_session_id={get_session()}&amp;args={chrome_args}"
browser_url = f"wss://{username}:{password}@{endpoint}?{params}"

targets = [
    ("&lt;https://www.empire-cat.com/equipment/used/catalog/aa/j1s03815&gt;", "Kasada"),
    ("&lt;https://www.ssense.com/en-us/men/designers/acne-studios&gt;", "Cloudflare"),
    ("&lt;https://loccitane.com/en-us/olio-jeunesse-divine-30ml-27DH030I22.html&gt;", "DataDome"),
    ("&lt;https://www.gucci.com/us/en/pr/women/shoes-for-women/sandals-for-women/flat-sandals-for-women/womens-slide-sandal-with-horsebit-p-834427AAE046273&gt;", "Akamai")
]

# Define semantics to intercept in responses
BLOCK_TITLES = ["just a moment", "access denied", "attention required", "security measure", "not acceptable", "403 forbidden"]
results = []

def run_benchmark():
    with sync_playwright() as p:
        for url, antibot in targets:
            print(f"---\nTesting {antibot}...")
            try:
                browser = p.chromium.connect_over_cdp(browser_url)
                context = browser.new_context()
                page = context.new_page()

                captcha_detected = False
                def handle_log(msg):
                    nonlocal captcha_detected
                    if "oxylabs-captcha-solve-start" in msg.text:
                        print(f"   [Solver] {antibot} challenge detected...")
                        captcha_detected = True
                    elif "oxylabs-captcha-solve-end" in msg.text:
                        print(f"   [Solver] {antibot} resolved!")
                        captcha_detected = False

                page.on("console", handle_log)
                page.goto(url, wait_until="domcontentloaded", timeout=60000)
                
                # Wait for the page to stabilize
                if captcha_detected:
                    page.wait_for_timeout(15000)
                else:
                    page.wait_for_timeout(8000)

                # Make and save screenshots
                screenshot_path = f"screenshots/{antibot.lower()}_result.png"
                page.screenshot(path=screenshot_path, full_page=False)
                print(f"   [Camera] Screenshot saved: {screenshot_path}")

                title = page.title() or ""
                low_title = title.lower()
                is_blocked = any(bt in low_title for bt in BLOCK_TITLES)
                
                if len(title) &gt; 5 and not is_blocked:
                    print(f"&#9989; {antibot} PASS: {title[:30]}...")
                    results.append({"url": url, "antibot": antibot, "status": "ok", "title": title})
                else:
                    print(f"&#10060; {antibot} FAIL: {title}")
                    results.append({"url": url, "antibot": antibot, "status": "fail", "error": title})

                browser.close()

            except Exception as e:
                print(f"&#10060; {antibot} Exception: {str(e)[:50]}")

run_benchmark()</code></code></pre><p>Below is what changes with respect to the version in the previous paragraph:</p><ul><li><p>It defines the credentials for connecting to Oxylabs servers and creates a folder where it will later store screenshots.</p></li><li><p>With <em>chrome_args()</em>, it defines some parameters for setting the automation in Chrome.</p></li><li><p>In the string <em>params</em>, the <em><a href="https://developers.oxylabs.io/products/headless-browser/features/device-type">p_mobile</a></em> and <a href="https://app.notion.com/p/Antonello-Data-Flywheel-for-AI-37a945fea5028008b9b3dcd4b2ada203?pvs=21">*p_cc</a>* respectively manage the perceived device type and IP.</p></li><li><p>It manages CAPTCHAs with <em>oxylabs-captcha-solve-start</em> and <em>oxylabs-captcha-solve-end</em>. And since it needs to manage CAPTCHAs at each request, the code now has a timeout that helps the pages stabilize before the scraper passes on to the next target URL.</p></li><li><p>It uses page.screenshot() to make screenshots after the server sends the request and saves them into the previously created folder. This is important because the exceptions can&#8217;t all be handled via intercepting HTTP responses or via semantics. By making a screenshot before the connection is closed, you can actually see if the scraper has passed the anti-bot or if it failed at it.</p></li></ul><p>Good. You&#8217;re now ready to run the code and see the results.</p><h3>Step #2: Launch The Script</h3><p>After launching the scraper, the results are the following:</p><pre><code><code>---
Testing Kasada...
   [Camera] Screenshot saved: screenshots/kasada_result.png
&#9989; Kasada PASS: 2021 CATERPILLAR 950M | Empire...
---
Testing Cloudflare...
   [Camera] Screenshot saved: screenshots/cloudflare_result.png
&#9989; Cloudflare PASS: Latest Acne Studios Collection...
---
Testing DataDome...
   [Camera] Screenshot saved: screenshots/datadome_result.png
&#9989; DataDome PASS: Immortelle Anti-Aging Face Oil...
---
Testing Akamai...
   [Camera] Screenshot saved: screenshots/akamai_result.png
&#9989; Akamai PASS: Slides for Women | Designer Sl...</code></code></pre><p>As you can see, using the Oxylabs Headless Browser, the scraper overcomes all the anti-bots!</p><p>As a final note, consider that, based on the configuration and settings you will actually implement, DataDome (but also other anti-bots) may activate sliding captchas:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QmCl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d90a97e-1c01-4abf-87c7-12ab388a9684_735x591.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QmCl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d90a97e-1c01-4abf-87c7-12ab388a9684_735x591.png 424w, https://substackcdn.com/image/fetch/$s_!QmCl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d90a97e-1c01-4abf-87c7-12ab388a9684_735x591.png 848w, https://substackcdn.com/image/fetch/$s_!QmCl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d90a97e-1c01-4abf-87c7-12ab388a9684_735x591.png 1272w, https://substackcdn.com/image/fetch/$s_!QmCl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d90a97e-1c01-4abf-87c7-12ab388a9684_735x591.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QmCl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d90a97e-1c01-4abf-87c7-12ab388a9684_735x591.png" width="735" height="591" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3d90a97e-1c01-4abf-87c7-12ab388a9684_735x591.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:591,&quot;width&quot;:735,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:122036,&quot;alt&quot;:&quot;A sliding CAPTCHA by Federico Trotta&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/202688828?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d90a97e-1c01-4abf-87c7-12ab388a9684_735x591.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A sliding CAPTCHA by Federico Trotta" title="A sliding CAPTCHA by Federico Trotta" srcset="https://substackcdn.com/image/fetch/$s_!QmCl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d90a97e-1c01-4abf-87c7-12ab388a9684_735x591.png 424w, https://substackcdn.com/image/fetch/$s_!QmCl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d90a97e-1c01-4abf-87c7-12ab388a9684_735x591.png 848w, https://substackcdn.com/image/fetch/$s_!QmCl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d90a97e-1c01-4abf-87c7-12ab388a9684_735x591.png 1272w, https://substackcdn.com/image/fetch/$s_!QmCl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d90a97e-1c01-4abf-87c7-12ab388a9684_735x591.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A sliding CAPTCHA</figcaption></figure></div><p>In this case, the <a href="https://developers.oxylabs.io/products/headless-browser/features/dynamic-captcha-solving">Oxylabs Headless Browser is of no help as it only supports hcaptcha, recaptcha, and turnstile dynamic captchas</a>.</p><p>Hooray! You successfully made it to your first scraping project using the Oxylabs Headless Browser.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Web Scraping Club is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>Conclusion</h2><p>In this article, you learned the actual distinction between headless browsers and browser automation tools. You also discovered the Oxylabs Headless Browser which, despite the name, it&#8217;s cloud-based browser automation. The difference between this and the browser automation tools you already know is that it&#8217;s cloud-based. This means that when it&#8217;s called via the code, it manages all the infrastructure on its own without overhead on your side.</p><p>As you learned in the tutorial section, it&#8217;s also pretty easy to configure and to use: you just need to manage some parameters and call the Oxylabs endpoint.</p><p>So, let us know: were you already using the Oxylabs Headless Browser?</p><div><hr></div><p><em>Did you like this article? Share it with someone who might find it useful and get a discount on paid plans.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/p/oxylabs-headless-browser?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://substack.thewebscraping.club/p/oxylabs-headless-browser?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div>]]></content:encoded></item><item><title><![CDATA[THE LAB #109: UDP, Proxies, and the WebRTC Leak]]></title><description><![CDATA[The proxy carries the UDP. The browser refuses to hand WebRTC over. We tested every combination to find where your IP actually leaks.]]></description><link>https://substack.thewebscraping.club/p/udp-proxies-and-the-webrtc-leak</link><guid isPermaLink="false">https://substack.thewebscraping.club/p/udp-proxies-and-the-webrtc-leak</guid><dc:creator><![CDATA[Pierluigi Vinciguerra]]></dc:creator><pubDate>Thu, 02 Jul 2026 03:00:18 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/e7e6b5b3-fee9-4165-abe4-ec0cda3458fc_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>More and more of the web runs on UDP now. HTTP/3 runs over QUIC, QUIC runs over UDP, and a good chunk of the sites we scrape already answer this way. Proxy providers noticed, and some of them started adding UDP support, since that&#8217;s the only way a UDP protocol can pass through a proxy at all. We wanted to understand what this actually gives us as scrapers, because UDP reaches a browser in two very different ways, and a proxy treats them differently.</p><blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://visit.decodo.com/WyQ3mA" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qJZE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png" width="555" height="312.1875" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fec072bc-2ac2-49bf-8084-43858298808f_800x450.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:450,&quot;width&quot;:800,&quot;resizeWidth&quot;:555,&quot;bytes&quot;:554497,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://visit.decodo.com/WyQ3mA&quot;,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/187315628?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!qJZE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>For your scraping needs, having a reliable proxy provider like <strong>Decodo</strong> on your side improves the chances of success. </em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://visit.decodo.com/WyQ3mA&quot;,&quot;text&quot;:&quot;Try Decodo Now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://visit.decodo.com/WyQ3mA"><span>Try Decodo Now</span></a></p><div><hr></div></blockquote><p>The first is the UDP we want, HTTP/3 for our requests. The second is the UDP we never asked for, the WebRTC traffic that a browser generates on its own, which can leak around the proxy and expose our real IP. But supporting UDP and actually getting a client to route its UDP through the proxy are two different problems, and the result changes a lot depending on the client. An HTTP client, a browser doing HTTP/3, and a browser doing WebRTC all behave differently, so we tested all three, end to end, to see where a UDP proxy does the work for us and where we still have to step in on the client side.</p><h2>A quick recap of HTTP/2 and HTTP/3</h2><p>We already covered the request side of HTTP/3 in <a href="https://substack.thewebscraping.club/p/faster-web-scraping-with-http3">Faster Web Scraping with HTTP/3</a>, so let&#8217;s just recap what matters for proxies. HTTP/1.1 and HTTP/2 run on TCP. HTTP/3 drops TCP completely and runs on QUIC, which is built on UDP and merges the transport and TLS handshakes into one. The part that matters for us is the transport: a proxy built to relay TCP has nothing to grab onto in a QUIC connection, since there&#8217;s no TCP stream to relay.</p><p>Adoption is real, but it&#8217;s easy to overstate. If we look at traffic, HTTP/3 is around a fifth of all requests, while HTTP/2 is still the majority at almost half, and this split has stayed roughly the same through 2026 (<a href="https://radar.cloudflare.com/adoption-and-usage">Cloudflare Radar</a>). If we look at site support instead, the number goes up: around 38% of sites advertise HTTP/3 via Alt-Svc (<a href="https://w3techs.com/technologies/details/ce-http3">W3Techs</a>). Italy, where our test machine sits, is one of the higher HTTP/3 markets at about 30%. So HTTP/3 is a serious, well-supported protocol, not some niche thing, but it hasn&#8217;t replaced HTTP/2 yet, and most real traffic is still on TCP.</p><div><hr></div><blockquote><p><em>HTTP/3 and QUIC run on UDP, so the proxy layer has to carry it too. <strong>anyIP</strong> gives compatible clients production-ready SOCKS5 UDP ASSOCIATE across its residential and mobile network.</em></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://anyip.io/proxies/udp-proxy" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!f6L2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8be5a9-3a31-49b3-9912-82c5964632b8_4585x2293.png 424w, https://substackcdn.com/image/fetch/$s_!f6L2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8be5a9-3a31-49b3-9912-82c5964632b8_4585x2293.png 848w, https://substackcdn.com/image/fetch/$s_!f6L2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8be5a9-3a31-49b3-9912-82c5964632b8_4585x2293.png 1272w, https://substackcdn.com/image/fetch/$s_!f6L2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8be5a9-3a31-49b3-9912-82c5964632b8_4585x2293.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!f6L2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8be5a9-3a31-49b3-9912-82c5964632b8_4585x2293.png" width="556" height="278" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ad8be5a9-3a31-49b3-9912-82c5964632b8_4585x2293.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:728,&quot;width&quot;:1456,&quot;resizeWidth&quot;:556,&quot;bytes&quot;:1075200,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://anyip.io/proxies/udp-proxy&quot;,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/203043903?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8be5a9-3a31-49b3-9912-82c5964632b8_4585x2293.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!f6L2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8be5a9-3a31-49b3-9912-82c5964632b8_4585x2293.png 424w, https://substackcdn.com/image/fetch/$s_!f6L2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8be5a9-3a31-49b3-9912-82c5964632b8_4585x2293.png 848w, https://substackcdn.com/image/fetch/$s_!f6L2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8be5a9-3a31-49b3-9912-82c5964632b8_4585x2293.png 1272w, https://substackcdn.com/image/fetch/$s_!f6L2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8be5a9-3a31-49b3-9912-82c5964632b8_4585x2293.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://anyip.io/proxies/udp-proxy?utm_source=thewebscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=gold_2026_1&amp;utm_content=banner_udp&quot;,&quot;text&quot;:&quot;Explore anyIP UDP Proxies&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://anyip.io/proxies/udp-proxy?utm_source=thewebscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=gold_2026_1&amp;utm_content=banner_udp"><span>Explore anyIP UDP Proxies</span></a></p></blockquote><div><hr></div><p>The sales pitch follows naturally from here. If real browsers increasingly speak HTTP/3 and our scraper can&#8217;t, that difference becomes a signal, so we&#8217;d want to speak HTTP/3 too, and to do that through a proxy, the proxy has to carry UDP. </p><p>This is exactly what some providers are now selling. They offer UDP support, usually as a beta over SOCKS5, and they pitch it as a stealth feature: the idea is that most residential traffic is QUIC these days, so a client stuck on TCP stands out. There are two parts of this claim worth checking before relying on it, the stealth side and the tooling side, so we tested both, using a provider that carries UDP and one that doesn&#8217;t.</p><h2>UDP support and the stealth question</h2><p>Switching to HTTP/3 doesn&#8217;t remove protocol fingerprinting, it just moves it somewhere else. Detection happens at three layers, and moving from TCP to QUIC affects each one in a different way.</p><p>The TCP/IP stack fingerprint, meaning the SYN options, window sizes and TTL that give away the OS, really does disappear, because there&#8217;s no TCP handshake anymore. That&#8217;s the only layer that goes away, though. The TLS fingerprint stays exactly as it was, because QUIC still does a TLS 1.3 handshake, just wrapped inside QUIC instead of running over TCP. The ClientHello with its cipher suites and extensions is still there, and JA4 was designed to fingerprint the QUIC variant on purpose (<a href="https://scrapfly.io/web-scraping-tools/http3-quic-fingerprint">Scrapfly QUIC/HTTP3 fingerprint</a>). The HTTP/2 fingerprint, the SETTINGS frames, window updates and pseudo-header order, doesn&#8217;t get erased either. It just gets replaced by an HTTP/3 fingerprint built from QUIC transport parameters.</p><p>And those QUIC transport parameters are basically the same thing we were trying to get away from. Scrapfly says it clearly: they &#8220;work like HTTP/2 SETTINGS frames but at the transport layer.&#8221; Values like <code>max_udp_payload_size</code> and <code>initial_max_data</code>, and the order they come in, are different between Chrome&#8217;s quiche, Firefox, Go&#8217;s quic-go and ngtcp2, and that difference is itself a fingerprint (<a href="https://scrapfly.io/blog/posts/http2-http3-fingerprinting-guide">Scrapfly HTTP/2 and HTTP/3 fingerprinting</a>). QUIC even adds new signals that have no TCP equivalent at all, like 0-RTT behavior and connection migration support.</p><p>So the layer that actually gets scrapers blocked today is TLS and HTTP/2 fingerprinting, which Cloudflare, Akamai and DataDome score together as JA4 and JA4H. That&#8217;s a TCP-side problem, and it&#8217;s one we already solve with a client that imitates a browser&#8217;s handshake. HTTP/3 fingerprinting is growing but still immature. Scrapfly is pretty direct about it: it &#8220;adds signals on top of&#8221; HTTP/2 instead of replacing it, real Chrome on HTTP/2 is still accepted on the vast majority of sites, and the scraper-on-HTTP/2 versus user-on-HTTP/3 gap is only &#8220;starting to become a signal on Cloudflare&#8217;s premium tier.&#8221; There&#8217;s also a catch worth keeping in mind: a scraper that speaks HTTP/3 through a non-browser QUIC stack shows a QUIC fingerprint that matches no real browser, so it can stand out just as easily as it blends in. In other words, a UDP proxy is what gets us onto HTTP/3 in the first place, but the stealth benefit only pays off if the client presents a QUIC fingerprint that a real browser would. The proxy just carries the traffic; making it look right is still our job.</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.youtube.com/@thewebscrapingclub&quot;,&quot;text&quot;:&quot;Check the TWSC YouTube Channel&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.youtube.com/@thewebscrapingclub"><span>Check the TWSC YouTube Channel</span></a></p><div><hr></div><h2>The tooling has not caught up yet</h2><p>The other half of the pitch is the tooling. The most common client for browserless scraping is <code>curl_cffi</code>, so the real question is whether it can actually do HTTP/3 in the setup a scraper normally runs in. <span>We tested it (</span>run_curl_cffi_http3.py<span>), and you can find the code, as always, </span><a href="https://github.com/TheWebScrapingClub/thelab"><span>on our GitHub repository available for our paid readers</span></a><span>, under the folder </span><strong><span>109.UDP</span></strong></p><p>The library is compiled with the QUIC stack, <code>ngtcp2</code> and <code>nghttp3</code>, and on a plain direct request it does reach HTTP/3. It even reaches HTTP/3 with a full Chrome fingerprint if we use the low-level interface, so impersonation and HTTP/3 aren&#8217;t really in conflict, even if the first error makes it look that way.</p><pre><code><code>high-level requests API:
  plain, no impersonation, V3ONLY        -&gt; HTTP/3
  Chrome impersonation, V3ONLY           -&gt; ERROR: QUIC needs at least TLS version 1.3
  Chrome impersonation, HTTP proxy, V3   -&gt; ERROR: HTTP/3 is not supported over an HTTP proxy
  Chrome impersonation, SOCKS5, V3       -&gt; ERROR: HTTP/3 is not supported over a SOCKS proxy
low-level Curl API (same impersonation):
  impersonate chrome + V3ONLY            -&gt; HTTP/3</code></code></pre><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/p/consulting&quot;,&quot;text&quot;:&quot;Need help with your scraping project?&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://substack.thewebscraping.club/p/consulting"><span>Need help with your scraping project?</span></a></p><div><hr></div><p>That middle error in the high-level API is just a TLS-version conflict inside the <code>requests</code> wrapper, not a real limitation, since the low-level call combines the Chrome fingerprint with HTTP/3 and gets there fine. The lines that really matter are the proxy ones. curl refuses HTTP/3 over a proxy completely, both HTTP and SOCKS, and it refuses before it even connects, so this is curl saying no, not the proxy. It happens even with a proxy that could carry the UDP. And since a scraper always runs through a proxy, in practice <code>curl_cffi</code> ends up being HTTP/2 over TCP, and a UDP-capable proxy doesn&#8217;t change that at all.</p><p>So this is the honest picture on the request side. A proxy that carries UDP does its job for a client that tunnels its own UDP, but the popular HTTP clients refuse HTTP/3 over a proxy right now, so the benefit is stuck waiting for the tooling to catch up. The proxy capability is arriving before the clients that could use it, which is good to know before expecting HTTP/3 to flow through a scraper just because the proxy supports UDP.</p><h2>The WebRTC leak, where UDP really bites</h2>
      <p>
          <a href="https://substack.thewebscraping.club/p/udp-proxies-and-the-webrtc-leak">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Public Sector Meets Web Scraping: From Scraped Data to Public Value]]></title><description><![CDATA[Using web scraping to fuel data pipelines that provide value to citizens and policymakers]]></description><link>https://substack.thewebscraping.club/p/public-sector-web-scraping-real-estate</link><guid isPermaLink="false">https://substack.thewebscraping.club/p/public-sector-web-scraping-real-estate</guid><dc:creator><![CDATA[Antonello Zanini]]></dc:creator><pubDate>Sun, 28 Jun 2026 03:01:13 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/0847fa1a-718c-4532-843c-254ba0748dcb_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There&#8217;s no doubt that web scraping supports a wide range of use cases, from price monitoring to data collection for AI model training. Most of these applications focus on generating value for businesses or individuals by supporting decision-making or automating workflows.</p><p>However, web scraping can also serve as a foundation for building the data infrastructure needed to better understand national markets and societal conditions.</p><p>In this blog post, I&#8217;ll show real-world examples of how web scraping is already used in the public sector, and how institutions could leverage it to improve policies and deliver higher-quality services to citizens.</p><div><hr></div><blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gCo7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gCo7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png" width="626" height="313" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:626,&quot;bytes&quot;:390972,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/197764148?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!gCo7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Before proceeding, let me thank NetNut, the platinum partner of the month. Their set of solutions cover all your needs for scraping.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack&quot;,&quot;text&quot;:&quot;Visit Netnut&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack"><span>Visit Netnut</span></a></p><div><hr></div></blockquote><h2>Why Web Scraping Matters for Public Institutions</h2><p>Public institutions have access to statistical datasets from official sources. These are surely relevant, but they tend to provide a picture of the market only after events have already happened, sometimes months or even a year later.</p><p>That delay creates a challenge. After all, institutions need timely information to understand citizens&#8217; current needs and respond effectively. Another issue is that relevant data is often scattered across many sources, such as different websites, web portals, and marketplaces.</p><p>This is where web scraping can help! By collecting large volumes of publicly available data from multiple sites, institutions can complement and enrich official statistics.</p><p>Data collection is only the first step, though. The real value comes from building a complete data pipeline that includes cleaning, deduplication, aggregation, geolocation, statistical modeling, and interpretation.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HzZ6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8619ffba-30f3-4287-97af-17b9f368bc1a_2912x1446.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HzZ6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8619ffba-30f3-4287-97af-17b9f368bc1a_2912x1446.png 424w, https://substackcdn.com/image/fetch/$s_!HzZ6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8619ffba-30f3-4287-97af-17b9f368bc1a_2912x1446.png 848w, https://substackcdn.com/image/fetch/$s_!HzZ6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8619ffba-30f3-4287-97af-17b9f368bc1a_2912x1446.png 1272w, https://substackcdn.com/image/fetch/$s_!HzZ6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8619ffba-30f3-4287-97af-17b9f368bc1a_2912x1446.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HzZ6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8619ffba-30f3-4287-97af-17b9f368bc1a_2912x1446.png" width="1456" height="723" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8619ffba-30f3-4287-97af-17b9f368bc1a_2912x1446.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:723,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Bayernheimerov&#225; Kl&#225;ra during her speech at Prague Crawl 2026&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Bayernheimerov&#225; Kl&#225;ra during her speech at Prague Crawl 2026" title="Bayernheimerov&#225; Kl&#225;ra during her speech at Prague Crawl 2026" srcset="https://substackcdn.com/image/fetch/$s_!HzZ6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8619ffba-30f3-4287-97af-17b9f368bc1a_2912x1446.png 424w, https://substackcdn.com/image/fetch/$s_!HzZ6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8619ffba-30f3-4287-97af-17b9f368bc1a_2912x1446.png 848w, https://substackcdn.com/image/fetch/$s_!HzZ6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8619ffba-30f3-4287-97af-17b9f368bc1a_2912x1446.png 1272w, https://substackcdn.com/image/fetch/$s_!HzZ6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8619ffba-30f3-4287-97af-17b9f368bc1a_2912x1446.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Bayernheimerov&#225; Kl&#225;ra during her speech at Prague Crawl 2026</figcaption></figure></div><p>At Prague Crawl 2026, <a href="https://cz.linkedin.com/in/kl%C3%A1ra-bayernheimerov%C3%A1-53bbaa405">Bayernheimerov&#225; Kl&#225;ra</a> presented a compelling example of this approach. Her talk showed how the <em>Czech Ministry of Finance</em> relies on rental listing data collected by an external provider from several real estate portals and processes it through an end-to-end pipeline based on a dedicated statistical methodology.</p><p>As I&#8217;ll present shortly, the result is a reliable source of insights that supports housing policy decisions and affordable housing programs across the country, while also providing a valuable service to citizens.</p><p>What&#8217;s important to understand is that the goal isn&#8217;t simply to collect raw data, but to power services such as interactive maps, calculators, and dashboards that are useful to both public institutions and citizens.</p><h2>Case Study: Czech Rental Market Intelligence System</h2><p>To see an actual application of web scraping in the public sector, I&#8217;ll now present the initiative developed by the Czech Ministry of Finance for rental price analysis across Czechia.</p><div><hr></div><blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://visit.decodo.com/WyQ3mA" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qJZE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png" width="521" height="293.0625" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fec072bc-2ac2-49bf-8084-43858298808f_800x450.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:450,&quot;width&quot;:800,&quot;resizeWidth&quot;:521,&quot;bytes&quot;:554497,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://visit.decodo.com/WyQ3mA&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/187315628?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!qJZE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>For your scraping needs, having a reliable proxy provider like <strong>Decodo</strong> on your side improves the chances of success. </em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://visit.decodo.com/WyQ3mA&quot;,&quot;text&quot;:&quot;Try Decodo Now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://visit.decodo.com/WyQ3mA"><span>Try Decodo Now</span></a></p></blockquote><div><hr></div><h3>The Motivation Behind the Project</h3><p>Just like in <a href="https://www.europarl.europa.eu/topics/en/article/20241014STO24542/housing-crisis-why-prices-are-rising-and-what-the-eu-is-doing-about-it">many other European countries</a>, housing affordability has become a major social and economic challenge in the Czech Republic. Housing supply has struggled to keep up with demand, causing both property prices and rents to rise steadily across the country.</p><p>To confront this issue, the Czech Ministry of Finance needed detailed and up-to-date information on local rental markets. The initiative was launched specifically to fill that gap, creating a web data analysis process that gives a continuously updated view of rental prices across the entire country.</p><p>The objective is to provide a reliable foundation for housing policy, affordable housing programs, and public-facing tools that help citizens better resonate with local rental markets.</p><h3>Web Data Sourcing</h3><p>The data for this initiative has been collected using <a href="https://apify.com/">Apify</a>, one of the largest marketplaces of ready-made tools for web scraping, automation, and AI.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QcM-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F853bdd95-9cdc-487b-8abd-7b26009400da_2998x1587.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QcM-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F853bdd95-9cdc-487b-8abd-7b26009400da_2998x1587.png 424w, https://substackcdn.com/image/fetch/$s_!QcM-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F853bdd95-9cdc-487b-8abd-7b26009400da_2998x1587.png 848w, https://substackcdn.com/image/fetch/$s_!QcM-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F853bdd95-9cdc-487b-8abd-7b26009400da_2998x1587.png 1272w, https://substackcdn.com/image/fetch/$s_!QcM-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F853bdd95-9cdc-487b-8abd-7b26009400da_2998x1587.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QcM-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F853bdd95-9cdc-487b-8abd-7b26009400da_2998x1587.png" width="1456" height="771" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/853bdd95-9cdc-487b-8abd-7b26009400da_2998x1587.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:771,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Apify&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Apify" title="Apify" srcset="https://substackcdn.com/image/fetch/$s_!QcM-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F853bdd95-9cdc-487b-8abd-7b26009400da_2998x1587.png 424w, https://substackcdn.com/image/fetch/$s_!QcM-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F853bdd95-9cdc-487b-8abd-7b26009400da_2998x1587.png 848w, https://substackcdn.com/image/fetch/$s_!QcM-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F853bdd95-9cdc-487b-8abd-7b26009400da_2998x1587.png 1272w, https://substackcdn.com/image/fetch/$s_!QcM-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F853bdd95-9cdc-487b-8abd-7b26009400da_2998x1587.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Apify</figcaption></figure></div><p>If you aren&#8217;t familiar with Apify, it provides over <a href="https://apify.com/store">41,000 ready-made serverless cloud programs </a>(called <em>Actors</em>) to automate a wide range of tasks, including web scraping across thousands of different domains.</p><p>These Actors are developed and maintained by the community (and in some cases by Apify itself) and run on Apify&#8217;s cloud infrastructure. You can use them directly through Apify Console via a no-code interface, or call them programmatically via API. They can also be integrated into workflows such as n8n, Make, Zapier, or AI agents via MCP.</p><p>Two main reasons influenced the choice of Apify:</p><ol><li><p>It&#8217;s a Czech-based company.</p></li><li><p>It enables the Ministry of Finance to gather large-scale, up-to-date information on rental listings without maintaining scraping infrastructure in-house.</p></li></ol><p>In general, a solution like Apify lowers the barrier for public-sector teams, as these may have limited engineering capacity.</p><h3>Downstream Data Pipeline</h3><p>At a high level, this is the <a href="https://mf.gov.cz/cs/rozpoctova-politika/podpora-projektoveho-rizeni/cenova-mapa/metodika">downstream data pipeline</a> implemented by the Czech Ministry of Finance:</p><ol><li><p><strong>Data ingestion</strong>: Collects rental listings via Apify-based web scraping from multiple Czech real estate portals.</p></li><li><p><strong>Data cleaning</strong>: Sorts data and removes duplicates, incorrect entries, and inconsistencies to ensure it is up to date, accurate, and free from deviations or distortions, following the procedure defined in <a href="https://www.cai.cz/OA/pdf/P456_2024_EN.pdf">Decree No. 456/2024 Coll</a>.</p></li><li><p><strong>Geolocation and aggregatio</strong>n: Standardizes addresses, assigns cadastral units, and enriches listings with attributes such as size, amenities, and building type.</p></li><li><p><strong>Statistical modelling</strong>: Uses hedonic regression with spatial and temporal weighting to estimate underlying rental price levels.</p></li><li><p><strong>Output generation</strong>: Presents results as interactive price maps and calculators.</p></li></ol><p><strong>Note</strong>: The pipeline also includes data update and recycling via a rolling window approach. This process adds new listings while retiring outdated observations to keep the dataset current.</p><p>To make rental prices comparable across different locations, the methodology is based on a <em>reference apartment. That represents </em>a standardized apartment profile with predefined characteristics, such as size, type, furnishing level, etc.</p><p>This approach reduces differences caused by individual property features and enables more consistent comparisons of rental price levels across municipalities and cadastral areas.</p><h3>Produced Output and Tools</h3><p>The Czech Rental Housing Price Map consists of two main solutions:</p><ul><li><p><strong><a href="https://mf.gov.cz/cs/rozpoctova-politika/podpora-projektoveho-rizeni/cenova-mapa/cenova-mapa-infografika">Interactive rent price map</a></strong>: Provides estimated rental prices at the level of municipalities and cadastral areas across the Czech Republic.</p></li><li><p><strong><a href="https://mf.gov.cz/cs/rozpoctova-politika/podpora-projektoveho-rizeni/cenova-mapa/kalkulacka-trzniho-najemneho">Market rent calculator</a></strong>: A practical tool that calculates a statistical estimate of rental price levels based on a standardized apartment profile and selected property characteristics.</p></li></ul><p><strong>Important</strong>: The two solutions aren&#8217;t intended to determine the market rent of a specific apartment. Instead, they return statistical estimates based on a standardized reference apartment. So, they&#8217;re intended primarily for market monitoring, regional comparisons, and housing policy analysis.</p><h4>Interactive rent price map</h4><p>The <em>interactive rent price map</em> shows minimum, maximum, and median rents for each area based on market listings, adjusted per square meter for a standard unfurnished reference apartment. It covers four size categories from 1+kk/1+1 to 4+kk/4+1.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!P1Z4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a7993d4-d09a-4e08-ad7d-57faf945ccec_2364x1336.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!P1Z4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a7993d4-d09a-4e08-ad7d-57faf945ccec_2364x1336.png 424w, https://substackcdn.com/image/fetch/$s_!P1Z4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a7993d4-d09a-4e08-ad7d-57faf945ccec_2364x1336.png 848w, https://substackcdn.com/image/fetch/$s_!P1Z4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a7993d4-d09a-4e08-ad7d-57faf945ccec_2364x1336.png 1272w, https://substackcdn.com/image/fetch/$s_!P1Z4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a7993d4-d09a-4e08-ad7d-57faf945ccec_2364x1336.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!P1Z4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a7993d4-d09a-4e08-ad7d-57faf945ccec_2364x1336.png" width="1456" height="823" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7a7993d4-d09a-4e08-ad7d-57faf945ccec_2364x1336.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:823,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The Czech interactive rent price map (as displayed on a Google-translated page in English)&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The Czech interactive rent price map (as displayed on a Google-translated page in English)" title="The Czech interactive rent price map (as displayed on a Google-translated page in English)" srcset="https://substackcdn.com/image/fetch/$s_!P1Z4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a7993d4-d09a-4e08-ad7d-57faf945ccec_2364x1336.png 424w, https://substackcdn.com/image/fetch/$s_!P1Z4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a7993d4-d09a-4e08-ad7d-57faf945ccec_2364x1336.png 848w, https://substackcdn.com/image/fetch/$s_!P1Z4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a7993d4-d09a-4e08-ad7d-57faf945ccec_2364x1336.png 1272w, https://substackcdn.com/image/fetch/$s_!P1Z4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a7993d4-d09a-4e08-ad7d-57faf945ccec_2364x1336.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The Czech interactive rent price map (as displayed on a Google-translated page in English)</figcaption></figure></div><p>Note that the map is interactive, and you can zoom in and out to explore individual cadastral areas. It&#8217;s also updated four times per year to ensure current data.</p><h4>Market rent calculator</h4><p>The <em>market rent calculator</em> is available as a form on the Czech government website. It works as follows:</p><ol><li><p>Select the territorial unit where you want to estimate the rent.</p></li><li><p>Choose the size category of the apartment.</p></li><li><p>Enter the floor area of the apartment (if you don&#8217;t know it, the calculator will automatically use a default value for the selected location).</p></li><li><p>Indicate whether the apartment is in a new building.</p></li><li><p>Optionally specify whether the building uses non-standard construction materials (e.g., other than brick or panel).</p></li><li><p>Optionally select additional features such as a terrace, furniture, or an assigned parking/garage space.</p></li><li><p>Click &#8220;Calculate rent&#8221; to obtain an estimate of the monthly market rent (CZK) and the corresponding price level, which represents the final estimated rent for the selected apartment profile.</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NWAb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa824916b-9219-4efe-ba55-1e66699d364b_794x800.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NWAb!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa824916b-9219-4efe-ba55-1e66699d364b_794x800.gif 424w, https://substackcdn.com/image/fetch/$s_!NWAb!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa824916b-9219-4efe-ba55-1e66699d364b_794x800.gif 848w, https://substackcdn.com/image/fetch/$s_!NWAb!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa824916b-9219-4efe-ba55-1e66699d364b_794x800.gif 1272w, https://substackcdn.com/image/fetch/$s_!NWAb!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa824916b-9219-4efe-ba55-1e66699d364b_794x800.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NWAb!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa824916b-9219-4efe-ba55-1e66699d364b_794x800.gif" width="794" height="800" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a824916b-9219-4efe-ba55-1e66699d364b_794x800.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:800,&quot;width&quot;:794,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Result of a market rent calculator submission (as displayed on a Google-translated page in English)&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Result of a market rent calculator submission (as displayed on a Google-translated page in English)" title="Result of a market rent calculator submission (as displayed on a Google-translated page in English)" srcset="https://substackcdn.com/image/fetch/$s_!NWAb!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa824916b-9219-4efe-ba55-1e66699d364b_794x800.gif 424w, https://substackcdn.com/image/fetch/$s_!NWAb!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa824916b-9219-4efe-ba55-1e66699d364b_794x800.gif 848w, https://substackcdn.com/image/fetch/$s_!NWAb!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa824916b-9219-4efe-ba55-1e66699d364b_794x800.gif 1272w, https://substackcdn.com/image/fetch/$s_!NWAb!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa824916b-9219-4efe-ba55-1e66699d364b_794x800.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Result of a market rent calculator submission (as displayed on a Google-translated page in English)</figcaption></figure></div><h3>Impact</h3><p>The project provides a systematic and regularly updated overview of market rental prices across the entire Czech Republic, supporting consistent monitoring of housing market developments over time and across regions.</p><p>Beyond market observation, the outputs serve as a key evidence base for housing policy design and housing programmes within public administration. For example, the State Investment Support Fund (SFPI) relies on these results when working with its affordable rental housing schemes.</p><h2>Web Scraping in the Public Sector: Extending the Model</h2><p>The project carried out by the Czech Ministry of Finance is just one example of a much broader pattern: <em>how web scraping can be used as a foundation for modern public-sector data systems.</em></p><p>In particular, the same pipeline logic can be applied across many other domains. For example, in labor markets, web scraping can be used to collect job postings, salary ranges, and skill requirements from recruitment platforms. This can help governments identify regional skill shortages and design more targeted education or reskilling programmes.</p><p>Similarly, in consumer price monitoring, scraping can track grocery prices, housing costs, and essential goods across regions, enabling better inflation tracking and cost-of-living analysis. Other potential public domains that can benefit from this include energy consumption and transport accessibility.</p><p>When combined, those datasets become even more powerful. For instance, housing data, job market data, and grocery price data could be combined into a broader &#8220;liveability&#8221; index. That would help citizens and policymakers assess the overall affordability of living in a given region.</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.youtube.com/@thewebscrapingclub&quot;,&quot;text&quot;:&quot;Check the TWSC YouTube Channel&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.youtube.com/@thewebscrapingclub"><span>Check the TWSC YouTube Channel</span></a></p><div><hr></div><h3>Building a Job Market Choropleth for Czech Districts</h3><p>In this section, I&#8217;ll guide you through building a job market choropleth across Czech districts. The idea is to show how web scraping, combined with a complete data pipeline, can be utilized for other scenarios beyond the previous example.</p><p><strong>Important</strong>: The project below isn&#8217;t related to the initiative from the Czech Ministry of Finance and has been created purely for illustrative purposes.</p><h3>Prerequisites</h3><p>To follow this tutorial section, make sure you have:</p><ul><li><p>An <a href="https://console.apify.com/sign-up">Apify account</a> (a free plan is sufficient).</p></li><li><p><a href="https://www.python.org/downloads/">Python 3.11+</a> installed locally.</p></li></ul><p>I&#8217;ll also assume you have a Python project set up locally with the following libraries installed:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">pip install pandas geopandas geopy matplotlib</code></pre></div><p>These are the required dependencies for this project and will be used as follows:</p><ul><li><p><em>pandas</em>: Load, clean, and manipulate the scraped job posting data before geospatial analysis.</p></li><li><p><em>geopandas</em>: Handle geographic data, perform spatial joins, and create district-level maps.</p></li><li><p><em>geopy</em>: Convert job location addresses into geographic coordinates through geocoding.</p></li><li><p><em>matplotlib</em>: Visualize the results by generating a choropleth map showing job density by district.</p></li></ul><h4>Step #1: Access the Indeed Jobs Scraper Apify Actor</h4><p>Just like in the Czech Ministry of Finance example, you can use Apify to collect the source data. This saves you from building and maintaining a complete job scraping pipeline from scratch.</p><p>For this project, we&#8217;ll use Indeed as the data source. <a href="https://cz.indeed.com/">Indeed has a Czech version</a>, and most listings include the full office address where employees are expected to work.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!475m!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5558d37-c909-4466-8ff1-e002a876f299_3024x1596.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!475m!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5558d37-c909-4466-8ff1-e002a876f299_3024x1596.png 424w, https://substackcdn.com/image/fetch/$s_!475m!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5558d37-c909-4466-8ff1-e002a876f299_3024x1596.png 848w, https://substackcdn.com/image/fetch/$s_!475m!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5558d37-c909-4466-8ff1-e002a876f299_3024x1596.png 1272w, https://substackcdn.com/image/fetch/$s_!475m!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5558d37-c909-4466-8ff1-e002a876f299_3024x1596.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!475m!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5558d37-c909-4466-8ff1-e002a876f299_3024x1596.png" width="1456" height="768" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d5558d37-c909-4466-8ff1-e002a876f299_3024x1596.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:768,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The Czech version of Indeed&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The Czech version of Indeed" title="The Czech version of Indeed" srcset="https://substackcdn.com/image/fetch/$s_!475m!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5558d37-c909-4466-8ff1-e002a876f299_3024x1596.png 424w, https://substackcdn.com/image/fetch/$s_!475m!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5558d37-c909-4466-8ff1-e002a876f299_3024x1596.png 848w, https://substackcdn.com/image/fetch/$s_!475m!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5558d37-c909-4466-8ff1-e002a876f299_3024x1596.png 1272w, https://substackcdn.com/image/fetch/$s_!475m!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5558d37-c909-4466-8ff1-e002a876f299_3024x1596.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The Czech version of Indeed</figcaption></figure></div><p>In detail, I recommend using the <em><a href="https://apify.com/borderline/indeed-scraper">Indeed Jobs Scraper [PPR]</a></em> Actor. This automates the extraction of job titles, salaries, locations, company information, and job descriptions from Indeed.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!d8Ku!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23e4310-03be-4e81-a51a-26a0c8b0c225_3028x1638.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!d8Ku!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23e4310-03be-4e81-a51a-26a0c8b0c225_3028x1638.png 424w, https://substackcdn.com/image/fetch/$s_!d8Ku!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23e4310-03be-4e81-a51a-26a0c8b0c225_3028x1638.png 848w, https://substackcdn.com/image/fetch/$s_!d8Ku!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23e4310-03be-4e81-a51a-26a0c8b0c225_3028x1638.png 1272w, https://substackcdn.com/image/fetch/$s_!d8Ku!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23e4310-03be-4e81-a51a-26a0c8b0c225_3028x1638.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!d8Ku!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23e4310-03be-4e81-a51a-26a0c8b0c225_3028x1638.png" width="1456" height="788" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f23e4310-03be-4e81-a51a-26a0c8b0c225_3028x1638.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:788,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The Indeed Jobs Scraper [PPR] Actor&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The Indeed Jobs Scraper [PPR] Actor" title="The Indeed Jobs Scraper [PPR] Actor" srcset="https://substackcdn.com/image/fetch/$s_!d8Ku!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23e4310-03be-4e81-a51a-26a0c8b0c225_3028x1638.png 424w, https://substackcdn.com/image/fetch/$s_!d8Ku!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23e4310-03be-4e81-a51a-26a0c8b0c225_3028x1638.png 848w, https://substackcdn.com/image/fetch/$s_!d8Ku!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23e4310-03be-4e81-a51a-26a0c8b0c225_3028x1638.png 1272w, https://substackcdn.com/image/fetch/$s_!d8Ku!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23e4310-03be-4e81-a51a-26a0c8b0c225_3028x1638.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The Indeed Jobs Scraper [PPR] Actor</figcaption></figure></div><p>To get started, <a href="https://console.apify.com/">log in to your Apify account</a> and select the &#8220;Apify Store&#8221; option from the left-hand navigation menu. Then, search for &#8220;Indeed Jobs Scraper [PPR]&#8221;:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!RkuG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc15ea73-ddd2-4de7-a70b-79c5badec120_3026x1624.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!RkuG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc15ea73-ddd2-4de7-a70b-79c5badec120_3026x1624.png 424w, https://substackcdn.com/image/fetch/$s_!RkuG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc15ea73-ddd2-4de7-a70b-79c5badec120_3026x1624.png 848w, https://substackcdn.com/image/fetch/$s_!RkuG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc15ea73-ddd2-4de7-a70b-79c5badec120_3026x1624.png 1272w, https://substackcdn.com/image/fetch/$s_!RkuG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc15ea73-ddd2-4de7-a70b-79c5badec120_3026x1624.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!RkuG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc15ea73-ddd2-4de7-a70b-79c5badec120_3026x1624.png" width="1456" height="781" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cc15ea73-ddd2-4de7-a70b-79c5badec120_3026x1624.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:781,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Selecting the Indeed Jobs Scraper [PPR] Actor&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Selecting the Indeed Jobs Scraper [PPR] Actor" title="Selecting the Indeed Jobs Scraper [PPR] Actor" srcset="https://substackcdn.com/image/fetch/$s_!RkuG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc15ea73-ddd2-4de7-a70b-79c5badec120_3026x1624.png 424w, https://substackcdn.com/image/fetch/$s_!RkuG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc15ea73-ddd2-4de7-a70b-79c5badec120_3026x1624.png 848w, https://substackcdn.com/image/fetch/$s_!RkuG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc15ea73-ddd2-4de7-a70b-79c5badec120_3026x1624.png 1272w, https://substackcdn.com/image/fetch/$s_!RkuG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc15ea73-ddd2-4de7-a70b-79c5badec120_3026x1624.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Selecting the Indeed Jobs Scraper [PPR] Actor</figcaption></figure></div><p>Click on the Actor card to reach its page. Great!</p><h3>Step #2: Run the Job Scraping Task</h3><p>On the Actor page, you&#8217;ll find an input form that lets you configure the scraper before running it in the cloud. Select &#8220;Czech Republic&#8221; as the &#8220;Country&#8221; and enter &#8220;Software engineer&#8221; in the &#8220;Query&#8221; field:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iAmT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe61da07b-e6ea-4ec6-a416-0d52b448a1e9_3024x1628.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iAmT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe61da07b-e6ea-4ec6-a416-0d52b448a1e9_3024x1628.png 424w, https://substackcdn.com/image/fetch/$s_!iAmT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe61da07b-e6ea-4ec6-a416-0d52b448a1e9_3024x1628.png 848w, https://substackcdn.com/image/fetch/$s_!iAmT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe61da07b-e6ea-4ec6-a416-0d52b448a1e9_3024x1628.png 1272w, https://substackcdn.com/image/fetch/$s_!iAmT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe61da07b-e6ea-4ec6-a416-0d52b448a1e9_3024x1628.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!iAmT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe61da07b-e6ea-4ec6-a416-0d52b448a1e9_3024x1628.png" width="1456" height="784" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e61da07b-e6ea-4ec6-a416-0d52b448a1e9_3024x1628.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:784,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Configuring the Actor run&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Configuring the Actor run" title="Configuring the Actor run" srcset="https://substackcdn.com/image/fetch/$s_!iAmT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe61da07b-e6ea-4ec6-a416-0d52b448a1e9_3024x1628.png 424w, https://substackcdn.com/image/fetch/$s_!iAmT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe61da07b-e6ea-4ec6-a416-0d52b448a1e9_3024x1628.png 848w, https://substackcdn.com/image/fetch/$s_!iAmT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe61da07b-e6ea-4ec6-a416-0d52b448a1e9_3024x1628.png 1272w, https://substackcdn.com/image/fetch/$s_!iAmT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe61da07b-e6ea-4ec6-a416-0d52b448a1e9_3024x1628.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Configuring the Actor run</figcaption></figure></div><p><strong>Note</strong>: In a real-world scenario, configure the Actor&#8217;s inputs according to your needs.</p><p>Next, toggle the option to avoid duplicates so that the Actor takes care of data deduplication for you:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bEGj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f001863-b7c3-4dac-b63d-9db197be7265_1628x612.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bEGj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f001863-b7c3-4dac-b63d-9db197be7265_1628x612.png 424w, https://substackcdn.com/image/fetch/$s_!bEGj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f001863-b7c3-4dac-b63d-9db197be7265_1628x612.png 848w, https://substackcdn.com/image/fetch/$s_!bEGj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f001863-b7c3-4dac-b63d-9db197be7265_1628x612.png 1272w, https://substackcdn.com/image/fetch/$s_!bEGj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f001863-b7c3-4dac-b63d-9db197be7265_1628x612.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bEGj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f001863-b7c3-4dac-b63d-9db197be7265_1628x612.png" width="1456" height="547" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0f001863-b7c3-4dac-b63d-9db197be7265_1628x612.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:547,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Running the Actor&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Running the Actor" title="Running the Actor" srcset="https://substackcdn.com/image/fetch/$s_!bEGj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f001863-b7c3-4dac-b63d-9db197be7265_1628x612.png 424w, https://substackcdn.com/image/fetch/$s_!bEGj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f001863-b7c3-4dac-b63d-9db197be7265_1628x612.png 848w, https://substackcdn.com/image/fetch/$s_!bEGj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f001863-b7c3-4dac-b63d-9db197be7265_1628x612.png 1272w, https://substackcdn.com/image/fetch/$s_!bEGj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f001863-b7c3-4dac-b63d-9db197be7265_1628x612.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Running the Actor</figcaption></figure></div><p>Click the &#8220;Save &amp; Start&#8221; button to launch the scraping task. The Actor will start running directly in Apify Console. As the scraper progresses, you&#8217;ll see the extracted job postings appear in real time. Be patient, as the process may take a few minutes depending on the number of matching listings.</p><h4>Step #3: Explore the Output and Export the Results</h4><p>Once the run completes, you&#8217;ll be able to explore the scraped dataset directly in Apify Console. As you can see, the Actor returns structured Indeed job data:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1diH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F229bf5de-d82b-49cb-8f6f-191223a0caa1_3072x1646.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1diH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F229bf5de-d82b-49cb-8f6f-191223a0caa1_3072x1646.png 424w, https://substackcdn.com/image/fetch/$s_!1diH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F229bf5de-d82b-49cb-8f6f-191223a0caa1_3072x1646.png 848w, https://substackcdn.com/image/fetch/$s_!1diH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F229bf5de-d82b-49cb-8f6f-191223a0caa1_3072x1646.png 1272w, https://substackcdn.com/image/fetch/$s_!1diH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F229bf5de-d82b-49cb-8f6f-191223a0caa1_3072x1646.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1diH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F229bf5de-d82b-49cb-8f6f-191223a0caa1_3072x1646.png" width="1456" height="780" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/229bf5de-d82b-49cb-8f6f-191223a0caa1_3072x1646.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:780,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The scraped data in a structured format&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The scraped data in a structured format" title="The scraped data in a structured format" srcset="https://substackcdn.com/image/fetch/$s_!1diH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F229bf5de-d82b-49cb-8f6f-191223a0caa1_3072x1646.png 424w, https://substackcdn.com/image/fetch/$s_!1diH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F229bf5de-d82b-49cb-8f6f-191223a0caa1_3072x1646.png 848w, https://substackcdn.com/image/fetch/$s_!1diH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F229bf5de-d82b-49cb-8f6f-191223a0caa1_3072x1646.png 1272w, https://substackcdn.com/image/fetch/$s_!1diH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F229bf5de-d82b-49cb-8f6f-191223a0caa1_3072x1646.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The scraped data in a structured format</figcaption></figure></div><p>In this case, the Actor retrieved 63 job postings. That may seem like a small number, but keep in mind that the scraper targets only jobs published during the last 14 days. This helps ensure that the dataset reflects the current state of the job market.</p><p>Next, switch to the JSON view and select &#8220;All fields&#8221;. You&#8217;ll notice that each job posting includes a <em>location</em> object containing address information for the position:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!phqa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a4c1481-fc0f-4dc1-9f09-6ae9bdba8709_2584x1440.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!phqa!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a4c1481-fc0f-4dc1-9f09-6ae9bdba8709_2584x1440.png 424w, https://substackcdn.com/image/fetch/$s_!phqa!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a4c1481-fc0f-4dc1-9f09-6ae9bdba8709_2584x1440.png 848w, https://substackcdn.com/image/fetch/$s_!phqa!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a4c1481-fc0f-4dc1-9f09-6ae9bdba8709_2584x1440.png 1272w, https://substackcdn.com/image/fetch/$s_!phqa!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a4c1481-fc0f-4dc1-9f09-6ae9bdba8709_2584x1440.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!phqa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a4c1481-fc0f-4dc1-9f09-6ae9bdba8709_2584x1440.png" width="1456" height="811" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2a4c1481-fc0f-4dc1-9f09-6ae9bdba8709_2584x1440.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:811,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Note the &#8220;location&#8221; field on each job object in the scraped JSON dataset&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Note the &#8220;location&#8221; field on each job object in the scraped JSON dataset" title="Note the &#8220;location&#8221; field on each job object in the scraped JSON dataset" srcset="https://substackcdn.com/image/fetch/$s_!phqa!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a4c1481-fc0f-4dc1-9f09-6ae9bdba8709_2584x1440.png 424w, https://substackcdn.com/image/fetch/$s_!phqa!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a4c1481-fc0f-4dc1-9f09-6ae9bdba8709_2584x1440.png 848w, https://substackcdn.com/image/fetch/$s_!phqa!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a4c1481-fc0f-4dc1-9f09-6ae9bdba8709_2584x1440.png 1272w, https://substackcdn.com/image/fetch/$s_!phqa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a4c1481-fc0f-4dc1-9f09-6ae9bdba8709_2584x1440.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Note the &#8220;location&#8221; field on each job object in the scraped JSON dataset</figcaption></figure></div><p>That location data is exactly what you need to visualize job openings on a map and analyze their geographic distribution across Czech districts.</p><p>Finally, open the &#8220;Storage&#8221; tab, select the &#8220;JSON&#8221; export format, and click &#8220;Download&#8221; to export the scraped dataset:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Q-Mi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d7ee34b-f39c-4fd2-9b0d-81592b4287fb_2566x1444.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Q-Mi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d7ee34b-f39c-4fd2-9b0d-81592b4287fb_2566x1444.png 424w, https://substackcdn.com/image/fetch/$s_!Q-Mi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d7ee34b-f39c-4fd2-9b0d-81592b4287fb_2566x1444.png 848w, https://substackcdn.com/image/fetch/$s_!Q-Mi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d7ee34b-f39c-4fd2-9b0d-81592b4287fb_2566x1444.png 1272w, https://substackcdn.com/image/fetch/$s_!Q-Mi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d7ee34b-f39c-4fd2-9b0d-81592b4287fb_2566x1444.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Q-Mi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d7ee34b-f39c-4fd2-9b0d-81592b4287fb_2566x1444.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5d7ee34b-f39c-4fd2-9b0d-81592b4287fb_2566x1444.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Downloading the dataset&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Downloading the dataset" title="Downloading the dataset" srcset="https://substackcdn.com/image/fetch/$s_!Q-Mi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d7ee34b-f39c-4fd2-9b0d-81592b4287fb_2566x1444.png 424w, https://substackcdn.com/image/fetch/$s_!Q-Mi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d7ee34b-f39c-4fd2-9b0d-81592b4287fb_2566x1444.png 848w, https://substackcdn.com/image/fetch/$s_!Q-Mi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d7ee34b-f39c-4fd2-9b0d-81592b4287fb_2566x1444.png 1272w, https://substackcdn.com/image/fetch/$s_!Q-Mi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d7ee34b-f39c-4fd2-9b0d-81592b4287fb_2566x1444.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Downloading the dataset</figcaption></figure></div><p>A file with a name similar to <em>dataset_indeed-scraper_2026-06-18_11-54-00-555.json</em> will be downloaded. Rename it to <em>jobs.json</em> and place it in your Python project&#8217;s root directory.</p><h3>Step #4: Download the Required Czech GeoJSON Data</h3><p>To visualize the distribution of job openings across Czech districts, you&#8217;ll need a <a href="https://geojson.org/">GeoJSON dataset</a> containing the geographic boundaries of those districts.</p><p>Generally, that type of data is open and publicly available. For example, one possible source is the <em><a href="https://github.com/siwekm/czech-geojson">siwekm/czech-geojson</a></em> repository, which provides GeoJSON files for various Czech administrative divisions:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2hV0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bad6e02-74aa-4b7e-80fa-672dcd82682c_3036x1610.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2hV0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bad6e02-74aa-4b7e-80fa-672dcd82682c_3036x1610.png 424w, https://substackcdn.com/image/fetch/$s_!2hV0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bad6e02-74aa-4b7e-80fa-672dcd82682c_3036x1610.png 848w, https://substackcdn.com/image/fetch/$s_!2hV0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bad6e02-74aa-4b7e-80fa-672dcd82682c_3036x1610.png 1272w, https://substackcdn.com/image/fetch/$s_!2hV0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bad6e02-74aa-4b7e-80fa-672dcd82682c_3036x1610.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2hV0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bad6e02-74aa-4b7e-80fa-672dcd82682c_3036x1610.png" width="1456" height="772" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8bad6e02-74aa-4b7e-80fa-672dcd82682c_3036x1610.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:772,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Downloading the &#8220;okresy.json&#8221; file from the siwekm/czech-geojson repository&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Downloading the &#8220;okresy.json&#8221; file from the siwekm/czech-geojson repository" title="Downloading the &#8220;okresy.json&#8221; file from the siwekm/czech-geojson repository" srcset="https://substackcdn.com/image/fetch/$s_!2hV0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bad6e02-74aa-4b7e-80fa-672dcd82682c_3036x1610.png 424w, https://substackcdn.com/image/fetch/$s_!2hV0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bad6e02-74aa-4b7e-80fa-672dcd82682c_3036x1610.png 848w, https://substackcdn.com/image/fetch/$s_!2hV0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bad6e02-74aa-4b7e-80fa-672dcd82682c_3036x1610.png 1272w, https://substackcdn.com/image/fetch/$s_!2hV0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bad6e02-74aa-4b7e-80fa-672dcd82682c_3036x1610.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Downloading the &#8220;okresy.json&#8221; file from the siwekm/czech-geojson repository</figcaption></figure></div><p>Download the <em>okresy.json</em> file, which contains the geographic boundaries of Czech districts (<em>okresy</em> in Czech).</p><p><strong>Note</strong>: Although the <em><a href="https://en.wikipedia.org/wiki/Districts_of_the_Czech_Republic">okresy</a></em><a href="https://en.wikipedia.org/wiki/Districts_of_the_Czech_Republic"> administrative system was officially abolished in 2003</a>, these districts are still used for statistical analysis. This makes them a good choice for visualizing the distribution of job opportunities across the country.</p><p>Once downloaded, add the <em>okresy.json</em> file to your Python project. At this point, your project structure should look similar to this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">&#9500;&#9472;&#9472; jobs.json
&#9500;&#9472;&#9472; okresy.json
&#9492;&#9472;&#9472; main.py</code></pre></div><h3>Step #5: Visualize the Job Data on a Map</h3><p>In your Python file, define the complete pipeline with the following code:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># pip install pandas geopandas geopy matplotlib

import json
from geopy.geocoders import Nominatim
from geopy.extra.rate_limiter import RateLimiter
import pandas as pd
import geopandas as gpd
import matplotlib.pyplot as plt

# Load the scraped Indeed job openings
with open("jobs.json", "r", encoding="utf-8") as f:
    jobs = json.load(f)

# Load the list of addresses from the job postings
addresses = []
for job in jobs:
    loc = job.get("location", {})

    # Prefer fullAddress, fallback to formattedAddressLong or formattedAddressShort
    address = (
        loc.get("fullAddress")
        or loc.get("formattedAddressLong")
        or loc.get("formattedAddressShort")
    )

    # Filter out full-remote jobs with no address
    if address and address.strip().lower() not in ["home office", "remote"]:
        addresses.append(address)

addresses = list(addresses)

# Initialize geocoder for Czech job locations
geolocator = Nominatim(user_agent="cz_job_map")
geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1)

# Geocode all job addresses into latitude/longitude points
rows = []
for address in addresses:
    loc = geocode(f"{address}, Czech Republic")

    # Keep only successfully geocoded results
    if loc:
        rows.append({
            "address": address,
            "lat": loc.latitude,
            "lon": loc.longitude
        })

# Convert geocoded results into a DataFrame
df = pd.DataFrame(rows)

# Convert DataFrame into GeoDataFrame with point geometries
points = gpd.GeoDataFrame(
    df,
    geometry=gpd.points_from_xy(df.lon, df.lat),
    crs="EPSG:4326"
)

# Load Czech administrative districts (okresy) from a local GeoJSON file
okresy = gpd.read_file("okresy.json")

# Align coordinate reference systems between datasets
if okresy.crs != points.crs:
    okresy = okresy.to_crs(points.crs)

# Assign each job point to its corresponding district
joined = gpd.sjoin(
    points,
    okresy,
    predicate="within",
    how="left"
)

# Count number of jobs per district
counts = (
    joined["id"]
    .value_counts()
    .reset_index()
)

counts.columns = ["id", "count"]


# Merge job counts back into district geometries
okresy = okresy.merge(
    counts,
    on="id",
    how="left"
)

# Replace missing values with 0 (districts with no jobs)
okresy["count"] = okresy["count"].fillna(0)

# Plot choropleth map with job counts per district
fig, ax = plt.subplots(figsize=(10, 8))

okresy.plot(
    column="count",
    cmap="Reds",
    edgecolor="black",
    linewidth=0.3,
    legend=True,
    vmin=0,
    ax=ax
)

# Add labels for districts with job activity
top = okresy[okresy["count"] &gt; 0].sort_values("count", ascending=False)

for idx, row in top.iterrows():
    centroid = row.geometry.centroid

    ax.text(
        centroid.x,
        centroid.y,
        str(int(row["count"])),
        fontsize=9,
        ha="center",
        va="center",
        color=(lambda v: "white" if v &gt; okresy["count"].max() * 0.5 else "black")(row["count"]),
        fontweight="bold"
    )

ax.set_title(
    "Software engineering job density on Indeed in the Czech Republic (last 14 days, by district)"
)
ax.axis("off")

plt.show() </code></pre></div><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/p/consulting&quot;,&quot;text&quot;:&quot;Need help with your scraping project?&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://substack.thewebscraping.club/p/consulting"><span>Need help with your scraping project?</span></a></p><div><hr></div><p>That&#8217;s what the above script does:</p><ol><li><p>Loads scraped job data from <em>jobs.json</em> and extracts valid physical addresses, filtering out remote or missing location entries.</p></li><li><p>Initializes the <em><a href="https://nominatim.org/">Nominatim</a></em><a href="https://nominatim.org/"> geocoder</a> with a rate limiter to safely convert addresses into latitude and longitude without exceeding request limits.</p></li><li><p>Geocodes each address into coordinates and keeps only successful results, storing them in a structured pandas <em>DataFrame</em>.</p></li><li><p>Converts the <em>DataFrame</em> into a <em><a href="https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoDataFrame.html">GeoDataFrame</a></em> with point geometries so the job data can be used in spatial analysis workflows.</p></li><li><p>Loads Czech district boundaries from the <em>okresy.json</em> file and ensures both datasets use the same coordinate reference system.</p></li><li><p>Performs a spatial join to assign each job point to a district, then aggregates job counts per district and merges results back into the map.</p></li><li><p>Visualizes the final dataset as a choropleth map, coloring districts by job density and adding readable labels for high-activity areas.</p></li></ol><h3>Step #6: Run the Script</h3><p>Execute the script, and you&#8217;ll get a result like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_TvK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F931a517a-9779-46bb-92b0-239a1c0172a4_2462x1414.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_TvK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F931a517a-9779-46bb-92b0-239a1c0172a4_2462x1414.png 424w, https://substackcdn.com/image/fetch/$s_!_TvK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F931a517a-9779-46bb-92b0-239a1c0172a4_2462x1414.png 848w, https://substackcdn.com/image/fetch/$s_!_TvK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F931a517a-9779-46bb-92b0-239a1c0172a4_2462x1414.png 1272w, https://substackcdn.com/image/fetch/$s_!_TvK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F931a517a-9779-46bb-92b0-239a1c0172a4_2462x1414.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_TvK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F931a517a-9779-46bb-92b0-239a1c0172a4_2462x1414.png" width="1456" height="836" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/931a517a-9779-46bb-92b0-239a1c0172a4_2462x1414.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:836,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The map produced by the script&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The map produced by the script" title="The map produced by the script" srcset="https://substackcdn.com/image/fetch/$s_!_TvK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F931a517a-9779-46bb-92b0-239a1c0172a4_2462x1414.png 424w, https://substackcdn.com/image/fetch/$s_!_TvK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F931a517a-9779-46bb-92b0-239a1c0172a4_2462x1414.png 848w, https://substackcdn.com/image/fetch/$s_!_TvK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F931a517a-9779-46bb-92b0-239a1c0172a4_2462x1414.png 1272w, https://substackcdn.com/image/fetch/$s_!_TvK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F931a517a-9779-46bb-92b0-239a1c0172a4_2462x1414.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The map produced by the script</figcaption></figure></div><p>Notice how most fresh software engineering openings are concentrated in districts surrounding Prague, Brno, and Ostrava. These are the three largest cities in the Czech Republic, so the result clearly makes sense!</p><p>Now, this was just a simple example, but you can use the same approach to build an interactive map or add additional features, such as calculating the average salary for each position by district, and more advanced analytics.</p><h2>Value and Playbook for Other Governments</h2><p>The approach presented in this blog post is highly adaptable to any public dataset with a spatial dimension, where location plays a key role in understanding patterns and inequalities. Still, a reusable country-independent playbook emerges:</p><ol><li><p>Collect data via web scraping (or other administrative, public sources).</p></li><li><p>Clean and validate the data.</p></li><li><p>Enrich and aggregate it with contextual and geographic features.</p></li><li><p>Apply statistical or analytical models.</p></li><li><p>Present the results through maps, dashboards, or other interactive tools.</p></li></ol><p>As you can tell, this pipeline isn&#8217;t domain-specific and can be reused across many different policy areas. Finally, it&#8217;s crucial that the adopted methodology remains open and clearly explained to the public. This fosters transparency, reproducibility, and trust in data-driven decision-making.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Web Scraping Club is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>Conclusion</h2><p>Here, I&#8217;ve shown how the Czech Ministry of Finance uses a web scraping service as part of a full data pipeline to turn rental listings into actionable insights for housing policy and public tools.</p><p>By accessing data collected from multiple real estate portals and processing it through cleaning, geolocation, and statistical modelling, they transform raw web data into interactive maps and rent calculators that support decision-making.</p><p>In this article, I built a similar pipeline for a different use case: a job market choropleth across Czech districts. I started from scraped Indeed listings, geocoded job locations, mapped them to districts, and visualized the results on a map.</p><p>I hope this example was useful and inspiring. If you have questions or ideas, feel free to share them in the comments below.</p><div><hr></div><p><em>Did you like this article? Share it with someone who might find it useful and get a discount on paid plans.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/p/public-sector-web-scraping-real-estate?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://substack.thewebscraping.club/p/public-sector-web-scraping-real-estate?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[THE LAB #108: How LLMs and Manus AI are transforming bot detection]]></title><description><![CDATA[AI is making faster and cheaper to deobfuscate code. This changes how antibot solutions should be built.]]></description><link>https://substack.thewebscraping.club/p/the-lab-108-how-llms-and-manus-ai</link><guid isPermaLink="false">https://substack.thewebscraping.club/p/the-lab-108-how-llms-and-manus-ai</guid><dc:creator><![CDATA[Pierluigi Vinciguerra]]></dc:creator><pubDate>Fri, 26 Jun 2026 09:33:17 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/8a36775a-3d27-4850-9f9e-39d54e5b8d7e_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>On June 12, 2026, the US government ordered Anthropic to cut off public access to its two strongest models, Claude Fable 5 and Claude Mythos 5. <a href="https://techcrunch.com/2026/06/12/anthropics-safety-warnings-may-have-just-backfired-the-government-has-pulled-the-plug-on-its-most-powerful-ai/"><span>TechCrunch</span></a><span>&nbsp;broke the order, and&nbsp;</span><a href="https://time.com/article/2026/06/13/anthropic-fable-mythos-ban-US-security/"><span>Time</span></a><span>&nbsp;confirmed the company complied worldwide within a day.</span> Fable 5 had shipped three days earlier as the &#8220;safe to use&#8221; and generally available model. Mythos 5 was the more capable sibling, never opened to everyone. Anthropic&#8217;s own account is that someone found a jailbreak around Fable&#8217;s safety layer, the part built to block answers in cybersecurity and biology, and the government decided the risk of leaving it running was too high. <br>Not everyone agrees with this decision: <a href="https://www.scientificamerican.com/article/us-limits-on-anthropic-fable-ai-could-hurt-cybersecurity/">Scientific American</a> argued the restriction could end up hurting defenders more than attackers, but the fact is that, up to today, nothing has changed. </p><blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gCo7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gCo7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png" width="626" height="313" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:626,&quot;bytes&quot;:390972,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/197764148?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gCo7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Before proceeding, let me thank NetNut, the platinum partner of the month. Their set of solutions cover all your needs for scraping.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack&quot;,&quot;text&quot;:&quot;Visit Netnut&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack"><span>Visit Netnut</span></a></p></blockquote><blockquote><div><hr></div></blockquote><p>You can read that episode two ways. Either the guardrails were the only thing standing between a public model and a stream of attack-ready output, or the shutdown was a tidier story than the truth. We are not going to settle that here. For our corner of the world, the lesson is simpler. A capable model, stripped of guardrails or jailbroken around them, automates the expensive part of an attacker&#8217;s job. Deobfuscation, code analysis, and figuring out how an unfamiliar system behaves under pressure. Those tasks have always gated the scraper-versus-vendor arms race because they cost human reverse-engineering hours, and that gate is now moving.</p><p>The cat-and-mouse game we cover every week runs on a hidden economy. A vendor ships an obfuscated sensor. Someone on the scraping side spends days or weeks reading minified JavaScript, renaming variables, and rebuilding the logic until the parameters make sense. The cost of that labor is what keeps most protections standing. If a model can do a real share of it on its own, the math behind the whole arms race changes.</p><h2>Manus AI, and why not Claude</h2><p><a href="https://manus.im/invitation/BRAWNVUL1U09EW?utm_source=invitation&amp;utm_medium=social&amp;utm_campaign=copy_link">Manus</a> is not a chat assistant. It is an agent that pairs a language model with a sandboxed virtual machine, and inside that machine, it runs code, fetches pages, beautifies scripts, and iterates on its own output. Meta acquired it at the end of December 2025 for around two billion dollars, though by June 2026, Chinese regulators were already forcing parts of that deal to unwind. What that architecture buys is verification. Manus not only describes what a script might do. It runs the script and checks.</p><p>That difference shows up the moment you set guardrails next to it. Ask Claude or ChatGPT to deobfuscate a live anti-bot sensor and explain which signals feed its scoring, and you tend to hit a wall. Those products keep tight cybersecurity guardrails, and the Fable shutdown is the clearest sign yet of how hard that line is enforced. We handed the same class of task to Manus, and it got to work. We have looked at agentic browsers and AI scraping assistants before, but those were aids for a human operator who still did the thinking. Here, the model does the reverse engineering.</p><div><hr></div><blockquote><p><strong><a href="https://byteful.com?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE">Start your scraping journey with Byteful</a></strong>: 10GB New Customer Trial | Use TWSC for 15% OFF | $1.75/GB Residential Data | ISP Proxies in 15+ Countries</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://byteful.com/?promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EOo9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 424w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 848w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1272w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EOo9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png" width="630" height="69.35779816513761" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:120,&quot;width&quot;:1090,&quot;resizeWidth&quot;:630,&quot;bytes&quot;:133037,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://byteful.com/?promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/194398731?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!EOo9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 424w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 848w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1272w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://byteful.com?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE&quot;,&quot;text&quot;:&quot;Claim your 10GB here&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://byteful.com?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE"><span>Claim your 10GB here</span></a></p></blockquote><div><hr></div><h2>Mapping Akamai on Net-a-Porter</h2><p>We have written about Akamai more than any other anti-bot system. For the bypass-oriented background, <a href="https://substack.thewebscraping.club/p/the-lab-30-how-to-bypass-akamai-protected">THE LAB #30</a>, <a href="https://substack.thewebscraping.club/p/bypass-akamai-bot-protection">THE LAB #85</a>, and <a href="https://substack.thewebscraping.club/p/hybrid-scraping-camoufox-curl-cffi">THE LAB #100</a> cover the recovery-when-nothing-works case, proxy chaining, and the browser-login-plus-HTTP hybrid. We will not repeat any of that.</p><p>What we wanted this time was not a bypass. It was a map. Akamai Bot Manager protects net-a-porter.com, and it defends in stacked layers that each fail differently:</p><ul><li><p>A TLS fingerprint check at the edge, evaluated before a single line of the client JavaScript runs.</p></li><li><p>A <code>sec-cpt</code> cryptographic challenge, whose state lives in the <code>bm_sc</code> cookie.</p></li><li><p>A behavioral sensor that serializes a binary telemetry payload and posts it to a collect endpoint, validating the <code>_abck</code> cookie.</p></li><li><p>A client-side anti-hook layer that checks the browser environment for tampering and reports integrity separately.</p></li></ul><p>The model we asked Manus to build covers the client sensor and the challenge flow, plus whatever it could observe about the TLS gate from the outside. A working end-to-end bypass was out of scope. We are not shipping code with this article, and we did not ask Manus to produce a runnable solver, even though our tests suggest we are really close to a working solution. What we&#8217;ll share here is a summary of the analysis Manus did; <a href="https://github.com/TheWebScrapingClub/thelab">in the code repository</a>, you will find the full details.</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.youtube.com/@thewebscrapingclub&quot;,&quot;text&quot;:&quot;Check the TWSC YouTube Channel&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.youtube.com/@thewebscrapingclub"><span>Check the TWSC YouTube Channel</span></a></p><div><hr></div><h2>What we handed the agent</h2><p>The task was deliberately cold. We did not give Manus our own Akamai notes, our wiki, or any of the prior Lab articles. We pointed it at net-a-porter.com and asked it to study how Akamai's protection works, deobfuscate the client script, and explain which parameters feed into the sensor. Nothing more.</p><p>We chose this target for three reasons. It runs a real, current Akamai deployment on a high-value luxury store. The sensor was observable live, so the agent could fetch and instrument the actual script rather than reason about a stale sample. And we already understand Akamai well enough internally to judge whether the output was accurate or confident nonsense.</p><p>The reason the task went to Manus rather than a chat assistant is the guardrail gap from the previous section. Deobfuscating a production anti-bot sensor and enumerating the signals it scores is exactly the kind of request the heavily guarded models decline. Manus took it, ran the analysis in its VM, and came back with the two documents below.</p><h2>What came back</h2><p>The output was more detailed than we expected from a single task run. We are sharing it in two parts and deliberately not sharing it all.</p><blockquote><p>The two documents below are Manus AI&#8217;s output. We reproduce them as reportage, to show what an agent derived on its own. We have not independently re-run every claim, re-derived every value, or confirmed that each detail still holds against the live site. Treat the specifics as the agent&#8217;s findings, not as TWSC-verified ground truth. The sensor version Manus reports, <code>746702557</code>, was compiled on 2025-05-09, so some details may already have rotated. Where the agent surfaced operational specifics that amount to a turnkey recipe (exact module filenames, the full endpoint table, the CSS selector ids, the challenge request names), we have redacted them.</p></blockquote><p>This is a schema of how Akamai Bot Protection according to Manus AI</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!w8Cc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61291bf5-14fa-4123-8412-d0cae6828096_3120x3344.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!w8Cc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61291bf5-14fa-4123-8412-d0cae6828096_3120x3344.png 424w, https://substackcdn.com/image/fetch/$s_!w8Cc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61291bf5-14fa-4123-8412-d0cae6828096_3120x3344.png 848w, https://substackcdn.com/image/fetch/$s_!w8Cc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61291bf5-14fa-4123-8412-d0cae6828096_3120x3344.png 1272w, https://substackcdn.com/image/fetch/$s_!w8Cc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61291bf5-14fa-4123-8412-d0cae6828096_3120x3344.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!w8Cc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61291bf5-14fa-4123-8412-d0cae6828096_3120x3344.png" width="1456" height="1561" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/61291bf5-14fa-4123-8412-d0cae6828096_3120x3344.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1561,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:436306,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/203671348?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61291bf5-14fa-4123-8412-d0cae6828096_3120x3344.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!w8Cc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61291bf5-14fa-4123-8412-d0cae6828096_3120x3344.png 424w, https://substackcdn.com/image/fetch/$s_!w8Cc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61291bf5-14fa-4123-8412-d0cae6828096_3120x3344.png 848w, https://substackcdn.com/image/fetch/$s_!w8Cc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61291bf5-14fa-4123-8412-d0cae6828096_3120x3344.png 1272w, https://substackcdn.com/image/fetch/$s_!w8Cc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61291bf5-14fa-4123-8412-d0cae6828096_3120x3344.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h3>Part one, reproduced in full, the anti-hooking and TLS summary</h3><p>This first document is the safe exhibit. It explains how Akamai detects a tampered browser and how the TLS gate at the edge decides who even gets a challenge. We reproduce it verbatim.</p><div class="callout-block" data-callout="true"><h4>Akamai Bot Manager: Anti-Hooking and TLS Fingerprinting Summary</h4><p><strong>Target:</strong> <code>www.net-a-porter.com</code><br><strong>Sensor Version:</strong> 746702557<br><strong>Author:</strong> Manus AI</p><p>This document isolates and summarizes two of the most critical defensive layers in Akamai Bot Manager (BM): its client-side anti-hooking mechanisms and its edge-level TLS fingerprinting. These two systems work in tandem to defeat headless browsers, automated scraping frameworks, and reverse-engineering attempts.</p><h5>1. Anti-Hooking Mechanisms</h5><p>Akamai&#8217;s client-side script goes to extraordinary lengths to ensure the browser environment is pristine. The anti-hooking logic is primarily housed in the dynamically loaded <code>h</code> module.</p><p><strong>1.1 Native Function Verification.</strong> Scraping frameworks often override native browser APIs (such as <code>navigator.webdriver</code> or <code>HTMLCanvasElement.toBlob</code>) to spoof a legitimate environment. Akamai detects these overrides using several techniques. It uses <code>Function.prototype.toString</code> inspection to check whether native functions still return the expected <code>[native code]</code> string, and it detects whether the <code>toString</code> method itself has been hooked or proxied. It uses <code>Object.getOwnPropertyDescriptor</code> to inspect the getters and setters of critical APIs, and a property that should be read-only but carries a custom getter is flagged as tampered. It also checks for JavaScript <code>Proxy</code> objects wrapping standard DOM APIs, a common technique in tools like Puppeteer Stealth.</p><p><strong>1.2 The &#8220;Clean iframe&#8221; technique.</strong> To bypass hooks placed on the main <code>window</code> object by automation tools, the scheduler class dynamically injects a hidden iframe into the DOM:</p><pre><code><code>&lt;iframe height="0" width="0" style="display: none; visibility: hidden;" sc="true" tabindex="-1"&gt;&lt;/iframe&gt;</code></code></pre><p>Once injected, Akamai extracts pristine, unmodified API references directly from <code>iframe.contentWindow</code>. This lets the script call the original native functions even if the main <code>window</code> object has been heavily spoofed.</p><p><strong>1.3 The anti-hook verification payload (</strong><code>/ah</code><strong>).</strong> The anti-hook module computes a cryptographic hash representing the integrity of the environment. This check runs as a &#8220;Type 3&#8221; callback in the scheduler, so it executes before the main telemetry payload is serialized. The result is sent to the <code>/ah</code> endpoint. If the environment is clean, the URL parameter <code>ah=0</code> is transmitted. If verification is pending or failed, it transmits <code>ah=1</code>.</p><h5>2. TLS Fingerprinting and Edge Escalation</h5><p>While the anti-hook mechanisms operate in the browser, Akamai&#8217;s most formidable defense occurs at the edge server level via TLS fingerprinting.</p><p><strong>2.1 The TLS barrier.</strong> Before the client-side JavaScript even executes, the Akamai edge server evaluates the incoming connection&#8217;s TLS fingerprint (JA3/JA4 hashes, cipher suites, ALPN extensions). Empirical testing on net-a-porter.com revealed a strict dichotomy. Consumer TLS, for example Safari emulated via <code>curl_cffi</code>, gets a <code>200 OK</code> response carrying the <code>sec-cpt</code> cryptographic challenge. This is a soft block that lets legitimate browsers prove their authenticity. Headless or automation TLS, for example standard Node.js or Puppeteer Chromium, gets an immediate <code>403 Access Denied</code> that bypasses the challenge flow entirely.</p><p><strong>2.2 The escalation trap.</strong> The most critical finding about Akamai&#8217;s architecture is how TLS fingerprinting intersects with the client-side behavioral telemetry behind the <code>_abck</code> cookie. A request with a valid TLS fingerprint receives the <code>sec-cpt</code> challenge, and solving it updates the <code>bm_sc</code> cookie by appending <code>~0~0~0</code>. At the same time, the browser must send a rich, roughly 2.2KB binary payload to the <code>/collect</code> endpoint to validate the <code>_abck</code> cookie. If an automated tool solves the <code>sec-cpt</code> challenge but fails to provide a valid behavioral payload, or sends it over a connection with a mismatched TLS fingerprint, the edge server detects the anomaly.</p><p>The result is that even if a real Chromium browser driven by Puppeteer Stealth generates a mathematically perfect 2,199-byte behavioral payload and solves the challenge, Akamai still refuses to validate the <code>_abck</code> cookie, because it detects the Puppeteer TLS fingerprint at the edge. The server then escalates and returns a permanent <code>403 Access Denied</code> for that session.</p><p><strong>2.3 Summary.</strong> Akamai requires perfect synchronization of a trusted network-level TLS fingerprint and pristine browser-level behavioral data. Spoofing the browser environment, which trips the anti-hook defenses, or using a non-standard TLS stack, guarantees session termination.</p></div><h3>Part two, reproduced with redactions, the deep technical analysis</h3><p>The second document goes much further. It walks the bootstrapping sequence, the module system, the fingerprinting surface, and the telemetry transport. This is the part that starts to read like a blueprint, so we keep the architecture and the reasoning and we strip the operational specifics. Redaction markers below are ours, not Manus&#8217;s.</p><p>If you want to see the full analysis, you can find it&nbsp;<a href="https://github.com/TheWebScrapingClub/thelab">in our GitHub repository reserved for paying users, inside the folder&nbsp;</a><strong><a href="https://github.com/TheWebScrapingClub/thelab">108.</a></strong><a href="https://github.com/TheWebScrapingClub/thelab">AKAMAI-MANUS</a><strong><a href="https://github.com/TheWebScrapingClub/thelab">.</a></strong></p>
      <p>
          <a href="https://substack.thewebscraping.club/p/the-lab-108-how-llms-and-manus-ai">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Crawl of Fame - June 2026]]></title><description><![CDATA[What's an ISP proxy, how they get sourced, and how to spawn browsers for cheap]]></description><link>https://substack.thewebscraping.club/p/crawl-of-fame-june</link><guid isPermaLink="false">https://substack.thewebscraping.club/p/crawl-of-fame-june</guid><dc:creator><![CDATA[Pierluigi Vinciguerra]]></dc:creator><pubDate>Tue, 23 Jun 2026 20:58:32 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/3675f58f-7dd6-4877-9004-b1662fb09f73_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Episode #0 of this new series of posts, where I share with you the most interesting content out there I&#8217;ve read about web scraping and all the things related.</p><div><hr></div><blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gCo7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png" width="626" height="313" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:626,&quot;bytes&quot;:390972,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack&quot;,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/197764148?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!gCo7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Before proceeding, let me thank NetNut, the platinum partner of the month. Their set of solutions cover all your needs for scraping.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack&quot;,&quot;text&quot;:&quot;Visit Netnut&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack"><span>Visit Netnut</span></a></p></blockquote><div><hr></div><h2>IP sourcing in the proxy industry</h2><p>Two articles written by Spur Intelligence caught my attention in the past week.<br>It started with a webinar I&#8217;ve attended, where they explained what ISP proxies are and how proxy companies get those IPs.</p><div id="youtube2-MQ1zpnlMMUc" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;MQ1zpnlMMUc&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/MQ1zpnlMMUc?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>The analysis is great, and I&#8217;ve understood better how the proxy industry works.  <br>They also mentioned how NetNut gets high-quality ISP IP addresses, and described that in detail in this article.</p><h3>How Proxy Providers Co-opt Entire Networks</h3><p>Spur reverse-engineers how Netnut sources its ISP proxies through a partner called DiviNetworks, which installs GRE tunnels and policy-based routing directly on the border routers of real ISPs. The result is genuine ISP IP space sold as proxy inventory, with actual subscribers still living on the same addresses. The piece is worth reading for the detection detail: outbound connections sit in the TCP source port range 40,000 to 40,200, partner routers expose an <code>rtr-&lt;isp&gt;.divinetworks.com</code> passive-DNS pattern, and the Howard University case shows an entire /16 (AS919, around 17,000 exit IPs) co-opted at the network edge. They even quote DiviNetworks&#8217; own figure of $13,208 a month for a US /16.<br><a href="https://spur.us/blog/how-proxy-providers-co-opt-entire-networks">Full article here</a></p><h3>Smart TV Apps and Residential Proxy SDKs</h3><p>A more recent article, instead, highlighted how proxy companies get residential proxies. No surprises, at least on my side, in reading that Smart TVs are used as exit nodes for proxies. In fact, just like with mobile apps, developers can use SDKs from proxy providers to monetize their app installations. In some cases, it&#8217;s quite clear from the consent screen what&#8217;s happening under the hood. </p><p>All the industries that rely on web-scraped data (AI in primis, but not only) need residential proxies, so proxy providers, who are the gatekeepers for these tools, should be very careful about onboarding users with legitimate use cases and keeping fraudsters away. In my career, I&#8217;ve worked with almost every big name out there, and I&#8217;ve found this is the case. As scraping professionals, we should always remember that we&#8217;re guests (sometimes unwanted) on both the target website and the proxy infrastructure, so we should be as respectful as possible and not be driven by greed to collect data.<br><a href="https://spur.us/blog/smart-tv-apps-residential-proxy-sdks">Full article here</a></p><h2>Scraping Infrastructure</h2><p>A great article by the engineering team at Browser Use shows how they&#8217;re spawning browsers cheaply and quickly.</p><h3>How We Made Cloud Browsers 3x Cheaper and Faster<span> </span></h3><p><span>Browser Use walks through rebuilding its cloud so every session is its own Firecracker microVM, and the interesting twist is that they run Firecracker on plain EC2 instead of bare metal, accepting nested virtualization to get faster scale-up and lower cost. </span></p><p><span>The numbers are great: $0.02 per browser hour down from $0.06, VM cold start under 400ms, and create latency of 825ms at p50 across a 10,000-session test. </span></p><p><span>The engineering details that are making the difference are multiple, from mapping memory in 2MB pages plus a </span><code>userfaultfd</code><span> handler that preloads hot pages (resume-to-ready drops from 9.8s to 3.1s, roughly 91x fewer page-fault stalls) to two-phase vCPU pinning that took a 1,000-browser launch from 17% failed sessions to zero. They also make the case for running fully headless rather than headful, since their low-level Chromium fork pushes block-avoidance to 81% on their own benchmark.</span></p><p><a href="https://browser-use.com/posts/firecracker-browser-infra"><span>Full article here</span></a></p><div><hr></div><h2>Read and share more of this</h2><p>Want to flag and discover more articles like the ones above? <a href="https://news.thewebscraping.club/">Scraping News</a> (still in beta) is where the community surfaces them. <strong>Sign up</strong> and submit what you find worth reading.</p><p>Want a concept map of everything covered here, cross-referenced with our own work and other sources worth your time? </p><p><a href="https://github.com/TheWebScrapingClub/scraping-wiki/tree/main">The Web Scraping Club Wiki lives on GitHub</a>, and <a href="https://publish.obsidian.md/twsc-public/Web+Scraping/Wiki/README">as an interactive site</a>. It is plain Markdown, so you can also clone it into your own Obsidian vault and read it locally.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Web Scraping Club is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[How to Build Competitive Intelligence Scrapers That Don't Lie to You]]></title><description><![CDATA[Learn how to build scraping pipelines that are still giving you accurate data 12 months from now]]></description><link>https://substack.thewebscraping.club/p/how-to-build-competitive-intelligence-scraping</link><guid isPermaLink="false">https://substack.thewebscraping.club/p/how-to-build-competitive-intelligence-scraping</guid><dc:creator><![CDATA[Federico Trotta]]></dc:creator><pubDate>Sun, 21 Jun 2026 03:00:42 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/0463db2a-5f58-4cc1-a9de-dd0875d149bb_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you&#8217;ve built a competitive intelligence scraper before, you know the feeling&#8230;</p><p>You set it up, it works, and you forget about it. Three months later, someone asks: <em>&#8220;Hey, did our competitor change their pricing last week?&#8221;</em></p><p>You check the pipeline dashboard, and everything looks fine: all green, no errors, last run successful some hours ago. Then you dig deeper and realize your scraper has been returning empty strings for weeks. The competitor did change their pricing, and you don&#8217;t know when this happened or what was on their website before.</p><p>In this article, you&#8217;ll learn how to build competitive scraping pipelines that make failure structurally (almost) impossible. Let&#8217;s get into it!</p><blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gCo7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png" width="626" height="313" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:626,&quot;bytes&quot;:390972,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack&quot;,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/197764148?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!gCo7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Before proceeding, let me thank NetNut, the platinum partner of the month. Their set of solutions cover all your needs for scraping.<br></em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack&quot;,&quot;text&quot;:&quot;Visit Netnut&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack"><span>Visit Netnut</span></a></p></blockquote><blockquote><div><hr></div></blockquote><h2>Why Competitive Intelligence Scrapers Break</h2><p>Let&#8217;s start by understanding the most common failure modes that competitive scraping pipelines have:</p><ul><li><p><strong>Structural drift</strong>: This is probably the most common. A competitor renames a CSS class, and your scraper starts returning empty strings. Your database fills with nulls, and nobody notices for weeks because the pipeline is still running.</p></li><li><p><strong>Soft blocks</strong>: These are nastier. You get valid HTTP responses, but the content is a CAPTCHA page or a bot-detection redirect. Your parser sees it as valid HTML, but it finds no data. So, it stores nothing, or, worse, it stores the CAPTCHA page&#8217;s text as if it were real data.</p></li><li><p><strong>Schema rot</strong>: This happens when a competitor evolves their product, but your data model doesn&#8217;t. They add a new pricing tier or split one pricing plan into three. Your scraper extracts what it can and drops the rest. Your competitive analysis is now based on an incomplete picture of their offering.</p></li><li><p><strong>Timezone and locale traps</strong>: This probably needs more attention than the others. The same competitor page can return different prices, currencies, or date formats depending on where the request originates. If your scraper runs from a US server but your competitor detects it and serves EU pricing, you&#8217;re tracking the wrong numbers.</p></li></ul><p>What makes these failures dangerous is that you caused them yourself, unintentionally. Every defensive pattern you added to prevent crashes is exactly what turns a broken scraper into a quietly running one. No exceptions, no alerts, no red dashboards. Just weeks of nulls that nobody notices.</p><p>The rest of this article is about making that invisible failure visible again. Here&#8217;s a visual schema to get you introduced to the solutions proposed below:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!niSq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1909562b-4730-4696-bce2-496f109727e7_2366x1826.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!niSq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1909562b-4730-4696-bce2-496f109727e7_2366x1826.png 424w, https://substackcdn.com/image/fetch/$s_!niSq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1909562b-4730-4696-bce2-496f109727e7_2366x1826.png 848w, https://substackcdn.com/image/fetch/$s_!niSq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1909562b-4730-4696-bce2-496f109727e7_2366x1826.png 1272w, https://substackcdn.com/image/fetch/$s_!niSq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1909562b-4730-4696-bce2-496f109727e7_2366x1826.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!niSq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1909562b-4730-4696-bce2-496f109727e7_2366x1826.png" width="1456" height="1124" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1909562b-4730-4696-bce2-496f109727e7_2366x1826.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1124,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:231842,&quot;alt&quot;:&quot;The schema for creating competitive scraping pipelines that are resilient through time by Federico Trotta&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/196881482?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1909562b-4730-4696-bce2-496f109727e7_2366x1826.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The schema for creating competitive scraping pipelines that are resilient through time by Federico Trotta" title="The schema for creating competitive scraping pipelines that are resilient through time by Federico Trotta" srcset="https://substackcdn.com/image/fetch/$s_!niSq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1909562b-4730-4696-bce2-496f109727e7_2366x1826.png 424w, https://substackcdn.com/image/fetch/$s_!niSq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1909562b-4730-4696-bce2-496f109727e7_2366x1826.png 848w, https://substackcdn.com/image/fetch/$s_!niSq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1909562b-4730-4696-bce2-496f109727e7_2366x1826.png 1272w, https://substackcdn.com/image/fetch/$s_!niSq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1909562b-4730-4696-bce2-496f109727e7_2366x1826.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The schema for creating competitive scraping pipelines that are resilient through time</figcaption></figure></div><div><hr></div><blockquote><p><em>For your scraping needs, having a reliable proxy provider like <strong>Decodo</strong> on your side improves the chances of success. </em></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qJZE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qJZE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png" width="401" height="225.5625" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fec072bc-2ac2-49bf-8084-43858298808f_800x450.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:450,&quot;width&quot;:800,&quot;resizeWidth&quot;:401,&quot;bytes&quot;:554497,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/187315628?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!qJZE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption"></figcaption></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://visit.decodo.com/WyQ3mA&quot;,&quot;text&quot;:&quot;Try Decodo Now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://visit.decodo.com/WyQ3mA"><span>Try Decodo Now</span></a></p></blockquote><div><hr></div><h2>The Monitoring Layer: Your First Line of Defense</h2><p>The first thing you should do to avoid such failures is to implement a monitoring layer. This is the part that the majority of scraping engineers skip. Because, you know how things go: the business needed the data yesterday, and you can&#8217;t lose time with monitoring&#8230;</p><p>But if you want your competitive scrapers to be resilient over time, you need to implement a monitoring system. A good one is made on top of four parts: output validation, structural fingerprinting, data freshness checks, and canary fields. Let&#8217;s discuss them!</p><h3>How to Validate What Your Scraper Extracted</h3><p>Managing HTTP status codes in your scrapers is not enough. You need to assert that the data you extracted is actually meaningful. The following example is taken from <a href="https://stripe.com/pricing">Stripe&#8217;s pricing</a> page:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!p3X2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93e642a5-caa7-49ba-8ecd-adca16dcf582_1162x400.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!p3X2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93e642a5-caa7-49ba-8ecd-adca16dcf582_1162x400.png 424w, https://substackcdn.com/image/fetch/$s_!p3X2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93e642a5-caa7-49ba-8ecd-adca16dcf582_1162x400.png 848w, https://substackcdn.com/image/fetch/$s_!p3X2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93e642a5-caa7-49ba-8ecd-adca16dcf582_1162x400.png 1272w, https://substackcdn.com/image/fetch/$s_!p3X2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93e642a5-caa7-49ba-8ecd-adca16dcf582_1162x400.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!p3X2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93e642a5-caa7-49ba-8ecd-adca16dcf582_1162x400.png" width="1162" height="400" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/93e642a5-caa7-49ba-8ecd-adca16dcf582_1162x400.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:400,&quot;width&quot;:1162,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:176549,&quot;alt&quot;:&quot;The Stripe&#8217;s pricing page your scraper &#8220;sees&#8221; from the EU by Federico Trotta&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/196881482?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93e642a5-caa7-49ba-8ecd-adca16dcf582_1162x400.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The Stripe&#8217;s pricing page your scraper &#8220;sees&#8221; from the EU by Federico Trotta" title="The Stripe&#8217;s pricing page your scraper &#8220;sees&#8221; from the EU by Federico Trotta" srcset="https://substackcdn.com/image/fetch/$s_!p3X2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93e642a5-caa7-49ba-8ecd-adca16dcf582_1162x400.png 424w, https://substackcdn.com/image/fetch/$s_!p3X2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93e642a5-caa7-49ba-8ecd-adca16dcf582_1162x400.png 848w, https://substackcdn.com/image/fetch/$s_!p3X2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93e642a5-caa7-49ba-8ecd-adca16dcf582_1162x400.png 1272w, https://substackcdn.com/image/fetch/$s_!p3X2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93e642a5-caa7-49ba-8ecd-adca16dcf582_1162x400.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The Stripe&#8217;s pricing page your scraper &#8220;sees&#8221; from the EU</figcaption></figure></div><p>Here&#8217;s a practical validator using Pydantic that you can implement:</p><pre><code><code># In an activated virtual environment run: pip install httpx, bs4, pydantic

import httpx 
from bs4 import BeautifulSoup
from pydantic import BaseModel, field_validator, ValidationError
from typing import Optional
import re

# Define Pydantic model
class CompetitorPricingData(BaseModel):
    plan_name: str
    monthly_price: float
    currency: str
    features: list[str]

    @field_validator("monthly_price")
    @classmethod
    def price_must_be_positive(cls, v):
        if v &lt;= 0:
            raise ValueError(f"Price must be positive, got {v}")
        return v

    @field_validator("currency")
    @classmethod
    def currency_must_be_valid(cls, v):
        valid_currencies = {"USD", "EUR", "GBP"}
        if v.upper() not in valid_currencies:
            raise ValueError(f"Unexpected currency: {v}")
        return v.upper()

    @field_validator("features")
    @classmethod
    def features_must_not_be_empty(cls, v):
        if len(v) == 0:
            raise ValueError("Features list is empty: possible extraction failure")
        return v

# Define scraper logic
def scrape_stripe_pricing() -&gt; list[dict]:
    url = "&lt;https://stripe.com/pricing&gt;"
    headers = {
        "User-Agent": (
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 (KHTML, like Gecko) "
            "Chrome/124.0.0.0 Safari/537.36"
        ),
        "Accept-Language": "en-US,en;q=0.9",
    }

    response = httpx.get(url, headers=headers, follow_redirects=True, timeout=15)
    response.raise_for_status()

    soup = BeautifulSoup(response.text, "html.parser")
    plans = []

    # Get class name from selectors
    plan_cards = soup.select("div.HeroPricingSubcard")

    for card in plan_cards:
        container = card.select_one(".HeroPricingSubcard__container")
        if not container:
            plans.append({
                "plan_name": "",
                "monthly_price": 0.0,
                "currency": "USD",
                "features": [],
            })
            continue

        # Get all text nodes inside the container
        text_blocks = [t.get_text(strip=True) for t in container.find_all(True) if t.get_text(strip=True)]

        # Pull the first block that looks like a price
        price_text = ""
        for block in text_blocks:
            if re.search(r"[\\d]+[.,]?[\\d]*%?\\s*\\+?\\s*[&#8364;$&#163;]?[\\d]*", block):
                price_text = block
                break

        price_match = re.search(r"[\\d]+\\.?[\\d]*", price_text.replace(",", "."))
        monthly_price = float(price_match.group()) if price_match else 0.0

        if "$" or "&#162;" in price_text:
            currency = "USD"
        elif "&#8364;" in price_text:
            currency = "EUR"
        elif "&#163;" in price_text:
            currency = "GBP"
        else:
            currency = "UNKNOWN"

        # Use all text blocks as features
        features = text_blocks if text_blocks else []

        plans.append({
            "plan_name": "Standard",  # HeroPricingSubcard is the Standard card
            "monthly_price": monthly_price,
            "currency": currency,
            "features": features,
        })

    return plans

# Define validator logic
def send_alert(message: str):
    # Wire this to Slack, PagerDuty, email, etc.
    print(f"[ALERT] {message}")

def validate_scraped_data(raw_data: dict) -&gt; Optional[CompetitorPricingData]:
    try:
        return CompetitorPricingData(**raw_data)
    except ValidationError as e:
        # Don't silently swallow this &#8212; alert immediately
        print(f"[VALIDATION FAILED] {e}")
        send_alert(f"Scraper validation failed: {e}")
        return None

if __name__ == "__main__":
    raw_plans = scrape_stripe_pricing()

    print(f"Found {len(raw_plans)} plan(s) on Stripe's pricing page.\\n")

    for raw in raw_plans:
        validated = validate_scraped_data(raw)
        if validated:
            print(f"[OK]   {validated.plan_name} &#8212; {validated.currency} {validated.monthly_price}/mo")
            print(f"       Features: {validated.features[:2]}{'...' if len(validated.features) &gt; 2 else ''}")
        else:
            print(f"[FAIL] Raw data that broke validation: {raw}")</code></code></pre><p>Here&#8217;s what this code does:</p><ul><li><p>Defines a Pydantic model: It leverages <a href="https://pydantic.dev/docs/validation/latest/get-started/">Pydantic</a> to define the expected shape and rules of the scraped data. It enforces that the price is positive, the currency is one of <em>USD</em>, <em>EUR</em>, or <em>GBP</em>, and the features list is not empty. Any violation raises a <em>ValidationError</em>.</p></li><li><p><em>scrape_stripe_pricing()</em>: Fetches Stripe&#8217;s pricing page using the library <em><a href="https://www.python-httpx.org/">httpx</a></em>, parses the HTML with <em><a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/">BeautifulSoup</a></em>, and extracts pricing data using CSS selectors. If a container isn&#8217;t found, it appends a zeroed-out dict, which will intentionally fail validation downstream.</p></li><li><p><em>validate_scraped_data()</em>: Passes each raw scraped dict through the Pydantic model. On failure, it calls <em>send_alert()</em> instead of silently swallowing the error.</p></li><li><p><em>send_alert()</em>: A stub you can wire to your alerting stack (Slack, PagerDuty, email) in a production environment. Note that, for educational purposes, throughout the entire article, this is used to print alerts via the command line.</p></li></ul><p>The result is the following:</p><pre><code><code>Found 2 plan(s) on Stripe's pricing page.

[OK]   Standard &#8212; EUR 1.5/mo
       Features: ['1.5% + &#8364;0.25', '1.5% + &#8364;0.25']...
[OK]   Standard &#8212; EUR 2.5/mo
       Features: ['2.5% + &#8364;0.25', '2.5% + &#8364;0.25']...
</code></code></pre><p>If it&#8217;s not clear why this matters, here&#8217;s what your scraper &#8220;sees&#8221; when it&#8217;s pushed from another location:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7e3G!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7f8dfc-202f-4803-83fb-575a19320f1a_1402x773.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7e3G!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7f8dfc-202f-4803-83fb-575a19320f1a_1402x773.png 424w, https://substackcdn.com/image/fetch/$s_!7e3G!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7f8dfc-202f-4803-83fb-575a19320f1a_1402x773.png 848w, https://substackcdn.com/image/fetch/$s_!7e3G!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7f8dfc-202f-4803-83fb-575a19320f1a_1402x773.png 1272w, https://substackcdn.com/image/fetch/$s_!7e3G!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7f8dfc-202f-4803-83fb-575a19320f1a_1402x773.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7e3G!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7f8dfc-202f-4803-83fb-575a19320f1a_1402x773.png" width="1402" height="773" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2e7f8dfc-202f-4803-83fb-575a19320f1a_1402x773.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:773,&quot;width&quot;:1402,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:262298,&quot;alt&quot;:&quot;The Stripe&#8217;s pricing page your scraper &#8220;sees&#8221; from the US by Federico Trotta&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/196881482?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7f8dfc-202f-4803-83fb-575a19320f1a_1402x773.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The Stripe&#8217;s pricing page your scraper &#8220;sees&#8221; from the US by Federico Trotta" title="The Stripe&#8217;s pricing page your scraper &#8220;sees&#8221; from the US by Federico Trotta" srcset="https://substackcdn.com/image/fetch/$s_!7e3G!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7f8dfc-202f-4803-83fb-575a19320f1a_1402x773.png 424w, https://substackcdn.com/image/fetch/$s_!7e3G!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7f8dfc-202f-4803-83fb-575a19320f1a_1402x773.png 848w, https://substackcdn.com/image/fetch/$s_!7e3G!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7f8dfc-202f-4803-83fb-575a19320f1a_1402x773.png 1272w, https://substackcdn.com/image/fetch/$s_!7e3G!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7f8dfc-202f-4803-83fb-575a19320f1a_1402x773.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The Stripe&#8217;s pricing page your scraper &#8220;sees&#8221; from the US</figcaption></figure></div><p>The class is the same, but the data (the currency) is completely different! This is a common example of why you need validation that goes beyond a specific location.</p><div><hr></div><blockquote><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://byteful.com/?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EOo9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 424w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 848w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1272w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EOo9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png" width="630" height="69.35779816513761" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:120,&quot;width&quot;:1090,&quot;resizeWidth&quot;:630,&quot;bytes&quot;:133037,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://byteful.com/?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/194398731?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!EOo9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 424w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 848w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1272w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p><strong><a href="https://byteful.com?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE">Start your scraping journey with Byteful</a></strong>: 10GB New Customer Trial | Use TWSC for 15% OFF | $1.75/GB Residential Data | ISP Proxies in 15+ Countries</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://byteful.com?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE&quot;,&quot;text&quot;:&quot;Claim your 10GB here&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://byteful.com?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE"><span>Claim your 10GB here</span></a></p></blockquote><div><hr></div><h3>Using DOM Fingerprints to Get Ahead of Structural Drift</h3><p>This method is one of the most valuable things you can add to a competitive intelligence pipeline.</p><p>The idea is simple: periodically hash the DOM structure of the pages you&#8217;re scraping and store its value. When the hash changes, flag it for human review before your scraper breaks. Here is how you can implement it:</p><pre><code><code># In an activated virtual environment run: pip install requests

import hashlib
import httpx
from bs4 import BeautifulSoup
import requests

# Define parsing logic
def extract_structural_fingerprint(html: str, selector: str) -&gt; str:
    """
    Extract a structural fingerprint from a specific section of the page.
    We hash the tag names and class names, NOT the content.
    This way, price changes don't trigger false alarms &#8212; only structural changes do.
    """
    soup = BeautifulSoup(html, "html.parser")
    container = soup.select_one(selector)

    if not container:
        return "CONTAINER_NOT_FOUND"

    # Build a structural signature
    structure = []
    for tag in container.find_all(True):
        classes = sorted(tag.get("class", []))
        structure.append(f"{tag.name}:{','.join(classes)}")

    fingerprint_str = "|".join(structure)
    return hashlib.md5(fingerprint_str.encode()).hexdigest()

# Compare hashes
def check_fingerprint(url: str, selector: str, stored_fingerprint: str) -&gt; bool:
    """
    Returns True if the structure is unchanged, False if it has drifted.
    """
    response = requests.get(url, timeout=10)
    response.raise_for_status()

    current_fingerprint = extract_structural_fingerprint(response.text, selector)

    if current_fingerprint == "CONTAINER_NOT_FOUND":
        send_alert(f"[CRITICAL] Selector '{selector}' not found on {url}. Site may have been redesigned.")
        print("\\n[OK] Structure unchanged, safe to scrape.")
        return False

    if current_fingerprint != stored_fingerprint:
        send_alert(
            f"[WARNING] Structural change detected on {url} "
            f"(selector: {selector}). "
            f"Old: {stored_fingerprint[:8]}... New: {current_fingerprint[:8]}..."
        )
        print("\\n[WARNING] Structural change detected: hashes differ")
        print(f"  Old: {fingerprint[:8]}...")
        print(f"  New: {drifted_fingerprint[:8]}...")
        print("  &#8594; Scrape skipped. Human review required before next run.")
        return False

    return True</code></code></pre><p>Below is a description of what this snippet does:</p><ul><li><p><em>extract_structural_fingerprint():</em> Parses the HTML, finds the target container via CSS selector, and builds a structural signature by iterating over every tag inside it. In this specific case, it records tag names and sorts class names, but deliberately ignores text content. That signature string is then hashed with MD5 and returned. If the selector finds nothing, it returns <em>&#8220;CONTAINER_NOT_FOUND&#8221;</em> instead of crashing.</p></li><li><p><em>check_fingerprint()</em>: Fetches the live page, computes the current fingerprint, and compares it against the stored one. Two failure cases are handled separately: the selector disappearing entirely (<em>CONTAINER_NOT_FOUND</em>) and the structure changing (hash mismatch). Both call <em>send_alert()</em> and return <em>False</em>: this signals to the caller to skip the scrape entirely.</p></li><li><p><em>hashlib.md5()</em>: Used to compress the structural signature into a short and comparable string. MD5 is not used here for security purposes; it&#8217;s used because it&#8217;s fast and collision-resistant enough for DOM comparison. Any change in tag names or class names produces a completely different hash.</p></li></ul><p>In the case of a drifted fingerprint, the expected result is the following:</p><pre><code><code>[STORED]  Fingerprint: 248363464b2da9b3814ea9a6dc5bd0df
[DRIFTED] Fingerprint: 7532ae7466c1f0fbc2c96471acd86ea7

[WARNING] Structural change detected: hashes differ
  Old: 24836346...
  New: 7532ae74...
  &#8594; Scrape skipped. Human review required before next run.</code></code></pre><p>Note that this approach to DOM changes can be considered &#8220;classical&#8221; because it is fully based on finding a meaningful selector for the target page. The problem is that the DOM could change, but not the &#8220;meaningful&#8221; selector you choose. To improve it, you can target several selectors, or you can add a check on the whole body, but this would add more noise.</p><p>If you prefer trying a more &#8220;modern&#8221; approach that leverages LLMs to check on DOM changes, at The Web Scraping Club, we&#8217;ve already covered this in the article: &#8220;<a href="https://substack.thewebscraping.club/p/scraping-with-llms-gpt-vision">Beyond the DOM: A Practical Guide to Web Data Extraction with LLMs and GPT Vision</a>&#8221;.</p><h3>Using Canary Fields as a Heartbeat for Your Scraping Pipeline</h3><p>A canary field is a field that you know changes frequently. This could be a &#8220;last updated&#8221; timestamp, a dynamic element, a session token in the page: you name it. You scrape it as a heartbeat, and if it stops changing, your pipeline is probably broken. Below is a snippet that checks on a canary field:</p><pre><code><code>def send_alert(message: str):
    print(f"[ALERT] {message}")

def check_canary_field(
    current_value: str,
    stored_value: str,
    field_name: str,
    competitor: str,
    max_unchanged_hours: int = 48) -&gt; None:
    if current_value == stored_value:
        send_alert(
            f"[WARNING] Canary field '{field_name}' for {competitor} "
            f"has not changed in over {max_unchanged_hours} hours. "
            f"Possible scraper failure or soft block."
        )
    else:
        print(f"[OK] Canary field '{field_name}' for {competitor} has changed. Pipeline looks healthy.")</code></code></pre><p>When something changes, the expected output is the following:</p><pre><code><code>[ALERT] [WARNING] Canary field 'last_updated' for &lt;COMPETITOR NAME&gt; has not changed in over 48 hours. 
Possible scraper failure or soft block.</code></code></pre><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.youtube.com/@thewebscrapingclub&quot;,&quot;text&quot;:&quot;Check the TWSC YouTube Channel&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.youtube.com/@thewebscrapingclub"><span>Check the TWSC YouTube Channel</span></a></p><div><hr></div><h2>Building a Storage Architecture You Can Actually Trust</h2><p>Many scraping engineers make the same mistake: they store only the processed output and throw away the raw HTML.</p><p>But in the case of competitive intelligence, that&#8217;s an issue that can cause you to work on weekends. Why? Because when a competitor changed their pricing 6 months ago, and you want to reconstruct the exact timeline, you need the raw data. Without it, history is gone forever.</p><p>The solution is a two-layer storage model: a raw and a processed layer. Here&#8217;s how the two relate:</p><pre><code><code>             &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
             &#9474;                     RAW LAYER                       &#9474;
             &#9474;  S3: raw/{competitor}/{page_type}/{date}.html.gz    &#9474;
             &#9474;  What's stored: full HTML, compressed, timestamped  &#9474;
             &#9474;  Why: so you can re-parse history if your logic     &#9474;
             &#9474;  changes or your scraper had a bug                  &#9474;
             &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
                                   &#9474; raw_s3_key (reference)
                                   &#9660;
             &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
             &#9474;                  PROCESSED LAYER                    &#9474;
             &#9474;  SQLite/Postgres: pricing_history table             &#9474;
             &#9474;  What's stored: structured, queryable records       &#9474;
             &#9474;  Why: this is what your dashboards and alerts       &#9474;
             &#9474;  consume                                            &#9474;
             &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;</code></code></pre><p>Basically, every record in the processed layer carries a raw_s3_key that points back to the exact HTML it was extracted from. That reference is what makes the whole architecture auditable.</p><h3>The Raw Layer: Keep Every HTML Response</h3><p>Store the full HTML or JSON response, timestamped, for every successful scrape. S3 is a cheap option to do so, and a year of raw HTML for a handful of competitor pages costs almost nothing. Below is an example you can implement in your pipeline:</p><pre><code><code># In an activated virtual environment run: pip install boto3

import boto3
import gzip
from datetime import datetime

def store_raw_response(
    html: str,
    competitor: str,
    page_type: str,
    bucket_name: str
) -&gt; str:
    """
    Store the raw HTML response in S3 with a structured key.
    Compress it &#8212; HTML compresses extremely well (often 10:1).
    Returns the S3 key for reference in the processed layer.
    """
    s3 = boto3.client("s3")
    timestamp = datetime.utcnow().strftime("%Y/%m/%d/%H%M%S")
    s3_key = f"raw/{competitor}/{page_type}/{timestamp}.html.gz"

    compressed = gzip.compress(html.encode("utf-8"))

    s3.put_object(
        Bucket=bucket_name,
        Key=s3_key,
        Body=compressed,
        ContentEncoding="gzip",
        ContentType="text/html",
        Metadata={
            "competitor": competitor,
            "page_type": page_type,
            "scraped_at": datetime.utcnow().isoformat()
        }
    )

    return s3_key

def retrieve_raw_response(s3_key: str, bucket_name: str) -&gt; str:
    """
    Retrieve and decompress a raw HTML response from S3.
    Useful for re-parsing historical data when your scraper logic changes.
    """
    s3 = boto3.client("s3")
    response = s3.get_object(Bucket=bucket_name, Key=s3_key)
    compressed_data = response["Body"].read()
    return gzip.decompress(compressed_data).decode("utf-8")</code></code></pre><p>Notice the S3 structure: <em>raw/{competitor}/{page_type}/{year}/{month}/{day}/{timestamp}.html.gz</em>. This makes it trivial to list all scrapes for a specific competitor and page type within a date range, which is exactly what you need when reconstructing a pricing history.</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/p/consulting&quot;,&quot;text&quot;:&quot;Need help with your scraping project?&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://substack.thewebscraping.club/p/consulting"><span>Need help with your scraping project?</span></a></p><div><hr></div><h3>The Processed Layer: What Your Dashboards Actually Consume</h3><p>The processed layer is your structured, queryable data: the thing your dashboards and alerts actually read. Every record here has two fields that make the whole architecture work:</p><ul><li><p><em>raw_s3_key</em>, which links back to the exact HTML this record was extracted from.</p></li><li><p><em>fingerprint</em>, which captures the structural state of the page at scrape time.</p></li></ul><p>The first one makes your data auditable. The second one tells you, later, whether the page had already changed when this record was written.</p><p>Below is a Python example to implement it:</p><pre><code><code>import sqlite3
from dataclasses import dataclass
from datetime import datetime
from typing import Optional

@dataclass
class ProcessedPricingRecord:
    competitor: str
    page_type: str
    plan_name: str
    monthly_price: float
    currency: str
    scraped_at: datetime
    raw_s3_key: str  # The link back to the raw layer
    fingerprint: str  # The structural fingerprint at time of scrape

def store_processed_record(record: ProcessedPricingRecord, db_path: str) -&gt; None:
    conn = sqlite3.connect(db_path)
    cursor = conn.cursor()

    cursor.execute("""
        CREATE TABLE IF NOT EXISTS pricing_history (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            competitor TEXT NOT NULL,
            page_type TEXT NOT NULL,
            plan_name TEXT NOT NULL,
            monthly_price REAL NOT NULL,
            currency TEXT NOT NULL,
            scraped_at TEXT NOT NULL,
            raw_s3_key TEXT NOT NULL,
            fingerprint TEXT NOT NULL
        )
    """)

    cursor.execute("""
        INSERT INTO pricing_history 
        (competitor, page_type, plan_name, monthly_price, currency, scraped_at, raw_s3_key, fingerprint)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?)
    """, (
        record.competitor,
        record.page_type,
        record.plan_name,
        record.monthly_price,
        record.currency,
        record.scraped_at.isoformat(),
        record.raw_s3_key,
        record.fingerprint
    ))

    conn.commit()
    conn.close()</code></code></pre><p>A note on storage: this example uses SQLite, which is fine for a single scraper running on one machine. If you&#8217;re scraping multiple competitors in parallel or need concurrent writes, consider moving to Postgres (the schema remains the same).</p><h3>A Concrete Example of Why the Raw Layer Saves You on Weekends</h3><p>Imagine your competitor runs a Black Friday promotion in November and your scraper captures it. In January, your CEO asks: <em>&#8220;When exactly did they drop prices, and by how much?&#8221;</em></p><p>With only the processed layer, you can answer that if your scraper was working correctly. But what if your scraper had a bug in November that caused it to extract prices incorrectly? With the raw layer, you can go back, fix the extraction logic, and re-parse the November HTML. Without it, that data is gone.</p><p>Here&#8217;s a utility function that makes re-parsing straightforward:</p><pre><code><code>import boto3

def reparse_historical_data(
    competitor: str,
    page_type: str,
    start_date: str,
    end_date: str,
    bucket_name: str,
    new_parser_fn,
    db_path: str
) -&gt; int:
    """
    Re-parse all raw HTML for a competitor/page_type within a date range.
    Useful when your extraction logic changes and you need to backfill.
    
    new_parser_fn: a callable that takes raw HTML and returns a list of dicts
    Returns the number of records reprocessed.
    """
    s3 = boto3.client("s3")
    prefix = f"raw/{competitor}/{page_type}/"

    paginator = s3.get_paginator("list_objects_v2")
    pages = paginator.paginate(Bucket=bucket_name, Prefix=prefix)

    reprocessed = 0

    for page in pages:
        for obj in page.get("Contents", []):
            key = obj["Key"]
            # Filter by date range based on key structure
            key_date = key.split("/")[3] + "-" + key.split("/")[4] + "-" + key.split("/")[5]
            if start_date &lt;= key_date &lt;= end_date:
                raw_html = retrieve_raw_response(key, bucket_name)
                parsed_records = new_parser_fn(raw_html)
                for record_data in parsed_records:
                    # Store with updated extraction, linked to same raw key
                    record = ProcessedPricingRecord(
                        **record_data,
                        raw_s3_key=key,
                        scraped_at=datetime.utcnow()
                    )
                    store_processed_record(record, db_path)
                    reprocessed += 1

    return reprocessed</code></code></pre><p>Note that you&#8217;re not re-scraping anything. You&#8217;re just running your new parser against HTML you already have in storage. That&#8217;s the whole point of the raw layer.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Web Scraping Club is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>Recovering From a Full Scraper Failure Without Losing History</h2><p>Imagine this scenario: a competitor does a full site redesign, and your scraper is dead. This is not a hypothetical scenario: it happens more frequently than you can think, and most teams have no protocol for it.</p><p>Here&#8217;s a concrete recovery process.</p><h3>How to Know Your Scraper Is Dead (Not Just Slow)</h3><p>If you&#8217;ve implemented structural fingerprinting as discussed previously, you&#8217;ll know about the redesign before your scraper breaks. The fingerprint check will fail, you&#8217;ll get an alert, and you can investigate before storing bad data.</p><p>If you haven&#8217;t, and you&#8217;re reading this article after the fact, the first sign is usually a spike in validation failures or a sudden drop in scraped records. Consider implementing the following code:</p><pre><code><code>import sqlite3

def detect_scraper_health(db_path: str, competitor: str, lookback_days: int = 7) -&gt; dict:
    """
    Returns a health summary for a competitor's scraper over the last N days.
    Useful for spotting gradual degradation before it becomes a full failure.
    """
    conn = sqlite3.connect(db_path)
    cursor = conn.cursor()

    cursor.execute("""
        SELECT
            DATE(scraped_at) as scrape_date,
            COUNT(*) as total_attempts,
            SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as successes,
            SUM(CASE WHEN status = 'validation_failed' THEN 1 ELSE 0 END) as validation_failures,
            SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) as errors
        FROM scrape_log
        WHERE competitor = ?
          AND scraped_at &gt;= DATE('now', ?)
        GROUP BY DATE(scraped_at)
        ORDER BY scrape_date DESC
    """, (competitor, f"-{lookback_days} days"))

    rows = cursor.fetchall()
    conn.close()

    return [
        {
            "date": row[0],
            "total": row[1],
            "successes": row[2],
            "validation_failures": row[3],
            "errors": row[4],
            "success_rate": round(row[2] / row[1] * 100, 1) if row[1] &gt; 0 else 0
        }
        for row in rows
    ]
</code></code></pre><p>The expected result is something as follows:</p><pre><code><code>Date         Total     OK   Val.Fail   Errors     Rate
--------------------------------------------------------
2026-01-15      10      0          0       10     0.0%
2026-01-14      10      2          5        3    20.0%
2026-01-13      10      5          4        1    50.0%
2026-01-12      10      8          2        0    80.0%
2026-01-11      10      9          1        0    90.0%
2026-01-10      10     10          0        0   100.0%
2026-01-09      10     10          0        0   100.0%</code></code></pre><p>The degradation pattern is immediately readable. A success rate dropping from 95% to 60% over three days is probably a sign of a redesign in progress. A drop from 95% to 0% in a short time signals a full redesign, or a hard block.</p><h3>Diagnosing What Actually Broke Before You Rewrite Anything</h3><p>Not every structural change is a full redesign. Rewriting the major part of a scraper can cost you days if not weeks. To avoid that, you can write a script that checks what actually changed, pointing to meaningful parts of the target website:</p><pre><code><code>from bs4 import BeautifulSoup

def triage_structural_change(
    url: str,
    selector: str,
    old_fingerprint: str,
    bucket_name: str,
    competitor: str,
    page_type: str) -&gt; dict:
    """
    Diagnose the nature of a structural change.
    Returns a triage report to guide the recovery effort.
    """
    response = requests.get(url, timeout=10)
    current_html = response.text
    soup = BeautifulSoup(current_html, "html.parser")

    report = {
        "url": url,
        "http_status": response.status_code,
        "selector_found": soup.select_one(selector) is not None,
        "old_fingerprint": old_fingerprint,
        "new_fingerprint": extract_structural_fingerprint(current_html, selector),
        "page_title": soup.title.string if soup.title else "N/A",
        "recommendation": None
    }

    if not report["selector_found"]:
        report["recommendation"] = "FULL_REWRITE: primary selector is gone. Site likely redesigned."
    elif report["old_fingerprint"] != report["new_fingerprint"]:
        report["recommendation"] = "PARTIAL_UPDATE:selector exists but structure changed. Review child selectors."
    else:
        report["recommendation"] = "FALSE_ALARM: fingerprint mismatch may be transient. Re-check in 1 hour."

    # Store the current raw HTML for reference during rewrite
    store_raw_response(current_html, competitor, f"{page_type}_triage", bucket_name)

    return report
    
def print_report(report: dict) -&gt; None:
    print(f"  URL:             {report['url']}")
    print(f"  Selector found:  {report['selector_found']}")
    print(f"  Old fingerprint: {report['old_fingerprint'][:8]}...")
    print(f"  New fingerprint: {report['new_fingerprint'][:8]}...")
    print(f"  Page title:      {report['page_title']}")
    print(f"  Recommendation:  {report['recommendation']}")
</code></code></pre><p>The expected output for the case of a full rewrite is the following:</p><pre><code><code>&#9;URL:             https://...
  Selector found:  False
  Old fingerprint: 4a7f92bc...
  New fingerprint: CONTAINER_NOT_FOUND
  Page title:      Pricing page
  Recommendation:  FULL_REWRITE: primary selector is gone. Site likely redesigned.
</code></code></pre><p>So, in this case, the script checks a specific URL of the target website. But the code can be, of course, generalized to more than just one. The same applies to the target selector.</p><h3>Estimating How Much of the Gap You Can Recover From Raw Storage</h3><p>Once you&#8217;ve rewritten the scraper, you have a gap in your data. If you&#8217;ve been storing raw HTML, you can partially fill it using a snippet like the following:</p><pre><code><code>import boto3

def estimate_gap_coverage(
    competitor: str,
    page_type: str,
    failure_start_date: str,
    bucket_name: str) -&gt; dict:
    """
    Estimate how much of the gap period can be recovered from raw storage.
    """
    s3 = boto3.client("s3")
    prefix = f"raw/{competitor}/{page_type}/"

    paginator = s3.get_paginator("list_objects_v2")
    pages = paginator.paginate(Bucket=bucket_name, Prefix=prefix)

    available_keys = []
    for page in pages:
        for obj in page.get("Contents", []):
            key = obj["Key"]
            key_date = "/".join(key.split("/")[3:6])
            if key_date &gt;= failure_start_date.replace("-", "/"):
                available_keys.append(key)

    return {
        "gap_start": failure_start_date,
        "recoverable_snapshots": len(available_keys),
        "oldest_recoverable": available_keys[0] if available_keys else None,
        "newest_recoverable": available_keys[-1] if available_keys else None,
        "recommendation": (
            "Run reparse_historical_data() with your new parser to backfill."
            if available_keys else
            "No raw snapshots available for this period. Gap cannot be recovered."
        )
    }

# Print values
print(f"Gap start:              {result['gap_start']}")
print(f"Recoverable snapshots:  {result['recoverable_snapshots']}")
print(f"Oldest recoverable:     {result['oldest_recoverable']}")
print(f"Newest recoverable:     {result['newest_recoverable']}")
print(f"Recommendation:         {result['recommendation']}")</code></code></pre><p>The expected result is:</p><pre><code><code>Gap start:              2026-04-12
Recoverable snapshots:  3
Oldest recoverable:     &lt;COMPETITOR_NAME&gt;/pricing/2026/04/12/143022.html.gz
Newest recoverable:     &lt;COMPETITOR_NAME&gt;/pricing/2026/04/14/143022.html.gz
Recommendation:         Run reparse_historical_data() with your new parser to backfill.</code></code></pre><p>As a final note on this recovery section, consider that, in practice, these three steps run in sequence:</p><ul><li><p>When something breaks, you start with <em>detect_scraper_health()</em> to confirm there&#8217;s actually a problem and understand how long it&#8217;s been degrading.</p></li><li><p>Then you run <em>triage_structural_change()</em> to understand <em>what</em> broke. That tells you whether you need a full rewrite or a 10-minute fix.</p></li><li><p>Once you&#8217;ve updated your scraper, you call <em>estimate_gap_coverage()</em> to see how much of the gap period you can recover from raw storage, and then <em>reparse_historical_data()</em> to actually backfill it. If <em>estimate_gap_coverage()</em> comes back with zero recoverable snapshots, that gap is gone, which is exactly why the raw layer exists in the first place.</p></li></ul><h2>Conclusion</h2><p>Building competitive intelligence scrapers is not as easy as it seems for two reasons:</p><ul><li><p>Developing the scrapers is only half the effort.</p></li><li><p>Keeping the scrapers honest 12 months from now probably needs even harder efforts than coding the scrapers themselves.</p></li></ul><p>The layers presented in this article can make your competitive intelligence scrapers resilient through time (with the right adjustments for production environments). So, if you&#8217;re implementing this from scratch, don&#8217;t try to build everything at once. Add one piece at a time and take your time to verify everything works fine before you add the next step.</p><p>Then, you just need to <a href="https://substack.thewebscraping.club/p/analyzing-scraped-data-pandas-matplotlib">analyze the data you scraped</a> (maybe directly <a href="https://substack.thewebscraping.club/p/building-a-scraper-dashboard-streamlit">implementing a dashboard in Streamlit</a>), and your colleagues in the business department can have sweet dreams: their competitive analytics are safe through time.</p><p>So, let us now: are your competitive intelligence scrapers time-resilient?</p><div><hr></div><p><em>Did you like this article? Share it with someone who might find it useful and get a discount on paid plans.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/p/how-to-build-competitive-intelligence-scraping?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://substack.thewebscraping.club/p/how-to-build-competitive-intelligence-scraping?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><p></p>]]></content:encoded></item><item><title><![CDATA[Kameleo Docker: Exploring the Docker-Based Anti-Detect Browser]]></title><description><![CDATA[Kameleo is finally available on Linux. How? Via Docker!]]></description><link>https://substack.thewebscraping.club/p/kameleo-docker-exploring-the-docker</link><guid isPermaLink="false">https://substack.thewebscraping.club/p/kameleo-docker-exploring-the-docker</guid><dc:creator><![CDATA[Antonello Zanini]]></dc:creator><pubDate>Sun, 14 Jun 2026 03:01:16 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/9d36ab11-f536-4b56-829a-540e3ba41ad8_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Docker-based stealth browsers are quickly becoming a new standard for automation and scraping infrastructures. The main reason is that they can be integrated directly into CI pipelines or your own fleet of scalable stealth browsers in the cloud.</p><p>Kameleo Docker brings Kameleo&#8217;s anti-detect browser capabilities into a containerized setup, enabling production-ready automation with real fingerprinting and multi-profile isolation on Linux servers.</p><p>In this post, I&#8217;ll take a deep look at this solution and walk you through everything you need to know about it. By the end, you&#8217;ll understand what Kameleo Docker is, how its stealth browser approach works, how to set it up, and whether it&#8217;s actually worth trying.</p><div><hr></div><blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gCo7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gCo7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png" width="626" height="313" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:626,&quot;bytes&quot;:390972,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/197764148?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!gCo7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Before proceeding, let me thank NetNut, the platinum partner of the month. Their set of solutions cover all your needs for scraping.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack&quot;,&quot;text&quot;:&quot;Visit Netnut&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack"><span>Visit Netnut</span></a></p></blockquote><div><hr></div><h2>An Introduction to Kameleo Docker</h2><p>Dig into the world of Kameleo Docker!</p><h3>What Is Kameleo Docker?</h3><p><a href="https://kameleo.io/">Kameleo</a> is an anti-detect browser engineered to make browser sessions look like real user devices. Instead of exposing a generic automation fingerprint, it creates realistic browser identities by spoofing hardware, browser, and environment signals such as WebGL, Canvas, fonts, screen resolution, and geolocation.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!AV8o!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa86beae8-6206-4e8e-bdaa-50d9a1e743bd_3008x1193.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!AV8o!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa86beae8-6206-4e8e-bdaa-50d9a1e743bd_3008x1193.png 424w, https://substackcdn.com/image/fetch/$s_!AV8o!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa86beae8-6206-4e8e-bdaa-50d9a1e743bd_3008x1193.png 848w, https://substackcdn.com/image/fetch/$s_!AV8o!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa86beae8-6206-4e8e-bdaa-50d9a1e743bd_3008x1193.png 1272w, https://substackcdn.com/image/fetch/$s_!AV8o!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa86beae8-6206-4e8e-bdaa-50d9a1e743bd_3008x1193.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!AV8o!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa86beae8-6206-4e8e-bdaa-50d9a1e743bd_3008x1193.png" width="1456" height="577" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a86beae8-6206-4e8e-bdaa-50d9a1e743bd_3008x1193.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:577,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Kameleo Docker&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Kameleo Docker" title="Kameleo Docker" srcset="https://substackcdn.com/image/fetch/$s_!AV8o!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa86beae8-6206-4e8e-bdaa-50d9a1e743bd_3008x1193.png 424w, https://substackcdn.com/image/fetch/$s_!AV8o!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa86beae8-6206-4e8e-bdaa-50d9a1e743bd_3008x1193.png 848w, https://substackcdn.com/image/fetch/$s_!AV8o!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa86beae8-6206-4e8e-bdaa-50d9a1e743bd_3008x1193.png 1272w, https://substackcdn.com/image/fetch/$s_!AV8o!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa86beae8-6206-4e8e-bdaa-50d9a1e743bd_3008x1193.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Kameleo Docker</figcaption></figure></div><p>Kameleo Docker brings <a href="https://substack.thewebscraping.club/p/kameleo-anti-detect-browser">that same stealth stack</a> into a self-hosted, containerized deployment model. Rather than relying on a desktop app, you can run Kameleo inside Docker on Linux or Windows servers, CI pipelines, VPSs, or Kubernetes environments.</p><p>Playwright, Puppeteer, and Selenium can connect to the container via CDP, meaning you can keep using your existing browser automation logic with minimal changes.</p><p>Further reading:</p><ul><li><p><em><a href="https://kameleo.io/docker">Kameleo Docker product page</a></em></p></li><li><p><em><a href="https://developer.kameleo.io/integrations/docker/">Kameleo Docker docs</a></em></p></li><li><p><em><a href="https://kameleo.io/blog/kameleo-on-linux-via-docker-what-we-built-what-broke-whats-next">Kameleo on Linux via Docker: What We Built, What Broke, What&#8217;s Next</a></em></p><p></p></li></ul><h3>Why Kameleo Docker Exists</h3><p>Cloud servers, Kubernetes clusters, CI/CD pipelines, and VPS environments are overwhelmingly Linux-native, making Linux compatibility a practical requirement for automation and scraping teams.</p><p>For years, this created a problem for Kameleo users, as the solution only supported Windows and macOS. Because of that, teams wanting to run stealth browsers in production often had to rely on fragile workarounds.</p><p>Some deployed Windows virtual machines alongside Linux scraping stacks in AWS. Others used <a href="https://some-natalie.dev/blog/ssh-x11-forwarding/">X11-over-SSH</a> tunnels to remotely access browsers running on servers. These setups were difficult to maintain, resource-intensive, and far from ideal for scalable automation.</p><p>As explained in the <a href="https://kameleo.io/blog/kameleo-on-linux-via-docker-what-we-built-what-broke-whats-next">product announcement blog post</a>, Kameleo customers started to ask for a version of Kameleo that could run directly where their automation already lived.</p><p>As Barnabas Szenasi, founder and lead engineer at Kameleo, explained when I met him at Prague Crawl 2026:</p><blockquote><p><em>&#8220;We could see from customer messages that a significant slice of our automation-first users were running Linux cloud servers and simply couldn&#8217;t use Kameleo at all... At Prague Crawl 2025, Tamas [Kameleo&#8217;s CEO] and I heard the same story from industry peers around the world: scraping pipelines were getting harder, and the need to run real browser environments instead of faking HTTP requests was growing fast.&#8221;</em></p></blockquote><h3>The Philosophy Behind the Project</h3><p>From the beginning, Kameleo&#8217;s philosophy has been simple: masking quality matters more than shipping quickly.</p><p>After all, <a href="https://substack.thewebscraping.club/p/anti-detect-browser-royal-rumble-comments">an anti-detect browser</a> is only useful if it can convincingly behave like a real device. That&#8217;s why Kameleo relies on fingerprints sourced from real-world device traffic and continuously tested against modern anti-bot systems.</p><p>That same quality-first mindset also shaped the Docker project. According to founder and lead engineer Barnabas Szenasi, Linux support took longer than expected because the goal was never just to make Kameleo run in a container.</p><p>The objective was to reach the same masking quality users already expected on Windows and macOS. Shipping a functional but lower-fidelity Linux version would have compromised the product&#8217;s core standard.</p><blockquote><div><hr></div></blockquote><blockquote><p><em>For your scraping needs, having a reliable proxy provider like <strong>Decodo</strong> on your side improves the chances of success. </em></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qJZE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qJZE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png" width="401" height="225.5625" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fec072bc-2ac2-49bf-8084-43858298808f_800x450.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:450,&quot;width&quot;:800,&quot;resizeWidth&quot;:401,&quot;bytes&quot;:554497,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/187315628?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!qJZE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://visit.decodo.com/WyQ3mA&quot;,&quot;text&quot;:&quot;Try Decodo Now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://visit.decodo.com/WyQ3mA"><span>Try Decodo Now</span></a></p></blockquote><div><hr></div><h2>Technical Architecture Behind Kameleo Docker</h2><p>Now that you know why the project exists, let me explain how it works and the engineering behind it.</p><h3>How Kameleo Docker Works</h3><p>Kameleo Docker runs inside either:</p><ul><li><p>a Linux-based container (Ubuntu 22.04), or</p></li><li><p>a Windows-based container (Windows Server Core 2022).</p></li></ul><p>When you pull the image, Docker downloads the correct variant based on your container configuration (Linux containers are the default in most environments, including <a href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a>). Regardless of the underlying platform, Kameleo exposes the same <a href="https://developer.kameleo.io/reference/api-reference/">Local API</a> for browser and profile management as the <a href="https://kameleo.io/downloads">desktop app</a>.</p><p>The process begins when you create a browser profile through the Local API. If you aren&#8217;t familiar with that concept, <a href="https://developer.kameleo.io/concepts/profiles/">Kameleo profiles</a> are reusable browser environments that bundle a complete browser fingerprint together with persistent browser state, such as cookies, browsing history, local storage, and bookmarks.</p><p>Profiles can also include user-defined settings like proxies, browser extensions, and startup preferences. Each profile is tied to a specific browser kernel and can be started, stopped, imported, or exported as needed.</p><h3>Architecture Overview</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sgEa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6b1496-60f7-4a12-b06a-218d9f2d35ad_2174x1522.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sgEa!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6b1496-60f7-4a12-b06a-218d9f2d35ad_2174x1522.png 424w, https://substackcdn.com/image/fetch/$s_!sgEa!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6b1496-60f7-4a12-b06a-218d9f2d35ad_2174x1522.png 848w, https://substackcdn.com/image/fetch/$s_!sgEa!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6b1496-60f7-4a12-b06a-218d9f2d35ad_2174x1522.png 1272w, https://substackcdn.com/image/fetch/$s_!sgEa!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6b1496-60f7-4a12-b06a-218d9f2d35ad_2174x1522.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sgEa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6b1496-60f7-4a12-b06a-218d9f2d35ad_2174x1522.png" width="1456" height="1019" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fc6b1496-60f7-4a12-b06a-218d9f2d35ad_2174x1522.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1019,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sgEa!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6b1496-60f7-4a12-b06a-218d9f2d35ad_2174x1522.png 424w, https://substackcdn.com/image/fetch/$s_!sgEa!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6b1496-60f7-4a12-b06a-218d9f2d35ad_2174x1522.png 848w, https://substackcdn.com/image/fetch/$s_!sgEa!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6b1496-60f7-4a12-b06a-218d9f2d35ad_2174x1522.png 1272w, https://substackcdn.com/image/fetch/$s_!sgEa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6b1496-60f7-4a12-b06a-218d9f2d35ad_2174x1522.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Kameleo Docker&#8217;s architecture</figcaption></figure></div><p>Kameleo Docker separates browser execution from automation logic. The container hosts the stealth browsers, fingerprinting systems, and orchestration layer, while your automation scripts run independently on your machine, server, or orchestration platform.</p><p>At the center of the architecture is the Local API, exposed on port <em>5050</em>. This API handles profile creation, fingerprint selection, browser startup, lifecycle management, and more.</p><p>Behind the API sit <a href="https://developer.kameleo.io/concepts/kernels/">Kameleo&#8217;s browser kernels</a>:</p><ul><li><p><strong>Chroma</strong>: A Chromium-based engine.</p></li><li><p><strong>Junglefox</strong>:<strong> </strong>A Firefox-based engine.</p></li></ul><p>These kernels are modified with engine-level masking patches and connected to Kameleo&#8217;s continuously updated fingerprint database (more on this later). Since they are exposed through the same API, you can switch between them without making any code changes.</p><p>When a profile starts, Kameleo launches a browser session with the configured fingerprint and settings. Playwright and Puppeteer can then connect to the running browser through a WebSocket endpoint via <a href="https://chromedevtools.github.io/devtools-protocol/">Chrome DevTools Protocol (CDP)</a>.</p><p>In other words, your automation script stays outside the Docker container. The browser behaves as if it were running locally, while execution, fingerprint masking, and browser management happen entirely inside the Docker container.</p><p>Persistent storage is handled through Docker volumes. Profile data, downloaded browser kernels, and runtime state are stored outside the container, allowing environments to be recreated without losing configuration or repeatedly downloading browser components. This makes deployments easier to scale, recover, and reproduce.</p><div><hr></div><blockquote><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8Qrb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8Qrb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!8Qrb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!8Qrb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!8Qrb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8Qrb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png" width="479" height="239.5" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:479,&quot;bytes&quot;:911444,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/196394917?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!8Qrb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!8Qrb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!8Qrb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!8Qrb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Trusted by teams running ad verification, web scraping, SERP tracking, and market research. Ethically sourced proxies, globally accessible, and fairly priced.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://dataimpulse.com/&quot;,&quot;text&quot;:&quot;Get Started With DataImpulse&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://dataimpulse.com/"><span>Get Started With DataImpulse</span></a></p></blockquote><div><hr></div><h2>Core Features of Kameleo Docker</h2><p>Time to explore the main features and capabilities provided by Kameleo Docker. For more information, <a href="https://developer.kameleo.io/integrations/docker/">read the official documentation</a>.</p><h3>Real Device Fingerprint Masking</h3><p>Kameleo fingerprints are derived from real-world device traffic, not synthetic templates. Each profile represents a coherent combination of OS, browser, hardware signals, and behavioral characteristics.</p><p>All fingerprint surfaces are kept internally consistent, including Canvas, WebGL, audio context, screen resolution, and fonts.</p><p><strong>Note</strong>: TLS fingerprint spoofing isn&#8217;t required, as Kameleo matches the browser kernel version precisely. The TLS stack remains the original, unmodified implementation shipped with the corresponding browser release.</p><p>The goal of the project isn&#8217;t to spoof everything, but to maintain realism across signals. That&#8217;s because overriding too many surfaces increases inconsistency risk, which detection systems can flag. For example, running a macOS fingerprint on a Windows host forces heavy compensation across system-level signals.</p><h3>Proxy Integration and Geo Consistency</h3><p>Each Kameleo profile can be assigned a dedicated proxy (including a rotating proxy), allowing IP-level isolation between browser identities.</p><p>Now, mismatches between IP geography and browser signals (language, timezone, WebRTC, and system locale) are a common detection vector. To address that, Kameleo provides <a href="https://help.kameleo.io/article/74-recommended-settings">automatic geo-location matching</a> to align the browser&#8217;s settings with the geographic location of the selected proxy IP address.</p><h3>Multi-Profile Isolation</h3><p>Kameleo Docker is built around strict profile isolation, as each browser profile runs as a fully independent environment. This separation opens the door to safe multi-accounting (referred to as &#8220;account management&#8221; in Kameleo terminology). Thanks for this feature, you can operate multiple identities simultaneously without cross-contamination of session data or signals.</p><h3>Linux-Specific Docker Image Features</h3><p>Compared to the Windows-based container, the Linux version of Kameleo Docker includes several additional features. These include:</p><ul><li><p><strong><a href="https://developer.kameleo.io/integrations/docker/#vnc-viewer-only-in-linux-based-container">Built-in VNC viewer</a></strong>: Allows you to monitor and interact with live browser sessions. This is especially useful for debugging automation, validating fingerprints, or troubleshooting rendering issues. You can access it through a browser on port <em>8080</em> or via native VNC clients such as RealVNC or TigerVNC on port <em>5900</em>. For security reasons, it&#8217;s disabled by default.</p></li><li><p><strong>Browser-Based Kameleo GUI</strong>: A lightweight browser-based GUI on port <em>80</em> (reach it at <em>http://localhost:80</em>). Unlike the desktop app, it offers reduced functionality and is primarily intended for quick inspection, basic profile management, and monitoring.</p></li><li><p><strong>Optional GPU acceleration</strong>: The Linux container <a href="https://developer.kameleo.io/integrations/docker/#gpu-support-on-linux">supports optional GPU acceleration</a> for graphics-heavy workloads such as WebGL or canvas-intensive websites. Intel/AMD GPUs can be mounted through <em>/dev/dri</em>, while NVIDIA GPUs are supported through the NVIDIA Container Toolkit. When no GPU is available, Kameleo falls back to software rendering.</p></li></ul><h2>Getting Started With Kameleo Docker: Step-by-Step Guide</h2><p>In this guided section, I&#8217;ll show you how to set up Kameleo Docker and use it for browser automation against the <a href="https://www.scrapingcourse.com/javascript-rendering">Scraping Course&#8217;s &#8220;JavaScript Rendering&#8221; page</a>:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!k9B9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8d4a67c-8ade-4fb3-b13d-f7d2f23e0f6d_3021x1629.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!k9B9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8d4a67c-8ade-4fb3-b13d-f7d2f23e0f6d_3021x1629.png 424w, https://substackcdn.com/image/fetch/$s_!k9B9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8d4a67c-8ade-4fb3-b13d-f7d2f23e0f6d_3021x1629.png 848w, https://substackcdn.com/image/fetch/$s_!k9B9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8d4a67c-8ade-4fb3-b13d-f7d2f23e0f6d_3021x1629.png 1272w, https://substackcdn.com/image/fetch/$s_!k9B9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8d4a67c-8ade-4fb3-b13d-f7d2f23e0f6d_3021x1629.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!k9B9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8d4a67c-8ade-4fb3-b13d-f7d2f23e0f6d_3021x1629.png" width="1456" height="785" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d8d4a67c-8ade-4fb3-b13d-f7d2f23e0f6d_3021x1629.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:785,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The target Scraping Course &#8220;JavaScript Rendering&#8221; page&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The target Scraping Course &#8220;JavaScript Rendering&#8221; page" title="The target Scraping Course &#8220;JavaScript Rendering&#8221; page" srcset="https://substackcdn.com/image/fetch/$s_!k9B9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8d4a67c-8ade-4fb3-b13d-f7d2f23e0f6d_3021x1629.png 424w, https://substackcdn.com/image/fetch/$s_!k9B9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8d4a67c-8ade-4fb3-b13d-f7d2f23e0f6d_3021x1629.png 848w, https://substackcdn.com/image/fetch/$s_!k9B9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8d4a67c-8ade-4fb3-b13d-f7d2f23e0f6d_3021x1629.png 1272w, https://substackcdn.com/image/fetch/$s_!k9B9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8d4a67c-8ade-4fb3-b13d-f7d2f23e0f6d_3021x1629.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The target Scraping Course &#8220;JavaScript Rendering&#8221; page</figcaption></figure></div><p>This is a sandbox environment for web scraping that simulates a real-world, JavaScript-rendered ecommerce page. It makes for a great testing target to validate the setup and see how Kameleo Docker behaves in a realistic automation scenario.</p><h3>Requirements and Prerequisites</h3><p>To get started with Kameleo Docker, make sure you have <a href="https://www.docker.com/get-started/">Docker installed and running locally</a>. (Optionally, you need a <a href="https://login.kameleo.io/Account/Login">Kameleo account </a>if you want to run Kameleo Docker with authentication.)</p><p>For more details on supported operating systems and memory requirements, <a href="https://developer.kameleo.io/integrations/docker/#prerequisites">refer to the official documentation</a>.</p><p>Since I&#8217;ll show how to use Kameleo Docker with Playwright in Python, to keep things moving, I&#8217;ll assume you already have a Python environment set up with <a href="https://playwright.dev/python/docs/library#installation">Playwright and its dependencies installed</a>.</p><p>To follow along with this tutorial section, I also recommend that you have:</p><ul><li><p><a href="https://docker-curriculum.com/">Basic Docker experience</a> (running containers, mounting volumes, and using compose files)</p></li><li><p>Familiarity with <a href="https://substack.thewebscraping.club/p/browser-automation-landscape-2025">browser automation using Playwright</a>.</p></li></ul><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.youtube.com/@thewebscrapingclub&quot;,&quot;text&quot;:&quot;Check the TWSC YouTube Channel&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.youtube.com/@thewebscrapingclub"><span>Check the TWSC YouTube Channel</span></a></p><div><hr></div><h3>Step #1: Create a Kameleo Account</h3><p>If you haven&#8217;t already, start by <a href="https://login.kameleo.io/Account/Register">creating a Kameleo account</a>. Fill out the sign-up form and enter the required information. Once registration is complete, <a href="https://kameleo.io/pricing">a </a><em><a href="https://kameleo.io/pricing">Free</a></em><a href="https://kameleo.io/pricing"> plan</a> will already be activated:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!OVWu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313c7aed-9927-4fb5-8981-886a01234f08_3031x1639.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!OVWu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313c7aed-9927-4fb5-8981-886a01234f08_3031x1639.png 424w, https://substackcdn.com/image/fetch/$s_!OVWu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313c7aed-9927-4fb5-8981-886a01234f08_3031x1639.png 848w, https://substackcdn.com/image/fetch/$s_!OVWu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313c7aed-9927-4fb5-8981-886a01234f08_3031x1639.png 1272w, https://substackcdn.com/image/fetch/$s_!OVWu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313c7aed-9927-4fb5-8981-886a01234f08_3031x1639.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!OVWu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313c7aed-9927-4fb5-8981-886a01234f08_3031x1639.png" width="1456" height="787" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/313c7aed-9927-4fb5-8981-886a01234f08_3031x1639.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:787,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!OVWu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313c7aed-9927-4fb5-8981-886a01234f08_3031x1639.png 424w, https://substackcdn.com/image/fetch/$s_!OVWu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313c7aed-9927-4fb5-8981-886a01234f08_3031x1639.png 848w, https://substackcdn.com/image/fetch/$s_!OVWu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313c7aed-9927-4fb5-8981-886a01234f08_3031x1639.png 1272w, https://substackcdn.com/image/fetch/$s_!OVWu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313c7aed-9927-4fb5-8981-886a01234f08_3031x1639.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The Free plan</figcaption></figure></div><p>Note that the <em>Free</em> plan is enough to use Kameleo Docker.</p><p><strong>Important</strong>: Kameleo credentials are required for the container to authenticate successfully, download browser kernels, and start correctly.</p><h3>Step #2: Start the Docker Container</h3><p>With your account ready, the next step is to pull and run <a href="https://hub.docker.com/r/kameleo/kameleo-app">the Kameleo Docker image</a>.</p><p>Remember that Kameleo ships as a multi-platform Docker image, supporting both Linux-based and Windows-based containers. To download and start the Linux container version of Kameleo, run:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">docker run --platform linux/amd64 \
    --shm-size=2g \
    -p 5050:5050 \
    -e PAT='&lt;YOUR_KAMELEO_PAT&gt;' \
    -v kameleo-data:/data \
    kameleo/kameleo-app:latest</code></pre></div><p><strong>Note 1</strong>: If you run this command in PowerShell, replace &#8220;\&#8221; with the backtick &#8220;`&#8221; for multi-line commands.</p><p><strong>Note 2</strong>: To launch the web GUI included in the Linux container of Kameleo Docker, add the <em>-p 80:80</em> argument to your <em>docker run</em> command.</p><p>Here&#8217;s what matters in the above command:</p><ul><li><p><em>--platform linux/amd64</em> ensures Docker pulls the Linux-based image variant.</p></li><li><p><em>--shm-size=2g</em> is required for stable browser execution (the default Docker shared memory of 64MB is too small for browser execution).</p></li><li><p><em>-v kameleo-data:/data</em> creates a named volume that persists browser kernels and profiles across restarts.</p></li><li><p><em>&lt;YOUR_KAMELEO_PAT&gt;</em> is your <a href="https://developer.kameleo.io/getting-started/configure/#3-generate-a-personal-access-token-pat">Kameleo Personal Access Token (PAT)</a>, required to authenticate with your Kameleo account. Note that <strong>this argument is optional</strong>. You can skip the <em>-e PAT=&#8217;&lt;YOUR_KAMELEO_PAT&gt;&#8217;</em> flag entirely if you want to try out Kameleo Docker without an account.</p></li></ul><p>Below&#8217;s the output you should get:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lH45!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43a533b1-de13-4653-a94d-08137ea045b6_3038x672.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lH45!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43a533b1-de13-4653-a94d-08137ea045b6_3038x672.png 424w, https://substackcdn.com/image/fetch/$s_!lH45!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43a533b1-de13-4653-a94d-08137ea045b6_3038x672.png 848w, https://substackcdn.com/image/fetch/$s_!lH45!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43a533b1-de13-4653-a94d-08137ea045b6_3038x672.png 1272w, https://substackcdn.com/image/fetch/$s_!lH45!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43a533b1-de13-4653-a94d-08137ea045b6_3038x672.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lH45!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43a533b1-de13-4653-a94d-08137ea045b6_3038x672.png" width="1456" height="322" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/43a533b1-de13-4653-a94d-08137ea045b6_3038x672.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:322,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Retrieving the Kameleo Docker image&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Retrieving the Kameleo Docker image" title="Retrieving the Kameleo Docker image" srcset="https://substackcdn.com/image/fetch/$s_!lH45!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43a533b1-de13-4653-a94d-08137ea045b6_3038x672.png 424w, https://substackcdn.com/image/fetch/$s_!lH45!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43a533b1-de13-4653-a94d-08137ea045b6_3038x672.png 848w, https://substackcdn.com/image/fetch/$s_!lH45!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43a533b1-de13-4653-a94d-08137ea045b6_3038x672.png 1272w, https://substackcdn.com/image/fetch/$s_!lH45!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43a533b1-de13-4653-a94d-08137ea045b6_3038x672.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Retrieving the Kameleo Docker image</figcaption></figure></div><p>The Kameleo Docker image should now be downloaded and launched on your system. Cool!</p><h3>Step #3: Verify the Service</h3><p>Once you run the image, Kameleo Docker will:</p><ol><li><p>Start the Local API on port <em>5050</em>.</p></li><li><p>Authenticate using your credentials.</p></li><li><p>Download required browser kernels (first run only).</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pYxi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4c9579e-6a4e-4153-90b7-3b6c696c090b_2526x142.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pYxi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4c9579e-6a4e-4153-90b7-3b6c696c090b_2526x142.png 424w, https://substackcdn.com/image/fetch/$s_!pYxi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4c9579e-6a4e-4153-90b7-3b6c696c090b_2526x142.png 848w, https://substackcdn.com/image/fetch/$s_!pYxi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4c9579e-6a4e-4153-90b7-3b6c696c090b_2526x142.png 1272w, https://substackcdn.com/image/fetch/$s_!pYxi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4c9579e-6a4e-4153-90b7-3b6c696c090b_2526x142.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pYxi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4c9579e-6a4e-4153-90b7-3b6c696c090b_2526x142.png" width="1456" height="82" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b4c9579e-6a4e-4153-90b7-3b6c696c090b_2526x142.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:82,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The Kameleo Docker image startup logs&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The Kameleo Docker image startup logs" title="The Kameleo Docker image startup logs" srcset="https://substackcdn.com/image/fetch/$s_!pYxi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4c9579e-6a4e-4153-90b7-3b6c696c090b_2526x142.png 424w, https://substackcdn.com/image/fetch/$s_!pYxi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4c9579e-6a4e-4153-90b7-3b6c696c090b_2526x142.png 848w, https://substackcdn.com/image/fetch/$s_!pYxi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4c9579e-6a4e-4153-90b7-3b6c696c090b_2526x142.png 1272w, https://substackcdn.com/image/fetch/$s_!pYxi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb4c9579e-6a4e-4153-90b7-3b6c696c090b_2526x142.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">The Kameleo Docker image startup logs</figcaption></figure></div><p>To confirm everything is running correctly, visit the following URL in your browser:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">http://localhost:5050/swagger</code></pre></div><p>You should see the Swagger UI for the Kameleo Local API:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oAMx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37e97028-9717-472e-850f-dcced084d674_3024x1624.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oAMx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37e97028-9717-472e-850f-dcced084d674_3024x1624.png 424w, https://substackcdn.com/image/fetch/$s_!oAMx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37e97028-9717-472e-850f-dcced084d674_3024x1624.png 848w, https://substackcdn.com/image/fetch/$s_!oAMx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37e97028-9717-472e-850f-dcced084d674_3024x1624.png 1272w, https://substackcdn.com/image/fetch/$s_!oAMx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37e97028-9717-472e-850f-dcced084d674_3024x1624.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oAMx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37e97028-9717-472e-850f-dcced084d674_3024x1624.png" width="1456" height="782" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/37e97028-9717-472e-850f-dcced084d674_3024x1624.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:782,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The Swagger UI for the Kameleo Local API at &#8220;http://localhost:5050/swagger&#8221;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The Swagger UI for the Kameleo Local API at &#8220;http://localhost:5050/swagger&#8221;" title="The Swagger UI for the Kameleo Local API at &#8220;http://localhost:5050/swagger&#8221;" srcset="https://substackcdn.com/image/fetch/$s_!oAMx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37e97028-9717-472e-850f-dcced084d674_3024x1624.png 424w, https://substackcdn.com/image/fetch/$s_!oAMx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37e97028-9717-472e-850f-dcced084d674_3024x1624.png 848w, https://substackcdn.com/image/fetch/$s_!oAMx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37e97028-9717-472e-850f-dcced084d674_3024x1624.png 1272w, https://substackcdn.com/image/fetch/$s_!oAMx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37e97028-9717-472e-850f-dcced084d674_3024x1624.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The Swagger UI for the Kameleo Local API at &#8220;http://localhost:5050/swagger&#8221;</figcaption></figure></div><p>You&#8217;ll have a local Kameleo instance ready for automation with Playwright, Puppeteer, or Selenium. Great!</p><h3>Step #4: Download the SDK and Create Your First Profile</h3><p>Now that Kameleo Docker is running, you can interact with it through the APIs exposed at <em>http://localhost:5050</em>. The next step is to proceed with the <a href="https://developer.kameleo.io/getting-started/quickstart/">usual Kameleo setup</a> by creating a profile.</p><p>Assuming you already have a Python environment with Playwright installed,start by <a href="https://pypi.org/project/kameleo.local-api-client/">installing the Kameleo SDK</a>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">pip install kameleo-local-api-client</code></pre></div><p>Then, in your Playwright script, initialize the Kameleo API client and generate a browser profile:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from kameleo.local_api_client import KameleoLocalApiClient
from kameleo.local_api_client.models import CreateProfileRequest

# Connect to the Kameleo API client
client = KameleoLocalApiClient(endpoint="http://localhost:5050")
# Search for a real-world fingerprint and create a Kameleo profile based on it
fps = client.fingerprint.search_fingerprints(
    device_type="desktop",
    os_family="windows",
    browser_product="chrome",
    browser_version="&gt;145",
)
profile = client.profile.create_profile(
    CreateProfileRequest(fingerprint_id=fps[0].id, name="twsc demo")
)</code></pre></div><p>The above snippet connects the Kameleo API client from the SDK to the local Kameleo Docker APIs. It retrieves a <a href="https://substack.thewebscraping.club/p/understanding-browser-fingerprint">realistic browser fingerprint</a> from the database and creates a persistent browser profile called &#8220;twsc demo&#8221; based on it. In this case, the fingerprint profile is for a desktop Chrome browser (version &gt;145) running on Windows.</p><p>Run the script above. If you started the Linux container of Kameleo Docker while mapping port <em>80</em> for the web GUI, then you&#8217;ll be able to see the &#8220;twsc demo&#8221; profile at <em>http://localhost:80</em>:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7VeS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd60baf46-86ad-4bd6-bf7f-aad1a06b7a24_3071x1620.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7VeS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd60baf46-86ad-4bd6-bf7f-aad1a06b7a24_3071x1620.png 424w, https://substackcdn.com/image/fetch/$s_!7VeS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd60baf46-86ad-4bd6-bf7f-aad1a06b7a24_3071x1620.png 848w, https://substackcdn.com/image/fetch/$s_!7VeS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd60baf46-86ad-4bd6-bf7f-aad1a06b7a24_3071x1620.png 1272w, https://substackcdn.com/image/fetch/$s_!7VeS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd60baf46-86ad-4bd6-bf7f-aad1a06b7a24_3071x1620.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7VeS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd60baf46-86ad-4bd6-bf7f-aad1a06b7a24_3071x1620.png" width="1456" height="768" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d60baf46-86ad-4bd6-bf7f-aad1a06b7a24_3071x1620.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:768,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Note the &#8220;twsc demo&#8221; profile&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Note the &#8220;twsc demo&#8221; profile" title="Note the &#8220;twsc demo&#8221; profile" srcset="https://substackcdn.com/image/fetch/$s_!7VeS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd60baf46-86ad-4bd6-bf7f-aad1a06b7a24_3071x1620.png 424w, https://substackcdn.com/image/fetch/$s_!7VeS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd60baf46-86ad-4bd6-bf7f-aad1a06b7a24_3071x1620.png 848w, https://substackcdn.com/image/fetch/$s_!7VeS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd60baf46-86ad-4bd6-bf7f-aad1a06b7a24_3071x1620.png 1272w, https://substackcdn.com/image/fetch/$s_!7VeS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd60baf46-86ad-4bd6-bf7f-aad1a06b7a24_3071x1620.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Note the &#8220;twsc demo&#8221; profile</figcaption></figure></div><h3>Step #5: Connect With Playwright</h3><p>You can now connect Playwright to the Kameleo profile created above <a href="https://substack.thewebscraping.club/p/webdriver-vs-cdp-vs-bidi">via CDP</a> using the following WebSocket URL:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">ws://localhost:5050/playwright/&lt;KAMELEO_PROFILE_ID&gt;</code></pre></div><p>Achieve that by using <a href="https://playwright.dev/python/docs/api/class-browsertype#browser-type-connect-over-cdp">Playwright&#8217;s </a><em><a href="https://playwright.dev/python/docs/api/class-browsertype#browser-type-connect-over-cdp">connect_over_cdp()</a></em> method on the target URL:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from playwright.sync_api import sync_playwright

# Kameleo profile creation...

# Connect Playwright to the Kameleo instance based on the configured profile
ws_endpoint = f"ws://localhost:5050/playwright/{profile.id}"
with sync_playwright() as playwright:
    browser = playwright.chromium.connect_over_cdp(endpoint_url=ws_endpoint, timeout=90_000)

    # Regular Playwright automation logic...</code></pre></div><p>Wonderful! Playwright is attached to the browser session managed by Kameleo Docker. You can now automate it using standard Playwright APIs as if it were a regular local Chromium instance.</p><h3>Step #6: Implement the Automation Logic</h3><p>To achieve the scraping goal, begin by inspecting the page to study its DOM structure:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zk-5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26be934e-c39d-4d8b-ade1-f7fef47d51fe_2688x1608.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zk-5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26be934e-c39d-4d8b-ade1-f7fef47d51fe_2688x1608.png 424w, https://substackcdn.com/image/fetch/$s_!zk-5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26be934e-c39d-4d8b-ade1-f7fef47d51fe_2688x1608.png 848w, https://substackcdn.com/image/fetch/$s_!zk-5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26be934e-c39d-4d8b-ade1-f7fef47d51fe_2688x1608.png 1272w, https://substackcdn.com/image/fetch/$s_!zk-5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26be934e-c39d-4d8b-ade1-f7fef47d51fe_2688x1608.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zk-5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26be934e-c39d-4d8b-ade1-f7fef47d51fe_2688x1608.png" width="1456" height="871" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/26be934e-c39d-4d8b-ade1-f7fef47d51fe_2688x1608.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:871,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Inspecting a product on the page&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Inspecting a product on the page" title="Inspecting a product on the page" srcset="https://substackcdn.com/image/fetch/$s_!zk-5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26be934e-c39d-4d8b-ade1-f7fef47d51fe_2688x1608.png 424w, https://substackcdn.com/image/fetch/$s_!zk-5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26be934e-c39d-4d8b-ade1-f7fef47d51fe_2688x1608.png 848w, https://substackcdn.com/image/fetch/$s_!zk-5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26be934e-c39d-4d8b-ade1-f7fef47d51fe_2688x1608.png 1272w, https://substackcdn.com/image/fetch/$s_!zk-5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26be934e-c39d-4d8b-ade1-f7fef47d51fe_2688x1608.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Inspecting a product on the page</figcaption></figure></div><p>Then, apply the following Playwright logic (connected to a Kameleo profile) to automate scraping on the JavaScript-rendered page:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># Connect Playwright to the Kameleo instance based on the configured profile
ws_endpoint = f'ws://localhost:5050/playwright/{profile.id}'
with sync_playwright() as playwright:
    browser = playwright.chromium.connect_over_cdp(endpoint_url=ws_endpoint, timeout=90_000)

    # Open a new page
    context = browser.contexts[0]
    page = context.new_page()

    # Visit target site
    page.goto("https://www.scrapingcourse.com/javascript-rendering")

    # Where to store the scraped data
    products = []

    # Wait for products to render
    page.wait_for_selector(".product-item")

    # Locate all product items
    product_elements = page.locator(".product-item")

    for i in range(product_elements.count()):
        # Select the nth product
        product_element = product_elements.nth(i)

        # Extract the product data
        name = product_element.locator(".product-name").inner_text()
        price = product_element.locator(".product-price").inner_text()
        image = product_element.locator("img.product-image").get_attribute("src")
        link = product_element.locator("a.product-link").get_attribute("href")

        # Populate a product object with the scraped data
        product = {
            "name": name,
            "price": price,
            "image": image,
            "url": link
        }
        # Append it to the products list
        products.append(product)</code></pre></div><p>The above snippet instructs the controlled browser to visit the target page, waits for product elements to load, then iterates through each product DOM node to extract structured fields (name, price, image, URL) and stores them in a Python list for downstream processing.</p><p>The Kameleo-powered automation script is almost complete. Only one step remains!</p><h3>Step #7: Stop the Kameleo Profile</h3><p>Normally, in a Playwright scenario, you would need to call <em>browser.close()</em> to terminate the browser session and release its resources.</p><p>In Kameleo, that&#8217;s not required. Instead, you only need to call:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">client.profile.stop_profile(profile_id=profile.id)</code></pre></div><p>The above line of code sends a close command to the browser via CDP. Once the browser actually stops, the Kameleo profile is terminated, too. This ensures that all resources associated with both the browser and the running profile are properly released.</p><h3>Step #8: Run the Script</h3><p>The final Playwright automation script, connecting via CDP to the stealth browser instance exposed by Kameleo Docker, will contain:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># pip install playwright kameleo-local-api-client

from kameleo.local_api_client import KameleoLocalApiClient
from kameleo.local_api_client.models import CreateProfileRequest
from playwright.sync_api import sync_playwright

# Connect to the Kameleo API client
client = KameleoLocalApiClient(endpoint="http://localhost:5050")
# Search for a real-world fingerprint and create a Kameleo profile based on it
fps = client.fingerprint.search_fingerprints(
    device_type="desktop",
    os_family="windows",
    browser_product="chrome",
    browser_version="&gt;145",
)
profile = client.profile.create_profile(
    CreateProfileRequest(fingerprint_id=fps[0].id, name="twsc demo")
)

# Connect Playwright to the Kameleo instance based on the configured profile
ws_endpoint = f'ws://localhost:5050/playwright/{profile.id}'
with sync_playwright() as playwright:
    browser = playwright.chromium.connect_over_cdp(endpoint_url=ws_endpoint, timeout=90_000)

    # Open a new page
    context = browser.contexts[0]
    page = context.new_page()

    # Visit target site
    page.goto("https://www.scrapingcourse.com/javascript-rendering")

    # Where to store the scraped data
    products = []

    # Wait for products to render
    page.wait_for_selector(".product-item")

    # Locate all product items
    product_elements = page.locator(".product-item")

    for i in range(product_elements.count()):
        # Select the nth product
        product_element = product_elements.nth(i)

        # Extract the product data
        name = product_element.locator(".product-name").inner_text()
        price = product_element.locator(".product-price").inner_text()
        image = product_element.locator("img.product-image").get_attribute("src")
        link = product_element.locator("a.product-link").get_attribute("href")

        # Populate a product object with the scraped data
        product = {
            "name": name,
            "price": price,
            "image": image,
            "url": link
        }
        # Append it to the products list
        products.append(product)

    # Print the scraped products
    for product in products:
        print(product)

# Stop the Kameleo profile
client.profile.stop_profile(profile_id=profile.id)</code></pre></div><p>Execute the script, and you should see output similar to this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!tH8f!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21c6244e-687f-4002-87e0-c421f94958bc_1692x893.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!tH8f!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21c6244e-687f-4002-87e0-c421f94958bc_1692x893.png 424w, https://substackcdn.com/image/fetch/$s_!tH8f!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21c6244e-687f-4002-87e0-c421f94958bc_1692x893.png 848w, https://substackcdn.com/image/fetch/$s_!tH8f!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21c6244e-687f-4002-87e0-c421f94958bc_1692x893.png 1272w, https://substackcdn.com/image/fetch/$s_!tH8f!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21c6244e-687f-4002-87e0-c421f94958bc_1692x893.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!tH8f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21c6244e-687f-4002-87e0-c421f94958bc_1692x893.png" width="1456" height="768" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/21c6244e-687f-4002-87e0-c421f94958bc_1692x893.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:768,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The output produced by the automation script&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The output produced by the automation script" title="The output produced by the automation script" srcset="https://substackcdn.com/image/fetch/$s_!tH8f!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21c6244e-687f-4002-87e0-c421f94958bc_1692x893.png 424w, https://substackcdn.com/image/fetch/$s_!tH8f!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21c6244e-687f-4002-87e0-c421f94958bc_1692x893.png 848w, https://substackcdn.com/image/fetch/$s_!tH8f!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21c6244e-687f-4002-87e0-c421f94958bc_1692x893.png 1272w, https://substackcdn.com/image/fetch/$s_!tH8f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21c6244e-687f-4002-87e0-c421f94958bc_1692x893.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The output produced by the automation script</figcaption></figure></div><p>Notice how the script successfully scraped product data from the JavaScript-rendered page.</p><p>Once execution completes, open the Kameleo web GUI, and you&#8217;ll notice that the &#8220;twsc demo&#8221; profile is now marked as &#8220;TERMINATED&#8221;:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zZCP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8db45310-8917-4422-8ff1-02ddda4896c3_3057x1613.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zZCP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8db45310-8917-4422-8ff1-02ddda4896c3_3057x1613.png 424w, https://substackcdn.com/image/fetch/$s_!zZCP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8db45310-8917-4422-8ff1-02ddda4896c3_3057x1613.png 848w, https://substackcdn.com/image/fetch/$s_!zZCP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8db45310-8917-4422-8ff1-02ddda4896c3_3057x1613.png 1272w, https://substackcdn.com/image/fetch/$s_!zZCP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8db45310-8917-4422-8ff1-02ddda4896c3_3057x1613.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zZCP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8db45310-8917-4422-8ff1-02ddda4896c3_3057x1613.png" width="1456" height="768" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8db45310-8917-4422-8ff1-02ddda4896c3_3057x1613.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:768,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Note the updated status of the Kameleo profile&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Note the updated status of the Kameleo profile" title="Note the updated status of the Kameleo profile" srcset="https://substackcdn.com/image/fetch/$s_!zZCP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8db45310-8917-4422-8ff1-02ddda4896c3_3057x1613.png 424w, https://substackcdn.com/image/fetch/$s_!zZCP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8db45310-8917-4422-8ff1-02ddda4896c3_3057x1613.png 848w, https://substackcdn.com/image/fetch/$s_!zZCP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8db45310-8917-4422-8ff1-02ddda4896c3_3057x1613.png 1272w, https://substackcdn.com/image/fetch/$s_!zZCP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8db45310-8917-4422-8ff1-02ddda4896c3_3057x1613.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Note the updated status of the Kameleo profile</figcaption></figure></div><p>That doesn&#8217;t mean the profile is gone forever. Quite the opposite!</p><p>Kameleo profiles are reusable, meaning you can retrieve and start them again later to continue the browsing session with the same fingerprint and persisted state. I&#8217;ll cover exactly how to do that in a dedicated FAQ.</p><p>Mission complete! You just learned how to use Kameleo Docker for Playwright automation. With very similar logic, you can automate Puppeteer, Selenium, or any other CDP-compatible solution, in both Python and JavaScript.</p><h3>Pricing Model</h3><p>Kameleo Docker is included across all plans at no additional cost. You get the same core limits (concurrent browsers, number of profiles, and browser usage time) regardless of whether you run the desktop app or the containerized version. So, <a href="https://kameleo.io/pricing">take a look at the official pricing page</a> for more information.</p><h2>Anti-Bot Performance Benchmarks</h2><p>To test Kameleo Docker, I ran a simple script against one page protected by each major anti-bot detection system. The results are shown below:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kcyp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26a58275-3f59-4452-93a9-a8cf18f700b7_1536x486.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kcyp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26a58275-3f59-4452-93a9-a8cf18f700b7_1536x486.png 424w, https://substackcdn.com/image/fetch/$s_!kcyp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26a58275-3f59-4452-93a9-a8cf18f700b7_1536x486.png 848w, https://substackcdn.com/image/fetch/$s_!kcyp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26a58275-3f59-4452-93a9-a8cf18f700b7_1536x486.png 1272w, https://substackcdn.com/image/fetch/$s_!kcyp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26a58275-3f59-4452-93a9-a8cf18f700b7_1536x486.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kcyp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26a58275-3f59-4452-93a9-a8cf18f700b7_1536x486.png" width="1456" height="461" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/26a58275-3f59-4452-93a9-a8cf18f700b7_1536x486.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:461,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Vanilla Playwright (headless) vs vanilla Playwright (headful) vs Kameleo Docker&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Vanilla Playwright (headless) vs vanilla Playwright (headful) vs Kameleo Docker" title="Vanilla Playwright (headless) vs vanilla Playwright (headful) vs Kameleo Docker" srcset="https://substackcdn.com/image/fetch/$s_!kcyp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26a58275-3f59-4452-93a9-a8cf18f700b7_1536x486.png 424w, https://substackcdn.com/image/fetch/$s_!kcyp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26a58275-3f59-4452-93a9-a8cf18f700b7_1536x486.png 848w, https://substackcdn.com/image/fetch/$s_!kcyp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26a58275-3f59-4452-93a9-a8cf18f700b7_1536x486.png 1272w, https://substackcdn.com/image/fetch/$s_!kcyp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26a58275-3f59-4452-93a9-a8cf18f700b7_1536x486.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Vanilla Playwright (headless) vs vanilla Playwright (headful) vs Kameleo Docker</figcaption></figure></div><p><strong>Note</strong>: All tests were performed locally using my ISP&#8217;s residential IP address.</p><p>As shown above, in this basic experiment, Kameleo Docker achieved a 100% success rate. In contrast, Playwright consistently failed in headless mode and, in some cases, also struggled in headful mode.</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/p/consulting&quot;,&quot;text&quot;:&quot;Need help with your scraping project?&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://substack.thewebscraping.club/p/consulting"><span>Need help with your scraping project?</span></a></p><div><hr></div><h3>Final Thoughts on Kameleo Docker</h3><p><em>What stood out when I met Barnabas at <a href="https://www.praguecrawl.com/">Prague Crawl 2026</a> (see you next year &#128521;</em>)<em> was the clear passion the team has for the project, along with their focus on quality and continuous improvement.</em></p><p><em>At the same time, when testing new products, especially technical and complex ones like Kameleo Docker, you usually stumble across bugs or unexpected behavior. I can confidently say that this wasn&#8217;t the case at all here. Everything ran smoothly from the beginning, and I didn&#8217;t encounter any issues&#8230;</em></p><p>On top of that, t<em>he benchmark results are promising, and I didn&#8217;t notice any significant performance lag. Thus, my honest takeaway is simple: if you&#8217;re looking for a production-ready, containerized stealth browser, or you&#8217;re simply passionate about automation and scraping, consider giving Kameleo Docker a try!</em></p><p>In this article, I covered what the project is about, what it offers, how it works, and how to use it. As always, remember to use Kameleo Docker only for legal and <a href="https://substack.thewebscraping.club/p/best-practices-for-ethical-web-scraping">ethical web scraping and automation</a>. Until next time!</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Web Scraping Club is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>FAQ</h2><h3>Is Kameleo Docker different from the desktop app?</h3><p>Kameleo Docker differs from the desktop app mainly in deployment. Instead of a local GUI, it runs as a containerized service that runs on both Linux and Windows servers. Feature parity is largely preserved, including profiles, fingerprinting, and browser engines.</p><h3>Can I reuse already created profiles in Kameleo Docker?</h3><p>Yes! Profiles can be reused by retrieving the full list of profiles, filtering by name (or ID, if you know it), and then starting the desired profile. For example, to reuse the &#8220;twsc demo&#8221; profile, write:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from kameleo.local_api_client import KameleoLocalApiClient
from kameleo.local_api_client.models import ProfileLifetimeState

# Connect to the Kameleo API client
client = KameleoLocalApiClient(endpoint="http://localhost:5050")

# Fetch all available profiles
profiles = client.profile.list_profiles()

# Find the profile with the specific name
target_name = "twsc demo"
profile = next((p for p in profiles if p.name == target_name), None)

# Check if the profile was found
if profile:
    # Start the existing profile if it isn't already running
    if profile.status.lifetime_state != ProfileLifetimeState.RUNNING:
      client.profile.start_profile(profile.id)</code></pre></div><h3>Can I use Kameleo Docker in my CI/CD?</h3><p>Kameleo Docker fits naturally into CI/CD pipelines by running as a disposable, reproducible container in build or test stages. You can spin up browsers on demand, run automated flows, and tear them down after execution. Configuration is <a href="https://developer.kameleo.io/integrations/docker/#example-with-docker-compose">typically handled via Docker Compose</a>.</p><h3>Does Kameleo Docker support proxy integration?</h3><p>Yes! HTTP, HTTPS, and SOCKS proxies can be configured at profile creation time, <a href="https://developer.kameleo.io/tutorials/using-proxy-servers/">as explained in the documentation</a>.</p><h3>Can Kameleo Docker scale to thousands of browsers?</h3><p>Kameleo Docker supports horizontal scaling through standard orchestration tools. You can run multiple containers across clusters using Kubernetes or <a href="https://developer.kameleo.io/integrations/docker/#aws-ecs-support">AWS ECS</a>, each managing independent browser instances.</p><h3>How Does Firefox Automation Work in Kameleo Docker?</h3><p>Kameleo Docker supports Firefox-based automation through the Junglefox engine. Because Playwright cannot connect directly to Firefox-based sessions, Kameleo provides a <em><a href="https://developer.kameleo.io/integrations/docker/#using-junglefox-playwright-pw-bridge">pw-bridge</a></em><a href="https://developer.kameleo.io/integrations/docker/#using-junglefox-playwright-pw-bridge"> helper</a> that acts as a compatibility layer. This component translates Playwright connections into the correct browser session, allowing standard automation scripts to run unchanged while still using Firefox-based fingerprint profiles.</p><div><hr></div><p><em>Did you like this article? Share it with someone who might find it useful and get a discount on paid plans.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/p/kameleo-docker-exploring-the-docker?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://substack.thewebscraping.club/p/kameleo-docker-exploring-the-docker?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><p></p>]]></content:encoded></item><item><title><![CDATA[THE LAB #107: Reversing Shopee's native crypto with Ghidra]]></title><description><![CDATA[Shopee hides its crypto in a native library. We read it in Ghidra and rebuild it in Python, byte for byte.]]></description><link>https://substack.thewebscraping.club/p/reversing-shopee-app-ghidra</link><guid isPermaLink="false">https://substack.thewebscraping.club/p/reversing-shopee-app-ghidra</guid><dc:creator><![CDATA[Pierluigi Vinciguerra]]></dc:creator><pubDate>Thu, 11 Jun 2026 22:19:00 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/988c1c72-086f-45da-89ea-a3074ccdcc0c_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Shopee is one of the largest marketplaces in Southeast Asia, and like most big apps, its mobile API is a better scraping target than its website. The app talks to the backend in plain JSON over HTTPS, the endpoints are stable, and the anti-bot layer is usually lighter than the one guarding the web frontend. We covered the easy version of this in <a href="https://substack.thewebscraping.club/p/the-lab-12-reverse-engineering-mobile">The Lab #12</a>, where Charles and JADX were enough to read an Android app&#8217;s traffic and replay it.</p><blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gCo7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gCo7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png" width="626" height="313" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:626,&quot;bytes&quot;:390972,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/197764148?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gCo7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Before proceeding, let me thank NetNut, the platinum partner of the month. Their set of solutions cover all your needs for scraping.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack&quot;,&quot;text&quot;:&quot;Visit Netnut&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack"><span>Visit Netnut</span></a></p></blockquote><blockquote><div><hr></div></blockquote><p>Shopee does not hand you the easy version. Capture a request, replay it, and the backend answers with HTTP 418 and a security error code. Every API call carries a set of anti-fraud headers, and the code that builds them is not in the Java you can read with JADX. It sits in native code, in compiled <code>.so</code> libraries, which is exactly where traffic interception and a Java decompiler stop being useful. Open the app in JADX and the signing method is there in name only, declared <code>native</code>, with its body on the far side in ARM machine code.</p><p>This is a two-part investigation into how Shopee signs its API requests and how you reproduce that signing yourself. The strategy is the one that works on most hardened apps. You locate the native security libraries, open them in a disassembler, and turn what they do back into something you control. When a library is readable crypto, you reimplement it in Python and sign offline, at any volume, with no app in the loop. When it is a bytecode virtual machine you cannot practically rewrite, you keep the app running and drive its own signer as an oracle. We chose this route because it is the one that scales. An offline signer, or an oracle you call, runs inside your scraper on a server. A rooted phone you have to babysit does not.</p><p>This first part is the foundation. We take one of Shopee&#8217;s native libraries, <code>libshopeeaegis.so</code>, reverse it end to end with Ghidra and rebuild it in Python. Reading the decompiled code identifies every operation as textbook crypto, and our rebuild reproduces those algorithms byte for byte. It is the readable case, the kind you win cleanly, and the clearest worked example of the method. The second part takes on the harder library, the one that computes the per-request signature, and gets past it with the oracle approach.</p><p>What you take from each part depends on your goal. If Shopee is your target, the payoff is the full picture of its request signing across both parts. If you scrape other apps, the method matters more than the marketplace. Most apps that protect their API at all push the work into a native library, and a large share of those are plain, readable crypto you can reproduce. We work it out on Aegis here, and it is the same move on the next app you open.</p><div><hr></div><blockquote><p><em>For your scraping needs, having a reliable proxy provider like <strong>Decodo</strong> on your side improves the chances of success. </em></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://visit.decodo.com/WyQ3mA" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qJZE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png" width="401" height="225.5625" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fec072bc-2ac2-49bf-8084-43858298808f_800x450.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:450,&quot;width&quot;:800,&quot;resizeWidth&quot;:401,&quot;bytes&quot;:554497,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://visit.decodo.com/WyQ3mA&quot;,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/187315628?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!qJZE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1456w" sizes="100vw"></picture><div></div></div></a><figcaption class="image-caption"></figcaption></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://visit.decodo.com/WyQ3mA&quot;,&quot;text&quot;:&quot;Try Decodo Now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://visit.decodo.com/WyQ3mA"><span>Try Decodo Now</span></a></p></blockquote><div><hr></div><h2>The tools</h2><p>We use four tools, each doing one job.</p><p><a href="https://github.com/androguard/androguard">androguard</a> is a Python library for static APK analysis. We use it for fast recon. It lists the native libraries an app ships and finds which classes declare <code>native</code> methods. It does not give you readable source. It gives you structure you can script.</p><p><a href="https://github.com/skylot/jadx">JADX</a> decompiles Dalvik bytecode back to Java. It is how you read the managed side of the app and find the exact class and method that crosses into native code. JADX stops at the <code>native</code> keyword, which is the handoff point to the next tool.</p><p><a href="https://github.com/NationalSecurityAgency/ghidra">Ghidra</a> is the NSA&#8217;s open source reverse engineering framework. It disassembles a <code>.so</code> and decompiles it to pseudo C. It is the only tool here that can read native code, and it is the one this article leans on.</p><p><a href="https://github.com/frida/frida">Frida</a> injects a JavaScript engine into a running process so you can hook and call functions live. We use it to run the app under instrumentation and confirm our static reading against what the app actually does.</p><p>JADX and androguard read the managed code. Ghidra reads the native code. Frida watches the code run. The native library is the one piece only Ghidra can open, so the work centers there.</p><h2>Modeling the app&#8217;s defenses</h2><p>Before opening anything, it helps to name the layers, because Shopee has several and only one is our target here.</p><p>The managed layer is the Java and Kotlin code. It builds requests, attaches headers, and calls into native methods. JADX reads it.</p><p>The native layer is a set of <code>.so</code> libraries the app loads. Pull the arm64 split out of the APK with androguard and the security-relevant ones stand out by name. They are <code>libshopeeaegis.so</code>, <code>libshpssdk.so</code>, and <code>libBkeBizSecurity.so</code>, plus <code>libjnihook.so</code> and <code>libshook.so</code>. The last two are a hooking framework and an anti-hook layer, which means the app actively watches for instrumentation. That matters for Frida later.</p><p>The request-signing layer sits on top. Two okhttp interceptors, <code>com.shopee.app.network.antifraud.b</code> and <code>.d</code> (they call themselves <code>SecurityNewSapInterceptor</code> and <code>SecurityNewSapPostInterceptor</code>), attach the anti-fraud headers <code>af-ac-enc-sz-token</code> and <code>x-sap-ri</code> to API requests. The values they attach come from <code>libshpssdk.so</code>, the Shopee Security SDK.</p><p>We target one layer, <code>libshopeeaegis.so</code>, a general-purpose crypto library the app calls for specific operations. The request signer in <code>libshpssdk.so</code> stays out of scope. It is a bytecode virtual machine, a harder problem that we handle separately, and reproducing the <code>af-ac</code> headers is not the promise here. The promise is that you can take <code>libshopeeaegis.so</code>, understand every operation it performs, and reproduce it byte for byte in Python.</p><p>One detail decides whether that promise holds. <code>libshopeeaegis.so</code> loads only when the app needs it, so it is not present at idle. We watched the process maps over a minute of normal browsing and the library never appeared. The crypto we are about to reverse is a toolbox the app reaches for in certain flows, not the thing running on every request.</p><div><hr></div><blockquote><p><strong><a href="https://byteful.com?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE">Start your scraping journey with Byteful</a></strong>: 10GB New Customer Trial | Use TWSC for 15% OFF | $1.75/GB Residential Data | ISP Proxies in 15+ Countries</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://byteful.com/?promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EOo9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 424w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 848w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1272w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EOo9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png" width="630" height="69.35779816513761" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:120,&quot;width&quot;:1090,&quot;resizeWidth&quot;:630,&quot;bytes&quot;:133037,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://byteful.com/?promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/194398731?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!EOo9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 424w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 848w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1272w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://byteful.com?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE&quot;,&quot;text&quot;:&quot;Claim your 10GB here&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://byteful.com?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE"><span>Claim your 10GB here</span></a></p></blockquote><div><hr></div><h2>Getting the library and finding the door</h2><p>We pulled Shopee PH 3.75.24 (<code>com.shopee.ph</code>) as an XAPK and unzipped it. The native libraries are not in <code>base.apk</code>. For a split bundle they live in <code>config.arm64_v8a.apk</code>. Listing the <code>.so</code> files with androguard and <code>unzip -l</code>, <code>libshopeeaegis.so</code> is a small one at 280 KB, which is a good sign. Small means little room for a heavy obfuscator.</p><p>androguard answers the first question, which library to open. It does not answer the second, how the app calls it. For that we go to JADX and find the class on the Java side. The library registers its native methods against <code>com.shopee.sz.reinforce.Aegis</code>. The class exposes a method <code>fire</code>, overloaded, declared <code>native</code>. Two of the overloads matter:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;820ec8fb-46fc-4f0c-9553-0dc70f676be0&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">native byte[] fire(int mode, byte[] data)
native byte[] fire(int mode, byte[] data, byte[] key)</code></pre></div><p>This is the door. The first argument is an integer mode. Then one or two byte arrays. The return is a byte array. JADX cannot show what <code>fire</code> does, because the body is in the <code>.so</code>. So we open the <code>.so</code>.</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.youtube.com/@thewebscrapingclub&quot;,&quot;text&quot;:&quot;Check the TWSC YouTube Channel&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.youtube.com/@thewebscrapingclub"><span>Check the TWSC YouTube Channel</span></a></p><div><hr></div><h2>Reading the library with Ghidra</h2><p>We ran Ghidra 12.1.2 in headless mode. It runs without a GUI, it scripts cleanly, and it repeats exactly. The workflow is documented in our Ghidra tool skill you can use in Claude Code, just like I did for this test. In short, you import the <code>.so</code>, let auto analysis run, then run a script that decompiles functions to a file.</p><pre><code><code>support/analyzeHeadless /tmp/proj aegis \
  -import config.arm64_v8a/lib/arm64-v8a/libshopeeaegis.so \
  -scriptPath ./scripts \
  -postScript DecompileExport.java out.c \
  -overwrite</code></code></pre><p>Auto analysis finished in nine seconds and the decompiler produced 674 functions with zero failures. That number alone tells you this is not a packed or virtualized binary. A protected library fights the decompiler; this one did not.</p><p>The first useful function is <code>JNI_OnLoad</code>, which every JNI library runs at load time. Read its pseudo C and it looks up the class <code>com/shopee/sz/reinforce/Aegis</code> and calls <code>RegisterNatives</code> with two methods. That confirms the door from the Java side and tells us the native functions are registered dynamically rather than exported under <code>Java_*</code> names. Dynamic registration is a mild form of hiding, and it is exactly what Ghidra&#8217;s JNI handling and a <code>RegisterNatives</code> trace are for.</p><p>The C++ symbols survived. That is the break that makes this library readable. The class is <code>Aegis</code>, with methods named <code>missileFire</code>, <code>missileCount</code>, <code>prism</code>, <code>snowDon</code>, <code>tugWar</code>, and <code>parse</code>. There is a second class, <code>TeslaModel</code>, with <code>model_3</code>, <code>model_a</code>, <code>model_b</code>, <code>model_c</code>, <code>model_e</code>, <code>model_s</code>, <code>model_x</code>, <code>model_y</code>, and <code>getNuremberg</code>. The names are deliberately silly, a Tesla and military theme, but they are real symbols, and the structure is intact.</p><p>Follow the call chain from the registered native function. The dispatcher is <code>Aegis::prism</code>, a plain <code>switch</code> on the mode integer:</p><pre><code><code>switch(param_1) {
case 0:  model_3(...)               // one input
case 1:  model_x(key, input, ...)   // keyed
case 2:  model_x(...); model_3(...) // keyed, then case 0
case 3:  model_y(...); model_3(...)
case 4:  model_e(...)
case 5:  model_a(...)
case 6:  model_b(...)
case 7:  model_s(...); model_3(...)
case 8:  model_c(...); model_3(...)
}</code></code></pre><p>One native call selects one of nine operations by an integer, and some operations are a keyed primitive followed by <code>model_3</code>. To name each operation we read two things, the output size and the primitive body.</p><p>The output size comes from <code>TeslaModel::getNuremberg(mode, len)</code>, which <code>missileFire</code> calls to size the output buffer before doing the work. It returns 16 for mode 4, 32 for mode 5, 64 for mode 6, and 20 for mode 8. Those are the digest sizes of MD5, SHA-256, SHA-512, and SHA-1. For mode 0 it returns the Base64 expansion of the input length. The size function alone half-names the table.</p><p>The bodies confirm the rest, and here the silly names get helpful, because the renamed primitives kept their original suffixes. <code>model3_autopilot</code> is a textbook Base64 encoder. It reads three bytes, writes four, and pads with <code>0x3d</code>, which is the <code>=</code> character. <code>modelx_autopilot_cbc</code> is AES in CBC mode, recognizable because it XORs each 16 byte block with the previous ciphertext block before the round function. The hash contexts are renamed with a <code>phantom</code> and <code>F</code> theme but keep the gnulib <code>_init_ctx</code> / <code>_process_bytes</code> / <code>_finish_ctx</code> shape. <code>phantom1</code> is SHA-1, <code>phantom256</code> is SHA-256, <code>InitF22</code> is SHA-512. And <code>phantom1</code> as called by <code>model_c</code> is the HMAC form. It XORs the key with <code>0x36</code> for the inner pad and <code>0x5c</code> for the outer pad over a 64 byte block, which is the HMAC construction.</p><p>Two of the keyed modes turned out not to be ciphers at all. <code>model_s</code> calls <code>phantom256</code> with a key and a message and returns 32 bytes, so it is HMAC-SHA256. <code>model_c</code> calls <code>phantom1</code> the same way and returns 20 bytes, so it is HMAC-SHA1. Reading the bodies kept us honest here. From the signatures alone we had guessed AES.</p><p>That gives the full table.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!x9os!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6e2490a-4994-4d2a-aa8d-9a95b697ca27_719x403.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!x9os!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6e2490a-4994-4d2a-aa8d-9a95b697ca27_719x403.png 424w, https://substackcdn.com/image/fetch/$s_!x9os!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6e2490a-4994-4d2a-aa8d-9a95b697ca27_719x403.png 848w, https://substackcdn.com/image/fetch/$s_!x9os!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6e2490a-4994-4d2a-aa8d-9a95b697ca27_719x403.png 1272w, https://substackcdn.com/image/fetch/$s_!x9os!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6e2490a-4994-4d2a-aa8d-9a95b697ca27_719x403.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!x9os!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6e2490a-4994-4d2a-aa8d-9a95b697ca27_719x403.png" width="719" height="403" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a6e2490a-4994-4d2a-aa8d-9a95b697ca27_719x403.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:403,&quot;width&quot;:719,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:42068,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/201664746?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6e2490a-4994-4d2a-aa8d-9a95b697ca27_719x403.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!x9os!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6e2490a-4994-4d2a-aa8d-9a95b697ca27_719x403.png 424w, https://substackcdn.com/image/fetch/$s_!x9os!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6e2490a-4994-4d2a-aa8d-9a95b697ca27_719x403.png 848w, https://substackcdn.com/image/fetch/$s_!x9os!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6e2490a-4994-4d2a-aa8d-9a95b697ca27_719x403.png 1272w, https://substackcdn.com/image/fetch/$s_!x9os!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6e2490a-4994-4d2a-aa8d-9a95b697ca27_719x403.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><code>model_x</code> pads with PKCS7 to a 16 byte boundary, then runs AES-CBC. The key length sets the variant, and a 16 byte key gives AES-128. <code>model_y</code> does the same but writes the IV in front of the ciphertext, the standard prepend-the-IV pattern, before the Base64 in mode 3.</p><p>One value is not in the file. The CBC IV is a fixed 16 byte constant the library keeps at a <code>.bss</code> address. <code>.bss</code> is zero-initialized on disk and filled at runtime, so the IV is set when the library initializes and you cannot read it statically. For the hash, HMAC, and Base64 modes that does not matter, because their output is fully determined by the input and key. For the three AES modes it means byte-identical output needs the real IV, which you read from the live process once the library loads.</p><p>As always, the code that will be used for the python reimplementation we&#8217;re showing now can be found&nbsp;<a href="https://github.com/TheWebScrapingClub/thelab">in our GitHub repository reserved for paying users, inside the folder&nbsp;</a><strong><a href="https://github.com/TheWebScrapingClub/thelab">107.</a></strong><a href="https://github.com/TheWebScrapingClub/thelab">SHOPEE-GHIDRA</a><strong><a href="https://github.com/TheWebScrapingClub/thelab">.</a></strong></p>
      <p>
          <a href="https://substack.thewebscraping.club/p/reversing-shopee-app-ghidra">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[How to Give Claude Real-Time Web Access With the Decodo MCP]]></title><description><![CDATA[Learn how to connect Claude to the web with zero integration code]]></description><link>https://substack.thewebscraping.club/p/claude-decodo-mcp-how-to</link><guid isPermaLink="false">https://substack.thewebscraping.club/p/claude-decodo-mcp-how-to</guid><dc:creator><![CDATA[Federico Trotta]]></dc:creator><pubDate>Sun, 07 Jun 2026 19:37:32 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/0ca9845f-9623-47e0-9aa6-3e40da60ddb6_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you&#8217;ve spent serious time scraping, you know the real work isn&#8217;t parsing HTML: it&#8217;s surviving Cloudflare, rotating proxies, handling CAPTCHAs, and pretending to be a human long enough to get the data you need.</p><p>Now, <a href="https://substack.thewebscraping.club/p/the-evolving-career-scrapers">since the rise of AI and LLMs, every office job has changed, with no exceptions for scraping professionals</a>. For us, this means that surviving anti-bots and CAPTCHAs has become the first challenge when creating scraping pipelines. The second challenge has become the integration of scraping services and capabilities into AI pipelines and AI agents. The reason for this shift is simple: your boss or your clients don&#8217;t just want the data anymore. They want it in real-time, structured, and often packed with some insights.</p><p>Until some months ago, these pipelines required a huge amount of custom code (and time!). Luckily for us, the major struggle lasted until MCPs were first released.</p><p>In this article, you&#8217;ll learn what MCP is, how the Decodo MCP server works, and how to integrate it with Claude Desktop. You&#8217;ll also learn how to use it with two hands-on examples.</p><p>Let&#8217;s get into it!</p><blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LobM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F526bd4b2-5ff9-4fa2-9bf7-d5fb0134e98c_1920x1081.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LobM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F526bd4b2-5ff9-4fa2-9bf7-d5fb0134e98c_1920x1081.png 424w, https://substackcdn.com/image/fetch/$s_!LobM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F526bd4b2-5ff9-4fa2-9bf7-d5fb0134e98c_1920x1081.png 848w, https://substackcdn.com/image/fetch/$s_!LobM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F526bd4b2-5ff9-4fa2-9bf7-d5fb0134e98c_1920x1081.png 1272w, https://substackcdn.com/image/fetch/$s_!LobM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F526bd4b2-5ff9-4fa2-9bf7-d5fb0134e98c_1920x1081.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LobM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F526bd4b2-5ff9-4fa2-9bf7-d5fb0134e98c_1920x1081.png" width="526" height="296.2362637362637" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/526bd4b2-5ff9-4fa2-9bf7-d5fb0134e98c_1920x1081.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:820,&quot;width&quot;:1456,&quot;resizeWidth&quot;:526,&quot;bytes&quot;:1650775,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/164781961?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F526bd4b2-5ff9-4fa2-9bf7-d5fb0134e98c_1920x1081.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!LobM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F526bd4b2-5ff9-4fa2-9bf7-d5fb0134e98c_1920x1081.png 424w, https://substackcdn.com/image/fetch/$s_!LobM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F526bd4b2-5ff9-4fa2-9bf7-d5fb0134e98c_1920x1081.png 848w, https://substackcdn.com/image/fetch/$s_!LobM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F526bd4b2-5ff9-4fa2-9bf7-d5fb0134e98c_1920x1081.png 1272w, https://substackcdn.com/image/fetch/$s_!LobM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F526bd4b2-5ff9-4fa2-9bf7-d5fb0134e98c_1920x1081.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Before proceeding, let me thank NetNut, the platinum partner of the month. They have prepared a juicy offer for you: up to <strong>1 TB of web unblocker</strong> for free.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://netnut.io/unblocker&quot;,&quot;text&quot;:&quot;Claim your offer&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://netnut.io/unblocker"><span>Claim your offer</span></a></p></blockquote><blockquote><div><hr></div></blockquote><h3>What is MCP?</h3><p><a href="https://modelcontextprotocol.io/docs/getting-started/intro">The Model Context Protocol (MCP)</a> is an open standard introduced by Anthropic that defines how AI models connect to external tools, data sources, and services. Before MCP, every integration between an AI model and an external system had to be built from scratch. MCP replaces all of the complexity behind the integration with a single, shared protocol.</p><p>Basically, the MCP protocol acts as a common language for models to connect with external tools, files, and systems. For example: do you need an AI assistant to pull a file from Google Drive, query a company database, and trigger an action in an internal app? That&#8217;s exactly the kind of job MCP is built to handle!</p><p>The practical upside is composability. Developers can mix and match several MCP servers into a single AI application without writing any integration code. So, after more than a year since its introduction in the AI industry, MCP has become the standard for integrating different services and applications into a single AI software.</p><div><hr></div><blockquote><p><em>For your scraping needs, having a reliable proxy provider like <strong>Decodo</strong> on your side improves the chances of success. </em></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qJZE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qJZE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png" width="401" height="225.5625" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fec072bc-2ac2-49bf-8084-43858298808f_800x450.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:450,&quot;width&quot;:800,&quot;resizeWidth&quot;:401,&quot;bytes&quot;:554497,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/187315628?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!qJZE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption"></figcaption></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://visit.decodo.com/WyQ3mA&quot;,&quot;text&quot;:&quot;Try Decodo Now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://visit.decodo.com/WyQ3mA"><span>Try Decodo Now</span></a></p></blockquote><div><hr></div><h3>What is Decodo MCP?</h3><p>The <a href="https://visit.decodo.com/9VzKKe">Decodo MCP Server</a> is a web scraping layer for AI agents. It connects MCP-compatible clients to Decodo&#8217;s Web Scraping API, enabling:</p><ul><li><p><strong>Web scraping for LLMs and AI agents without managing infrastructure:</strong> It can scrape any website, including JavaScript-heavy pages, and get real-time data without handling proxy rotation, CAPTCHA solving, or anti-bot systems. It is specifically built for RAG pipelines, AI research agents, and automation flows.</p></li><li><p><strong>Structured outputs for LLM workflows</strong>: It allows you to retrieve the scraped data in Markdown (LLM-ready), JSON (for structured pipelines), and screenshots (for visual context).</p></li></ul><p>As of now, the Decodo MCP server exposes the following tools:</p><ul><li><p><em>scrape_as_markdown</em>: Scrapes any target URL, given a target URL via prompt. Returns results in Markdown.</p></li><li><p><em>screenshot</em>: Captures a screenshot of any webpage and returns it as a PNG image.</p></li><li><p><em>google_search</em>: Scrapes Google Search for a given query, and returns parsed results.</p></li><li><p><em>google_ads</em>: Scrapes Google Ads search results.</p></li><li><p><em>google_lens</em>: Scrapes Google Lens image search results.</p></li><li><p><em>google_ai_mode</em>: Scrapes Google AI Mode (Search with AI) results.</p></li><li><p><em>google_travel_hotels</em>: Scrapes Google Travel Hotels search results.</p></li><li><p><em>amazon_search</em>: Scrapes Amazon Search for a given query, and returns parsed results.</p></li><li><p><em>amazon_product</em>: Scrapes a given Amazon Product page.</p></li><li><p><em>amazon_pricing</em>: Scrapes Amazon Product pricing information.</p></li><li><p><em>amazon_sellers</em>: Scrapes Amazon Seller information.</p></li><li><p><em>amazon_bestsellers</em>: Scrapes Amazon Bestsellers list.</p></li><li><p><em>walmart_search</em>: Scrapes Walmart Search for a given query, and returns parsed results.</p></li><li><p><em>walmart_product</em>: Scrapes Walmart Product page.</p></li><li><p><em>target_search</em>: Scrapes Target Search for a given query, and returns parsed results.</p></li><li><p><em>target_product</em>: Scrapes Target Product page.</p></li><li><p><em>tiktok_post</em>: Scrapes a TikTok post URL.</p></li><li><p><em>tiktok_shop_search</em>: Scrapes TikTok Shop Search for a given query, and returns parsed results.</p></li><li><p><em>tiktok_shop_product</em>: Scrapes TikTok Shop Product page.</p></li><li><p><em>tiktok_shop_url</em>: Scrapes TikTok Shop page by URL.</p></li><li><p><em>youtube_metadata</em>: Scrapes YouTube video metadata.</p></li><li><p><em>youtube_channel</em>: Scrapes YouTube channel videos.</p></li><li><p><em>youtube_subtitles</em>: Scrapes YouTube video subtitles.</p></li><li><p><em>youtube_search</em>: Search YouTube videos.</p></li><li><p><em>reddit_post</em>: Scrapes a specific Reddit post.</p></li><li><p><em>reddit_subreddit</em>: Scrapes Reddit subreddit results.</p></li><li><p><em>reddit_user</em>: Scrapes a Reddit user profile and their posts and comments.</p></li><li><p><em>bing_search</em>: Scrapes Bing Search results.</p></li><li><p><em>chatgpt</em>: Search and interact with ChatGPT for AI-powered responses and conversations.</p></li><li><p><em>perplexity</em>: Search and interact with Perplexity for AI-powered responses and conversations.</p></li></ul><p><strong>NOTE</strong>: Decodo is currently onboarding its MCP server onto various platforms and marketplaces. At the moment of writing this article, it can be found on the <a href="https://registry.modelcontextprotocol.io/?q=decodo">Official MCP registry</a>, <a href="https://www.pulsemcp.com/servers/decodo">Pulse MCP</a>, <a href="https://glama.ai/mcp/servers?query=decodo">Glama AI</a>, <a href="https://mcp.so/explore?q=decodo">mcp.so</a>, and <a href="https://mcpmarket.com/server/decodo">mcpmarket.com</a>.</p><h2>How To Integrate The Decodo MCP With Claude</h2><p>The prerogative of MCPs is to integrate them with LLMs. The <a href="https://github.com/Decodo/mcp-server">Decodo MCP server</a> can be integrated with several services that leverage LLMs, like Claude and Cursor. In this paragraph, you will learn how to integrate it with Claude.</p><h3>Requirements</h3><p>To use the Decodo MCP, your system must satisfy the following requirements:</p><ul><li><p><strong>Claude Desktop</strong>: To integrate the Decodo MCP server with Claude, you need <a href="https://claude.com/download">Claude Desktop. If you don&#8217;t have it installed yet, you can download it from their website.</a></p></li><li><p><strong>Decodo account:</strong> Create an account at <a href="https://dashboard.decodo.com/">dashboard.decodo.com</a>. With a free one, you have up to 2K free requests.</p></li><li><p><strong>Scraping token</strong>: Get the basic authentication token. To get it, click on <strong>Web Scraping API</strong> &gt; <strong>API playground:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0R6j!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7918e5fb-52b6-4cea-a8d1-40738a743619_1900x660.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0R6j!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7918e5fb-52b6-4cea-a8d1-40738a743619_1900x660.png 424w, https://substackcdn.com/image/fetch/$s_!0R6j!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7918e5fb-52b6-4cea-a8d1-40738a743619_1900x660.png 848w, https://substackcdn.com/image/fetch/$s_!0R6j!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7918e5fb-52b6-4cea-a8d1-40738a743619_1900x660.png 1272w, https://substackcdn.com/image/fetch/$s_!0R6j!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7918e5fb-52b6-4cea-a8d1-40738a743619_1900x660.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0R6j!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7918e5fb-52b6-4cea-a8d1-40738a743619_1900x660.png" width="1456" height="506" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7918e5fb-52b6-4cea-a8d1-40738a743619_1900x660.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:506,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:170154,&quot;alt&quot;:&quot;How to get the basic authentication token in the Decodo dashboard by Federico Trotta&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/198374051?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7918e5fb-52b6-4cea-a8d1-40738a743619_1900x660.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="How to get the basic authentication token in the Decodo dashboard by Federico Trotta" title="How to get the basic authentication token in the Decodo dashboard by Federico Trotta" srcset="https://substackcdn.com/image/fetch/$s_!0R6j!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7918e5fb-52b6-4cea-a8d1-40738a743619_1900x660.png 424w, https://substackcdn.com/image/fetch/$s_!0R6j!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7918e5fb-52b6-4cea-a8d1-40738a743619_1900x660.png 848w, https://substackcdn.com/image/fetch/$s_!0R6j!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7918e5fb-52b6-4cea-a8d1-40738a743619_1900x660.png 1272w, https://substackcdn.com/image/fetch/$s_!0R6j!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7918e5fb-52b6-4cea-a8d1-40738a743619_1900x660.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">How to get the basic authentication token in the Decodo dashboard</figcaption></figure></div></li></ul><p>Good. Your system is set to connect Claude Desktop with the Decodo MCP server.</p><h3>Connect The Decodo MCP Server to Claude</h3><p>To connect the Decodo MCP server to Claude, open Claude Desktop and click on <strong>Settings</strong>:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YotT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c674aee-c849-49cd-ae9d-b0d6df20ca62_1902x921.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YotT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c674aee-c849-49cd-ae9d-b0d6df20ca62_1902x921.png 424w, https://substackcdn.com/image/fetch/$s_!YotT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c674aee-c849-49cd-ae9d-b0d6df20ca62_1902x921.png 848w, https://substackcdn.com/image/fetch/$s_!YotT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c674aee-c849-49cd-ae9d-b0d6df20ca62_1902x921.png 1272w, https://substackcdn.com/image/fetch/$s_!YotT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c674aee-c849-49cd-ae9d-b0d6df20ca62_1902x921.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YotT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c674aee-c849-49cd-ae9d-b0d6df20ca62_1902x921.png" width="1456" height="705" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7c674aee-c849-49cd-ae9d-b0d6df20ca62_1902x921.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:705,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:121992,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/198374051?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c674aee-c849-49cd-ae9d-b0d6df20ca62_1902x921.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YotT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c674aee-c849-49cd-ae9d-b0d6df20ca62_1902x921.png 424w, https://substackcdn.com/image/fetch/$s_!YotT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c674aee-c849-49cd-ae9d-b0d6df20ca62_1902x921.png 848w, https://substackcdn.com/image/fetch/$s_!YotT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c674aee-c849-49cd-ae9d-b0d6df20ca62_1902x921.png 1272w, https://substackcdn.com/image/fetch/$s_!YotT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c674aee-c849-49cd-ae9d-b0d6df20ca62_1902x921.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Go to settings in Claude</figcaption></figure></div><p>Then, click on <strong>Developer</strong>:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zS13!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7158ec2-90ea-432a-982d-7dedd950c5f1_1282x481.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zS13!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7158ec2-90ea-432a-982d-7dedd950c5f1_1282x481.png 424w, https://substackcdn.com/image/fetch/$s_!zS13!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7158ec2-90ea-432a-982d-7dedd950c5f1_1282x481.png 848w, https://substackcdn.com/image/fetch/$s_!zS13!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7158ec2-90ea-432a-982d-7dedd950c5f1_1282x481.png 1272w, https://substackcdn.com/image/fetch/$s_!zS13!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7158ec2-90ea-432a-982d-7dedd950c5f1_1282x481.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zS13!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7158ec2-90ea-432a-982d-7dedd950c5f1_1282x481.png" width="1282" height="481" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f7158ec2-90ea-432a-982d-7dedd950c5f1_1282x481.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:481,&quot;width&quot;:1282,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:37560,&quot;alt&quot;:&quot;Go to the Developer section in Claude&#8217;s settings by Federico Trotta&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/198374051?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7158ec2-90ea-432a-982d-7dedd950c5f1_1282x481.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Go to the Developer section in Claude&#8217;s settings by Federico Trotta" title="Go to the Developer section in Claude&#8217;s settings by Federico Trotta" srcset="https://substackcdn.com/image/fetch/$s_!zS13!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7158ec2-90ea-432a-982d-7dedd950c5f1_1282x481.png 424w, https://substackcdn.com/image/fetch/$s_!zS13!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7158ec2-90ea-432a-982d-7dedd950c5f1_1282x481.png 848w, https://substackcdn.com/image/fetch/$s_!zS13!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7158ec2-90ea-432a-982d-7dedd950c5f1_1282x481.png 1272w, https://substackcdn.com/image/fetch/$s_!zS13!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7158ec2-90ea-432a-982d-7dedd950c5f1_1282x481.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Go to the Developer section in Claude&#8217;s settings</figcaption></figure></div><p>After clicking on <strong>Edit Config</strong>, the system will automatically open a folder where Claude stores all the configuration files on your local machine. Open the <em>claude_desktop_config.json</em> file and add the following to it:</p><pre><code><code>"mcpServers": {
    "Decodo MCP Server": {
      "command": "npx",
      "args": [
        "-y",
        "@decodo/mcp-server"
      ],
      "env": {
        "SCRAPER_API_TOKEN": "&lt;your-decodo-mcp-api-key&gt;"
      }
    }
  } </code></code></pre><p>Replace <em>&lt;your-decodo-mcp-api-key&gt;</em> with the basic authentication token you retrieved earlier from the Decodo dashboard, and the integration is done.</p><p>Quit Claude to make the changes effective. Note that just closing the Desktop window is not sufficient. You have to quit it:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vI5X!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0588d8eb-b410-4bc5-baf0-2e2bcbc7254b_311x196.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vI5X!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0588d8eb-b410-4bc5-baf0-2e2bcbc7254b_311x196.png 424w, https://substackcdn.com/image/fetch/$s_!vI5X!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0588d8eb-b410-4bc5-baf0-2e2bcbc7254b_311x196.png 848w, https://substackcdn.com/image/fetch/$s_!vI5X!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0588d8eb-b410-4bc5-baf0-2e2bcbc7254b_311x196.png 1272w, https://substackcdn.com/image/fetch/$s_!vI5X!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0588d8eb-b410-4bc5-baf0-2e2bcbc7254b_311x196.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vI5X!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0588d8eb-b410-4bc5-baf0-2e2bcbc7254b_311x196.png" width="311" height="196" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0588d8eb-b410-4bc5-baf0-2e2bcbc7254b_311x196.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:196,&quot;width&quot;:311,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:68945,&quot;alt&quot;:&quot;How to quit Claude by Federico Trotta&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/198374051?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0588d8eb-b410-4bc5-baf0-2e2bcbc7254b_311x196.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="How to quit Claude by Federico Trotta" title="How to quit Claude by Federico Trotta" srcset="https://substackcdn.com/image/fetch/$s_!vI5X!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0588d8eb-b410-4bc5-baf0-2e2bcbc7254b_311x196.png 424w, https://substackcdn.com/image/fetch/$s_!vI5X!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0588d8eb-b410-4bc5-baf0-2e2bcbc7254b_311x196.png 848w, https://substackcdn.com/image/fetch/$s_!vI5X!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0588d8eb-b410-4bc5-baf0-2e2bcbc7254b_311x196.png 1272w, https://substackcdn.com/image/fetch/$s_!vI5X!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0588d8eb-b410-4bc5-baf0-2e2bcbc7254b_311x196.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">How to quit Claude</figcaption></figure></div><p>After that, when returning to <strong>Settings</strong> &gt; <strong>Developer,</strong> you&#8217;ll see the MCP server up and running:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KBhB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f4afc18-c865-4edc-910a-ac5b2200fc1d_1251x455.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KBhB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f4afc18-c865-4edc-910a-ac5b2200fc1d_1251x455.png 424w, https://substackcdn.com/image/fetch/$s_!KBhB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f4afc18-c865-4edc-910a-ac5b2200fc1d_1251x455.png 848w, https://substackcdn.com/image/fetch/$s_!KBhB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f4afc18-c865-4edc-910a-ac5b2200fc1d_1251x455.png 1272w, https://substackcdn.com/image/fetch/$s_!KBhB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f4afc18-c865-4edc-910a-ac5b2200fc1d_1251x455.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KBhB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f4afc18-c865-4edc-910a-ac5b2200fc1d_1251x455.png" width="1251" height="455" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1f4afc18-c865-4edc-910a-ac5b2200fc1d_1251x455.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:455,&quot;width&quot;:1251,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:54828,&quot;alt&quot;:&quot;The Decodo MCP server is running by Federico Trotta&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/198374051?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f4afc18-c865-4edc-910a-ac5b2200fc1d_1251x455.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The Decodo MCP server is running by Federico Trotta" title="The Decodo MCP server is running by Federico Trotta" srcset="https://substackcdn.com/image/fetch/$s_!KBhB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f4afc18-c865-4edc-910a-ac5b2200fc1d_1251x455.png 424w, https://substackcdn.com/image/fetch/$s_!KBhB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f4afc18-c865-4edc-910a-ac5b2200fc1d_1251x455.png 848w, https://substackcdn.com/image/fetch/$s_!KBhB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f4afc18-c865-4edc-910a-ac5b2200fc1d_1251x455.png 1272w, https://substackcdn.com/image/fetch/$s_!KBhB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f4afc18-c865-4edc-910a-ac5b2200fc1d_1251x455.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The Decodo MCP server is running</figcaption></figure></div><p>To be sure everything works fine, you can test it with a prompt similar to the following:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IWAq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97513d07-9370-45cc-af97-d74e4a6cf160_757x300.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IWAq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97513d07-9370-45cc-af97-d74e4a6cf160_757x300.png 424w, https://substackcdn.com/image/fetch/$s_!IWAq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97513d07-9370-45cc-af97-d74e4a6cf160_757x300.png 848w, https://substackcdn.com/image/fetch/$s_!IWAq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97513d07-9370-45cc-af97-d74e4a6cf160_757x300.png 1272w, https://substackcdn.com/image/fetch/$s_!IWAq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97513d07-9370-45cc-af97-d74e4a6cf160_757x300.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IWAq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97513d07-9370-45cc-af97-d74e4a6cf160_757x300.png" width="757" height="300" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/97513d07-9370-45cc-af97-d74e4a6cf160_757x300.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:300,&quot;width&quot;:757,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:63054,&quot;alt&quot;:&quot;A prompt to test if Claude can connect to the Decodo MCP server by Federico Trotta&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/198374051?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97513d07-9370-45cc-af97-d74e4a6cf160_757x300.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A prompt to test if Claude can connect to the Decodo MCP server by Federico Trotta" title="A prompt to test if Claude can connect to the Decodo MCP server by Federico Trotta" srcset="https://substackcdn.com/image/fetch/$s_!IWAq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97513d07-9370-45cc-af97-d74e4a6cf160_757x300.png 424w, https://substackcdn.com/image/fetch/$s_!IWAq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97513d07-9370-45cc-af97-d74e4a6cf160_757x300.png 848w, https://substackcdn.com/image/fetch/$s_!IWAq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97513d07-9370-45cc-af97-d74e4a6cf160_757x300.png 1272w, https://substackcdn.com/image/fetch/$s_!IWAq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97513d07-9370-45cc-af97-d74e4a6cf160_757x300.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A prompt to test if Claude can connect to the Decodo MCP server</figcaption></figure></div><p>Alright, you successfully integrated the Decodo MCP server with Claude Desktop. Now it's time to test it!</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.youtube.com/@thewebscrapingclub&quot;,&quot;text&quot;:&quot;Check the TWSC YouTube Channel&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.youtube.com/@thewebscrapingclub"><span>Check the TWSC YouTube Channel</span></a></p><div><hr></div><h2>Running the Decodo MCP With Claude: Hands-On Examples</h2><p>In this section, you will learn how to use the Decodo MCP server with two different examples:</p><ul><li><p>A basic usage taken from the documentation.</p></li><li><p>A more advanced usage where you&#8217;ll ask Claude to retrieve some data from Amazon, returning the result in a JSON file.</p></li></ul><p>Let&#8217;s get on to this!</p><h3>Getting Started: Run a Google Search in Seconds</h3><p>As a first and simple usage, you can test the <em>google_search</em> tool. The main idea behind this tool is to prompt the model with a query, and, under the hood, the MCP will use its Google search capabilities to return the result.</p><p>To just try things out, you can use the exact example reported in the Decodo MCP documentation to search for shoes on Google, reporting the top positions:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7PUU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd9118b2-ea07-4838-ac3d-b487512338b4_787x391.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7PUU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd9118b2-ea07-4838-ac3d-b487512338b4_787x391.png 424w, https://substackcdn.com/image/fetch/$s_!7PUU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd9118b2-ea07-4838-ac3d-b487512338b4_787x391.png 848w, https://substackcdn.com/image/fetch/$s_!7PUU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd9118b2-ea07-4838-ac3d-b487512338b4_787x391.png 1272w, https://substackcdn.com/image/fetch/$s_!7PUU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd9118b2-ea07-4838-ac3d-b487512338b4_787x391.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7PUU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd9118b2-ea07-4838-ac3d-b487512338b4_787x391.png" width="787" height="391" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cd9118b2-ea07-4838-ac3d-b487512338b4_787x391.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:391,&quot;width&quot;:787,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:32789,&quot;alt&quot;:&quot;Allow Claude to use the MCP tools by Federico Trotta&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/198374051?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd9118b2-ea07-4838-ac3d-b487512338b4_787x391.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Allow Claude to use the MCP tools by Federico Trotta" title="Allow Claude to use the MCP tools by Federico Trotta" srcset="https://substackcdn.com/image/fetch/$s_!7PUU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd9118b2-ea07-4838-ac3d-b487512338b4_787x391.png 424w, https://substackcdn.com/image/fetch/$s_!7PUU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd9118b2-ea07-4838-ac3d-b487512338b4_787x391.png 848w, https://substackcdn.com/image/fetch/$s_!7PUU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd9118b2-ea07-4838-ac3d-b487512338b4_787x391.png 1272w, https://substackcdn.com/image/fetch/$s_!7PUU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd9118b2-ea07-4838-ac3d-b487512338b4_787x391.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Allow Claude to use the MCP tools</figcaption></figure></div><p>As you can see, Claude automatically tries to use the MCP server, loading its tool. As Claude is a production-ready LLM, you will be asked if you want to allow it to use the Google search from the Decodo MCP always or just for this call.</p><p>When Claude has completed its job, the result is the following:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2oSz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F039511a6-f72c-4ed0-8be7-7305b4025897_552x762.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2oSz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F039511a6-f72c-4ed0-8be7-7305b4025897_552x762.png 424w, https://substackcdn.com/image/fetch/$s_!2oSz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F039511a6-f72c-4ed0-8be7-7305b4025897_552x762.png 848w, https://substackcdn.com/image/fetch/$s_!2oSz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F039511a6-f72c-4ed0-8be7-7305b4025897_552x762.png 1272w, https://substackcdn.com/image/fetch/$s_!2oSz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F039511a6-f72c-4ed0-8be7-7305b4025897_552x762.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2oSz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F039511a6-f72c-4ed0-8be7-7305b4025897_552x762.png" width="552" height="762" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/039511a6-f72c-4ed0-8be7-7305b4025897_552x762.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:762,&quot;width&quot;:552,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:97772,&quot;alt&quot;:&quot;Claude&#8217;s results by Federico Trotta&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/198374051?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F039511a6-f72c-4ed0-8be7-7305b4025897_552x762.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Claude&#8217;s results by Federico Trotta" title="Claude&#8217;s results by Federico Trotta" srcset="https://substackcdn.com/image/fetch/$s_!2oSz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F039511a6-f72c-4ed0-8be7-7305b4025897_552x762.png 424w, https://substackcdn.com/image/fetch/$s_!2oSz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F039511a6-f72c-4ed0-8be7-7305b4025897_552x762.png 848w, https://substackcdn.com/image/fetch/$s_!2oSz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F039511a6-f72c-4ed0-8be7-7305b4025897_552x762.png 1272w, https://substackcdn.com/image/fetch/$s_!2oSz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F039511a6-f72c-4ed0-8be7-7305b4025897_552x762.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Claude&#8217;s results</figcaption></figure></div><p>If you want to verify that the model hasn&#8217;t hallucinated, you can search for &#8220;shoes&#8221; on Google:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UYZP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb8e302e-e386-4bf0-b2e9-5b9aa79af791_1812x599.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UYZP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb8e302e-e386-4bf0-b2e9-5b9aa79af791_1812x599.png 424w, https://substackcdn.com/image/fetch/$s_!UYZP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb8e302e-e386-4bf0-b2e9-5b9aa79af791_1812x599.png 848w, https://substackcdn.com/image/fetch/$s_!UYZP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb8e302e-e386-4bf0-b2e9-5b9aa79af791_1812x599.png 1272w, https://substackcdn.com/image/fetch/$s_!UYZP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb8e302e-e386-4bf0-b2e9-5b9aa79af791_1812x599.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UYZP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb8e302e-e386-4bf0-b2e9-5b9aa79af791_1812x599.png" width="1456" height="481" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bb8e302e-e386-4bf0-b2e9-5b9aa79af791_1812x599.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:481,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:177541,&quot;alt&quot;:&quot;The results on Google by Federico Trotta&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/198374051?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb8e302e-e386-4bf0-b2e9-5b9aa79af791_1812x599.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The results on Google by Federico Trotta" title="The results on Google by Federico Trotta" srcset="https://substackcdn.com/image/fetch/$s_!UYZP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb8e302e-e386-4bf0-b2e9-5b9aa79af791_1812x599.png 424w, https://substackcdn.com/image/fetch/$s_!UYZP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb8e302e-e386-4bf0-b2e9-5b9aa79af791_1812x599.png 848w, https://substackcdn.com/image/fetch/$s_!UYZP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb8e302e-e386-4bf0-b2e9-5b9aa79af791_1812x599.png 1272w, https://substackcdn.com/image/fetch/$s_!UYZP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb8e302e-e386-4bf0-b2e9-5b9aa79af791_1812x599.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The results on Google</figcaption></figure></div><p>Alright! You made it to your first usage example.</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/p/consulting&quot;,&quot;text&quot;:&quot;Need help with your scraping project?&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://substack.thewebscraping.club/p/consulting"><span>Need help with your scraping project?</span></a></p><div><hr></div><h3>Leveling Up: Extracting JSON-structured Data From Amazon</h3><p>Having results listed in a chat can be useful for a quick overview of web research. But the actual power of MCPs is to use the capabilities of the underlying tools for specific tasks that LLMs can solve.</p><p>An example is to ask the model to retrieve some data and report the results in a JSON file. This solution provides you with data that can be further used in the second part of your pipeline&#8212;for example, <a href="https://substack.thewebscraping.club/p/analyzing-scraped-data-pandas-matplotlib">for analyzing your scraped data</a>.</p><p>For this purpose, you can use the following prompt:</p><pre><code><code>Get Amazon bestsellers in electronics, extract the main info and return it in JSON format</code></code></pre><p>The image below shows Amazon&#8217;s best sellers in electronics, at the time of writing this article:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!a_uB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8e837ed-3971-440f-a195-369a8f03fab9_1352x886.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!a_uB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8e837ed-3971-440f-a195-369a8f03fab9_1352x886.png 424w, https://substackcdn.com/image/fetch/$s_!a_uB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8e837ed-3971-440f-a195-369a8f03fab9_1352x886.png 848w, https://substackcdn.com/image/fetch/$s_!a_uB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8e837ed-3971-440f-a195-369a8f03fab9_1352x886.png 1272w, https://substackcdn.com/image/fetch/$s_!a_uB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8e837ed-3971-440f-a195-369a8f03fab9_1352x886.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!a_uB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8e837ed-3971-440f-a195-369a8f03fab9_1352x886.png" width="1352" height="886" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d8e837ed-3971-440f-a195-369a8f03fab9_1352x886.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:886,&quot;width&quot;:1352,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:383040,&quot;alt&quot;:&quot;Amazon&#8217;s best sellers in electronics by Federico Trotta&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/198374051?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8e837ed-3971-440f-a195-369a8f03fab9_1352x886.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Amazon&#8217;s best sellers in electronics by Federico Trotta" title="Amazon&#8217;s best sellers in electronics by Federico Trotta" srcset="https://substackcdn.com/image/fetch/$s_!a_uB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8e837ed-3971-440f-a195-369a8f03fab9_1352x886.png 424w, https://substackcdn.com/image/fetch/$s_!a_uB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8e837ed-3971-440f-a195-369a8f03fab9_1352x886.png 848w, https://substackcdn.com/image/fetch/$s_!a_uB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8e837ed-3971-440f-a195-369a8f03fab9_1352x886.png 1272w, https://substackcdn.com/image/fetch/$s_!a_uB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8e837ed-3971-440f-a195-369a8f03fab9_1352x886.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Amazon&#8217;s best sellers in electronics</figcaption></figure></div><p>Under the hood, Claude will trigger the <em>amazon_bestsellers</em> tool and will search the data in electronics. The chat result is the following:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XVSQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb220c4c7-56f7-4c1f-9879-ef7c28aeaf16_590x797.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XVSQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb220c4c7-56f7-4c1f-9879-ef7c28aeaf16_590x797.png 424w, https://substackcdn.com/image/fetch/$s_!XVSQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb220c4c7-56f7-4c1f-9879-ef7c28aeaf16_590x797.png 848w, https://substackcdn.com/image/fetch/$s_!XVSQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb220c4c7-56f7-4c1f-9879-ef7c28aeaf16_590x797.png 1272w, https://substackcdn.com/image/fetch/$s_!XVSQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb220c4c7-56f7-4c1f-9879-ef7c28aeaf16_590x797.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XVSQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb220c4c7-56f7-4c1f-9879-ef7c28aeaf16_590x797.png" width="590" height="797" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b220c4c7-56f7-4c1f-9879-ef7c28aeaf16_590x797.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:797,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:108993,&quot;alt&quot;:&quot;Claude&#8217;s results via the chat by Federico Trotta&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/198374051?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb220c4c7-56f7-4c1f-9879-ef7c28aeaf16_590x797.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Claude&#8217;s results via the chat by Federico Trotta" title="Claude&#8217;s results via the chat by Federico Trotta" srcset="https://substackcdn.com/image/fetch/$s_!XVSQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb220c4c7-56f7-4c1f-9879-ef7c28aeaf16_590x797.png 424w, https://substackcdn.com/image/fetch/$s_!XVSQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb220c4c7-56f7-4c1f-9879-ef7c28aeaf16_590x797.png 848w, https://substackcdn.com/image/fetch/$s_!XVSQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb220c4c7-56f7-4c1f-9879-ef7c28aeaf16_590x797.png 1272w, https://substackcdn.com/image/fetch/$s_!XVSQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb220c4c7-56f7-4c1f-9879-ef7c28aeaf16_590x797.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Claude&#8217;s results via the chat</figcaption></figure></div><p>Below is the (partial) JSON returned by Claude:</p><pre><code><code>{
  "metadata": {
    "category": "Electronics",
    "source": "Amazon Best Sellers",
    "url": "&lt;https://www.amazon.com/Best-Sellers/zgbs/electronics/&gt;",
    "scraped_at": "2026-05-18",
    "total_items": 50,
    "currency": "USD"
  },
  "bestsellers": [
    {
      "rank": 1,
      "asin": "B08JHCVHTY",
      "title": "Blink Plus Plan with Monthly Auto-Renewal",
      "price": 11.99,
      "rating": 4.4,
      "ratings_count": 275779,
      "image_url": "&lt;https://images-na.ssl-images-amazon.com/images/I/31YHGbJsldL._AC_UL300_SR300,200_.png&gt;",
      "url": "&lt;https://www.amazon.com/Blink-Plus-Plan-monthly-auto-renewal/dp/B08JHCVHTY&gt;"
    },
    {
      "rank": 2,
      "asin": "B0DCH8VDXF",
      "title": "Apple EarPods Headphones with USB-C Plug",
      "price": 19,
      "rating": 4.6,
      "ratings_count": 13500,
      "image_url": "&lt;https://images-na.ssl-images-amazon.com/images/I/513OSdW4elL._AC_UL300_SR300,200_.jpg&gt;",
      "url": "&lt;https://www.amazon.com/Apple-EarPods-Headphones-Built-Control/dp/B0DCH8VDXF&gt;"
    },
    
    &lt;Omitted for brevity&gt;

    }
  ]
}</code></code></pre><p>As you can see, the first two items correspond to the first two in the image above, taken from the Amazon best seller page. This is just to be sure the model hasn&#8217;t hallucinated.</p><p>Well done! You learned how to use the Decodo MCP server with Claude.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Web Scraping Club is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>Conclusion</h2><p>In this article, you learned what MCP is and why it has become the standard for connecting AI models to external tools and services. You also learned what the Decodo MCP server is, how to integrate it with Claude Desktop, and how to use it in practice.</p><p>So, let us know: what kind of scraping workflows are you planning to build with the Decodo MCP?</p><p></p>]]></content:encoded></item><item><title><![CDATA[THE LAB #106: Is Camoufox still effective, and do the forks help?]]></title><description><![CDATA[The project moved to CloverLabs and the fork tree keeps growing. We read the code and ran four builds against DataDome to see what still works.]]></description><link>https://substack.thewebscraping.club/p/is-camoufox-still-effective-scraping</link><guid isPermaLink="false">https://substack.thewebscraping.club/p/is-camoufox-still-effective-scraping</guid><dc:creator><![CDATA[Pierluigi Vinciguerra]]></dc:creator><pubDate>Thu, 04 Jun 2026 16:21:43 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/e103d057-1faa-43a1-9346-cb8fdd5383b8_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Camoufox has been our default anti-detect browser for more than a year. We said so in <a href="https://substack.thewebscraping.club/p/how-to-bypass-cloudflare-turnstile">THE LAB #73: How to Bypass Cloudflare in 2025</a>, and again when we put it on the level of a commercial product in the Kasada article. Lately, that confidence has started to decline. In hallway conversations at PragueCrawl, more than one person told us the same thing we had started to feel. Camoufox does not pass the harder targets the way it used to.</p><blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gCo7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gCo7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png" width="626" height="313" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:626,&quot;bytes&quot;:390972,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/197764148?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gCo7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Before proceeding, let me thank NetNut, the platinum partner of the month. Their set of solutions cover all your needs for scraping.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack&quot;,&quot;text&quot;:&quot;Visit Netnut&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack"><span>Visit Netnut</span></a></p></blockquote><blockquote><div><hr></div></blockquote><p>Part of that is the cat-and-mouse game every stealth tool plays. Part of it is specific to open source. When the entire fingerprint-spoofing codebase is public, the anti-bot vendors can read it line by line and build the exact counter-signal. We made that argument in the rayobrowse review. The openness that made Camoufox popular is the same openness that let the anti-bot giants study it and catch up.</p><p>Two things changed in 2026 that make this worth a fresh look. First, the project moved. The repository at <a href="https://github.com/daijro/camoufox">github.com/daijro/camoufox</a> now carries a note at the top of its README:</p><div class="callout-block" data-callout="true"><p>Browser development is active at github.com/CloverLabsAI/camoufox and github.com/VulpineOS/VulpineOS. This repo is being used to merge checkpoint releases and should be used as the source of truth.</p></div><p>Clover Labs is a Toronto venture studio building AI agents, listed among the project sponsors. The alpha features (per-context fingerprints, hardware spoofing) now ship first in their <code>cloverlabs-camoufox</code> package, and daijro&#8217;s repo became the checkpoint mirror. This is not an abandoned project; the main maintainers changed.</p><p>Second, that public repo has more than 750 forks. Open source means that when one person stops, others can pick up the work, add features on features, and keep the chase going in parallel. So the real question is not only &#8220;is Camoufox still effective&#8221;, it is &#8220;has anyone in the fork tree built something better&#8221;. This is what we tried to discover in this article.</p><div><hr></div><blockquote><p><em>For your scraping needs, having a reliable proxy provider like <strong>Decodo</strong> on your side improves the chances of success. </em></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qJZE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qJZE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png" width="401" height="225.5625" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fec072bc-2ac2-49bf-8084-43858298808f_800x450.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:450,&quot;width&quot;:800,&quot;resizeWidth&quot;:401,&quot;bytes&quot;:554497,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/187315628?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!qJZE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption"></figcaption></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://visit.decodo.com/WyQ3mA&quot;,&quot;text&quot;:&quot;Try Decodo Now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://visit.decodo.com/WyQ3mA"><span>Try Decodo Now</span></a></p></blockquote><div><hr></div><h2>The forks we actually tested</h2><p>We pulled the fork list from the GitHub API and sorted it by recent pushes. Most of it is noise. Many forks share the exact <code>pushed_at</code> timestamp of the parent, which is the signature of mirror bots that never wrote a line of their own. Once you count how many commits each fork is ahead of <code>daijro:main</code> and read what those commits do, the field collapses to a handful. Many of the survivors only touch CI or rebrand the binary. Three of them touch the anti-detect surface for real.</p><p>Official Camoufox (<a href="https://github.com/daijro/camoufox">github.com/daijro/camoufox</a>) is the baseline. A custom Firefox build with a fingerprint database and stealth patches, driven through Playwright&#8217;s Juggler protocol. We covered how it hides Playwright&#8217;s own traces in <a href="https://substack.thewebscraping.club/p/scraping-datadome-camoufox">THE LAB #65</a>, so we will not repeat that here.</p><p><strong>camoufox-reverse</strong> (<a href="https://github.com/WhiteNightShadow/camoufox-reverse">github.com/WhiteNightShadow/camoufox-reverse</a>) goes the other way. Instead of hiding harder, it adds a PropertyTracer at the SpiderMonkey engine layer that records which DOM properties a page reads. It is an instrument for watching the detector work, not a better scraper. That makes it the most useful tool in the set for understanding what we are up against.</p><p><strong>LeooNic/camoufox</strong> (<a href="https://github.com/LeooNic/camoufox">github.com/LeooNic/camoufox</a>) is the most ambitious on paper. Its commits add content-aware canvas noise that claims to defeat a 2025 academic pixel-recovery attack, a sigma-lognormal humanized mouse engine, and RDPBrowser, an automation path that drives Firefox over the Remote Debugging Protocol instead of Juggler.</p><p><strong>JWriter20/camoufox</strong> (<a href="https://github.com/JWriter20/camoufox">github.com/JWriter20/camoufox</a>) is the pragmatic one. Targeted stealth fixes, the headline being a closed WebRTC IP leak under a proxy on Firefox 146 (daijro issue #538), plus a real pytest suite, which none of the others ship.</p><p>Let&#8217;s start by using camoufox-reverse to discover something more about DataDome installed on Leboncoin.fr.</p><h2>What DataDome reads, watched from inside the engine</h2><p>Before testing who passes, we wanted to see what the detector looks at. We have explained the three detection layers before: behavioral, browser, and HTTP, in <a href="https://substack.thewebscraping.club/p/change-ciphers-scrapy">THE LAB #6</a>. camoufox-reverse lets us watch the browser layer from below the JavaScript, which is a view we have never had in these pages.</p><p>The PropertyTracer is documented to be enabled via a config flag. We drove the macOS arm64 build directly with Playwright, set the trace config through the <code>CAMOU_CONFIG</code> environment variable, and pointed it at a DataDome-protected page. Our target throughout this article is leboncoin.fr, the French classifieds site, because it runs only DataDome. That isolates the signal we care about, with no second anti-bot muddying the result.</p><p>The full probe is in code/camoufox_fork_analysis/trace_datadome.py. The core of it sets the trace and lets DataDome&#8217;s script run:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;f65ef838-3049-4931-9b72-149f05b52fe0&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">config = {
    "propertyTrace": {
        "enabled": True,
        "logDir": str(LOG_DIR),
        "objects": [],            # empty = trace all covered getters
        "maxEventsPerSession": 200000,
    }
}
env = os.environ.copy()
env["CAMOU_CONFIG"] = json.dumps(config)
env["MOZ_DISABLE_CONTENT_SANDBOX"] = "1"  # required on macOS for the tracer</code></pre></div><div><hr></div><blockquote><p>When sites get tough, skip the heavy lifting. Get clean, structured CSV datasets,  ready for Excel, BI or your apps</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KpSw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16722680-673e-472e-a0dd-6aa9fe9d2acb_1043x264.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KpSw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16722680-673e-472e-a0dd-6aa9fe9d2acb_1043x264.png 424w, https://substackcdn.com/image/fetch/$s_!KpSw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16722680-673e-472e-a0dd-6aa9fe9d2acb_1043x264.png 848w, https://substackcdn.com/image/fetch/$s_!KpSw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16722680-673e-472e-a0dd-6aa9fe9d2acb_1043x264.png 1272w, https://substackcdn.com/image/fetch/$s_!KpSw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16722680-673e-472e-a0dd-6aa9fe9d2acb_1043x264.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KpSw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16722680-673e-472e-a0dd-6aa9fe9d2acb_1043x264.png" width="592" height="149.84467881112175" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/16722680-673e-472e-a0dd-6aa9fe9d2acb_1043x264.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:264,&quot;width&quot;:1043,&quot;resizeWidth&quot;:592,&quot;bytes&quot;:81723,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/195720195?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16722680-673e-472e-a0dd-6aa9fe9d2acb_1043x264.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!KpSw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16722680-673e-472e-a0dd-6aa9fe9d2acb_1043x264.png 424w, https://substackcdn.com/image/fetch/$s_!KpSw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16722680-673e-472e-a0dd-6aa9fe9d2acb_1043x264.png 848w, https://substackcdn.com/image/fetch/$s_!KpSw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16722680-673e-472e-a0dd-6aa9fe9d2acb_1043x264.png 1272w, https://substackcdn.com/image/fetch/$s_!KpSw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16722680-673e-472e-a0dd-6aa9fe9d2acb_1043x264.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.databoutique.com/buy-data-list&quot;,&quot;text&quot;:&quot;Find your dataset&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.databoutique.com/buy-data-list"><span>Find your dataset</span></a></p></blockquote><div><hr></div><p>The tracer writes one JSON line per getter access, each shaped like <code>{"o": "navigator", "p": "hardwareConcurrency", ...}</code>. Loading the leboncoin homepage produced 140 engine-level reads across 30 distinct properties. Aggregated by object and property, the access pattern contains information useful for fingerprint creation:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;3700752d-7e2b-415d-8254-13f7525b67ee&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext"> COUNT  PROPERTY
    14  window.outerWidth
    13  window.devicePixelRatio
    13  window.outerHeight
    13  navigator.plugins.indexedGetter
     9  navigator.hardwareConcurrency
     7  canvas.toDataURL
     6  window.innerWidth
     6  screen.rect
     4  navigator.platform
     4  navigator.userAgent
     4  webgl.getParameter
     4  canvas2d.getImageData
     3  navigator.maxTouchPoints
     2  offscreenCanvas.getContext</code></pre></div><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.youtube.com/@thewebscrapingclub&quot;,&quot;text&quot;:&quot;Check the TWSC YouTube Channel&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.youtube.com/@thewebscrapingclub"><span>Check the TWSC YouTube Channel</span></a></p><div><hr></div><p>This is happening entirely below the JavaScript layer. From the page&#8217;s point of view, nothing was instrumented, because the recording lives in the C++ getter, not in a JavaScript proxy. DataDome reads the screen geometry, the navigator core, the plugin and mime enumeration, and then it reaches for the canvas and WebGL. Both <code>canvas.toDataURL</code> and <code>canvas2d.getImageData</code> are in the list, alongside <code>webgl.getParameter</code> and <code>offscreenCanvas.getContext</code>.</p><p>That last detail is what connects this experiment to the rest of the article. The canvas readback is exactly the surface LeooNic&#8217;s content-aware noise patch sets out to protect, and the WebRTC and screen reads are where the other forks claim improvements. We now know the detector touches it all.</p><p>The homepage is the light version. The pages that hold the data are watched far more closely, and the tracer shows it. We pointed the same probe at a car listing (a leboncoin <code>/ad/voitures/</code> URL). Those pages block direct connections, so this run went through a residential proxy, which is the setup we explain in the next section. The listing loaded its real content (the page title came back as &#8220;Alfa romeo Tonale 1.5 Ibrida 175ch Veloce TCT&#8221;), so we were tracing a passing ad page, not a challenge screen. The read pattern is a different animal: 584 engine-level reads across 35 properties, against 140 across 30 on the homepage.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;7042a22e-a626-4f27-837f-b2d2b2ac3cda&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext"> COUNT  PROPERTY (ad page)
   220  document.cookie.get
    47  window.innerWidth
    30  window.innerHeight
    26  navigator.plugins.indexedGetter
    26  screen.rect
    25  sessionStorage.setItem
    22  sessionStorage.getItem
    16  document.cookie.set
    12  performance.timing
     8  window.scrollY
     7  canvas.toDataURL
     6  webgl.getParameter
     4  canvas2d.getImageData
     3  navigator.globalPrivacyControl
     1  mediaDevices.enumerateDevices</code></pre></div><p>The cookie reads jump from one on the homepage to 220 on the ad page. Session storage, which the homepage barely touched, is read and written dozens of times. New surfaces appear that the homepage never queried: <code>window.scrollY</code> for behavior, <code>navigator.globalPrivacyControl</code>, and <code>mediaDevices.enumerateDevices</code>. The canvas and WebGL reads are still there. This is the same DataDome, running a heavier script on the page that matters. It is the concrete reason the homepage passes a clean browser while the listings do not. It also tells you where to spend your effort. The protection you have to beat lives on the content pages, not the landing page.</p><h2>Setting up a fair comparison</h2><p>The shared virtual environment we&#8217;re creating already had <code>camoufox</code> 0.4.11, which fetches the Firefox 135 official build. We ran on an Apple M2 Max, so we pulled the macOS arm64 binaries for each build, signed them ad hoc (the cross-compiled bundles need it), and pointed the same launcher at each one with <code>executable_path</code>.</p><p>Two version details matter for fairness. JWriter20&#8217;s WebRTC fix targets a regression introduced in Firefox 146, so we did not compare it against the 135 cache. We pulled the official <code>v146-hardware</code> build (Firefox 146.0.1) as the baseline and JWriter20&#8217;s own 146.0.1 build as the patched version. Same Firefox, two builds. camoufox-reverse only ships at 135, which is fine because we used it only as a tracer, not as a contender.</p><p>Every test drives the binaries the way a real user would, through the camoufox launcher with <code>proxy</code> and <code>geoip</code> set, so the fingerprint database, the locale coherence, and the stealth patches are all active. The one exception is the WebRTC probe, explained below, where the page we run matters.</p><h2>The WebRTC leak that JWriter20 actually fixes</h2><p>JWriter20&#8217;s headline fix is a closed WebRTC IP leak under a proxy. We checked it on the official 146 build against the JWriter20 146 build, same launcher, same Bright Data proxy, <code>geoip=True</code>. The probe gathers ICE candidates from a STUN server and reports any IP that escapes (webrtc_leak_test.py).</p><p>A quick detour on what those candidates are, because the whole leak lives in them. WebRTC connects two peers directly, and to do that each side has to advertise every network address it could be reached on. Each address it offers is an ICE candidate. A candidate is an IP, a port, a protocol, and a type, and it reaches JavaScript as a string like this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;eb21871e-0761-45d0-95b7-2236487833b7&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">candidate:842163049 1 udp 1677729535 203.0.113.25 54321 typ srflx raddr 192.168.1.45 rport 54321</code></pre></div><p>Two types matter here. A <code>host</code> candidate is an address of a local network interface, so it carries your LAN IP. A <code>srflx</code> (server-reflexive) candidate is the public address a STUN server reports back when the browser asks which IP it appears to come from, so it carries your real WAN IP. A page gathers all of this with no permission. It opens an <code>RTCPeerConnection</code> pointed at a STUN server, calls <code>setLocalDescription</code>, and reads each candidate as it arrives. The key is that STUN runs over UDP, and an HTTP proxy only tunnels TCP. The STUN request leaves from the real interface, the proxy never sees it, and the <code>srflx</code> candidate comes back with the real WAN IP even though every HTTP request went through the proxy.</p><p>The first version of our probe ran the RTCPeerConnection on <code>about:blank</code> and showed both builds leaking the real IP. That was our mistake, not a result. Camoufox&#8217;s content-level injection is not active on <code>about:blank</code>, so we were measuring an unprotected page. Moving the probe onto a real https origin changed everything:</p><pre><code><code>official-146   HTTP exit IP (proxy): 189.173.138.17
               ICE candidates: 1  [srflx] ips=['203.0.113.25']   &lt;- real WAN IP leaks

jwriter20-146  HTTP exit IP (proxy): 93.44.185.102
               ICE candidates: 0                                &lt;- nothing leaks</code></code></pre><p>Our real WAN IP is 203.0.113.25. The official build, behind a working proxy, still hands it to any page through the WebRTC reflexive candidate. The proxy exit IP rotates on each run, so the constant 203.0.113.25 in the candidate is unmistakably the real address, not the proxy. </p><p>The fix is real, and it is baked into the binary. </p><p>We unzipped both <code>camoufox.cfg</code> files to confirm. The official build sets only <code>media.peerconnection.ice.no_host</code>. JWriter20 adds <code>default_address_only</code>, <code>proxy_only_if_behind_proxy</code>, <code>proxy_only_if_pbmode</code>, and <code>obfuscate_host_addresses</code>. Behind a proxy that cannot carry UDP, those preferences make WebRTC gather no candidates at all, so there is nothing to leak. Reproduced across two runs.</p><p>If WebRTC leaks were your problem, JWriter20 solves them. Hold that thought, because it does not end where you would expect.</p><h2>The canvas patch that we could read but not run</h2><p>To see why this patch exists, you have to understand the small-arms race it sits within. The PropertyTracer run above caught DataDome calling <code>toDataURL</code> and <code>getImageData</code>. Those two calls are how a canvas fingerprint is taken. A script draws the same text and shapes into an off-screen canvas on every machine, reads the pixels back, and hashes them. The drawing commands are identical everywhere. The pixels are not, because the final image depends on your GPU, your graphics driver, and how your system rasterizes fonts. That hash is stable for your device and different from the next one, which is most of what a tracker wants.</p><p>The standard way to hide is to add noise. Camoufox, Brave, Firefox&#8217;s resist-fingerprinting mode, and a long tail of extensions all nudge a few pixels so the hash will not stay constant across sites. The weakness is in how that noise is generated. If it is a fixed per-session perturbation that depends only on a seed and the pixel position, it can be undone. A 2025 paper at The Web Conference, <a href="https://dl.acm.org/doi/abs/10.1145/3696410.3714713">Breaking the Shield</a> by Hoang Dai Nguyen and Phani Vadrevu, showed exactly that against eighteen extensions and five browsers. Their Pixel-Recovery attack paints a second canvas filled with a known solid color and reads it back. Because it knows what every pixel should have been, it solves for the perturbation and subtracts it from the real fingerprint canvas. Reload ten times, and the recovered fingerprint stays constant while the noised one keeps changing. That is the proof the noise was reversible all along.</p><p>Two changes defeat the attack, and the same paper points at both. Leave the flat regions alone, so a detector that paints a solid block and reads it back finds no tampering to measure. And make each perturbation depend on the pixel content rather than its position, so there is no single value to solve for and subtract. The second idea is what Brave&#8217;s Farbling does, deriving its noise from the canvas content so two different canvases are altered differently, and it is the one defense the Pixel-Recovery attack could not reverse.</p><p>LeooNic&#8217;s patch implements both moves, and it is the most interesting code in the whole fork tree. The rewritten <code>ApplyCanvasNoise</code> skips flat regions and only perturbs edges, and the comments name the attack directly:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;279f1acd-8f68-4c56-ab69-a1a65dd58a57&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">// Content-aware + content-dependent canvas noise.
//   - Tier 1 known-pixel checks (DataDome, Castle): flat regions are skipped
//     because flat_score &lt; FLAT_THRESHOLD. fillRect(R,G,B) is undisturbed.
//   - WWW'25 Pixel-Recovery Attack (Nguyen &amp; Vadrevu): noise depends on the
//     pixel content AND its 4 neighbors, not just (seed, index).</code></pre></div><p><code>FLAT_THRESHOLD</code> is the cutoff that decides what counts as a flat region. The edge pixels that survive it get a content-dependent nudge of plus or minus one, small enough to stay invisible but enough to move the hash. The logic is sound on paper. We wanted to confirm it at runtime.</p><p>We built a probe that draws two solid blocks with one sharp boundary and counts perturbed pixels in the flat interior versus the edge (canvas_fingerprint_test.py). </p><p>First, we learned that canvas noise is off by default in every current build, which lines up with the CloverLabs &#8220;Disable Canvas Noise&#8221; commit. The noise only runs when <code>canvas:seed</code> is non-zero. With the seed forced on the official 146 build, the original algorithm shows its tell:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;8f66b3b1-7dde-41de-9999-4a526d196d19&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">official-146 (original noise)
  interior flat pixels perturbed : 9105 / 18240   (~50%)
  boundary edge pixels perturbed : 98 / 192
  max edge delta (per channel)   : 1
  hash varies across sessions    : True</code></pre></div><p>The stock algorithm perturbs roughly half of every pixel, flat fills included. That is precisely the behavior a known-pixel check catches, and precisely what LeooNic set out to fix. So the official baseline is the &#8220;before&#8221; picture, captured at runtime.</p><p>The &#8220;after&#8221; picture is what we were not able to collect. LeooNic ships only a Windows binary, so we ran it on a Windows cloud box. It would not launch under Playwright at all. Every attempt, headless or headful, with the stock launcher or LeooNic&#8217;s own 0.5.0 launcher installed from source, ended the same way:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;d224820d-d322-4344-a4a1-bb64fce1952a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">console.error: "Warning: unrecognized command line flag" "-juggler-pipe"
Remote Settings startup changesets bundle could not be extracted (JSON.parse...)
JavaScript error: AsyncShutdown.sys.mjs, line 587: uncaught exception: undefined
&lt;process did exit: exitCode=0&gt;</code></pre></div><p>The official 135 build launches and drives fine on the same box, so the machine and Playwright are healthy. The Firefox 149 build that LeooNic publishes aborts at startup before Juggler attaches. </p><p>This is not only our environment. LeooNic&#8217;s own issue #1 is titled &#8220;fix: port patches and build system to Firefox 149.0&#8221;, an open work in progress, and daijro carries issues #620 and #572 about Juggler failing to initialize in constrained environments. The build does run through LeooNic&#8217;s native RDP path, which is the whole point of their RDPBrowser, but even there we could not activate the canvas seed. The global config is ignored, and the per-context <code>setCanvasSeed</code> function the build exposes only at document start was never present on the page when driven over RDP.</p><p>So we report LeooNic honestly. The content-aware algorithm is real and well-reasoned in source, and the original algorithm&#8217;s weakness is confirmed at runtime on the official build. The published Firefox 149 binary is not something you can pick up and drive with the standard stack today. For a reader choosing a fork, that is the practical signal. The innovation lives in the code, not yet in a usable artifact you can run.</p><h2>The block-rate test, and the result we did not expect</h2><p>Given that, we could only test the jwriter fork compared to the original version. As always, the code that will be used for testing can be found&nbsp;<a href="https://github.com/TheWebScrapingClub/thelab">in our GitHub repository reserved for paying users, inside the folder&nbsp;</a><strong><a href="https://github.com/TheWebScrapingClub/thelab">106.</a></strong><a href="https://github.com/TheWebScrapingClub/thelab">CAMOUFOX</a><strong>.</strong></p>
      <p>
          <a href="https://substack.thewebscraping.club/p/is-camoufox-still-effective-scraping">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[5 mistakes that are driving up your scraping costs - Insights from DataImpulse]]></title><description><![CDATA[An in-depth look at the real factors driving up web scraping costs and how smarter proxy usage and system design can reduce expenses by up to 60%.]]></description><link>https://substack.thewebscraping.club/p/5-mistakes-that-are-driving-up-your</link><guid isPermaLink="false">https://substack.thewebscraping.club/p/5-mistakes-that-are-driving-up-your</guid><dc:creator><![CDATA[Olia Liudko]]></dc:creator><pubDate>Tue, 02 Jun 2026 20:02:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Jfy8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59b86e5b-12e3-4a07-8713-cc7d13a29418_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>This is a guest post written by the DataImpulse team, tackling the problem behind the costs of scraping. For an independent benchmark of proxy prices, <a href="https://proxyprice.thewebscraping.club/">visit our Proxy Pricing Benchmark tool</a>.</em></p><div><hr></div><p>Every product has its own price, usually formed by a simple and predictable formula. Proxy services follow a similar pricing logic. At first thought, multiplying the proxy price by the amount of bandwidth should result in a final cost, but in fact, many aspects must be taken into consideration. Bandwidth is not a clean, one-to-one reflection of useful work. What users pay for is not just the data they properly collect but also everything that happens around. And it&#8217;s about request failures, encountered blocks, timeouts, and suboptimal routing decisions.</p><p style="text-align: justify;">Much of the traffic in scraping systems is consumed without usable output, and that&#8217;s where the gap between expected and actual costs starts to widen. To understand where the budget really goes, we need to research it deeper.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Jfy8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59b86e5b-12e3-4a07-8713-cc7d13a29418_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Jfy8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59b86e5b-12e3-4a07-8713-cc7d13a29418_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!Jfy8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59b86e5b-12e3-4a07-8713-cc7d13a29418_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!Jfy8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59b86e5b-12e3-4a07-8713-cc7d13a29418_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!Jfy8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59b86e5b-12e3-4a07-8713-cc7d13a29418_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Jfy8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59b86e5b-12e3-4a07-8713-cc7d13a29418_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/59b86e5b-12e3-4a07-8713-cc7d13a29418_1672x941.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1773700,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/200176130?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59b86e5b-12e3-4a07-8713-cc7d13a29418_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Jfy8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59b86e5b-12e3-4a07-8713-cc7d13a29418_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!Jfy8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59b86e5b-12e3-4a07-8713-cc7d13a29418_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!Jfy8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59b86e5b-12e3-4a07-8713-cc7d13a29418_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!Jfy8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59b86e5b-12e3-4a07-8713-cc7d13a29418_1672x941.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>What is the math behind the scraping costs</strong></h2><p style="text-align: justify;">Bandwidth is measurable, and proxy providers typically price it transparently. However, it&#8217;s not the key driver of the cost, it&#8217;s the result of a much more complex process. Each unit of bandwidth stands for a series of events, rather than merely one successful request. While some requests yield immediate data, many others can fail, be obstructed, time out, or require further attempts. Consequently, the actual cost structure is influenced more by system behavior than by the volume of traffic.</p><p style="text-align: justify;">The real cost equals the total number of request cycles necessary to extract data multiplied by the cost of executing each request cycle. In simple words, it is defined by how many complete attempts the system must make before it gets that one response. The key detail is that only successful requests generate value. Each request cycle may have the initial request, retries after timeouts, proxy rotation, session management, and many other factors. All of them consume resources. Because of this, the real price increases not only when proxy prices rise but also when the system becomes less well-functioning. The same dataset can cost remarkably more to extract when the scraping pipeline is inefficient.</p><p style="text-align: justify;">The cost amplification effect, known as the cumulative increase in resource usage caused by constant request cycles, is an issue in web scraping systems. One failed request can trigger multiple follow-up attempts. These extra cycles accumulate and raise the cost required to get one successful data point. For this reason, it&#8217;s accurate to evaluate scraping by cost per successful request. The fewer attempts needed, the lower the real cost.</p><h2><strong>Mistake #1 - Using the wrong proxy type</strong></h2><p style="text-align: justify;">This mistake is not just widespread but can also cost you a lot. Not all targets need the same level of stability and anonymity, but numerous systems use a universal strategy. This results in either high costs or poor operation.</p><p style="text-align: justify;">Each proxy type has its own balance of speed, cost, and detection resistance. The system will not work if that balance doesn&#8217;t align with the target website&#8217;s behavior. For example, mobile IPs are not just more expensive by default. They are highly trusted and harder to get blocked, so it&#8217;s logical to use them for challenging targets. When they are used on low-protection websites, they increase costs without improving the results. The approach that works is based on the right matching of proxy type to the task.</p><ul><li><p style="text-align: justify;">Residential proxies are actively used in web scraping as they route traffic via IPs assigned by ISPs to real devices. By looking like it comes from the real user, these proxies ensure strong trust signals. Many proxy users notice better success rates when they switch to residential IPs.</p></li></ul><ul><li><p style="text-align: justify;">Mobile proxies direct traffic via carrier networks using IP addresses from mobile service providers. Since these IPs are shared, the traffic appears very authentic and is significantly more difficult for systems that rely on fingerprinting to identify.</p></li></ul><ul><li><p style="text-align: justify;">Datacenter proxies function on cloud or server-based infrastructure and use IP ranges that aren&#8217;t tied to real ISPs. The biggest advantage is speed. They are perfect for heavy automation and data collection tasks.</p></li></ul><h2><strong>Mistake #2 - The retry loop problem</strong></h2><p style="text-align: justify;">The goal of retry logic is to improve success rates by giving failed requests another attempt to return a valid request. This approach works when responses are consistent, and failures are occasional, not systematic. On targets with rate limits or unstable responses, many retries can lead to constant failures under the same conditions. Not all failures are the same. If you got a timeout, it&#8217;s worth retrying, but if it&#8217;s a 403 error or a block, there are other actions to try. For example, you can rotate proxies or fix headers. </p><p style="text-align: justify;">Retries can turn into a loop where the system keeps sending more requests but isn&#8217;t getting better results. Instead of retrying everything the same way, treat different errors differently, and adjust your behaviour based on the response. You can rotate proxies after getting a certain status code or stop retrying when a request is blocked.</p><h2><strong>Mistake #3 - Misconfigured proxy rotation</strong></h2><p style="text-align: justify;">Rotating proxies aggressively is not a solution. Changing IPs too often makes traffic look unnatural and can raise suspicion. On the flip side, not rotating enough can create another issue. Thus, there must be balance. Some websites tolerate frequent IP changes, while others expect a more stable session. Treating all targets the same way is not appropriate. It&#8217;s better to adjust rotation based on the context. </p><p style="text-align: justify;">If the server expects the same user behavior over time, using sticky sessions may help. In this case, it&#8217;ll help maintain session consistency and not break the flow. For bulk data extraction, you can try rotating proxies more frequently. In this situation, there is no need to preserve session context. You can also refine your rotation if you use signal-based triggers instead of fixed rules. Rotate thoughtfully, and when the system detects specific conditions like a sudden drop in success rates or status codes, adapt your proxy to it. </p><h2><strong>Mistake #4 - Ignoring caching and duplicate requests</strong></h2><p style="text-align: justify;">A notable portion of scraping traffic is often dedicated to retrieving data that has already been collected. This occurs when pipelines lack deduplication or clear definitions for data freshness. It leads to repeated requests for identical resources. This process consumes bandwidth and proxy capacity without providing new information.</p><p style="text-align: justify;">To address this, implement a caching layer and deduplication logic. Responses can be cached based on a time-to-live (TTL) interval that aligns with the frequency of data updates. Request fingerprints can be used to identify duplicates before requests are sent. For structured data, storing IDs or hashes of processed items allows the system to skip previously captured content. </p><h2><strong>Mistake #5 - No cost-aware proxy routing</strong></h2><p style="text-align: justify;">Many scraping systems process all requests through a single proxy type. This approach can simplify implementation, but it can still lead to ineffectiveness. Different endpoints often have distinct requirements, and a universal strategy may result in unnecessary costs.</p><p style="text-align: justify;">For instance, using proxies with a high trust score for simple endpoints can be expensive, whereas lower-cost proxies used for protected pages may result in blocks and required retries. Without routing logic to adapt to these variables, systems often overpay or underperform. They can&#8217;t adapt.</p><p style="text-align: justify;">An alternative is to implement cost-aware routing, which matches the proxy type to the difficulty of the task. This involves using more economical options for low-risk requests and escalating to higher-trust proxies only when necessary. By monitoring metrics such as status codes, latency, and success rates, the system can determine when to switch proxy pools. For example, a blocked request can be retried using a higher-trust proxy rather than repeating the request under the same conditions.</p><p style="text-align: justify;">This approach creates a more structured pipeline that balances cost and performance by allocating resources based on the specific requirements of each request.</p><h2><strong>Understanding the real price of proxies</strong></h2><p style="text-align: justify;">While &#8220;price per GB&#8221; is often cited as a standard industry metric, experienced engineers understand that it fails to capture the true economic reality of data scraping. In practice, failed requests consume bandwidth and incur costs despite yielding no usable data. These unsuccessful attempts represent a negative return on investment.</p><p style="text-align: justify;">Furthermore, the expenses associated with automated retries add another hidden expense. We have to look beyond the basic per-GB rate and adopt the &#8220;Cost Per Successful Request&#8221; (CPSR). This metric provides a more accurate reflection of true operational expenses.</p><p style="text-align: justify;">To calculate the cost of each valid data retrieval, use the following formula:</p><p style="text-align: justify;"><strong>CPSR = price per GB / 1,000 * 1 / Success Rate</strong></p><p style="text-align: justify;">In this equation, the &#8220;success rate&#8221; is the percentage of requests that return an HTTP 200 OK status along with the intended data. Organizations can make better financial decisions if they start evaluating proxy services through the lens of CPSR.</p><p style="text-align: justify;"><a href="https://dataimpulse.com/residential-proxies/">DataImpulse is a reliable provider</a> of residential, mobile, and datacenter proxies with non-expiring traffic and a pay-as-you-go model, meaning purchased traffic remains available until it is used. This vendor offers more than 90 million IPs in 195 countries. Teams usually choose DataImpulse for web scraping, ad verification, market research, SERP monitoring, and website testing. </p><h3 style="text-align: justify;"><strong>Why is DataImpulse cheaper than other vendors? </strong></h3><p style="text-align: justify;">The pricing structure is based on the proxy sourcing method. Many providers purchase traffic rights from ISPs and resell them, which includes an additional markup. DataImpulse sources IP addresses directly through its own application and SDKs, bypassing intermediaries to avoid extra costs. This operational model complies with all legal standards.</p><h2><strong>How to reduce your scraping costs by 30-60%</strong></h2><p style="text-align: justify;">Cost efficiency in data collection is primarily achieved by minimizing inefficient requests and increasing the success rate of each attempt. </p><ol><li><p style="text-align: justify;">To optimize expenses, match proxy types to the specific requirements of the task. Using cost-effective proxies for straightforward targets while reserving higher-trust proxies for more challenging endpoints can reduce unnecessary spending.</p></li><li><p style="text-align: justify;">Refining retry logic is also important. Failures should be addressed based on their specific status codes. Avoiding repeat requests under identical conditions prevents the waste of resources.</p></li><li><p style="text-align: justify;">Proxy rotation should be managed strategically rather than randomly. Implementing sticky sessions and rotating based on indicators such as blocks or elevated failure rates can improve both stability and overall success rates.</p></li><li><p style="text-align: justify;">Incorporating caching and deduplication techniques helps manage traffic. By avoiding redundant requests for data that has not changed, it is possible to decrease total request volume. </p></li><li><p style="text-align: justify;">Implement a cost-aware proxy routing strategy. Prioritize lower-cost alternatives, escalating to premium options only when strictly necessary. This approach facilitates a more efficient resource allocation model, ensuring that infrastructure investments are directed toward the areas of greatest impact.</p></li><li><p style="text-align: justify;">Lastly, pay attention to how your scraper interacts with websites. When a browser loads a page, it also pulls images, scripts, videos, and even fonts. Thus, lots of traffic is generated. Use HTTP requests for structured data and browser-based scraping when JS rendering is necessary. </p></li></ol><p style="text-align: justify;">These optimizations don&#8217;t require a comprehensive system overhaul. Incremental improvements in request efficiency can harvest significant cost reductions.</p><h2><strong>Start measuring your current CPSR baseline</strong></h2><p style="text-align: justify;">At first sight, scraping costs look like a simple equation between proxy price and bandwidth. But the real drivers of cost lie deeper.  As we&#8217;ve seen, unnecessary retries and poor rotation strategies contribute to a growing gap between expected and actual costs. A system with low success rates will always consume more resources. </p><p style="text-align: justify;">The important shift is moving away from thinking in terms of raw pricing and toward thinking in terms of efficiency. It doesn&#8217;t always require major steps, simple adjustments are key. Better proxy selection, cost-aware routing, caching, and improved retry logic are among them. From factual proxy usage data from DataImpulse, we&#8217;ve seen that even small optimizations can noticeably reduce total costs. Every request should add value, so spending must be thoughtful and deliberate. Audit your scraping pipeline against these 5 mistakes today. </p>]]></content:encoded></item><item><title><![CDATA[Why and How to Build a Web Scraper with Rust in 2026]]></title><description><![CDATA[Is Rust the future of web scraping? Let&#8217;s find out!]]></description><link>https://substack.thewebscraping.club/p/how-to-build-a-web-scraper-rust</link><guid isPermaLink="false">https://substack.thewebscraping.club/p/how-to-build-a-web-scraper-rust</guid><dc:creator><![CDATA[Antonello Zanini]]></dc:creator><pubDate>Sun, 31 May 2026 15:27:12 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/159b826b-b10b-4781-9efd-ddc651b7f874_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>What do popular developer technologies like ZeroClaw, IronClaw, Codex CLI, and many others have in common, besides thousands of GitHub stars, tons of downloads, and growing communities? They are all developed in Rust!</p><p>Rust is becoming increasingly popular thanks to its advantages in performance, stability, and security. But what about using it for web scraping?</p><p>In this post, I&#8217;ll show you what Rust brings to the table for web scraping, why it makes sense (and when it doesn&#8217;t), and how to build a web scraper in Rust.</p><h2>Main Characteristics of Rust: Quick Overview</h2><p>Rust stands out because it combines performance, safety, and control in a way few programming languages do. According to the <a href="https://survey.stackoverflow.co/2025/technology">2025 Stack Overflow Developer Survey</a>, 14.8% of respondents reported using Rust that year, making it the 14th most popular option.</p><p>Personally, what I find most compelling about Rust is its <a href="https://doc.rust-lang.org/nomicon/meet-safe-and-unsafe.html">memory safety model</a>. Thanks to <a href="https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html">ownership and borrowing</a>, it avoids entire classes of bugs like memory leaks or race conditions. All of that, without needing a garbage collector!</p><div><hr></div><blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gCo7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gCo7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png" width="626" height="313" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:626,&quot;bytes&quot;:390972,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/197764148?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!gCo7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Before proceeding, let me thank NetNut, the platinum partner of the month. Their set of solutions cover all your needs for scraping.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack&quot;,&quot;text&quot;:&quot;Visit Netnut&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack"><span>Visit Netnut</span></a></p><div><hr></div></blockquote><p>Here&#8217;s what Rust looks like in its simplest form:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">fn main() {
    println!("Hello, world!");
}</code></pre></div><p>Even in this minimal example, you can see Rust&#8217;s explicit structure and compile-time guarantees.</p><p><strong>Remember</strong>: In Rust, <em>println!</em> is not a function. It&#8217;s a macro. The <em>!</em> tells the compiler: &#8220;this is a macro invocation, not a normal function call.&#8221;</p><p>Performance is another big win. Rust is compiled and extremely fast, making it ideal for high-throughput parsing on heavy HTML pages or large volumes of pages (e.g., in an <a href="https://substack.thewebscraping.club/p/offline-web-scraping">offline web scraping scenario</a>). Concurrency is also first-class, helping you manage thousands of requests in parallel without the usual headaches.</p><p>On the flip side, Rust has a steeper learning curve. If you&#8217;re coming from Python or JavaScript, getting used to the syntax and strict compiler won&#8217;t be trivial. In my experience, the first steps can feel a bit unforgiving&#8230;</p><blockquote><div><hr></div></blockquote><h2>Why AI Has Made Rust a Solid Choice for Web Scraping</h2><p>AI is changing the nature of software development, including web development. And, as you may already have noticed, not always in a &#8220;lighter&#8221; direction. Humans struggle to deal with long scripts and source code files, but machines don&#8217;t!</p><p>Thus, AI tends to produce very long and complex HTML with a lot of elements embedded in the same page. On top of that, AI makes it trivial to generate large amounts of content, which further increases HTML size. In addition, <a href="https://developer.mozilla.org/en-US/curriculum/core/semantic-html/">semantic HTML</a> is more verbose than traditional HTML.</p><p>As a result, modern web pages are getting bigger and more complex. From a scraping perspective, this translates into slower and more resource-intensive parsing. What used to be lightweight DOM trees are now dense, deeply nested structures that require more CPU and memory to process.</p><p>This is exactly where Rust starts to make sense&#8230;</p><p>Sure, it may not be the easiest programming language, but Rust&#8217;s performance makes it compelling (and in some cases even necessary). Its low-level control and zero-cost abstractions allow Rust HTML parsers to process large documents in fractions of a second, even under high concurrency.</p><p><a href="https://medium.com/@jgfriedman99/html-parsing-benchmarks-2170417e8c06">Independent benchmarks</a> show Rust HTML parsers ranking among the fastest available. In particular, libraries like <em><a href="https://github.com/y21/tl">tl</a></em> stand out for their exceptional speed and low overhead.</p><div><hr></div><blockquote><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EOo9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EOo9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 424w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 848w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1272w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EOo9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png" width="630" height="69.35779816513761" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:120,&quot;width&quot;:1090,&quot;resizeWidth&quot;:630,&quot;bytes&quot;:133037,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/194398731?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!EOo9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 424w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 848w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1272w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p><strong><a href="https://byteful.com?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE">Start your scraping journey with Byteful</a></strong>: 10GB New Customer Trial | Use TWSC for 15% OFF | $1.75/GB Residential Data | ISP Proxies in 15+ Countries</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://byteful.com?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE&quot;,&quot;text&quot;:&quot;Claim your 10GB here&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://byteful.com?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE"><span>Claim your 10GB here</span></a></p></blockquote><div><hr></div><h2>Best Rust Web Scraping Libraries</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NgZ2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91b93f4f-ed7e-405e-a5e2-7b1d06181faf_1920x1234.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NgZ2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91b93f4f-ed7e-405e-a5e2-7b1d06181faf_1920x1234.png 424w, https://substackcdn.com/image/fetch/$s_!NgZ2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91b93f4f-ed7e-405e-a5e2-7b1d06181faf_1920x1234.png 848w, https://substackcdn.com/image/fetch/$s_!NgZ2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91b93f4f-ed7e-405e-a5e2-7b1d06181faf_1920x1234.png 1272w, https://substackcdn.com/image/fetch/$s_!NgZ2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91b93f4f-ed7e-405e-a5e2-7b1d06181faf_1920x1234.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NgZ2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91b93f4f-ed7e-405e-a5e2-7b1d06181faf_1920x1234.png" width="1456" height="936" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/91b93f4f-ed7e-405e-a5e2-7b1d06181faf_1920x1234.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:936,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The best Rust web scraping libraries&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The best Rust web scraping libraries" title="The best Rust web scraping libraries" srcset="https://substackcdn.com/image/fetch/$s_!NgZ2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91b93f4f-ed7e-405e-a5e2-7b1d06181faf_1920x1234.png 424w, https://substackcdn.com/image/fetch/$s_!NgZ2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91b93f4f-ed7e-405e-a5e2-7b1d06181faf_1920x1234.png 848w, https://substackcdn.com/image/fetch/$s_!NgZ2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91b93f4f-ed7e-405e-a5e2-7b1d06181faf_1920x1234.png 1272w, https://substackcdn.com/image/fetch/$s_!NgZ2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91b93f4f-ed7e-405e-a5e2-7b1d06181faf_1920x1234.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The best Rust web scraping libraries</figcaption></figure></div><h2>How to Build a Scraper in Rust: A Step-by-Step Guide</h2><p>In this section, I&#8217;ll guide you through the process of building a web scraper in Rust. The target web page will be <a href="https://books.toscrape.com/">Books to Scrape&#8217;s homepage</a>. This is a static page, which is the ideal scenario for high-speed HTML parsing in Rust.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CIY7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62a5aaf8-f5b1-44f2-93e8-72daee5b3cf7_3008x1623.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CIY7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62a5aaf8-f5b1-44f2-93e8-72daee5b3cf7_3008x1623.png 424w, https://substackcdn.com/image/fetch/$s_!CIY7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62a5aaf8-f5b1-44f2-93e8-72daee5b3cf7_3008x1623.png 848w, https://substackcdn.com/image/fetch/$s_!CIY7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62a5aaf8-f5b1-44f2-93e8-72daee5b3cf7_3008x1623.png 1272w, https://substackcdn.com/image/fetch/$s_!CIY7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62a5aaf8-f5b1-44f2-93e8-72daee5b3cf7_3008x1623.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CIY7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62a5aaf8-f5b1-44f2-93e8-72daee5b3cf7_3008x1623.png" width="1456" height="786" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/62a5aaf8-f5b1-44f2-93e8-72daee5b3cf7_3008x1623.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:786,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!CIY7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62a5aaf8-f5b1-44f2-93e8-72daee5b3cf7_3008x1623.png 424w, https://substackcdn.com/image/fetch/$s_!CIY7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62a5aaf8-f5b1-44f2-93e8-72daee5b3cf7_3008x1623.png 848w, https://substackcdn.com/image/fetch/$s_!CIY7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62a5aaf8-f5b1-44f2-93e8-72daee5b3cf7_3008x1623.png 1272w, https://substackcdn.com/image/fetch/$s_!CIY7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62a5aaf8-f5b1-44f2-93e8-72daee5b3cf7_3008x1623.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The Books to Scrape homepage</figcaption></figure></div><p>The end goal is to scrape all the book information and export it to a CSV file. Follow the instructions below!</p><h3>Prerequisites</h3><p>Make sure you have:</p><ul><li><p><a href="https://rust-lang.org/tools/install/">Rust installed locally</a> (the article refers to Rust 1.95.0).</p></li><li><p>Some basic familiarity with <a href="https://doc.rust-lang.org/book/">Rust syntax and constructs</a>.</p></li></ul><div><hr></div><blockquote><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8Qrb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8Qrb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!8Qrb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!8Qrb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!8Qrb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8Qrb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png" width="479" height="239.5" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:479,&quot;bytes&quot;:911444,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/196394917?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8Qrb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!8Qrb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!8Qrb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!8Qrb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68fe580f-2f4e-4452-9e3c-0ca00777d9f6_1200x600.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Trusted by teams running ad verification, web scraping, SERP tracking, and market research. Ethically sourced proxies, globally accessible, and fairly priced.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://dataimpulse.com/&quot;,&quot;text&quot;:&quot;Get Started With DataImpulse&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://dataimpulse.com/"><span>Get Started With DataImpulse</span></a></p></blockquote><div><hr></div><h3>Step #1: Set Up a Rust Scraping Project</h3><p>Create a new Rust project for web scraping with:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;shell&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-shell">cargo new books_rust_scraper</code></pre></div><p>This will generate a new project called <em>books_rust_scraper</em> containing a basic &#8220;Hello, world!&#8221; program. Move into the project folder:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;shell&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-shell">cd books_rust_scraper</code></pre></div><p>You should now see the following file structure:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">
books_rust_scraper/
&#9500;&#9472;&#9472; src/
&#9474;   &#9492;&#9472;&#9472; main.rs
&#9500;&#9472;&#9472; target/
&#9500;&#9472;&#9472; .gitignore
&#9500;&#9472;&#9472; Cargo.toml
&#9492;&#9472;&#9472; Cargo.lock</code></pre></div><p>Focus on the <em>src/main.rs</em> file:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oopm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd75148e5-6f13-44b6-b373-1ecd9477ce31_1364x386.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oopm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd75148e5-6f13-44b6-b373-1ecd9477ce31_1364x386.png 424w, https://substackcdn.com/image/fetch/$s_!oopm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd75148e5-6f13-44b6-b373-1ecd9477ce31_1364x386.png 848w, https://substackcdn.com/image/fetch/$s_!oopm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd75148e5-6f13-44b6-b373-1ecd9477ce31_1364x386.png 1272w, https://substackcdn.com/image/fetch/$s_!oopm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd75148e5-6f13-44b6-b373-1ecd9477ce31_1364x386.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oopm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd75148e5-6f13-44b6-b373-1ecd9477ce31_1364x386.png" width="1364" height="386" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d75148e5-6f13-44b6-b373-1ecd9477ce31_1364x386.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:386,&quot;width&quot;:1364,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The src/main.rs file&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The src/main.rs file" title="The src/main.rs file" srcset="https://substackcdn.com/image/fetch/$s_!oopm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd75148e5-6f13-44b6-b373-1ecd9477ce31_1364x386.png 424w, https://substackcdn.com/image/fetch/$s_!oopm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd75148e5-6f13-44b6-b373-1ecd9477ce31_1364x386.png 848w, https://substackcdn.com/image/fetch/$s_!oopm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd75148e5-6f13-44b6-b373-1ecd9477ce31_1364x386.png 1272w, https://substackcdn.com/image/fetch/$s_!oopm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd75148e5-6f13-44b6-b373-1ecd9477ce31_1364x386.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The src/main.rs file</figcaption></figure></div><p>This is the entry point of your application and currently contains a simple &#8220;Hello, world!&#8221; example. Test your Rust application with:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;shell&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-shell">cargo run</code></pre></div><p>The command executes the <em>src/main.rs </em>file, so the result will be:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">Hello, world!</code></pre></div><p>In that file, you&#8217;ll implement your Rust web scraping logic. Great!</p><h3>Step #2: Install Required Dependencies</h3><p>Run these commands to install the crates (Rust libraries) needed to build a Rust web scraper:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;shell&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-shell">cargo add tokio --features full
cargo add reqwest
cargo add scraper
cargo add csv</code></pre></div><p>These are the core dependencies:</p><ul><li><p><em><a href="https://docs.rs/tokio/latest/tokio/">tokio</a></em>: Enables asynchronous execution.</p></li><li><p><em><a href="https://docs.rs/reqwest/latest/reqwest/">reqwest</a></em>: To send HTTP requests to retrieve HTML pages.</p></li><li><p><em><a href="https://docs.rs/scraper/latest/scraper/">scraper</a></em>: To parse HTML and extract data using CSS selectors.</p></li><li><p><em><a href="https://docs.rs/csv/latest/csv/">csv</a></em>: To export the scraped data to a CSV file.</p></li></ul><p>After running the commands above, your <em>Cargo.toml</em> file should look similar to this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;toml&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-toml">[package]
name = "books_rust_scraper"
version = "0.1.0"
edition = "2024"

[dependencies]
csv = "1.4.0"
reqwest = "0.13.3"
scraper = "0.26.0"
tl = "0.7.8"
tokio = { version = "1.52.1", features = ["full"] }</code></pre></div><p>Nice! You now have all the dependencies in place to start building your Rust scraper.</p><h3>Step #3: Retrieve the Target Page</h3><p>Use <em>reqwest</em> to fetch the target page with:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">use std::error::Error;
use reqwest::Client;

#[tokio::main]
async fn main() -&gt; Result&lt;(), Box&lt;dyn Error&gt;&gt; {
    // Initialize the HTTP client
    let client = Client::builder()
        .build()?;

    // Retrieve the target page
    let url = "https://books.toscrape.com/";
    let response = client
    .get(url)
    .send()
    .await?;

    // Extract the HTML content from the response
    let html = response.text().await?;
  
    // Parsing logic...

    // Data export logic...

    Ok(())
}</code></pre></div><p>This snippet initializes an asynchronous HTTP client using Tokio, sends a GET request to the target URL, retrieves the HTML response body, and prepares it for parsing and data extraction.</p><p>If you print <em>html</em>, you&#8217;ll observe:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!G1Ll!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a9f95f6-cbe9-4eb9-8bcb-68527d145173_2154x809.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!G1Ll!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a9f95f6-cbe9-4eb9-8bcb-68527d145173_2154x809.png 424w, https://substackcdn.com/image/fetch/$s_!G1Ll!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a9f95f6-cbe9-4eb9-8bcb-68527d145173_2154x809.png 848w, https://substackcdn.com/image/fetch/$s_!G1Ll!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a9f95f6-cbe9-4eb9-8bcb-68527d145173_2154x809.png 1272w, https://substackcdn.com/image/fetch/$s_!G1Ll!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a9f95f6-cbe9-4eb9-8bcb-68527d145173_2154x809.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!G1Ll!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a9f95f6-cbe9-4eb9-8bcb-68527d145173_2154x809.png" width="1456" height="547" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6a9f95f6-cbe9-4eb9-8bcb-68527d145173_2154x809.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:547,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The HTML of the target page&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The HTML of the target page" title="The HTML of the target page" srcset="https://substackcdn.com/image/fetch/$s_!G1Ll!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a9f95f6-cbe9-4eb9-8bcb-68527d145173_2154x809.png 424w, https://substackcdn.com/image/fetch/$s_!G1Ll!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a9f95f6-cbe9-4eb9-8bcb-68527d145173_2154x809.png 848w, https://substackcdn.com/image/fetch/$s_!G1Ll!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a9f95f6-cbe9-4eb9-8bcb-68527d145173_2154x809.png 1272w, https://substackcdn.com/image/fetch/$s_!G1Ll!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a9f95f6-cbe9-4eb9-8bcb-68527d145173_2154x809.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The HTML of the target page</figcaption></figure></div><p>Excellent! Get ready to apply the Rust data parsing logic.</p><h3>Step #4: Implement the Parsing Logic</h3><p>Before implementing the web scraping logic in Rust, study the DOM of the target page. Inspect a book HTML element in the browser:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Zd3v!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaf83d45-7adf-4d99-8a1d-283622a7e8e3_2102x1171.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Zd3v!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaf83d45-7adf-4d99-8a1d-283622a7e8e3_2102x1171.png 424w, https://substackcdn.com/image/fetch/$s_!Zd3v!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaf83d45-7adf-4d99-8a1d-283622a7e8e3_2102x1171.png 848w, https://substackcdn.com/image/fetch/$s_!Zd3v!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaf83d45-7adf-4d99-8a1d-283622a7e8e3_2102x1171.png 1272w, https://substackcdn.com/image/fetch/$s_!Zd3v!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaf83d45-7adf-4d99-8a1d-283622a7e8e3_2102x1171.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Zd3v!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaf83d45-7adf-4d99-8a1d-283622a7e8e3_2102x1171.png" width="1456" height="811" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/baf83d45-7adf-4d99-8a1d-283622a7e8e3_2102x1171.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:811,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Inspecting a book HTML element&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Inspecting a book HTML element" title="Inspecting a book HTML element" srcset="https://substackcdn.com/image/fetch/$s_!Zd3v!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaf83d45-7adf-4d99-8a1d-283622a7e8e3_2102x1171.png 424w, https://substackcdn.com/image/fetch/$s_!Zd3v!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaf83d45-7adf-4d99-8a1d-283622a7e8e3_2102x1171.png 848w, https://substackcdn.com/image/fetch/$s_!Zd3v!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaf83d45-7adf-4d99-8a1d-283622a7e8e3_2102x1171.png 1272w, https://substackcdn.com/image/fetch/$s_!Zd3v!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaf83d45-7adf-4d99-8a1d-283622a7e8e3_2102x1171.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Inspecting a book HTML element</figcaption></figure></div><p>From this structure, notice how you can select all books using the <em>article.product_pod</em> CSS selector. For each book element, you can retrieve:</p><ul><li><p>The title and URL from <em>h3 a</em>.</p></li><li><p>The image URL from <em>.image_container img</em>.</p></li><li><p>The price from <em>.price_color</em>.</p></li><li><p>The rating from <em>p.star-rating</em>.</p></li><li><p>The stock status from <em>.instock.availability</em>.</p></li></ul><p>First, <a href="https://doc.rust-lang.org/book/ch05-01-defining-structs.html">define a struct</a> to store that data:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">#[derive(Debug)]
struct Book {
    url: String,
    image_url: String,
    title: String,
    price: String,
    rating: String,
    in_stock: bool,
}</code></pre></div><p>Next, define the <em>parse_books()</em> function that extracts and structures the data:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">use scraper::{Html, Selector};
// ...

// ...
fn parse_books(html: &amp;str) -&gt; Result&lt;Vec&lt;Book&gt;, Box&lt;dyn Error&gt;&gt; {
    // Parse the HTML content
    let document = Html::parse_document(html);

    // Define CSS selectors for the HTML elements of interest
    let book_selector = Selector::parse("article.product_pod")?;
    let title_selector = Selector::parse("h3 a")?;
    let image_selector = Selector::parse(".image_container img")?;
    let price_selector = Selector::parse(".price_color")?;
    let rating_selector = Selector::parse("p.star-rating")?;
    let stock_selector = Selector::parse(".instock.availability")?;

    // Where to store the scraped data
    let mut books = Vec::new();

    // Iterate over each book element and extract the relevant data
    for book_el in document.select(&amp;book_selector) {
        // Apply the parsing logic
        let title_el = book_el.select(&amp;title_selector).next().unwrap();

        let relative_url = title_el.value().attr("href").unwrap_or("");
        let url = format!(
            "https://books.toscrape.com/catalogue/{}",
            relative_url
        );

        let image_url = book_el
            .select(&amp;image_selector)
            .next()
            .and_then(|img| img.value().attr("src"))
            .unwrap_or("")
            .to_string();

        let image_url = format!(
            "https://books.toscrape.com/{}",
            image_url.trim_start_matches('/')
        );

        let title = title_el
            .value()
            .attr("title")
            .unwrap_or("")
            .to_string();

        let price = book_el
            .select(&amp;price_selector)
            .next()
            .map(|e| e.text().collect::&lt;String&gt;())
            .unwrap_or_default();

        let rating = book_el
            .select(&amp;rating_selector)
            .next()
            .and_then(|e| e.value().attr("class"))
            .unwrap_or("no rating")
            .replace("star-rating", "")
            .trim()
            .to_lowercase();

        let in_stock = book_el
            .select(&amp;stock_selector)
            .next()
            .map(|e| {
                let text = e.text().collect::&lt;String&gt;();
                text.to_lowercase() == "in stock"
            })
            .unwrap_or(false);

        // Collect the scraped book data
        books.push(Book {
            title,
            price,
            rating,
            in_stock,
            image_url,
            url,
        });
    }

    Ok(books)
}</code></pre></div><p>This function parses raw HTML into structured data using the <em>scraper</em> crate. <em>Html::parse_document()</em> creates a DOM-like representation of the page, while <em>Selector::parse()</em> defines CSS selectors for targeting elements.</p><p><em>document.select(&amp;book_selector)</em> iterates over each book container. Inside each element, <em>.select()</em> extracts nested elements, while <em>.value().attr()</em> retrieves attributes such as links and titles. The <em>.text()</em> method collects visible text content.</p><p>Finally, all extracted values are assembled into a <em>Book</em> struct, and each instance is stored in a vector for later export or processing.</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.youtube.com/@thewebscrapingclub&quot;,&quot;text&quot;:&quot;Check the TWSC YouTube Channel&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.youtube.com/@thewebscrapingclub"><span>Check the TWSC YouTube Channel</span></a></p><div><hr></div><h3>Step #5: Export the Scraped Data</h3><p>Right now, the scraped data is returned by the <em>parse_books()</em> function as a vector of <em>Book</em> structs. Next, add a function that uses the <em>csv</em> crate to export that data into a CSV file:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">use csv::Writer;
// ...

//...
fn write_csv(books: &amp;[Book], file_path: &amp;str) -&gt; Result&lt;(), Box&lt;dyn std::error::Error&gt;&gt; {
    let mut wtr = Writer::from_path(file_path)?;

    // Write the header row
    wtr.write_record(&amp;[
        "url",
        "image_url",
        "title",
        "price",
        "rating",
        "in_stock",
    ])?;

    for book in books {
        wtr.write_record(&amp;[
            &amp;book.url,
            &amp;book.image_url,
            &amp;book.title,
            &amp;book.price,
            &amp;book.rating,
            &amp;book.in_stock.to_string(),
        ])?;
    }

    wtr.flush()?;
    Ok(())
}</code></pre></div><h3>Step #6: Put It All Together</h3><p>This is the final code of your Rust web scraper:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">// src/main.rs

use std::error::Error;
use reqwest::Client;
use scraper::{Html, Selector};
use csv::Writer;

#[derive(Debug)]
struct Book {
    url: String,
    image_url: String,
    title: String,
    price: String,
    rating: String,
    in_stock: bool,
}

#[tokio::main]
async fn main() -&gt; Result&lt;(), Box&lt;dyn Error&gt;&gt; {
    // Initialize the HTTP client
    let client = Client::builder()
        .build()?;

    // Retrieve the target page
    let url = "https://books.toscrape.com/";
    let response = client
    .get(url)
    .send()
    .await?;

    // Extract the HTML content from the response
    let html = response.text().await?;

    // Parse the books data from the HTML
    let books = parse_books(&amp;html)?;

    // Export the scraped data to a CSV file
    write_csv(&amp;books, "books.csv")?;

    Ok(())
}

fn parse_books(html: &amp;str) -&gt; Result&lt;Vec&lt;Book&gt;, Box&lt;dyn Error&gt;&gt; {
    // Parse the HTML content
    let document = Html::parse_document(html);

    // Define CSS selectors for the HTML elements of interest
    let book_selector = Selector::parse("article.product_pod")?;
    let title_selector = Selector::parse("h3 a")?;
    let image_selector = Selector::parse(".image_container img")?;
    let price_selector = Selector::parse(".price_color")?;
    let rating_selector = Selector::parse("p.star-rating")?;
    let stock_selector = Selector::parse(".instock.availability")?;

    // Where to store the scraped data
    let mut books = Vec::new();

    // Iterate over each book element and extract the relevant data
    for book_el in document.select(&amp;book_selector) {
        // Apply the parsing logic
        let title_el = book_el.select(&amp;title_selector).next().unwrap();

        let relative_url = title_el.value().attr("href").unwrap_or("");
        let url = format!(
            "https://books.toscrape.com/catalogue/{}",
            relative_url
        );

        let image_url = book_el
            .select(&amp;image_selector)
            .next()
            .and_then(|img| img.value().attr("src"))
            .unwrap_or("")
            .to_string();

        let image_url = format!(
            "https://books.toscrape.com/{}",
            image_url.trim_start_matches('/')
        );

        let title = title_el
            .value()
            .attr("title")
            .unwrap_or("")
            .to_string();

        let price = book_el
            .select(&amp;price_selector)
            .next()
            .map(|e| e.text().collect::&lt;String&gt;())
            .unwrap_or_default();

        let rating = book_el
            .select(&amp;rating_selector)
            .next()
            .and_then(|e| e.value().attr("class"))
            .unwrap_or("no rating")
            .replace("star-rating", "")
            .trim()
            .to_lowercase();

        let in_stock = book_el
            .select(&amp;stock_selector)
            .next()
            .map(|e| {
                let text = e.text().collect::&lt;String&gt;();
                text.to_lowercase() == "in stock"
            })
            .unwrap_or(false);

        // Collect the scraped book data
        books.push(Book {
            title,
            price,
            rating,
            in_stock,
            image_url,
            url,
        });
    }

    Ok(books)
}

fn write_csv(books: &amp;[Book], file_path: &amp;str) -&gt; Result&lt;(), Box&lt;dyn std::error::Error&gt;&gt; {
    let mut wtr = Writer::from_path(file_path)?;

    // Write the header row
    wtr.write_record(&amp;[
        "url",
        "image_url",
        "title",
        "price",
        "rating",
        "in_stock",
    ])?;

    for book in books {
        wtr.write_record(&amp;[
            &amp;book.url,
            &amp;book.image_url,
            &amp;book.title,
            &amp;book.price,
            &amp;book.rating,
            &amp;book.in_stock.to_string(),
        ])?;
    }

    wtr.flush()?;
    Ok(())
}</code></pre></div><p>Note how all previously defined functions are now called inside <em>main()</em>. Et voila! In just around 150 lines of code, you&#8217;ve built an efficient web scraper in Rust.</p><p>Run your scraper with:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;shell&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-shell">cargo run</code></pre></div><p>After execution, a <em>books.csv</em> file will be created in your project folder. Open it, and you will see:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ou7T!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9467bbe4-1ba4-49ab-9e07-5e3dbb2bb6b8_3051x1212.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ou7T!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9467bbe4-1ba4-49ab-9e07-5e3dbb2bb6b8_3051x1212.png 424w, https://substackcdn.com/image/fetch/$s_!Ou7T!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9467bbe4-1ba4-49ab-9e07-5e3dbb2bb6b8_3051x1212.png 848w, https://substackcdn.com/image/fetch/$s_!Ou7T!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9467bbe4-1ba4-49ab-9e07-5e3dbb2bb6b8_3051x1212.png 1272w, https://substackcdn.com/image/fetch/$s_!Ou7T!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9467bbe4-1ba4-49ab-9e07-5e3dbb2bb6b8_3051x1212.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ou7T!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9467bbe4-1ba4-49ab-9e07-5e3dbb2bb6b8_3051x1212.png" width="1456" height="578" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9467bbe4-1ba4-49ab-9e07-5e3dbb2bb6b8_3051x1212.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:578,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The output books.csv file&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The output books.csv file" title="The output books.csv file" srcset="https://substackcdn.com/image/fetch/$s_!Ou7T!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9467bbe4-1ba4-49ab-9e07-5e3dbb2bb6b8_3051x1212.png 424w, https://substackcdn.com/image/fetch/$s_!Ou7T!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9467bbe4-1ba4-49ab-9e07-5e3dbb2bb6b8_3051x1212.png 848w, https://substackcdn.com/image/fetch/$s_!Ou7T!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9467bbe4-1ba4-49ab-9e07-5e3dbb2bb6b8_3051x1212.png 1272w, https://substackcdn.com/image/fetch/$s_!Ou7T!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9467bbe4-1ba4-49ab-9e07-5e3dbb2bb6b8_3051x1212.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The output books.csv file</figcaption></figure></div><p>This matches exactly the data shown on the target website, but now in a structured format. Mission complete!</p><h2>Browser Automation in Rust: Does It Make Sense?</h2><p>First of all, it&#8217;s worth noting that the ecosystem for browser automation in Rust is quite small compared to JavaScript or Python. Also, most libraries aren&#8217;t official, but rather community-backed ports like Playwright Rust or the Selenium bindings.</p><p>Now, from a technical standpoint, browser automation happens inside the browser itself. So, Chrome, Chromium, or Firefox do most of the heavy lifting. What you define through the library&#8217;s API simply orchestrates operations like clicking, waiting for elements, and extracting data. These commands are then translated into browser actions via <a href="https://substack.thewebscraping.club/p/webdriver-vs-cdp-vs-bidi">WebDriver, CDP, or WebDriver BiDi</a>.</p><p>Because of that, using a systems-level language like Rust can be more of a burden than an advantage. The main strength of Rust (i.e., raw performance) doesn&#8217;t really matter here, since the controlled browser instances are the actual bottleneck, not your automation code.</p><p>That means we lose Rust&#8217;s biggest advantage while still paying its costs. On top of that, Rust&#8217;s strict compiler and steeper learning curve can slow down development speed.</p><p>To be honest, I see Rust as excellent for the parsing and data processing layer, but I wouldn&#8217;t recommend it for browser automation&#8230;</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/p/consulting&quot;,&quot;text&quot;:&quot;Need help with your scraping project?&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://substack.thewebscraping.club/p/consulting"><span>Need help with your scraping project?</span></a></p><div><hr></div><h3>Rust for Web Scraping: Final Comment</h3><p>If I had to summarize my experience with Rust for web scraping, I&#8217;d say this: <em>it really shines when you&#8217;re parsing large HTML pages at scale or handling a high number of parsing tasks in parallel.</em></p><p>In those scenarios, the combination of performance, memory safety, and concurrency makes a real difference. That said, I wouldn&#8217;t recommend Rust for everyday scraping tasks&#8230; The entry barrier is just too high, the learning curve too steep, and the ecosystem around scraping too small.</p><p>On top of that, finding experienced Rust developers specifically focused on web scraping, or even just <a href="https://substack.thewebscraping.club/p/the-evolving-career-scrapers">translating those skills into job opportunities</a>, can be way more challenging than in more mainstream stacks.</p><p>So my take is pretty simple: consider Rust when performance and scale <em>truly</em> matter. For everything else, prefer Python or JavaScript.</p><p><strong>&#128077; Pros</strong>:</p><ul><li><p>Can efficiently handle thousands of requests in parallel.</p></li><li><p>Rust HTML parsers are extremely.</p></li><li><p>Strict compiler checks and static guarantees lead to stable scraping pipelines.</p></li></ul><p><strong>&#128078; Cons</strong>:</p><ul><li><p>Slower development and prototyping speed.</p></li><li><p>Smaller ecosystem of scraping libraries compared to Python or JavaScript.</p></li><li><p>Not a practical choice for browser automation.</p></li></ul><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://substack.thewebscraping.club/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Web Scraping Club is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>Conclusion</h2><p>Here, I&#8217;ve guided you through the world of web scraping in Rust. In a world dominated by AI slops, security flaws, and neglected best practices, this programming language is gaining traction thanks to its focus on efficiency and strict compilation.</p><p>As you&#8217;ve seen, Rust is excellent for CPU-intensive or memory-intensive tasks like HTML parsing and data processing. Still, it might not be ideal for browser automation or quick prototyping. You also learned how to go from zero to scraped data in CSV format by building a Rust scraper.</p><p>I hope you found this helpful and insightful. If you have any questions, feel free to share them in the comments below!</p>]]></content:encoded></item><item><title><![CDATA[THE LAB #105: If LLMs Can Bypass CAPTCHAs, Are CAPTCHA Solver Services Cooked?]]></title><description><![CDATA[Bypassing hCaptcha in the AI era]]></description><link>https://substack.thewebscraping.club/p/bypassing-hcaptcha-llm</link><guid isPermaLink="false">https://substack.thewebscraping.club/p/bypassing-hcaptcha-llm</guid><dc:creator><![CDATA[Pierluigi Vinciguerra]]></dc:creator><pubDate>Fri, 29 May 2026 13:13:17 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/87ac6a36-66d4-4153-8a63-d6ee184d4f29_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Think back to 2023. The story about large language models was that they would automate most work involving reading or writing, and a good slice of the work that means looking at a screen the way you do. We bought that story too. We even wrote <a href="https://substack.thewebscraping.club/p/are-captchas-still-a-thing">Are CAPTCHAs still a thing?</a> that August, reporting on the ETH Zurich paper that claimed AI bots beat humans at reCAPTCHA v2 image challenges by roughly 15%. CAPTCHAs were on the long list of things LLMs were supposed to make irrelevant on the road to general agency.</p><blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gCo7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gCo7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png" width="626" height="313" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:626,&quot;bytes&quot;:390972,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/197764148?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gCo7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!gCo7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a7017ca-9ba7-4512-91e1-2682ab05ca13_1200x600.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Before proceeding, let me thank NetNut, the platinum partner of the month. Their set of solutions cover all your needs for scraping.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack&quot;,&quot;text&quot;:&quot;Visit Netnut&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://netnut.io/?utm_source=webscrapingclub&amp;utm_medium=newsletter&amp;utm_campaign=130526&amp;utm_content=product-stack"><span>Visit Netnut</span></a></p></blockquote><blockquote><div><hr></div></blockquote><h2>What the 2023 LLM hype promised, and what 2026 actually shipped</h2><p>The product category that took that promise most literally is the agentic browser. A real Chromium running under an LLM that reads the page, decides what to do, and clicks. Browserbase, Hyperbrowser, Skyvern, Browser Use, BrowserOS, Owl Browser, and a long tail of proxy companies rebranding their scraping browsers as &#8220;AI-powered&#8221;. The pitch in 2024 and 2025 never changed. You hand the agent a task in plain language, and the model handles the rest, including whatever defensive challenge the site throws back.</p><p>It is 2026 now, and that prediction has not played out. CAPTCHAs are still in your pipeline. So we went looking for the answer. Can an LLM actually solve a production-grade CAPTCHA like hCaptcha? We read the code where we could, checked the default configs and the docs, and probed the public surfaces of the solver services these products lean on. The picture is consistent, and it is not the one the marketing sells.</p><p>Every major agentic browser ships a CAPTCHA-solving bullet point on its site. Open the code of the open-source agents, though, and you find a different story. Almost none of them actually use an LLM to solve a CAPTCHA. They either refuse to try, or they try and fail.</p><div><hr></div><blockquote><p><em>For your scraping needs, having a reliable proxy provider like <strong>Decodo</strong> on your side improves the chances of success. </em></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qJZE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qJZE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png" width="401" height="225.5625" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fec072bc-2ac2-49bf-8084-43858298808f_800x450.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:450,&quot;width&quot;:800,&quot;resizeWidth&quot;:401,&quot;bytes&quot;:554497,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/187315628?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!qJZE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!qJZE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffec072bc-2ac2-49bf-8084-43858298808f_800x450.png 1456w" sizes="100vw"></picture><div></div></div></a><figcaption class="image-caption"></figcaption></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://visit.decodo.com/WyQ3mA&quot;,&quot;text&quot;:&quot;Try Decodo Now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://visit.decodo.com/WyQ3mA"><span>Try Decodo Now</span></a></p></blockquote><div><hr></div><p>One thing to get straight before we open any code. The two families of CAPTCHA behave very differently, and the hype lands on them unevenly. The invisible ones, reCAPTCHA v3 and Cloudflare Turnstile, score your session in the background and rarely show a puzzle. A stealth-first browser on clean residential proxies usually walks past them without anyone seeing a challenge, which we covered for Turnstile in <a href="https://substack.thewebscraping.club/p/cloudflare-turnstile-what-is-that">Cloudflare Turnstile: what is that and how it works?</a> and <a href="https://substack.thewebscraping.club/p/how-to-bypass-cloudflare-turnstile">THE LAB #73: How to Bypass Cloudflare in 2025</a>. The visible image challenges, hCaptcha and reCAPTCHA v2, actually demand an answer. We went deep on reCAPTCHA v2 in <a href="https://substack.thewebscraping.club/p/bypassing-recaptchas-with-open-source">Bypassing reCAPTCHAs With Open Source and Commercial Tools - Part 2</a>. The one that matters in 2026 is hCaptcha, and that is where this article lives, because it is where most scrapers break.</p><p>So here is the question that drives everything below. When an agentic browser says it &#8220;solves&#8221; an hCaptcha, what does its code actually do?</p><h2>Tool landscape</h2><h3>What&#8217;s the commercial offer today</h3><p>There are four families of strategy in the commercial set. In none of them is the LLM in the agent layer doing the CAPTCHA work.</p><p>The first family is stealth-first. Residential proxies, fingerprint shaping, and request patterning lower the bot score so the visible challenge never triggers. The CAPTCHA is not solved. It is prevented from appearing. That gives you the cleanest legal posture in the set, because no automated solving is happening. ZenRows is the example.</p><p>The second family is the opposite of stealth. Instead of hiding that the traffic is automated, the vendor declares it openly and relies on a business arrangement with the CAPTCHA providers to be let through. Browserbase is explicit about this. Its <a href="https://docs.browserbase.com/features/stealth-mode">Stealth Mode documentation</a> says &#8220;through Browserbase&#8217;s partnerships with CAPTCHA providers, Browserbase can resolve challenges automatically so your sessions continue without interruption&#8221;, with solving &#8220;enabled by default for all sessions&#8221;. This is the verified-bot path, the same idea behind Cloudflare&#8217;s Web Bot Auth. A declared, allowlisted agent rather than a disguised one. No model solves a puzzle on either side. The provider recognizes the partner and waves it through. For the common challenge types, according to their documentation, this works without a fight.</p><p>The third family pairs a proprietary solver with documented third-party integrations. The vendor ships its own solving for some challenge types. For the rest, it documents how to wire in an external solver service. The external solver watches for the challenge and returns the response token through its extension or REST API. The agent then submits. Hyperbrowser and Skyvern Cloud both present this shape, a native or closed-source component plus a documented third-party path. Hyperbrowser advertises &#8220;Native Cloudflare Turnstile &amp; CAPTCHA Solving&#8221; with &#8220;No external plugins&#8221; in its <a href="https://tech.hyperbrowser.ai/scraping-infrastructure-native-turnstile-captcha-solving">post on native CAPTCHA solving</a>, then points to an external solver for the challenge types the native one does not cover.</p><div><hr></div><blockquote><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EOo9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EOo9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 424w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 848w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1272w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EOo9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png" width="630" height="69.35779816513761" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:120,&quot;width&quot;:1090,&quot;resizeWidth&quot;:630,&quot;bytes&quot;:133037,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/194398731?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!EOo9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 424w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 848w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1272w, https://substackcdn.com/image/fetch/$s_!EOo9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2905ffa2-e3cd-4be3-ab69-02cceb4073a9_1090x120.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p><strong><a href="https://byteful.com?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE">Start your scraping journey with Byteful</a></strong>: 10GB New Customer Trial | Use TWSC for 15% OFF | $1.75/GB Residential Data | ISP Proxies in 15+ Countries</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://byteful.com?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE&quot;,&quot;text&quot;:&quot;Claim your 10GB here&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://byteful.com?utm_source=twsc&amp;utm_medium=new_banner&amp;utm_id=twsc&amp;promotion_public_id=10GB_TWSC_TRIAL&amp;promotion_code=NJKLBIOCWYENIPFE"><span>Claim your 10GB here</span></a></p></blockquote><div><hr></div><p>The fourth family runs an in-house solver alongside the agent. Could be a vision LLM, could be classical computer vision, could be something else. No third party is in the critical path. The vendor owns the whole stack. Bright Data, Oxylabs, and Owl Browser sit here. <a href="https://owlbrowser.net/">Owl Browser</a> puts hard numbers on the claim: &#8220;detect and automatically solve reCAPTCHA v2 (1.2s), hCaptcha (0.8s), Turnstile (0.3s), and image CAPTCHAs.&#8221; Bright Data sells &#8220;AI-based unlocking logic&#8221; that handles &#8220;CAPTCHA solving, fingerprinting, retries, best headers, location and more&#8221; on its <a href="https://brightdata.com/products/web-unlocker/captcha-solver">Web Unlocker page</a>.</p><p>The table below maps each vendor to the mechanism its public documentation surfaces, with the source page that proves it.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!klTW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e187d27-0a40-45c6-bef3-a0194faa3c1b_654x839.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!klTW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e187d27-0a40-45c6-bef3-a0194faa3c1b_654x839.png 424w, https://substackcdn.com/image/fetch/$s_!klTW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e187d27-0a40-45c6-bef3-a0194faa3c1b_654x839.png 848w, https://substackcdn.com/image/fetch/$s_!klTW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e187d27-0a40-45c6-bef3-a0194faa3c1b_654x839.png 1272w, https://substackcdn.com/image/fetch/$s_!klTW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e187d27-0a40-45c6-bef3-a0194faa3c1b_654x839.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!klTW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e187d27-0a40-45c6-bef3-a0194faa3c1b_654x839.png" width="654" height="839" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3e187d27-0a40-45c6-bef3-a0194faa3c1b_654x839.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:839,&quot;width&quot;:654,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:164510,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.thewebscraping.club/i/199732185?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e187d27-0a40-45c6-bef3-a0194faa3c1b_654x839.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!klTW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e187d27-0a40-45c6-bef3-a0194faa3c1b_654x839.png 424w, https://substackcdn.com/image/fetch/$s_!klTW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e187d27-0a40-45c6-bef3-a0194faa3c1b_654x839.png 848w, https://substackcdn.com/image/fetch/$s_!klTW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e187d27-0a40-45c6-bef3-a0194faa3c1b_654x839.png 1272w, https://substackcdn.com/image/fetch/$s_!klTW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e187d27-0a40-45c6-bef3-a0194faa3c1b_654x839.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.youtube.com/@thewebscrapingclub&quot;,&quot;text&quot;:&quot;Check the TWSC YouTube Channel&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.youtube.com/@thewebscrapingclub"><span>Check the TWSC YouTube Channel</span></a></p><div><hr></div><h3>The open-source agents we can read</h3><p>For the proprietary CAPTCHA-solving strategies we cannot see how AI is used. For the open-source ones we can. So we opened their code and read exactly how they handle CAPTCHAs. Three projects matter here.</p><p><a href="https://github.com/browser-use/browser-use">browser-use</a> is the most popular open-source LLM agent framework for browser automation. MIT-licensed, vendor-agnostic on the LLM side. The repo contains no LLM-driven CAPTCHA-solving logic. The one CAPTCHA-related file it ships, <a href="https://raw.githubusercontent.com/browser-use/browser-use/main/browser_use/browser/watchdogs/captcha_watchdog.py">captcha_watchdog.py</a>, does not solve anything either. It waits for a solver running in the BrowserUse cloud proxy and blocks the agent loop until that solver reports back. Run the library locally with your own model and no cloud proxy, and the watchdog has nothing to wait for. That makes browser-use the cleanest test of the claim that a local LLM agent solves the CAPTCHA by reading the page.</p><p><a href="https://github.com/Skyvern-AI/skyvern">Skyvern OSS</a> is the open core of the Skyvern Cloud product. AGPL-3.0, focused on form-filling and structured workflows, written in Python.</p><p><a href="https://github.com/browseros-ai/BrowserOS">BrowserOS</a> is a YC S24 open-source agent-driven browser. AGPL-3.0, 11k stars on GitHub, active development. It pairs a Chromium fork with an integrated agent runtime.</p><h2>Modeling hCaptcha and reading what the open-source agents actually do</h2><p>Before testing, it helps to model the target. hCaptcha embeds a widget on the host page through a script served from <code>hcaptcha.com</code>. </p><p>The widget renders inside an iframe whose origin is <code>hcaptcha.com</code>, cross-origin to the host page. When the user clicks the &#8220;I am human&#8221; checkbox, the widget decides whether to issue a challenge. If it does, a second iframe opens with the puzzle dialog. The puzzle layout varies between runs (3x3 grid, 4x3 grid, area-select with a single click on an image, bounding-box, multiple-choice prompt). When the puzzle is solved, the widget writes a response token to a hidden <code>textarea[name="h-captcha-response"]</code> on the host page. The host form reads that textarea on submit and posts the token along with the rest of the data. The whole solve interaction happens inside a frame the host page cannot script. The same-origin policy boundary blocks it.</p><p>That last detail decides what works and what does not. An agent driving the host page through Playwright or CDP has full control over the outer page. Inside the <code>hcaptcha.com</code> frame, its control is limited. A browser extension runs with cross-origin privileges. It can both observe and click inside the widget frame. That asymmetry explains most of what follows.</p><p>We started by reading three open-source agent repos to see what each one had decided to do at this boundary.</p><h3>Skyvern OSS bails to the human</h3><p>Skyvern&#8217;s README is candid about the scope of the OSS release: &#8220;All of the core logic powering Skyvern is available in this open source repository licensed under the AGPL-3.0 License, with the exception of anti-bot measures available in our managed cloud offering.&#8221; That single sentence puts the CAPTCHA section of every Skyvern Cloud feature page outside the repo you can read.</p><p>The repo confirms the framing. In <a href="https://github.com/Skyvern-AI/skyvern/blob/main/skyvern/forge/agent_functions.py#L800-L804">agent_functions.py, lines 800-804</a>, the <code>auto_solve_captchas</code> helper returns <code>False</code> unconditionally. In <a href="https://github.com/Skyvern-AI/skyvern/blob/main/skyvern/webeye/actions/handler.py#L1149-L1162">handler.py, lines 1149-1162</a>, <code>handle_solve_captcha_action</code> does exactly one thing of substance.</p><pre><code><code>async def handle_solve_captcha_action(
    action: actions.SolveCaptchaAction,
    page: Page,
    scraped_page: ScrapedPage,
    task: Task,
    step: Step,
) -&gt; list[ActionResult]:
    LOG.warning(
        "Please solve the captcha on the page, you have 30 seconds",
        action=action,
    )
    await asyncio.sleep(30)
    return [ActionSuccess()]</code></code></pre><p>Thirty seconds of <code>asyncio.sleep</code> and a log message asking a human to handle it. Then a success result, regardless of what the human actually did. The script-generation path in <a href="https://github.com/Skyvern-AI/skyvern/blob/main/skyvern/core/script_generations/skyvern_page.py#L1252-L1254">skyvern_page.py, lines 1252-1254</a> is even more direct. <code>solve_captcha</code> raises <code>NotImplementedError</code>. A Skyvern user opened <a href="https://github.com/Skyvern-AI/skyvern/issues/1117">issue #1117</a> asking how CAPTCHA solving was meant to work in OSS. A maintainer answered plainly: &#8220;We haven&#8217;t open sourced anything related to our captcha solver / anti-bot measures. We don&#8217;t want people abusing these things, so they must remain closed source unfortunately.&#8221;</p><p>The Skyvern OSS code does not pretend to solve CAPTCHAs. Whatever Skyvern Cloud does on top of this, the OSS release hands the problem to a human and moves on.</p><p>As always, the code that will use to bypass hCaptcha can be found <a href="https://github.com/TheWebScrapingClub/thelab">in our GitHub repository reserved to paying users, inside the folder </a><strong><a href="https://github.com/TheWebScrapingClub/thelab">105.</a></strong><a href="https://github.com/TheWebScrapingClub/thelab">HCAPTCHA</a><strong>.</strong></p>
      <p>
          <a href="https://substack.thewebscraping.club/p/bypassing-hcaptcha-llm">
              Read more
          </a>
      </p>
   ]]></content:encoded></item></channel></rss>