You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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
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.
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.
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
pip install crawl4ai[pdf]
Run the example from the PDF parsing docs page, unmodified.
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
importasynciofromcrawl4aiimportAsyncWebCrawler, CrawlerRunConfigfromcrawl4ai.processors.pdfimportPDFCrawlerStrategy, PDFContentScrapingStrategyasyncdefmain():
# Exact example from docs/md_v2/advanced/pdf-parsing.mdpdf_crawler_strategy=PDFCrawlerStrategy()
pdf_scraping_strategy=PDFContentScrapingStrategy()
run_config=CrawlerRunConfig(scraping_strategy=pdf_scraping_strategy)
asyncwithAsyncWebCrawler(crawler_strategy=pdf_crawler_strategy) ascrawler:
pdf_url="https://arxiv.org/pdf/2310.06825.pdf"result=awaitcrawler.arun(url=pdf_url, config=run_config)
ifresult.success:
print(f"Successfully processed PDF: {result.url}")
else:
print(f"Failed to process PDF: {result.error_message}")
md=result.markdown.raw_markdownifhasattr(result.markdown, "raw_markdown") elseresult.markdownprint(f"...yet extracted markdown length is: {len(mdor'')}")
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
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.
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 returnresult.success = Truewith the extracted PDF content, as the page describes.Current Behavior
The documented
PDFCrawlerStrategy+PDFContentScrapingStrategypairing always returnssuccess: Falsewith:— on every PDF, every time. The extraction itself actually succeeds: the same
CrawlResultcarries 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 checksresult.success(as the docs example does) discards a perfectly good scrape.Root cause
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 populatescleaned_html/markdown from the PDF.is_blocked()only ever sees this pre-scrape placeholder, never the scraped output. Verified by instrumentingis_blocked(): it fires twice per crawl, both times with the 33-byte stub —crawl4ai/async_webcrawler.py:512), which classifies the attempt as blocked — so with retries/proxy_rotation_strategyconfigured, every proxy and retry is burned on a crawl that is actually succeeding, and a configuredfallback_fetch_functiongets invoked pointlessly;crawl4ai/async_webcrawler.py:629), which is what flipssuccess = False.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). APDFCrawlerStrategycrawl is a fourth case of the same shape:htmlis a stub by design, and the content lives elsewhere. It matches none of the existing exemptions (downloaded_filesis 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 throughAsyncWebCrawler(crawler_strategy=PDFCrawlerStrategy()).Steps to Reproduce
pip install crawl4ai[pdf]Failed to process PDF: Blocked by anti-bot protection: Near-empty content (33 bytes) with HTTP 200— whileresult.markdown.raw_markdowncontains the full extracted paper.Code snippets
Output:
OS
Linux
Python version
3.10
Related
is_blocked()post-crawl veto (this report is a deterministic instance of that mechanism misfiring on a documented first-party workflow)AsyncWebCrawler(crawler_strategy=AsyncHTTPCrawlerStrategy())withPDFContentScrapingStrategy— verified working (full extraction,success: True), since the HTTP strategy returns the real PDF bytes as the response body and the heuristic never fires.