Converting Raw Web Content to LLM-Ready Input
Clean HTML before sending it to an LLM to save tokens and prevent hallucinations.

Raw HTML was never built for a language model to read, and sending it to one anyway is like mailing someone a flat-pack dresser, screws, and the packing peanuts, and expecting them to just "understand bedroom furniture." A page's HTML wasn't built for an LLM to read in the first place. It was built for a browser, and that distinction is the root of most "AI scraping" headaches.
Why raw HTML breaks LLM pipelines
Every token a model reads costs something, in context space and often in actual dollars, and raw HTML spends those tokens recklessly. A single page mashes together layout instructions, analytics scripts, navigation menus, inline styles, and ad markup right alongside the sentence someone actually wants the model to read, and unlike a browser's rendering engine, the model has no built-in way to tell which parts matter. A model fed the same markup treats all of it as equally important text to reason over. It burns context on <div id="footer-nav"> instead of the sentence two inches below it. Cleaning the page up first, stripping the boilerplate, and converting it to plain Markdown produces substantial token savings compared to raw HTML, because the noise got removed.
There's a second, quieter failure mode riding along with the first. Without some way to fetch and clean live content from the web, the model ends up reasoning over facts it has no way to double-check or update. Hallucination and a model running out of room to think are what happens when the pipeline feeding the model was broken before the model ever saw a prompt. Fixing that means building a real pipeline: fetch the page, render it, strip it down, structure what's left. Each of those four stages gets its own section below, because each one can quietly wreck everything that comes after it.
The four-stage pipeline that converts a URL into model-ready input
Fetch means going and getting the raw HTTP response off the web; if the request gets blocked, times out, or comes back with half a page, nothing downstream has any data to work with, full stop on that attempt. Render means running the page's JavaScript so that anything built dynamically actually appears in the page's structure before anyone tries to pull content out of it; skip this step and a site built on React or a similar framework quietly hands back an empty shell. Strip means stripping out everything that isn't the actual content, the nav bars, the footers, the ad slots, the script tags, and this is the stage where most of the token savings either get won or get left on the table. Structure means taking what's left and putting it into Markdown or JSON, and which one makes sense depends entirely on what the system downstream plans to do with it.
A lot of practitioners tack two more steps onto that list, embedding the content and storing it in a vector database, but those belong to retrieval, not ingestion. The key engineering insight is that each stage must succeed independently: a flawless extraction step produces nothing if the fetch stage was blocked, and a perfect Markdown conversion is worthless if stripping was incomplete. The next three sections walk the territory.
Why fetching and rendering are hard on modern sites
Fetch and render fail as a pair in production. It helps to treat them as one combined problem instead of two separate checkboxes.
Start with render. Plenty of modern sites, anything built in React or Vue running in client-side mode, hand back a nearly blank HTML shell the moment a server responds, and only fill it in with actual content after JavaScript runs in the browser. A plain HTTP request grabs that shell and stops there, never triggering the JavaScript that would have filled it with the content anyone actually wanted. Running a real browser engine, Chromium, Firefox, or WebKit, solves that problem, because it executes the page's scripts the way a human's browser would. It also costs time and compute that a bare HTTP request never would, so every pipeline ends up trading rendering fidelity against raw throughput. Getting the full page sometimes means clicking, typing, and scrolling before extraction can even start.
Fetch carries its own set of landmines, and they sit underneath render, not beside it. They watch across the TLS handshake, the HTTP headers, JavaScript challenges that run in the visiting browser, and ongoing behavioral signals over the course of a session. None of that is optional to get past. If the page never loads, there's no content to extract, no matter how clever the extraction logic sitting downstream happens to be. Teams that lump "fetch" and "extract" into one mental bucket called "scraping" tend to pour their engineering effort into the extraction logic and starve the fetch layer, and then wonder why the pipeline returns empty results on exactly the sites that matter most.
How boilerplate stripping determines semantic density
Once a page is actually fetched and rendered, the DOM sitting in memory contains the article or the product listing, plus a parade of things nobody asked for: nav menus, site headers and footers, cookie banners, share buttons, ad containers, script tags, and tracking pixels, none of which disappear on their own just because JavaScript ran. Stripping is the step that removes all of it on purpose, and it deserves to be treated as a real engineering decision rather than a quick regex pass at the end of the pipeline.
If skipped, or done badly, the model spends its attention on tag soup and analytics code instead of the sentence that actually matters, a problem far better solved in the pipeline than left for the model to untangle on the fly. The tools for solving it well already exist: readability-style algorithms, text-to-markup density scoring, and main-content classifiers that look at heading structure and link density to guess which part of the page is the actual article. None of them nail it every time, but all of them beat handing the model the entire DOM and hoping for the best.
Under-stripping has a specific, visible failure mode. If the nav bar is left in, a retrieval system built on top of that content will happily match a user's question against the string "Home | About | Contact," since, as far as the retrieval math is concerned, that's just more text that showed up near the relevant words. Answer quality drops, and nobody notices why until someone goes digging through what actually got retrieved.
Over-stripping is the less obvious trap, and it's the one that burns technical teams specifically. Picture a developer documentation page where the entire point of fetching it was one code block showing the correct API call, or a pricing page where the one piece of information anyone cares about lives inside an HTML table. An aggressive stripping pass tuned to kill "boilerplate" can mistake that table or that code block for decoration and delete it right alongside the ad banners. Good stripping logic protects heading hierarchy, lists, tables, and code blocks on purpose, while still cutting everything that's genuinely just scaffolding around the content.
Markdown versus JSON as the output format
With the content fetched, rendered, and stripped down to what actually matters, the pipeline has one job left: decide what shape to hand it over in. Markdown and JSON aren't competing on quality. They serve different downstream consumers, so picking between them is a decision about what the pipeline is actually for.
Markdown fits when the goal is "give the model something readable," because it keeps headings, lists, tables, and code blocks intact in a format a model can read straight through without any separate parsing step.
Not every Markdown file that comes out of a converter earns the label "LLM-ready." The word doing the real work there is "optimized": if the output still has escaped HTML entities sitting in the text, broken link syntax, or raw image tags the model has no use for, it's still Markdown, just not the clean kind the model actually needs. For pipelines where every token costs something real, a "fit Markdown" mode that drops images and non-essential links trims the token count even further while keeping the heading and list structure a model needs to reason well.
None of this forces a single either-or choice across an entire pipeline. The same fetch can produce Markdown for a retrieval or RAG path and JSON for a structured-data or analytics path at the same time, as long as the stripping stage upstream did its job cleanly. Getting this decision right the first time saves a team from re-ingesting everything later once someone notices the format doesn't fit the use case. And once JSON is the target, a new question opens up: who, or what, decides which fields to pull out in the first place.
How model-powered extraction replaces brittle selectors
For years, pulling structured data off a page meant writing CSS selectors or XPath rules that describe exactly where the data sits: this price is in the span with class .product-price, this title is the second h1 on the page. The trouble is that those rules are brittle by nature. A site redesign, a renamed CSS class, or a switch from server-rendered pages to a client-rendered framework can snap the whole extractor without any warning, and it just quietly starts returning empty or garbled results.
LLM-powered extraction flips the instruction from "here's where the data lives" to "here's what data I want." Feed a model the cleaned HTML or Markdown, pair it with a plain-language instruction like "extract the product price, title, and rating," attach a JSON schema for the shape of the answer, and the model works out where that information lives on its own, adapting when the layout shifts, with no selectors to write or maintain. ScrapeGraphAI's crawl endpoint pushes this a step further by folding crawling and extraction into one call: it applies AI extraction to every page it discovers during the crawl itself, handing back either structured results in AI extraction mode or raw Markdown in markdown conversion mode, with depth, page limits, and domain restrictions all left configurable.
The next step past that is pipelines that notice their own extraction has started failing, work out why, and patch the extraction logic without anyone stepping in. Self-healing fixes the problem of a field moving somewhere else on the page. It does nothing at all for a site that blocks the fetch outright, because those are two completely separate problems, and a model adapting to a redesigned layout has no bearing whatsoever on a model's ability to get past Cloudflare.
Running LLM extraction on every single page adds inference cost that scales with page volume, so for anything running at real scale, the sane routing strategy is template-based extraction for pages whose structure rarely changes, with LLM extraction held in reserve for pages that are structurally unpredictable or valuable enough to justify the cost.
MCP as a Live Agent Capability
Everything described so far assumes a pipeline that runs, produces output, and sits there until something asks for it. MCP changes that assumption at the root. Instead of a job that runs on a schedule and drops results into a database for later, MCP lets an agent request a fresh scrape as a single tool call, mid-conversation, as part of one continuous reasoning loop. MCP itself is an open standard for connecting AI models to outside tools, databases, and APIs through one shared interface, and it's already built natively into flagship models from Anthropic, OpenAI, Google, and Microsoft.
Applied to web data specifically, that shift moves scraping from "kick off a job, come back later for results" to "the agent asks for the page right now, mid-task," letting it research, scrape, and synthesize an answer inside one unbroken loop instead of pausing to wait on a scheduled run. An extension called WebMCP pushes the idea further still, by moving MCP into the browser itself, so a website can act as its own MCP server that a visiting agent discovers and uses directly, with the site itself declaring what it's willing to offer rather than the agent reverse-engineering the DOM to find out. That's a genuine shift in posture, from an agent prying data loose from a site that never agreed to give it up, to a site handing data over on its own terms.
None of that replaces the four-stage pipeline described earlier. An MCP-connected setup still has to fetch, render, strip, and structure every single time it runs; MCP only changes when the pipeline fires and how the result gets delivered, not what the pipeline has to do once it's triggered.
Choosing between a managed API and a self-hosted stack for production pipelines
With all four stages and the MCP layer on the table, the remaining question is simply who builds and runs all of this.
The honest answer for most teams comes down to volume and how much the anti-bot fight actually matters to the pages they need. The only thing that changes is who's holding the wrench when one of those stages breaks.


