firekid@portfolio ~ boot
> Initializing Firekid OS v2.0...
> Loading modules...
> Mounting components...
> Compiling personality.rs...
> Running diagnostics...
> All systems operational.
> Welcome.
Back to Blog
CloudflareScrapingPlaywright

How I Beat Cloudflare to Build an Anime Downloader

Building PaheDL meant getting past Cloudflare's TLS fingerprinting. Here's what actually worked.

August 26, 20263 min read

The problem

PaheDL is an anime and manga downloader I built with Next.js on the frontend and a Cloudflare Worker plus Playwright running on Render doing the heavy lifting. The idea was simple: point it at a source site, grab the download links, hand them to the user.

The source site did not agree with that idea. It sat behind Cloudflare, and Cloudflare was not just checking headers or cookies. It was fingerprinting the TLS handshake itself. That means the usual bag of tricks (spoofing a User-Agent, rotating proxies, adding a few headers) does nothing, because the block happens before your HTTP request is even fully formed. The TLS client hello itself gives you away as a script and not a browser.

Why the easy fixes didn't work

My first instinct was the normal stuff:

  • Fake browser headers → no difference, the handshake still looked like Node
  • Rotating IPs → the fingerprint isn't tied to the IP, so this was pointless
  • Basic fetch-based scraping → blocked before a response even came back
  • A generic headless browser → closer, but still missing the actual vault URL the site generates dynamically

That last point was the real wall. Even once I got a real browser rendering the page, the download link wasn't sitting in the HTML. It was assembled client-side and fetched through a short-lived vault URL, generated on the fly and gone if you didn't catch it fast enough.

What actually worked

The fix was Playwright combined with Chrome DevTools Protocol's Fetch domain, used to intercept network requests as they happened rather than trying to scrape the rendered page after the fact.

Instead of asking "what does the final HTML look like," I asked "what request is the page itself making to get its data." Using CDP's Fetch domain, I could hook into the browser's own network layer and capture the vault URL the instant it was requested, before it expired.

This solved two problems at once:

  1. The TLS fingerprint problem went away because Playwright drives a real Chromium instance, so the handshake looks like an actual browser, not a script.
  2. The dynamic vault URL problem went away because I wasn't scraping rendered output, I was watching the network traffic directly.

The tradeoff

None of this is free. Running a full browser instance per request is heavier than a plain HTTP call, and Render is where that overhead lives. It works, but it's not the kind of thing you scale carelessly. If PaheDL ever needs to handle serious concurrent traffic, this is the part that gets revisited first, probably with a pool of browser contexts instead of spinning one up per request.

For now, it does exactly what it needs to do: get past a real anti-bot system without pretending the problem was simpler than it was.

Takeaway

If you're fighting Cloudflare and the fix isn't working, check what layer you're actually being blocked at. Headers and proxies fix nothing if the block happens at the TLS handshake. Sometimes the only real answer is to stop pretending to be a browser and just be one, then watch what it does instead of guessing from the output.