Skip to content

AI Outlooks

News and viewpoints on the latest in AI security

Primary Menu
  • Home
  • What’s new in AI
    • AI Security News
    • Agentic AI News
    • AI Regulation News
    • AI Research News
    • AI Model News
  • Solutions
  • Cybersecurity
    • AI security
    • OWASP
    • Ransomware
    • Shadow AI
  • Learn
    • AI security
    • LLM security
    • AI governance
    • AI compliance
    • Agentic AI
    • AI infrastructure
    • AI data security
  • Home
  • Guide
  • Web scraping for RAG pipelines: How to feed clean data to your LLMs
  • Guide

Web scraping for RAG pipelines: How to feed clean data to your LLMs

Staff May 5, 2026
pipeline

Every RAG pipeline dies the same way. Not at retrieval. Not at generation. At ingestion — the step nobody talks about until their vector store starts returning cookie banners as top-k results.

You scrape a page. You strip the HTML to flat text. You run it through a token-counting chunker. And then you’re surprised when retrieval quality tanks.

It doesn’t have to work that way. The web is the biggest, freshest knowledge base your RAG system can access — but only if you stop treating it like raw ore and start treating it like structured data. This guide covers the full pipeline: scraping approaches, content cleaning, structural chunking, vector storage with metadata, refresh architecture, and cost. No vendor pitch. Just what works.


Table of contents

  • What is web scraping for RAG?
  • Why web content is harder than you think for RAG
  • The scrape-to-retrieval pipeline
  • Scraping approaches: a decision framework
  • From HTML to clean, LLM-ready content
  • Chunking web content: use the DOM, not a token counter
  • Where most chunking strategies break down
  • Tools compared: Firecrawl, Crawl4AI, Browserless, and DIY
  • Embedding, storage, and retrieval for web-sourced data
  • Keeping your knowledge base fresh
  • Cost: DIY scraping vs. managed APIs
  • Common mistakes
  • FAQ

What is web scraping for RAG?

Web scraping for RAG is the process of extracting content from websites, cleaning it into LLM-ready markdown or structured text, and feeding it into a retrieval-augmented generation pipeline. Unlike internal document uploads — which are static and bounded — web-sourced RAG data stays current, broadens your knowledge base beyond company docs, and supports live source citations in generated responses.

The workflow looks straightforward on a whiteboard: scrape pages, chunk the content, embed the chunks, store them in a vector database, retrieve the best matches at query time, and generate a response. The hard part isn’t any single step. It’s making all of them work together without one bad decision at step 2 silently ruining retrieval quality at step 5.


Why web content is harder than you think for RAG

The garbage-in, garbage-out problem

A PDF is predictable. It has a title, sections, maybe a table or two. A modern web page? It has a nav bar with 40 links. A footer repeating the same site-wide text across every page. A cookie banner that renders inline and gets chunked as “content.” Five <div> trees that exist only for layout. A sidebar with “related posts” that share keywords with the article body — so they show up in embeddings looking relevant when they aren’t.

If you scrape raw HTML and feed it to an embedding model, you’re embedding the navigation, the ads, and the tracking scripts alongside the article body. Your vector similarity search can’t tell which tokens came from the main content and which came from a “Subscribe to our newsletter” bar. Result: retrieval surfaces pages because their boilerplate happens to overlap with the query, not because the page answers the question.

What raw HTML does to your embeddings

An Amazon product page runs to roughly 896,000 tokens as raw HTML. The actual product description? Maybe 500-800 tokens. The rest is JavaScript bundles, inline styles, tracking pixels, and layout scaffolding. Even a small docs page can be 80% boilerplate by token count.

When you embed that, the semantic signal from the article gets drowned in noise. Worse, cross-page boilerplate creates false positives — your retriever starts returning pages whose footers contain query terms, not pages that answer the question. The fixes are mechanical, not magical: extract main content first, strip boilerplate, preserve structure. We’ll get to exactly how.


The scrape-to-retrieval pipeline

Here’s the full pipeline as a reusable checklist:

  1. Discover URLs — sitemaps, RSS feeds, internal link crawl, or a curated seed list
  2. Fetch — HTTP for static pages, browser rendering for JS-heavy or protected pages
  3. Extract main content — strip nav, ads, sidebars, cookie banners
  4. Normalize output — clean markdown or structured text, preserve headings
  5. Attach metadata — source URL, title, fetch timestamp, content hash, language
  6. Chunk — split on semantic boundaries, not just token counts
  7. Embed — generate vectors for each chunk
  8. Store — vectors in a vector DB, metadata alongside
  9. Retrieve — similarity search, metadata-filtered when needed
  10. Generate — LLM call with retrieved context and citations
  11. Refresh — change detection, incremental updates, de-duplication

Steps 1-4 are where most teams burn weeks. Steps 5-6 are where the pipeline quietly fails even when steps 1-4 work fine. Steps 9-11 are where you find out whether the earlier decisions were sound.


Scraping approaches: a decision framework

There’s no single right answer. The approach depends on the site you’re scraping and what you’re willing to spend.

HTTP + Beautiful Soup / Trafilatura

For static HTML pages — documentation sites, blogs with server-rendered content, changelogs — a simple HTTP request plus content extraction is all you need. Trafilatura is purpose-built for this: it extracts main text, handles boilerplate removal, and outputs clean markdown.

import trafilatura
import requests

html = requests.get("https://docs.example.com").text
markdown = trafilatura.extract(html, output_format="markdown")

Cost: near zero. Limitation: blind to JavaScript. If the page renders its content client-side, you’ll get an empty shell.

Headless browser (Playwright, Puppeteer)

When content depends on JavaScript — SPAs, pages with lazy-loaded sections, anything behind client-side routing — you need a browser. Playwright gives you Chromium, Firefox, and WebKit in one API. Puppeteer is Chrome-only but has a stronger ecosystem.

The downside: you’re now running browser instances. Memory leaks, crash recovery, proxy rotation, fingerprint management — it all becomes your problem. For 50 pages a week, that’s fine. For 50,000 pages a day, it’s a full-time job.

Managed scraping APIs

Firecrawl, Browserless, and Apify handle the infrastructure — rendering, proxies, rate limiting, and content extraction — as a service. You trade money for time. Firecrawl returns clean markdown directly from any URL, no extraction step needed. Browserless gives you managed headless browsers with proxy support and stealth modes. Apify offers pre-built actors for common scraping patterns.

The tradeoff: you’re paying per page. At high volume, the bill adds up. But if your alternative is hiring a full-time engineer to maintain scraping infra, the API route wins on total cost for most teams.

The escalation ladder

Start cheap, escalate when you hit a wall:

  1. HTTP + Trafilatura works until you hit JS-rendered pages
  2. Headless browser works until you hit aggressive anti-bot defenses
  3. Managed API with proxy rotation works until the per-page cost exceeds your engineer’s salary

The key is building your pipeline so you can swap the scraping layer without touching the chunking, embedding, or retrieval code.


From HTML to clean, LLM-ready content

HTML-to-markdown: the highest-leverage improvement in the stack

This is the “free lunch” move. Convert scraped HTML to markdown before chunking and embedding, and retrieval quality improves — not because you tuned embeddings, but because the input representation is better.

Three things happen when you output markdown:

  1. Headings become # markers. They’re now natural chunk boundaries. A heading-aware splitter can use them directly without any heuristic guessing.
  2. Tables and lists are preserved structurally. LLMs read markdown tables more accurately than <table> HTML. And lists stay together — no splitting item 3 from its parent context.
  3. Token density improves. Markdown uses roughly 67% fewer tokens than equivalent HTML for the same semantic content. Fewer tokens means cheaper embeddings and less noise.

You can use turndown (JavaScript) or markdownify (Python) for the conversion. Or extract with Trafilatura, which outputs markdown natively.

Preserve structure, kill boilerplate

Mozilla’s Readability.js — the engine behind Firefox Reader View — is designed to extract the main article body from a web page. Combined with a markdown conversion step, it gives you exactly what you want: the article content, structured, without the chrome.

Run Readability on the rendered HTML, convert to markdown, and chunk the result. Your embeddings will encode what the page actually says, not what’s in the footer.

Metadata you need

Attach these to every chunk:

FieldWhy it matters
source_urlCite sources in generated responses
titleDisplay context in retrieval results
fetched_atKnow how stale the chunk is
content_hashDetect changes for incremental refresh
languageFilter or route by language
crawl_depthKnow how many hops from the seed URL

Without metadata, you can’t cite, refresh, or dedupe. With it, your retrieval layer supports filtering by source, recency, and domain — not just vector similarity.


Chunking web content: use the DOM, not a token counter

The single biggest mistake in web-to-RAG pipelines

Here’s how it usually goes. Someone scrapes a page. They strip the HTML to flat text — document.body.innerText or equivalent. Then they feed the blob to a RecursiveCharacterTextSplitter with chunk_size=1000 and chunk_overlap=200. And then they wonder why retrieval quality is terrible.

What went wrong? They destroyed every structural signal the DOM gave them for free.

Heading boundaries. Content isolation — <article>, <section>, <main>. Table and list atomicity. The main-content vs. chrome distinction. By the time the embedding model sees that flat blob, the retrieval surface is already ruined. Chunks slice through mid-paragraph, tables split from their headers, list items torn from their parent context, and nav boilerplate mixed into what was supposed to be the article body.

The fix: chunk on semantic boundaries

Use the HTML structure as your first segmentation grid:

  1. Extract main content — run a readability algorithm first to isolate the article body from the chrome
  2. Split on semantic elements — headings (h1–h6), <section>, <article>, <figure>, <table> are natural breakpoints
  3. Preserve atomic elements whole — a table stays one chunk, a code block stays one chunk, a list stays one chunk
  4. Fall back to length-based splitting only within a section — if an individual section is still too long, split it on paragraph boundaries, not mid-sentence

When you convert to markdown first, this becomes trivial. # headers are explicit boundaries. <table> becomes a markdown table block that a splitter can recognize as atomic. <pre><code> becomes a fenced code block.

Why this matters for retrieval

A chunk that contains one coherent idea retrieves cleanly. A chunk that’s a random 1,000-token slice across two section boundaries retrieves confusedly. The embedding model can’t distinguish “this text belongs together” from “this text was arbitrarily concatenated.”

When you chunk on structure, every chunk has a clear topical focus. Similarity search returns the right context. The LLM gets coherent supporting material. The whole pipeline works.

Chunk size and overlap

For web-markdown content specifically:

  • Target 500-900 tokens per chunk. Smaller goes too granular (loses context), larger risks hitting context window limits at retrieval time when you pull multiple chunks.
  • Overlap of 100-150 tokens is enough to prevent information loss at boundaries — but only when chunks are already cut on semantic boundaries. If you’re cutting mid-paragraph, no amount of overlap fixes the underlying problem.
  • Smaller chunks for lists and tables. A 3-row table doesn’t need to be 900 tokens. Keep it whole and let the size be what it is.

Where most chunking strategies break down

The naive semantic chunking trap

There’s a pattern I keep seeing: “split when the embedding cosine similarity between consecutive sentences drops below 0.7.” It sounds principled. In practice, it’s brittle in three ways:

  1. The threshold is dataset-dependent. 0.7 works for documentation but slices too aggressively on narrative content.
  2. Tiny drift triggers bad splits. A sentence with an unfamiliar proper noun drops similarity by 0.02 and suddenly you’ve cut a paragraph in half.
  3. You lose deterministic predictability. Debugging a retrieval failure when your chunk boundaries depend on a floating-point threshold is a special kind of headache.

I’ve ripped semantic chunking out of more pipelines than I’ve kept it in. Rule-based splitting on structural boundaries gives you consistent, predictable chunks that you can inspect and debug. When a retrieval fails, you can open the chunk file and see exactly what went wrong.

Using markdown as an intermediate representation

Markdown as the bridge between scraping and chunking has been the most surprising over-performer in pipelines I’ve worked on. You get three things for the price of one:

  • Headings as # markers are explicit chunk boundaries that cost zero processing
  • Tables and lists preserved structurally are LLM-friendly (models read markdown tables better than HTML) and embedding-friendly (the structure doesn’t get lost in tokenization)
  • Debugging becomes visual. Open a chunk file in any editor and read it. No need to parse HTML tags to understand what went into the embedding model

It’s one of those rare moves where retrieval quality improves measurably — before any embedding model tuning, before any reranking — just from changing the representation of the input text.


Tools compared: Firecrawl, Crawl4AI, Browserless, and DIY

FirecrawlCrawl4AIBrowserlessDIY (Playwright + Trafilatura)
JS renderingBuilt-inBuilt-inCore featureYou handle it
Markdown outputNative, LLM-optimizedVia configVia Readability.jsVia Trafilatura
Proxy supportAutomaticNone (self-hosted)Residential + customYou configure it
Anti-bot evasionManagedBasicUnblock API + BrowserQLYour problem
Cost at 10K pages~$99/mo (100K credits)$0 (self-hosted) + infra~$50-200/mo + usageInfra cost + engineer time
RAG pipeline fitBest one-call solutionBest for full controlBest for stealth needsBest when cost is primary
MCP/CLI supportYesVia pluginYesManual

When to choose each:

  • Firecrawl when you want clean markdown from any URL in one API call and don’t want to build scraping infrastructure. The markdown output is explicitly optimized for LLM consumption.
  • Crawl4AI when you need full control, are comfortable running your own infrastructure, and the open-source AGPL-3.0 license works for your use case. 65K GitHub stars means community support is strong.
  • Browserless when you need managed headless browsers with stealth features — sites that aggressively block automation, multi-step form interactions, geo-gated content.
  • DIY (Playwright + Trafilatura) when you’re scraping a known set of sites at moderate volume and the per-engineer cost of maintaining the scraper is lower than API credits.

You can also mix: use managed APIs for the hard pages, run your own headless browser for the rest. The pipeline shouldn’t care which scraper produced the markdown.


Embedding, storage, and retrieval for web-sourced data

Choosing a vector database

Not all vector DBs handle metadata filtering equally well. For web-sourced RAG, you’ll be filtering by source URL, fetch date, language, and content hash — often in combination with vector similarity search. The database you pick needs to handle hybrid queries without degrading performance.

DatabaseMetadata filtering qualityScale ceilingBest for
PineconeStrong (native filter support)Very highProduction, filtered retrieval
WeaviateStrong (GraphQL-native filters)Very highMulti-tenant, complex filtering
ChromaBasic (Python-native filters)ModerateDevelopment, prototyping
QdrantStrong (payload-based filters)Very highLow-latency filtered retrieval
MilvusStrong (scalar filtering)Very highMassive-scale deployments

If you’re filtering by source_url in every query (“only retrieve from these 3 documentation sites”), Pinecone or Weaviate will serve you better than Chroma, whose filtering is more limited.

Retrieval patterns

Basic similarity search works for the “search across everything” use case. No filtering, just top-k by cosine similarity. Good enough for demos. Not good enough when you have 10 different source domains and you want answers from the right one.

Metadata-filtered retrieval lets you scope the search: “only chunks from docs.stripe.com that were fetched in the last 7 days.” Almost every production RAG system needs this.

Hybrid search (vector + keyword) helps when the query contains specific terms that pure similarity search might miss. If someone asks “API key rotation policy,” keyword matching catches “rotation” even if the embedding model doesn’t associate it strongly with the document context.


Keeping your knowledge base fresh

Web content rots. Pages change, move, or disappear. If you scrape once and never refresh, your RAG system is a snapshot machine, not a live knowledge base.

Change detection

Don’t re-scrape everything blindly. Compare content hashes:

import hashlib

def hash_text(text: str) -> str:
    normalized = " ".join(text.split())
    return hashlib.sha256(normalized.encode()).hexdigest()

Hash the extracted, normalized text — not the raw HTML. Raw HTML changes constantly (timestamps, tracking IDs, A/B test variants) without the actual content changing. If the text hash matches what you have stored, skip the re-embed.

Refresh cadence by content type

  • Documentation pages: weekly. Docs change when products change, and you want to catch API updates.
  • Blog posts: never (once published). Unless you track corrections.
  • Pricing pages: daily if you’re building competitive intelligence.
  • Knowledge base articles: weekly, with immediate refresh triggers if users report stale answers.
  • Changelogs and release notes: daily. This is where users check first for new features.

Incremental re-crawls

A full re-crawl of 50,000 pages because 10 pages changed is wasteful. Instead:

  1. Crawl sitemaps and RSS feeds to detect new or updated URLs
  2. Compare Last-Modified headers or content hashes for existing URLs
  3. Re-scrape and re-embed only the changed pages
  4. Delete chunks for URLs removed from the sitemap

De-duplication

The same content appears at multiple URLs more often than you’d think: example.com/docs and example.com/docs/ (trailing slash), example.com/page?utm_source=twitter and example.com/page (tracking parameters), www.example.com and example.com (subdomain). Canonicalize URLs before storing and strip tracking parameters.

For near-duplicates — pages that share 90%+ of their content — compare normalized text hashes and keep the canonical version.


Cost: DIY scraping vs. managed APIs

Here’s a rough cost comparison at two scales, assuming you need JavaScript rendering on 30% of pages:

Approach1K pages/month100K pages/month
HTTP + Trafilatura (self-hosted)$0 (ignoring engineer time)$0-50 (infra if scheduled)
Playwright (self-hosted)$10-30 (server time)$200-500 (server + proxy costs)
Browserless (managed)$5-20$100-500 (usage-based)
Firecrawl (managed)$0-29 (free tier or hobby)$99-299 (standard/growth plan)
Crawl4AI (self-hosted)$10-30 (server time)$100-300 (infra)

The hidden cost nobody budgets for: proxy rotation with residential IPs. If you’re scraping sites that block datacenter IPs, residential proxy costs can run $5-15 per GB. At scale, that alone can make managed APIs cheaper than DIY.

The other hidden cost: engineer time. Maintaining Playwright scripts across site redesigns, debugging selector breakage, managing proxy pools — it’s a part-time job that grows with the number of sites you scrape.


Common mistakes

  1. Embedding raw HTML. You’re training your vector store on nav bars and footer text. Run Readability-style extraction first.
  2. Stripping HTML to flat text before chunking. This destroys heading boundaries, table structure, and content isolation. Convert to markdown instead.
  3. Naive semantic chunking. The 0.7 cosine similarity threshold is a trap. Rule-based structural splitting beats it for consistency and debuggability.
  4. No metadata. Without source_url and fetched_at, you can’t cite sources or know when a chunk went stale.
  5. No change detection. Re-crawling everything on a schedule burns compute and proxy budget. Hash content, compare, re-embed only what changed.
  6. Ignoring canonicalization. Duplicate chunks from example.com/docs and example.com/docs/ bloat your vector store and degrade retrieval precision.
  7. Using generic chunk sizes. 1,000 tokens might work for blog posts but is terrible for tables (too wide) or FAQ items (too granular). Match chunk size to content type.

FAQ

What is the best web scraping stack for RAG pipelines?
Start with HTTP + Trafilatura for static pages. Add Playwright when you hit JS-rendered content. Switch to a managed API (Firecrawl or Browserless) when anti-bot defenses or scale make DIY unsustainable. The stack should be modular so you can swap the scraping layer without changing the rest of the pipeline.

What are the top headless browser tools for global RAG?
Browserless provides managed headless browsers with residential proxy support and stealth features. Playwright (self-hosted) gives you cross-browser automation with strong defaults. Puppeteer is Chrome-only but has the strongest ecosystem. The right choice depends on whether you value managed infrastructure or full control.

What are the alternatives to heavy browser automation for RAG?
Prefer sitemaps and RSS feeds for URL discovery before running browsers. Extract underlying JSON endpoints when pages are thin shells over APIs. Use Readability.js-style extraction on static HTML to avoid rendering altogether. Pre-rendered or server-side rendered variants often exist at alternate URLs (like /amp or ?format=json).

What are some global-friendly scraping APIs with rotating proxies?
Firecrawl handles proxy rotation automatically. Browserless supports built-in residential proxies, third-party proxies, and sticky sessions via proxySticky. Apify offers geo-targeted residential proxies. For DIY, services like Bright Data and Oxylabs provide proxy pools you can plug into Playwright or Puppeteer.

How does RAG differ from deep research for web data?
RAG pulls answers from a pre-loaded corpus — it searches your vector store for relevant chunks and generates a response. Deep research runs adaptive search loops against the live web, where each result shapes the next query. They serve different purposes: RAG for answering from a known knowledge base, deep research for open-ended investigation across the entire web. Many production systems use both.

Continue Reading

Previous: How to manage technical debt in vibe coding: A practical framework
Next: How to build multimodal RAG with Gemini File Search in Node.js

More in AI security

  • Guide

The agentic AI security checklist: 12 controls to verify before you deploy

Staff September 4, 2026
Twelve controls to verify before you deploy an AI agent, each mapped to an OWASP ASI risk...
Read more Read more about The agentic AI security checklist: 12 controls to verify before you deploy
LLM jailbreak defense: techniques that actually stop attacks Jailbreak defense
  • Cybersecurity

LLM jailbreak defense: techniques that actually stop attacks

Staff July 28, 2026
How do enterprises secure AI data pipelines at production scale? safety
  • Cybersecurity

How do enterprises secure AI data pipelines at production scale?

Staff July 28, 2026
How companies can defend against AI model extraction attacks
  • Guide

How companies can defend against AI model extraction attacks

Staff July 23, 2026
What is a model inversion attack?
  • Glossary

What is a model inversion attack?

Staff July 22, 2026

Glossary

model router
  • LLMs

What is a model router for AI? A plain-English guide

Staff July 30, 2026
A model router for AI is a decision layer that picks which large language model answers each...
Read more Read more about What is a model router for AI? A plain-English guide
What is agentic SDLC?
  • Glossary

What is agentic SDLC?

Staff July 22, 2026
What is a model inversion attack?
  • Glossary

What is a model inversion attack?

Staff July 22, 2026
LLM system prompt leakage: what it is, how it works, and how to stop it agentic ai
  • Glossary

LLM system prompt leakage: what it is, how it works, and how to stop it

Staff July 15, 2026
What is LLM supply chain security? (OWASP LLM03:2025 explained) llm supply chain
  • Glossary

What is LLM supply chain security? (OWASP LLM03:2025 explained)

Staff July 14, 2026

Guides

The agentic AI security checklist: 12 controls to verify before you deploy
  • Guide

The agentic AI security checklist: 12 controls to verify before you deploy

Staff September 4, 2026
LLM jailbreak defense: techniques that actually stop attacks Jailbreak defense
  • Cybersecurity

LLM jailbreak defense: techniques that actually stop attacks

Staff July 28, 2026
How do enterprises secure AI data pipelines at production scale? safety
  • Cybersecurity

How do enterprises secure AI data pipelines at production scale?

Staff July 28, 2026
How companies can defend against AI model extraction attacks
  • Guide

How companies can defend against AI model extraction attacks

Staff July 23, 2026
What is a model inversion attack?
  • Glossary

What is a model inversion attack?

Staff July 22, 2026
How to prevent adversarial attacks on AI models
  • Guide

How to prevent adversarial attacks on AI models

Staff July 22, 2026
  • Home
  • What’s new in AI
  • Solutions
  • Cybersecurity
  • Learn
Copyright © All rights reserved. | by AF themes.