Skip to content

[Bug]: PDFCrawlerStrategy fails with "Blocked by anti-bot protection" - the documented PDF example is broken #2135

Description

@SohamKukreti

crawl4ai version

0.9.2 (reproduced on latest develop, ea26abb)

Expected Behavior

Running the exact example from the docs (Advanced → PDF Parsing, docs/md_v2/advanced/pdf-parsing.md) should return result.success = True with the extracted PDF content, as the page describes.

Current Behavior

The documented PDFCrawlerStrategy + PDFContentScrapingStrategy pairing always returns success: False with:

Blocked by anti-bot protection: Near-empty content (33 bytes) with HTTP 200

— on every PDF, every time. The extraction itself actually succeeds: the same CrawlResult carries the complete PDF markdown (25,297 chars for the docs' example URL). The result is self-contradictory — full content plus a failure flag — so any caller that checks result.success (as the docs example does) discards a perfectly good scrape.

Root cause

  1. PDFCrawlerStrategy.crawl() returns placeholder HTML — literally "Scraper will handle the real work", 33 bytes (crawl4ai/processors/pdf/__init__.py:17). The real extraction happens later in the scraping strategy, which populates cleaned_html/markdown from the PDF.
  2. is_blocked() only ever sees this pre-scrape placeholder, never the scraped output. Verified by instrumenting is_blocked(): it fires twice per crawl, both times with the 33-byte stub —
    • once inside the attempt loop (crawl4ai/async_webcrawler.py:512), which classifies the attempt as blocked — so with retries/proxy_rotation_strategy configured, every proxy and retry is burned on a crawl that is actually succeeding, and a configured fallback_fetch_function gets invoked pointlessly;
    • once in the final post-processing veto (crawl4ai/async_webcrawler.py:629), which is what flips success = False.
  3. 33 stripped bytes with HTTP 200 trips the near-empty-content heuristic (crawl4ai/antibot_detector.py:274).

Notably, the code already exempts three cases where this heuristic misfires — successful fallback fetches, raw: URLs, and binary downloads ("is_blocked() would misread '0 bytes html' as a block", async_webcrawler.py:619-628). A PDFCrawlerStrategy crawl is a fourth case of the same shape: html is a stub by design, and the content lives elsewhere. It matches none of the existing exemptions (downloaded_files is not set; the PDF goes through a temp file).

Is this reproducible?

Yes — deterministic, 100% of PDFs. Also reproduces with local file:// PDFs.

Inputs Causing the Bug

Any arun() call through AsyncWebCrawler(crawler_strategy=PDFCrawlerStrategy()).

Steps to Reproduce

  1. pip install crawl4ai[pdf]
  2. Run the example from the PDF parsing docs page, unmodified.
  3. Observe Failed to process PDF: Blocked by anti-bot protection: Near-empty content (33 bytes) with HTTP 200 — while result.markdown.raw_markdown contains the full extracted paper.

Code snippets

import asyncio
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig
from crawl4ai.processors.pdf import PDFCrawlerStrategy, PDFContentScrapingStrategy

async def main():
    # Exact example from docs/md_v2/advanced/pdf-parsing.md
    pdf_crawler_strategy = PDFCrawlerStrategy()
    pdf_scraping_strategy = PDFContentScrapingStrategy()
    run_config = CrawlerRunConfig(scraping_strategy=pdf_scraping_strategy)

    async with AsyncWebCrawler(crawler_strategy=pdf_crawler_strategy) as crawler:
        pdf_url = "https://arxiv.org/pdf/2310.06825.pdf"
        result = await crawler.arun(url=pdf_url, config=run_config)
        if result.success:
            print(f"Successfully processed PDF: {result.url}")
        else:
            print(f"Failed to process PDF: {result.error_message}")
            md = result.markdown.raw_markdown if hasattr(result.markdown, "raw_markdown") else result.markdown
            print(f"...yet extracted markdown length is: {len(md or '')}")

Output:

[SCRAPE].. ◆ https://arxiv.org/pdf/2310.06825.pdf  | ✓ | ⏱: 1.07s
[ERROR]... × https://arxiv.org/pdf/2310.06825.pdf  | Error: Blocked by anti-bot protection: Near-empty content (33 bytes) with HTTP 200
Failed to process PDF: Blocked by anti-bot protection: Near-empty content (33 bytes) with HTTP 200
...yet extracted markdown length is: 25297

OS

Linux

Python version

3.10

Related

  • Config option to disable is_blocked() post-crawl content veto #2058 — general request for a config option to disable the is_blocked() post-crawl veto (this report is a deterministic instance of that mechanism misfiring on a documented first-party workflow)
  • Workaround for anyone hitting this: use AsyncWebCrawler(crawler_strategy=AsyncHTTPCrawlerStrategy()) with PDFContentScrapingStrategy — verified working (full extraction, success: True), since the HTTP strategy returns the real PDF bytes as the response body and the heuristic never fires.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ⚙️ In-progressIssues, Features requests that are in Progress🐞 BugSomething isn't working📌 Root causedidentified the root cause of bug

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions