<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hugo.H</title>
    <description>The latest articles on DEV Community by Hugo.H (@bpx13303).</description>
    <link>https://dev.to/bpx13303</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4130351%2F4dd4ec71-dcbb-4cd1-8160-a9c0f28379d5.jpg</url>
      <title>DEV Community: Hugo.H</title>
      <link>https://dev.to/bpx13303</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bpx13303"/>
    <language>en</language>
    <item>
      <title>Six of my collectors returned zero. Three of them were never blocked. published: false</title>
      <dc:creator>Hugo.H</dc:creator>
      <pubDate>Thu, 08 Oct 2026 08:44:54 +0000</pubDate>
      <link>https://dev.to/bpx13303/six-of-my-collectors-returned-zero-three-of-them-were-never-blockedpublished-false-31mo</link>
      <guid>https://dev.to/bpx13303/six-of-my-collectors-returned-zero-three-of-them-were-never-blockedpublished-false-31mo</guid>
      <description>&lt;p&gt;I spent a morning pointing one collector at fourteen different sources. By lunchtime six of them were returning zero rows.&lt;/p&gt;

&lt;p&gt;My first instinct was the same one everybody has: they are blocking me. I was wrong on half of them, and that half cost me far more time than the real blocks did.&lt;/p&gt;

&lt;p&gt;Here is what the fourteen sources actually did, and why a zero tells you almost nothing on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;SIX DIFFERENT WAYS TO GET NOTHING&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A block is not one thing. Across those fourteen sources I hit six genuinely different behaviours, and only two of them look like failure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a flat refusal, immediate and honest&lt;/li&gt;
&lt;li&gt;a rate limit that appears after a few calls and then never lifts&lt;/li&gt;
&lt;li&gt;an interstitial challenge page served instead of the content&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;a success code with an empty body&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;a validation error that rejects the request before it is even considered&lt;/li&gt;
&lt;li&gt;a not found that is not actually a not found&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two of those are easy. The other four all answer in ways that your error handling will happily wave through.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;THE ONE THAT ACTUALLY FOOLED ME&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;One source answered with a success status code. Not a 403, not a redirect, not a challenge. A code in the 200 family, which every HTTP client on earth treats as "fine".&lt;/p&gt;

&lt;p&gt;The body was empty.&lt;/p&gt;

&lt;p&gt;My collector did what any collector does: it took the success code at face value, parsed nothing, found nothing, and moved on. It did that &lt;strong&gt;270 times in a row&lt;/strong&gt;. The run finished cleanly. No exception, no retry, no warning.&lt;/p&gt;

&lt;p&gt;Then it handed me a qualification rate of &lt;strong&gt;0.4 percent&lt;/strong&gt; and I sat there for twenty minutes rewriting my filters, convinced my targeting criteria were wrong.&lt;/p&gt;

&lt;p&gt;My criteria were fine. I had simply read 270 blank pages and measured the blankness very precisely.&lt;/p&gt;

&lt;p&gt;That is the whole lesson of the morning. &lt;strong&gt;A collector that is being quietly starved produces output that is indistinguishable from a collector that is working on a bad dataset.&lt;/strong&gt; You will debug the wrong layer, and you will do it confidently.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;THREE OF THE SIX WERE NEVER BLOCKED AT ALL&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This is the part I did not expect.&lt;/p&gt;

&lt;p&gt;Of the six sources returning zero, three were serving me everything I asked for, perfectly, the whole time.&lt;/p&gt;

&lt;p&gt;One had a page titled after the exact thing I was searching for. The content was right there. My collector discarded all of it because of an assumption I had made about the shape of its links, and that assumption was wrong.&lt;/p&gt;

&lt;p&gt;Another one told me, in plain text on the page, that my results were in a different section of the site. It printed the counts. I was querying the one section that happened to be empty, and it had been telling me so from the first attempt.&lt;/p&gt;

&lt;p&gt;A third had simply changed where it keeps its records since the last time anyone looked.&lt;/p&gt;

&lt;p&gt;Three sources out of fourteen. Zero rows each. &lt;strong&gt;Not a single one of them was protected.&lt;/strong&gt; I had spent the morning building defences against an enemy that was not in the room, while the actual problem was my own reading of pages I was already being handed.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;WHAT I CHECK BEFORE I BELIEVE A ZERO&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A zero is a question, not an answer. Before I touch anything defensive, I now establish three things in order:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Did I receive a real page, or a shape of a page?&lt;/strong&gt; A success code proves a conversation happened. It proves nothing about whether anything was said. The size and the substance of what came back are separate facts from the status, and they have to be measured separately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the page itself disagree with me?&lt;/strong&gt; Sites are surprisingly talkative about their own state. Counts, section labels, empty state messages. More than once the page has told me exactly why it had nothing for me, in a sentence I had not bothered to read because I was looking at my parser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is my own assumption the newest thing in the system?&lt;/strong&gt; Every collector carries beliefs about the shape of its target. Those beliefs were true when they were written. The target does not know that and does not care.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;WHY THIS COSTS REAL MONEY&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;On fourteen sources in one morning, this is an annoying detour.&lt;/p&gt;

&lt;p&gt;On a collector that has been feeding a product for eight months, it is a different story. The pipeline keeps running. The dashboards keep refreshing. The numbers stop moving, and nobody notices for weeks, because &lt;strong&gt;every system that watches whether the job ran will tell you it ran.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Almost nothing watches whether it delivered. That gap is where the expensive failures live, and it is the same gap whether you are blocked, half blocked, or not blocked at all and simply wrong.&lt;/p&gt;




&lt;p&gt;If your collector is returning less than it used to and nothing in the logs explains why, that is usually not a mystery, it is an unmeasured layer. I do that diagnosis as a fixed scope piece of work, and most of the time the cause is not where the team expected it to be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I take that work on Upwork.&lt;/strong&gt; Bring me the symptom and what you have already ruled out, and I will tell you which layer is actually lying to you.&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>monitoring</category>
      <category>dataengineering</category>
      <category>debugging</category>
    </item>
    <item>
      <title>The site said 141 listings. I delivered 109. It was 100%.</title>
      <dc:creator>Hugo.H</dc:creator>
      <pubDate>Tue, 06 Oct 2026 09:06:11 +0000</pubDate>
      <link>https://dev.to/bpx13303/the-site-said-141-listings-i-delivered-109-it-was-100-8nn</link>
      <guid>https://dev.to/bpx13303/the-site-said-141-listings-i-delivered-109-it-was-100-8nn</guid>
      <description>&lt;p&gt;I set myself a real-world test: collect every apartment listed for sale in one city, from a large property portal sitting behind a Cloudflare challenge, and deliver it the way a paying client would expect.&lt;/p&gt;

&lt;p&gt;The site's own counter said &lt;strong&gt;141 listings&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I delivered &lt;strong&gt;109&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And I could prove that 109 was &lt;strong&gt;100%&lt;/strong&gt; of what the site actually publishes.&lt;/p&gt;

&lt;p&gt;This post is about that gap, because it's the question every buyer of scraped data should ask, and almost nobody does: &lt;strong&gt;how do you know you got everything?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;THE WALL WAS NOT THE HARD PART&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The first request came back 403. So did the homepage. The site was running a JavaScript challenge that refuses anything that doesn't behave like a real visitor.&lt;/p&gt;

&lt;p&gt;Getting through took a session that respected the site's own rules: its crawling rules, a human pace, no forbidden paths, and no personal data collected at any point. Once that was in place, the listings loaded.&lt;/p&gt;

&lt;p&gt;That's where most scraping jobs stop. Rows in a file, job done.&lt;/p&gt;

&lt;p&gt;It's also exactly where the expensive mistakes start.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;141 VS 109: THE QUESTION THAT MATTERS&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A gap of 32 listings. Two possible stories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;My scraper missed 32 listings.&lt;/strong&gt; The dataset is incomplete, and every average, every price per m² built on it is quietly wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The counter overstates what the site actually serves.&lt;/strong&gt; The dataset is complete, and the counter is the thing that's off.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From the outside, both look identical. A file with 109 rows. The only way to know is to check.&lt;/p&gt;

&lt;p&gt;So I didn't trust a single path. I collected the same city through &lt;strong&gt;three independent entry points&lt;/strong&gt;, then compared them line by line.&lt;/p&gt;

&lt;p&gt;All three converged on the same 109 listings. Not one listing appeared in one path and not the others.&lt;/p&gt;

&lt;p&gt;The counter was counting things the site doesn't actually publish in its lists. &lt;strong&gt;109 was the full set.&lt;/strong&gt; Nobody has to take my word for it: the proof comes with the file.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;THE SILENT TRAPS ALONG THE WAY&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;None of these threw an error. All of them would have shipped a wrong dataset:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Number formats.&lt;/strong&gt; The listings write 72,30 m², with a decimal comma. Read the wrong way, that becomes 7,230 m². One apartment the size of a shopping mall, and the average surface of the whole city is garbage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infinite scroll that gives up early.&lt;/strong&gt; The list loads more results as you scroll. Stop a little too soon and you silently collect 11 listings, or 29, instead of 109.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partner ads mixed into the results.&lt;/strong&gt; Some cards look exactly like listings but come from other sites. Count them and your totals are inflated; drop them blindly and you lose real data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A city page that isn't only the city.&lt;/strong&gt; Nearby towns get mixed in with distances attached. Fine if you know it, misleading if you don't.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of these is invisible if you only check that the job finished.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;WHAT A CLIENT ACTUALLY BUYS&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Not 109 rows. Anyone can hand over rows.&lt;/p&gt;

&lt;p&gt;A client buys &lt;strong&gt;certainty&lt;/strong&gt;: a clean file, one row per real listing, every field normalised, and a short quality report showing coverage, missing fields per column, and how the total was verified.&lt;/p&gt;

&lt;p&gt;That report is the difference between data you can build decisions on and data you hope is right.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;TAKEAWAYS&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The wall is rarely the hardest part.&lt;/strong&gt; Proving completeness is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A site's own counter is not ground truth.&lt;/strong&gt; Verify against what it actually serves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silent traps don't crash.&lt;/strong&gt; They just make your numbers wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask your data supplier one question:&lt;/strong&gt; how do you know nothing is missing?&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;I build and maintain scrapers for protected and fast-changing sites, and every delivery comes with proof of what was collected and what wasn't. If you're not sure your dataset is complete, or your scraper keeps getting blocked, &lt;a href="https://www.upwork.com/freelancers/~016bb430964006d617" rel="noopener noreferrer"&gt;reach out on Upwork&lt;/a&gt; and I'll take a look.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>dataengineering</category>
      <category>dataquality</category>
      <category>automation</category>
    </item>
    <item>
      <title>My scraper returned HTTP 200 and zero rows. Nothing crashed.</title>
      <dc:creator>Hugo.H</dc:creator>
      <pubDate>Tue, 29 Sep 2026 08:45:07 +0000</pubDate>
      <link>https://dev.to/bpx13303/my-scraper-returned-http-200-and-zero-rows-nothing-crashed-3fe4</link>
      <guid>https://dev.to/bpx13303/my-scraper-returned-http-200-and-zero-rows-nothing-crashed-3fe4</guid>
      <description>&lt;p&gt;The worst scraping failure I've dealt with didn't throw a single error.&lt;/p&gt;

&lt;p&gt;No 403. No captcha. No timeout. The request came back &lt;strong&gt;HTTP 200, 884 KB of HTML, in 557 ms&lt;/strong&gt;. The parser ran fine. And it returned &lt;strong&gt;0 listings&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If nobody is watching, that run gets logged as a success. So does the next one, and the one after that. You find out weeks later, when someone asks why the price chart went flat on the 12th.&lt;/p&gt;

&lt;p&gt;Here's what happened, what it cost, and what I now put on every scraper I run.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;WHAT CHANGED&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The target was a large European classifieds site. One night they shipped a front end redesign. Same pages, same URLs, same data on screen. Different markup underneath.&lt;/p&gt;

&lt;p&gt;The scraper had been written against the old layout. Overnight, the parts it relied on simply stopped existing. Not one of them threw an error: they just matched nothing.&lt;/p&gt;

&lt;p&gt;The page itself was perfectly healthy. The data was still there, in plain sight. The scraper just couldn't see it anymore.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;WHY NOBODY NOTICED&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Every check that usually catches a broken scraper was green:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the server answered 200&lt;/li&gt;
&lt;li&gt;the page size was normal&lt;/li&gt;
&lt;li&gt;the response time was normal&lt;/li&gt;
&lt;li&gt;the job finished without a single exception&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From the outside, the run looked identical to a good day. The only thing missing was the only thing that mattered: the data.&lt;/p&gt;

&lt;p&gt;That's the trap. &lt;strong&gt;Most monitoring watches whether the scraper ran. Almost none watches whether it delivered.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;THE FIX&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I rebuilt the extraction so it no longer depends on the parts of a page that designers change every few months. Redesigns happen; the collector now rides through them instead of breaking on them.&lt;/p&gt;

&lt;p&gt;Result: &lt;strong&gt;0 → 505 listings across 4 requests, 100% field completeness&lt;/strong&gt; on price, mileage, year and location.&lt;/p&gt;

&lt;p&gt;And there was a second silent bug hiding inside the first fix. Some fields came back empty, with no error, no warning, nothing in the logs. It only showed up because I was counting empty fields column by column. &lt;strong&gt;A silent failure inside the fix for a silent failure.&lt;/strong&gt; Without that count, it would have shipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;WHAT I CHECK ON EVERY RUN NOW&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Status codes tell you whether the server answered. They say nothing about whether you got the data. So every run gets judged on its output:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Zero rows on a 200&lt;/strong&gt; is a failure, never an empty day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volume against the recent average&lt;/strong&gt;: a sudden drop means something changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Field completeness&lt;/strong&gt;: a scraper can find every row and still lose a field.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Any of these trips an alert within minutes, instead of someone noticing weeks later.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;TAKEAWAYS&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A 200 is not a success.&lt;/strong&gt; Judge a run by what it extracted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sites will change.&lt;/strong&gt; The question is whether your scraper survives it or quietly dies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The question isn't whether your scraper will break.&lt;/strong&gt; It's how fast you'll know.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;I build and maintain scrapers for protected and fast-changing sites, with this kind of monitoring built in. If your scraper is quietly returning less than it should, or getting blocked, &lt;a href="https://www.upwork.com/freelancers/~016bb430964006d617" rel="noopener noreferrer"&gt;reach out on Upwork&lt;/a&gt; and I'll take a look.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>dataengineering</category>
      <category>javascript</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Hugo.H</dc:creator>
      <pubDate>Sun, 20 Sep 2026 08:02:10 +0000</pubDate>
      <link>https://dev.to/bpx13303/-i8e</link>
      <guid>https://dev.to/bpx13303/-i8e</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/bpx13303/why-websites-block-your-data-explained-without-the-tech-jargon-2cgh" class="crayons-story__hidden-navigation-link"&gt;Why websites block your data (explained without the tech jargon)&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/bpx13303" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4130351%2F4dd4ec71-dcbb-4cd1-8160-a9c0f28379d5.jpg" alt="bpx13303 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/bpx13303" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Hugo.H
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Hugo.H
                
                
              
              &lt;div id="story-author-preview-content-4698307" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/bpx13303" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4130351%2F4dd4ec71-dcbb-4cd1-8160-a9c0f28379d5.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Hugo.H&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/bpx13303/why-websites-block-your-data-explained-without-the-tech-jargon-2cgh" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 20&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/bpx13303/why-websites-block-your-data-explained-without-the-tech-jargon-2cgh" id="article-link-4698307"&gt;
          Why websites block your data (explained without the tech jargon)
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webscraping"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webscraping&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/beginners"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;beginners&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/data"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;data&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/datascience"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;datascience&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/bpx13303/why-websites-block-your-data-explained-without-the-tech-jargon-2cgh" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt;&amp;nbsp;reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/bpx13303/why-websites-block-your-data-explained-without-the-tech-jargon-2cgh#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Why websites block your data (explained without the tech jargon)</title>
      <dc:creator>Hugo.H</dc:creator>
      <pubDate>Sun, 20 Sep 2026 07:51:26 +0000</pubDate>
      <link>https://dev.to/bpx13303/why-websites-block-your-data-explained-without-the-tech-jargon-2cgh</link>
      <guid>https://dev.to/bpx13303/why-websites-block-your-data-explained-without-the-tech-jargon-2cgh</guid>
      <description>&lt;p&gt;If you've ever tried to pull data off a website and hit a wall — an error, a blank page, a "prove you're human" screen — this is for you. No code, no jargon. Just what's actually happening and why it's harder than it looks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Think of the website as a nightclub
&lt;/h2&gt;

&lt;p&gt;A protected website has a bouncer at the door. His whole job is to let real people in and keep bots out. When your script tries to grab data, it walks up to that door — and the bouncer is watching.&lt;/p&gt;

&lt;p&gt;Here's the thing most people don't realise: the bouncer doesn't just check one thing. He checks a whole list, and he gets smarter every month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trick 1 — "just ask nicely"
&lt;/h2&gt;

&lt;p&gt;The simplest way to get data is to send a plain request, like knocking politely and asking for the list.&lt;/p&gt;

&lt;p&gt;On an unprotected site, this works. On a protected one, the bouncer takes one look and hands you a fake page instead of the real data.&lt;/p&gt;

&lt;p&gt;And here's the sneaky part: the site often says "OK, success!" while actually giving you a block page. So your tool thinks it worked, saves a page full of nothing, and you don't find out until much later. A "success" message doesn't mean you got the real thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trick 2 — "dress like a regular"
&lt;/h2&gt;

&lt;p&gt;Next idea: make your script look like a real web browser (Chrome, Safari…). Put on the right outfit so the bouncer thinks you belong.&lt;/p&gt;

&lt;p&gt;Problem: the bouncer doesn't just look at your clothes. He watches how you move, how you talk, tiny details a real regular has and a stranger in a costume doesn't. A good disguise gets you a second look — not through the door.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trick 3 — "send a robot dressed as a person"
&lt;/h2&gt;

&lt;p&gt;So you send a more convincing stand-in: an automated browser that really does load the page like a person would.&lt;/p&gt;

&lt;p&gt;Closer. But these automated browsers have tells — little tics that give them away. And the bouncer has been trained specifically to spot exactly those tics. Some nights you slip through; most nights you get pulled aside.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually works — send a real regular
&lt;/h2&gt;

&lt;p&gt;The trick that works isn't a better disguise. It's sending someone who genuinely belongs.&lt;/p&gt;

&lt;p&gt;Instead of faking a browser, you use a real one — the exact same program a normal person uses every day — and quietly guide it to collect what you need. From the bouncer's point of view there's nothing to catch, because there's nothing fake. It's a real regular walking in.&lt;/p&gt;

&lt;p&gt;That's the whole secret: stop pretending to be legit, and just be legit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters if you're paying for data
&lt;/h2&gt;

&lt;p&gt;Two things worth knowing if you're hiring someone (or thinking of DIY-ing it):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. "It loaded" doesn't mean "it worked."&lt;/strong&gt; The most expensive mistake is a file that looks full but is quietly full of block pages. Whoever does the job should check the data is real, not just that the page returned something.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Getting in once is easy. Staying in is the job.&lt;/strong&gt; The bouncer changes his checklist constantly. A scraper that works today can be dead next month. The real value isn't the clever trick to get past once — it's keeping the data flowing reliably over time.&lt;/p&gt;

&lt;p&gt;That second point is where most cheap solutions fall apart, and it's the part worth paying for.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you've got a site that keeps blocking you and you don't know why, tell me the symptom — a blank page, an error, a "prove you're human" screen — and I'll tell you what you're likely dealing with. Costs nothing to ask.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build and maintain scrapers for protected and fast-changing sites (Akamai, DataDome, Cloudflare). If yours is getting blocked or returning empty results, feel free to &lt;a href="https://www.upwork.com/freelancers/~016bb430964006d617" rel="noopener noreferrer"&gt;reach out on Upwork&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>beginners</category>
      <category>data</category>
      <category>datascience</category>
    </item>
    <item>
      <title>When curl, TLS spoofing and headless all fail: getting past Akamai Bot Manager</title>
      <dc:creator>Hugo.H</dc:creator>
      <pubDate>Sat, 19 Sep 2026 07:39:21 +0000</pubDate>
      <link>https://dev.to/bpx13303/when-curl-tls-spoofing-and-headless-all-fail-how-i-got-past-akamai-with-a-real-browser-46fj</link>
      <guid>https://dev.to/bpx13303/when-curl-tls-spoofing-and-headless-all-fail-how-i-got-past-akamai-with-a-real-browser-46fj</guid>
      <description>&lt;p&gt;I scrape data for a living, and most of that job is the same fight over and over: a site has data I need, and it really doesn't want me to have it. This is the story of one wall that beat every trick in the usual playbook, and how I finally got through.&lt;/p&gt;

&lt;h2&gt;
  
  
  The target
&lt;/h2&gt;

&lt;p&gt;I needed a set of listings from a large European car marketplace. Nothing exotic: title, price, a few fields per listing. The kind of thing that should take twenty minutes.&lt;/p&gt;

&lt;p&gt;It took a lot more than twenty minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 1: plain requests
&lt;/h2&gt;

&lt;p&gt;I started where everyone starts: a plain HTTP request with a normal User-Agent header.&lt;/p&gt;

&lt;p&gt;Blocked. Not a 404, not a timeout, a challenge page. And here's the first trap that eats a lot of beginners: the response came back 200 OK. Status says success, but the body is a bot wall, not the data. If your scraper only checks the status code, it thinks it won and happily saves a page of garbage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 1: a 200 is not a win. Read the body.&lt;/strong&gt; A block page returned as 200 OK is a failure, not data.&lt;/p&gt;

&lt;p&gt;This wasn't a missing header or a bad IP. The site was running Akamai Bot Manager, and it had already decided I wasn't a browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 2: look like a real browser (TLS spoofing)
&lt;/h2&gt;

&lt;p&gt;If the problem is "you don't look like a browser," the next move is to look more like one. Not just the User-Agent string, anyone can fake that, but the TLS fingerprint: the exact way a real browser negotiates the HTTPS connection.&lt;/p&gt;

&lt;p&gt;Still blocked.&lt;/p&gt;

&lt;p&gt;Matching the handshake gets you past the front door, but Akamai doesn't stop there. It runs JavaScript in the page that measures things a bare HTTP client simply doesn't have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 2: TLS fingerprinting is necessary but not sufficient.&lt;/strong&gt; Modern bot managers check what happens after the connection, inside a real page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 3: headless browser
&lt;/h2&gt;

&lt;p&gt;Fine, if it wants a browser, I'll give it a browser. Headless automation.&lt;/p&gt;

&lt;p&gt;Closer. But still flagged.&lt;/p&gt;

&lt;p&gt;Headless browsers leak. They announce themselves in dozens of small ways, and Akamai's script is built precisely to notice those. You can spend days patching each tell one by one, and the wall moves the following week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 3: a headless browser is still not a normal browser.&lt;/strong&gt; The gap is exactly what these systems are trained to detect.&lt;/p&gt;

&lt;h2&gt;
  
  
  What finally worked
&lt;/h2&gt;

&lt;p&gt;The breakthrough came from changing the question. Instead of asking "how do I fake a legitimate browser better?", I asked "what is this wall actually measuring, and what would it take to give it nothing to catch?"&lt;/p&gt;

&lt;p&gt;That shift led to an approach the wall had no reason to flag. The listings loaded, and I extracted 64 complete listings, every field intact. The method is documented and I reuse it on similar sites.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson under the lesson
&lt;/h2&gt;

&lt;p&gt;Here's the part that matters more than any single technique:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Getting past the wall once is an afternoon. Keeping it working for six months is a different job.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The wall changes. IPs get flagged. What passes today gets caught next month. The real work isn't the clever trick, it's the diagnosis (which wall am I even facing?), the maintenance, and knowing when to stop hammering and change approach entirely. Anyone can copy a snippet. Reading the wall in front of you is the actual skill.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you're stuck on a wall
&lt;/h2&gt;

&lt;p&gt;If you've got a site that keeps blocking you and you can't tell why, that's the interesting part, and I'm happy to look. Tell me the symptom (403? a 200 that's secretly a challenge? works in the browser but not in code?) and I'll tell you what you're most likely up against.&lt;/p&gt;

&lt;p&gt;That diagnosis is free. What you do with it is up to you.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I build and maintain scrapers for protected and fast-changing sites (Akamai, DataDome, Cloudflare). If yours is getting blocked or returning empty results, &lt;a href="https://www.upwork.com/freelancers/~016bb430964006d617" rel="noopener noreferrer"&gt;reach out on Upwork&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>python</category>
      <category>automation</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
