Developer Tools Python

The Web Is Full of Garbage (For LLMs): Why I Built My Own Crawler

Mahamudul Hasan
7 min read

When I started building RAG systems, I needed clean document corpora to test them against. The obvious source was the web. The obvious tool was BeautifulSoup or Scrapy. The obvious result was a pile of text that was nearly useless for anything involving a language model.

That frustration became Crawl4AI — a crawler designed specifically for the problem of getting LLM-ready content from the web. Building it taught me a lot about why "scrape the web" and "get clean text for an LLM" are almost opposite problems.

What a standard scraper gives you

A typical BeautifulSoup scrape of a web page gives you the full text content of every visible element on the page. That sounds right until you look at what's actually in a modern web page.

Consider a typical news article. The main article body might be 800 words. But the page also contains: a navigation menu with 15 items, a cookie consent banner with 200 words of legal text, a sidebar with "trending articles," social share buttons with invisible labels, a comment section header, 8 "related article" teasers each with a headline and 2-sentence summary, an advertisement with alt text, a footer with links to every page on the site, and boilerplate copyright text.

Feed that to a RAG system and ask a question about the article. The retriever has to sift through 3,000 words to find the 800 that are relevant. The noise degrades retrieval precision. The irrelevant chunks compete with the correct ones. You get worse answers from better source material.

The ratio problem: On many content sites, the relevant text — the article, the documentation, the actual content — represents less than 30% of the total text a standard scraper extracts. The rest is navigation, ads, footers, and boilerplate.

What "LLM-friendly" actually means

I spent time thinking about what an ideal document corpus looks like from the LLM's perspective. The answer came from watching what good RAG systems do manually that standard scrapers don't do automatically.

Good RAG input has four properties. It's semantically coherent — each chunk is about one thing and doesn't start or end mid-thought. It's structurally clean — headings that were headings in the original are still headings, code blocks are preserved as code blocks, not flattened into prose. It's metadata-rich — the source URL, publication date, and section heading are attached to each chunk, not discarded. And it's deduplicated — the same boilerplate sentence that appears on every page of a site appears once in the corpus, not 400 times.

Standard web scrapers give you none of these by default. They give you raw text and leave the rest to you.

How Crawl4AI approaches it differently

The core difference is the extraction pipeline. Instead of treating every text node as equivalent, Crawl4AI applies a hierarchy of extraction strategies based on content signals.

The first pass is content identification — distinguishing main body content from navigation, headers, footers, and sidebar elements. This uses a combination of structural signals (semantic HTML elements like <article>, <main>, role attributes) and density-based scoring (blocks of text with high word-to-HTML-element ratios are more likely to be content than navigation menus).

The second pass is structural conversion. Headings become Markdown headings with their hierarchy preserved. Code blocks become fenced code blocks with the language tag if it's specified in the HTML. Tables become Markdown tables. Lists become Markdown lists. The goal is output that retains the document's semantic structure — something you can chunk intelligently later.

Why Markdown output matters: Markdown preserves hierarchy and structure in plain text. A chunker that splits on ## headings will produce semantically coherent chunks. A chunker splitting a flat text dump will cut arbitrarily.

The third pass is metadata extraction and deduplication. Page title, canonical URL, publication date (where available), and section context are attached to each extracted block. Boilerplate detection — identifying repeated text blocks across a crawl — removes duplicated navigation and footer content from the corpus.

What surprised me about building this

The hardest part wasn't the extraction logic. It was JavaScript-rendered content. A significant and growing portion of the web has content that doesn't exist in the initial HTML response — it's rendered by JavaScript after page load. Standard HTTP-based scrapers miss this entirely.

Crawl4AI uses a headless browser (Playwright) for JavaScript-heavy pages, falling back to standard HTTP requests for static content. The headless browser path is significantly slower and more resource-intensive — it's a last resort, not the default. Detecting when to use it and when not to is itself a non-trivial classification problem.

The other surprise: rate limiting and politeness. Aggressive crawling is technically trivial and ethically questionable. I added configurable rate limiting, robots.txt respect, and crawl-delay headers by default. The crawler is designed for building small, targeted domain-specific datasets — not for scraping the entire web.

Who builds their own crawler in 2026?

The honest answer is: people who need something specific that existing tools don't do. General-purpose scrapers like Scrapy are excellent for structured data extraction. Tools like Firecrawl exist now with similar goals. When I built Crawl4AI, the specific combination I needed — LLM-ready Markdown output, structure preservation, metadata retention, and Python-native integration — wasn't available in a single tool I could use without a subscription.

The project is open-source and I use it in my own RAG pipelines. Whether it's the right tool for your use case depends entirely on what your use case actually is.

Key takeaways

  • Standard web scrapers give you raw text, not LLM-friendly content — the gap matters more than you think
  • Structure preservation (headings, code blocks, lists) enables better downstream chunking
  • Metadata attached at extraction time is far easier than reconstructing it later
  • JavaScript-rendered content is a growing share of the web; headless browser fallback is necessary but expensive
  • Boilerplate deduplication is one of the highest-value preprocessing steps and one of the most overlooked