Skip to content

Config option to disable is_blocked() post-crawl content veto #2058

Open

Description

@FirstCastSolutions423

is_blocked() (antibot_detector.py) is applied unconditionally in arun()'s post-processing and can mark successfully-fetched content as failed. Your own code already special-cases it twice in async_webcrawler.py — the comments note it "would misread 0 bytes html as a block" for binary downloads, and that real pages containing anti-bot script markers "trigger false positives" after fallback fetches. We hit a third case: small legitimate pages (a 378-byte file:// document) fail the minimal_text structural tier. Downstream consumers with their own quality pipelines currently have no way to receive the fetched content plus the verdict instead of a hard failure. Would you accept a PR adding a CrawlerRunConfig flag (e.g. check_blocked: bool = True) — or alternatively surfacing the verdict on a successful CrawlResult field — so callers can opt into making that judgment themselves?

Activity

  1. nightcityblade commented on Jul 19, 2026

    @nightcityblade
    Contributor

    Hi, I'd like to work on this. I'll submit a PR shortly.

  2. nightcityblade commented on Aug 27, 2026

    @nightcityblade
    Contributor

    I’m withdrawing my earlier claim so I don’t block anyone. This needs maintainer agreement on the public API shape (CrawlerRunConfig flag versus exposing the verdict) before a focused implementation would be appropriate.

  3. talelboussetta commented on Nov 15, 2026

    @talelboussetta
    Contributor

    Another case where this check vetoes a page that was fetched fine, reproduced on develop @ 2c0b9e9.

    A page that fills its body with JS 300 ms after load (the usual SPA pattern) comes back with the default config as:

    default            success=False status=200 error='Blocked by anti-bot protection: Structural: minimal_text on small page (270 bytes, 10 chars visible)' html_len=270
    wait_for=css:#t    success=True status=200 error='' html_len=336
    

    Nothing is blocking the crawl: the render just hasn't finished when the HTML is captured, and the same URL succeeds with wait_for. The "anti-bot" label points people at proxies and stealth settings when the fix is a wait.

    Repro (self-contained, serves its own page on 127.0.0.1:8799)
    import asyncio, threading, http.server, socketserver
    from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig, CacheMode
    
    HTML = b"""<!doctype html><html><head><title>app</title></head><body><div id='app'>Loading...</div>
    <script>setTimeout(function(){document.getElementById('app').innerHTML='<h1>Hello</h1><p id="t">Rendered content that arrives 300 ms after load.</p>';},300);</script>
    </body></html>"""
    
    class H(http.server.BaseHTTPRequestHandler):
        def do_GET(self):
            self.send_response(200); self.send_header("Content-Type", "text/html")
            self.send_header("Content-Length", str(len(HTML))); self.end_headers(); self.wfile.write(HTML)
        def log_message(self, *a): pass
    
    srv = socketserver.TCPServer(("127.0.0.1", 8799), H)
    threading.Thread(target=srv.serve_forever, daemon=True).start()
    
    async def main():
        url = "http://127.0.0.1:8799/"
        async with AsyncWebCrawler(config=BrowserConfig(headless=True, verbose=False)) as c:
            for label, cfg in [
                ("default", CrawlerRunConfig(cache_mode=CacheMode.BYPASS)),
                ("wait_for=css:#t", CrawlerRunConfig(cache_mode=CacheMode.BYPASS, wait_for="css:#t")),
            ]:
                r = await c.arun(url, config=cfg)
                print(f"{label:18} success={r.success} status={r.status_code} error={r.error_message!r} html_len={len(r.html or '')}")
    
    asyncio.run(main())
    srv.shutdown()

    The opt-out flag in #2088 would let people turn the check off, but a default crawl would still report this. A smaller change could help on its own: when the verdict comes only from the structural tier on a 200 response, say so in the message, for example Possibly incomplete render (minimal_text, 270 bytes); if the page renders with JavaScript, try wait_for or delay_before_return_html. I can open a PR for that if it's wanted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions