Cloudflare & Anti-Bot Fetcher โ€“ Gap-Filler for Crawlers avatar

Cloudflare & Anti-Bot Fetcher โ€“ Gap-Filler for Crawlers

Pricing

Pay per usage

Go to Apify Store
Cloudflare & Anti-Bot Fetcher โ€“ Gap-Filler for Crawlers

Cloudflare & Anti-Bot Fetcher โ€“ Gap-Filler for Crawlers

Fast HTTP by default, automatic Cloudflare/anti-bot bypass when blocked. The gap-filler for Website Content Crawler, RAG Web Browser, or any crawler that got blocked โ€” retries just their failed pages and returns clean, LLM-ready Markdown.

Pricing

Pay per usage

Rating

0.0

(0)

Developer

Vietaro Tools

Vietaro Tools

Maintained by Community

Actor stats

0

Bookmarked

11

Total users

8

Monthly active users

20 days ago

Last modified

Share

Reliable Markdown from difficult URLs for AI agents and RAG. Give it a list of URLs -- known-hard ones, or just a normal batch -- and get clean Markdown back, using fast HTTP by default and automatic stealth escalation only when a page is actually blocked.

Works two ways, equally well:

  • Standalone: hand it any list of URLs. Most resolve on the cheap fast tier; the hard ones (Cloudflare, DataDome, PerimeterX-style protection) automatically escalate to a stealth browser, without you paying stealth prices for pages that didn't need it.
  • As a fallback step: already running Website Content Crawler, RAG Web Browser, or any other scraper, and some pages came back blocked or empty? Point this Actor at that run's dataset instead of re-running your whole crawl -- see Gap-filler mode below.

Have a Website Content Crawler or RAG Web Browser dataset with failed or blocked pages sitting in Storage? Skip straight to Gap-filler mode below -- pick that dataset and get results in under 90 seconds, no need to re-run your whole crawl.

๐ŸŽฌ See it in action

Resilient Stealth Fetcher โ€” Fast to Stealth

Fast โ†’ blocked โ†’ automatic stealth โ†’ HTTP 200 โ†’ clean Markdown

The Actor starts with a lightweight fetch and automatically escalates to a stealth browser only when a site actually blocks it. You can also force stealth mode for sites known to require a browser.


Why use it?

โšก Fast first, stealth only when needed

URL
โ†“
Fast HTTP fetch
โ†“
Works? โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Yes โ†’ Clean Markdown
โ†“ No
Stealth browser (Cloudflare/anti-bot escalation)
โ†“
Clean Markdown

Verified results from real Actor runs:

TaskResultTier usedExtraction
example.comHTTP 200fasttitle โ†’ "Example Domain"
MDNHTTP 200fasttitle โ†’ "Add JavaScript to your web page"
Cloudflare Challenge pageHTTP 200stealthtitle โ†’ "Cloudflare Challenge"
Cloudflare Challenge, via WCC's dataset (Gap-filler)HTTP 200stealthRecovered after WCC itself failed -- 10 retries + UNBLOCKER proxy escalation, still blocked
Cloudflare Challenge, via RAG Web Browser's dataset (Gap-filler)HTTP 200stealthRecovered after RAG Web Browser itself failed -- blocked in under 3 seconds

Every row above is a successful extraction โ€” the Cloudflare-protected page returned a real HTTP 200 via automatic stealth escalation, not a placeholder or partial result.

You can also set forceStealth: true for sites already known to require a browser.


๐Ÿ’ฐ Pricing

Currently free to use while this Actor builds up usage history and reviews. Pay-per-event pricing (below) reflects what it will move to -- watch this Actor's Pricing tab in Console for its current live status.

Free allowance, permanent even after PPE is enabled: 300 fast-tier pages and 25 stealth-tier pages every month, on every account โ€” enough to fully evaluate this Actor before spending anything.

What triggers a chargePriceFree allowance / month
Actor start$0.005โ€”
Fast-tier page (no anti-bot resistance needed)$0.002300
Stealth-tier page (Cloudflare/anti-bot escalation)$0.0225
Structured field extracted (optional, via extractionSchema)$0.001โ€”

You're only ever charged the stealth price for pages that actually needed it โ€” a page resolved on the fast tier is never billed at the stealth rate.

Example volume (typical mix)Estimated cost
1,000 pages, ~80% fast / 20% stealth~$5.60
10,000 pages, ~80% fast / 20% stealth~$56.00

How this compares: the fast tier is priced close to general-purpose crawlers' cheapest tier, while including automatic escalation as a built-in option rather than a separate manual step. The stealth tier costs more than a generic headless-browser tier elsewhere -- that difference reflects automatic Cloudflare/DataDome-style challenge solving with a residential proxy, not just JS rendering.


๐Ÿงน Clean, LLM-ready Markdown

Turn web pages into useful Markdown instead of feeding your AI pipeline raw HTML, navigation, and page chrome.

The Markdown output preserves headings, paragraphs, links, lists/tables, and code blocks โ€” suitable for RAG pipelines, embeddings, AI agents, research, content monitoring, and knowledge-base ingestion.


๐ŸŽฏ Optional structured extraction

Request additional fields using simple CSS selectors:

[
{ "name": "title", "selector": "h1" },
{ "name": "author", "selector": "[rel='author']" }
]
{
"extracted": {
"title": "Example Domain",
"author": "John Doe"
}
}

Adaptive (self-healing) extraction

Fields extracted via extractionSchema use Scrapling's adaptive-relocation mechanism: when the exact selector stops matching, the field is relocated by DOM similarity instead of returning null. Verified end-to-end in Showcase 5, including the actual similarity score behind a real recovery (100% match). Still validate adaptive matching against your own target sites before relying on it unattended in production -- one verified site is a proof of the mechanism, not a guarantee across every possible page structure.


๐Ÿ”Œ Gap-filler mode: catch what other crawlers missed

This is the fastest way to get value from this Actor if you already use a general-purpose crawler. Website Content Crawler and RAG Web Browser are excellent at broad site ingestion, but neither automatically solves Cloudflare-style managed challenges -- WCC's own troubleshooting docs recommend manually switching crawler type and proxy group when blocked, rather than doing it automatically. That gap is exactly what this Actor is built to close.

ToolBest for
Website Content CrawlerBroad, multi-page site crawls -- deep-crawling a whole domain
RAG Web BrowserSearch-to-content workflows and quick single-URL fetches
This ActorThe pages either of those got blocked on -- Cloudflare/anti-bot escalation, not a general-purpose crawler

Not a replacement for either -- built to catch what they can't reach.

Point this Actor at that run's dataset, and it will scan for pages that came back blocked or thin, then retry just those URLs through the fast->stealth escalation ladder -- no need to re-run your whole crawl, and no need to switch tools for the pages that failed.

In the Apify Console input form, sourceDatasetId is a picker field -- but its search list generally only shows datasets you've explicitly named. Default/unnamed run datasets (which is what most actors, including WCC and RAG Web Browser, actually produce) usually won't appear in the dropdown even though they work correctly. If yours isn't listed, copy the dataset ID from Console (that run's Storage tab, or defaultDatasetId in the run's API response) and paste it directly into the picker's search box -- a valid pasted ID is accepted even when it's not shown as a suggestion. Via the API/JSON input, just pass the dataset ID as a plain string:

{
"sourceDatasetId": "your-prior-run-dataset-id",
"sourceActorHint": "website-content-crawler"
}

You can combine this with your own startUrls in the same run -- both are merged (deduplicated) before processing.

Works out of the box with:

  • Website Content Crawler
  • RAG Web Browser

Works with anything else via manual field mapping -- specify exactly which fields hold the URL and content:

{
"sourceDatasetId": "your-prior-run-dataset-id",
"sourceFieldMapping": {
"urlField": "pageUrl",
"contentField": "body",
"minContentLength": 50
}
}

urlField/contentField support dotted paths for nested fields (e.g. "metadata.url"). If you provide neither a recognized sourceActorHint nor a sourceFieldMapping, a generic fallback scans the item for a URL-shaped field and treats the longest text field as content -- less precise, but works on schemas we've never seen.

Scope limitation, stated plainly: this only works on datasets your own Apify account already has access to -- your own prior runs, not other users' private data. The workflow is: you run WCC (or similar) yourself first, then point this Actor at that same run's dataset ID.


๐Ÿš€ Quick start

{ "startUrls": [{ "url": "https://example.com" }] }

Force stealth for a site known to require a browser:

{ "startUrls": [{ "url": "https://example.com" }], "forceStealth": true }

With structured extraction:

{
"startUrls": [{ "url": "https://example.com" }],
"extractionSchema": [{ "name": "title", "selector": "h1" }]
}

๐Ÿงช Showcases

Each showcase includes the copyable input and the actual output from a real Actor run, so you can compare your own run against it.

1. Normal page โ†’ fast fetch

Input

{
"startUrls": [{ "url": "https://example.com" }],
"extractionSchema": [{ "name": "title", "selector": "h1" }],
"forceStealth": false,
"maxConcurrency": 1,
"maxUrlsPerRun": 1,
"fetchTimeoutSecs": 150,
"proxyConfiguration": { "useApifyProxy": false }
}

Output

{
"url": "https://example.com",
"success": true,
"statusCode": 200,
"fetchTier": "fast",
"extracted": { "title": "Example Domain" }
}

2. MDN โ†’ fast fetch + structured extraction

Input

{
"startUrls": [{ "url": "https://developer.mozilla.org/en-US/docs/Web/HTML/How_to/Add_JavaScript_to_your_web_page" }],
"extractionSchema": [{ "name": "title", "selector": "h1" }],
"forceStealth": false,
"maxConcurrency": 1,
"maxUrlsPerRun": 1,
"fetchTimeoutSecs": 150,
"proxyConfiguration": { "useApifyProxy": false }
}

Output

{
"url": "https://developer.mozilla.org/en-US/docs/Web/HTML/How_to/Add_JavaScript_to_your_web_page",
"success": true,
"statusCode": 200,
"fetchTier": "fast",
"extracted": { "title": "Add JavaScript to your web page" }
}

3. Cloudflare Challenge โ†’ automatic stealth escalation

Input

{
"startUrls": [{ "url": "https://www.scrapingcourse.com/cloudflare-challenge" }],
"extractionSchema": [{ "name": "title", "selector": "h1" }],
"forceStealth": false,
"markdownThreshold": 45,
"maxConcurrency": 1,
"maxUrlsPerRun": 1,
"fetchTimeoutSecs": 150,
"proxyConfiguration": { "useApifyProxy": false }
}

Output

{
"url": "https://www.scrapingcourse.com/cloudflare-challenge",
"success": true,
"statusCode": 200,
"fetchTier": "stealth",
"protectionDetected": "none",
"extracted": { "title": "Cloudflare Challenge" },
"markdown": "# Cloudflare Challenge\n## You bypassed the Cloudflare challenge! :D"
}

Note on proxy settings above: examples 1โ€“3 deliberately run with useApifyProxy: false to demonstrate the escalation ladder working even in the hardest case โ€” no proxy at all. For production use, set useApifyProxy: true (the input schema default) โ€” see Example 4. Real testing has shown this matters: without a proxy, the same Cloudflare-protected URL has succeeded in as little as 5-10 seconds in one run, and needed multiple internal solve-retry cycles (spanning nearly a minute) in another -- both against the exact same target. A residential proxy makes this far more consistent, not just theoretically safer.

Input

{
"startUrls": [{ "url": "https://www.scrapingcourse.com/cloudflare-challenge" }],
"extractionSchema": [{ "name": "title", "selector": "h1" }],
"forceStealth": false,
"markdownThreshold": 45,
"maxConcurrency": 1,
"maxUrlsPerRun": 1,
"fetchTimeoutSecs": 150,
"proxyConfiguration": { "useApifyProxy": true }
}

Output

{
"url": "https://www.scrapingcourse.com/cloudflare-challenge",
"success": true,
"statusCode": 200,
"fetchTier": "stealth",
"protectionDetected": "none",
"extracted": { "title": "Cloudflare Challenge" },
"markdown": "# Cloudflare Challenge\n## You bypassed the Cloudflare challenge! :D"
}

Same successful result as Example 3, confirmed with the recommended proxy setting.

5. Self-healing in action -- verified

Two-run test: Run A seeds a fingerprint with a working selector, Run B repeats the same URL and field name with a deliberately broken selector that matches nothing on the page.

Run A -- seed a fingerprint

{
"startUrls": [{ "url": "https://developer.mozilla.org/en-US/docs/Web/HTML/How_to/Add_JavaScript_to_your_web_page" }],
"extractionSchema": [{ "name": "heading", "selector": "h1" }],
"forceStealth": false,
"maxConcurrency": 1,
"maxUrlsPerRun": 1,
"fetchTimeoutSecs": 150,
"proxyConfiguration": { "useApifyProxy": true }
}
{ "success": true, "statusCode": 200, "fetchTier": "fast", "extracted": { "heading": "Add JavaScript to your web page" } }

Run B -- same URL/field, selector changed to h1.this-class-no-longer-exists

{
"startUrls": [{ "url": "https://developer.mozilla.org/en-US/docs/Web/HTML/How_to/Add_JavaScript_to_your_web_page" }],
"extractionSchema": [{ "name": "heading", "selector": "h1.this-class-no-longer-exists" }],
"forceStealth": false,
"maxConcurrency": 1,
"maxUrlsPerRun": 1,
"fetchTimeoutSecs": 150,
"proxyConfiguration": { "useApifyProxy": true }
}
{ "success": true, "statusCode": 200, "fetchTier": "fast", "extracted": { "heading": "Add JavaScript to your web page" } }

The field was recovered even though the selector matched nothing on the page. This isn't just a pass/fail result -- the run log shows the actual relocation mechanism at work:

DEBUG: Highest probability was 100.0%
DEBUG: Top 5 best matching elements are:
100.0 -> [<data='<h1>Add JavaScript to your web page</h1>' parent='<div class="layout__header...'>]
62.24 -> [<data='<mdn-survey></mdn-survey>' ...>]
49.28 -> [<data='<p> Your blueprint for a better internet...' ...>]

The correct <h1> scored a perfect 100% match against the fingerprint saved in Run A, comfortably ahead of the next-best candidate (62%) -- a clean, unambiguous relocation, not a lucky guess near the threshold.

6. Graceful failure handling

Input

{
"startUrls": [{ "url": "https://this-domain-does-not-exist-abcxyz123.com" }],
"extractionSchema": [],
"forceStealth": false,
"maxConcurrency": 1,
"maxUrlsPerRun": 1,
"fetchTimeoutSecs": 30,
"proxyConfiguration": { "useApifyProxy": true }
}

Output

{
"url": "https://this-domain-does-not-exist-abcxyz123.com",
"success": false,
"error": "Error: Page.goto: net::ERR_TUNNEL_CONNECTION_FAILED at https://this-domain-does-not-exist-abcxyz123.com/"
}

The run completed normally with success: false and a real, actionable error message โ€” it did not crash or block other URLs in the same run.

Re-verified after protectionDetected was added: the field is correctly absent here, not defaulted to "none". This connection never got far enough to receive any response at all (a DNS/tunnel-level failure), so there was nothing to inspect for anti-bot challenges -- "none" would incorrectly claim "checked, found clean," when the honest answer is "never got that far." statusCode and fetchTier are absent for the same reason.

A failed or timed-out URL is recorded with success: false and an error message โ€” it never stops the rest of a multi-URL run.

7. Gap-filler mode โ€” verified against two real crawlers

Both tested end-to-end against the same Cloudflare-gated URL, each blocked by a different crawler first, then correctly detected and recovered via sourceDatasetId -- not a coincidence of also passing the URL in startUrls.

Case A: Website Content Crawler

WCC's own log shows it hit a real wall: 10 retries, escalation to Apify's UNBLOCKER proxy group, still 403 on every attempt, then a hard Timeout 60000ms exceeded -- 0 succeeded, 1 failed, ~135 seconds spent.

Pointing this Actor at that run's dataset:

{ "sourceDatasetId": "<WCC run's dataset ID>", "sourceActorHint": "website-content-crawler" }
Gap-filler: found 1 failed/thin-content URL(s) in source dataset '...', 1 of which are new (not already in startUrls).
...
Done: 1/1 succeeded. Stealth tier used on 1/1 URLs.
{ "success": true, "statusCode": 200, "fetchTier": "stealth", "extracted": { "title": "Cloudflare Challenge" } }

Case B: RAG Web Browser

RAG Web Browser also failed on the same URL -- a clean 403 in under 3 seconds (it doesn't retry as aggressively as WCC), recorded in its dataset as crawl.requestStatus: "failed" with empty text.

Same gap-filler call, sourceActorHint: "rag-web-browser":

Gap-filler: found 1 failed/thin-content URL(s) in source dataset '...', 1 of which are new (not already in startUrls).
...
Done: 1/1 succeeded. Stealth tier used on 1/1 URLs.
{ "success": true, "statusCode": 200, "fetchTier": "stealth", "extracted": { "title": "Cloudflare Challenge" } }

What this confirms: gap-filler mode correctly reads both crawlers' native dataset formats without manual field mapping, correctly identifies the specific failed item (not a lucky guess), and successfully retries just that URL through the stealth escalation ladder -- recovering a real 200 where both general-purpose crawlers returned nothing.

Stealth-tier success against hard-gated sites is not guaranteed on every attempt -- see the FAQ below ("Will this work on any Cloudflare-protected site?").


๐Ÿ“ค Output

Each URL produces a result containing url, fetchedAt, and success. On a response being received, it also includes statusCode, fetchTier (fast or stealth), protectionDetected (cloudflare | datadome | perimeterx | bot-challenge | none), markdown, and extracted. On failure, error is included instead -- and if the failure happened before any response was received at all (e.g. DNS/connection errors), statusCode, fetchTier, and protectionDetected are omitted entirely rather than defaulted to a guessed value -- see Showcase 6 for a real example.

protectionDetected tells you which anti-bot system, if any, was identified on the final response -- useful for measuring block rates and spotting which domains need special handling across a batch. On a successful stealth-tier fetch, none confirms the challenge was actually cleared, not just that the solve attempt didn't error out.

{
"url": "https://example.com",
"fetchedAt": "2026-08-16T14:21:22.097540+00:00",
"success": true,
"statusCode": 200,
"fetchTier": "fast",
"protectionDetected": "none",
"markdown": "# Example Domain\n\nThis domain is for use in documentation examples...",
"extracted": {
"title": "Example Domain",
"paragraph1": "This domain is for use in documentation examples..."
}
}

Note on the showcases above: Showcases 3 and 4 (Cloudflare bypass) have been re-run and verified with real protectionDetected values ("none", confirming the challenge was actually cleared). Showcase 6 has also been re-verified -- its correct behavior is the field being absent, not "none" (see Showcase 6 for why). Showcases 1, 2, 5, and 7 were captured before this field was added and don't show it yet -- the detection logic itself is deterministic and unit-tested, but those specific output blocks will only be updated here once re-run and captured for real, not backfilled with a plausible-looking guess.


When should I use this?

Good fit

  • AI / RAG โ€” ingest documentation, public information, and web content
  • AI agents โ€” provide agents with clean web-page content
  • Research โ€” collect readable content from multiple websites
  • Competitive intelligence โ€” monitor pages that may be difficult to fetch
  • SEO & agencies โ€” recurring content and page audits
  • Sales & RevOps โ€” collect public company and product information
  • Automation โ€” use the results in n8n, LangChain, CrewAI, MCP, or custom workflows
  • Already using Website Content Crawler, RAG Web Browser, or similar โ€” use Gap-filler mode to catch the pages they couldn't reach, without re-running your whole crawl

When to use something else

If you need...Consider instead
Login-only / account-gated pagesA custom Actor with session/auth handling โ€” this Actor only accesses publicly reachable pages
Structured data from a specific major site (LinkedIn, Amazon, etc.)A dedicated site-specific Actor โ€” usually cheaper and more structured than generic CSS selectors
Guaranteed success on every requestNo tool can promise this โ€” sites change and anti-bot systems evolve
Pages with no anti-bot protection at allA simpler/cheaper HTTP-only crawler may be more cost-effective than paying for stealth-tier headroom you won't use

โ“ FAQ

Do I need my own proxy? No โ€” Apify Proxy is used by default and is strongly recommended, especially for the stealth tier (see Example 4). You can supply your own proxy if you have an existing contract.

Is this free to try? Yes โ€” every account gets 300 free fast-tier pages and 25 free stealth-tier pages every month, permanently, not just during a trial window.

What happens if a page fails or times out? It's recorded with success: false and an error field (see Showcase 6). The rest of the run continues normally.

Will this work on any Cloudflare-protected site? The stealth tier is built to solve Cloudflare-style managed and embedded challenges automatically. Results can vary by target โ€” validate against your specific site before relying on it for unattended production jobs.

Can this fill in the gaps from a Website Content Crawler or RAG Web Browser run? Yes -- see "Gap-filler mode" above. Point sourceDatasetId at a prior run's dataset and it retries the failed/thin-content pages through the stealth escalation ladder. Only works on datasets your own account already has access to.

Is the "self-healing" claim real, or marketing? Real and verified -- see Showcase 5 for a reproducible before/after test, including the actual relocation confidence score (100% match) from a real run log, not just a claimed pass/fail. It's not infallible on every possible page: validate it against your own target sites, especially pages with many visually similar elements.


โš™๏ธ Input

FieldPurpose
startUrlsURLs to fetch
extractionSchemaOptional {name, selector} fields, matched adaptively
forceStealthSkip directly to the stealth tier
markdownThresholdMarkdown boilerplate pruning aggressiveness
maxConcurrencyParallel URL limit
maxUrlsPerRunMaximum URLs processed per run
fetchTimeoutSecsPer-URL timeout
proxyConfigurationApify Proxy (recommended) or your own proxy
sourceDatasetIdOptional. Dataset ID from a prior run of another actor -- enables gap-filler mode
sourceActorHintOptional. auto (default), website-content-crawler, or rag-web-browser
sourceFieldMappingOptional. Manual {urlField, contentField, minContentLength} for unrecognized source actors

๐Ÿงฉ Built for AI and automation

Web page โ†’ Clean Markdown โ†’ RAG / embeddings / AI agent / automation

Compatible with n8n, LangChain, CrewAI, MCP, and custom Python/JavaScript workflows.


Open source

Markdown generation uses Crawl4AI (Apache 2.0).

This product includes software developed by UncleCode (https://x.com/unclecode) as part of the Crawl4AI project (https://github.com/unclecode/crawl4ai).

Stealth fetching and adaptive extraction use Scrapling (BSD-3-Clause), with Camoufox (MPL-2.0) powering the stealth-browser tier.


Acceptable use

You are responsible for complying with each target site's Terms of Service, robots.txt, applicable laws, and Apify's Acceptable Use Policy. This Actor provides a general-purpose fetching capability โ€” use stealth features only where you have the right to access the target content.


Try it

Give it a normal URL and let it stay fast. Give it a difficult or protected URL and let it escalate when necessary.

Fast when possible. Stealth when needed. Clean Markdown every time.