Live Data Playbook

Extracting Structured Data From JavaScript-Heavy Competitor Sites

Render JavaScript, dodge bot detection, and extract structured data in the right order.

· 11 min read · Updated
Cover illustration for “Extracting Structured Data From JavaScript-Heavy Competitor Sites”
Competitive Intelligence · September 22, 2026 · 11 min read · 2,494 words

Competitor pricing pages, feature tables, and job boards mostly live on JavaScript-heavy sites, and pulling clean data off them means solving three separate problems, in order, not just throwing a headless browser at the URL and hoping. Rendering, bot detection, and structured extraction each have their own failure modes, and skipping one doesn't save time, it just moves the breakage downstream where it's harder to find.

Why JavaScript-heavy sites break traditional scraping pipelines

Fetching a modern competitor site the old-fashioned way, with a plain HTTP request, often returns a response that looks like a punchline: <div id="root"></div> and not much else. No prices. No inventory counts. No feature grid. The content hasn't loaded because nothing has told the browser to go fetch it, since nothing in that response is a browser.

This isn't a BeautifulSoup problem or a Cheerio problem specifically; those tools do what they're built to do. They just can't see past the shell, because the pricing grids, inventory counts, and feature tables a competitor intelligence team actually wants are rendered by JavaScript after the page loads, not baked into the HTML it arrives with. A static scraper pointed at a site like that comes back empty-handed, reporting success the whole way.

This architecture is widespread but not universal. React and Vue power single-page apps all over the modern web, and Next.js adds a wrinkle of its own: hybrid rendering, part server, part client. Server-side rendering has made a real comeback too, through frameworks like Next.js and Remix, so it would be a mistake to assume every scraping target is a client-rendered SPA. Some of those "server-rendered" pages still fetch their real content client-side after the initial load, especially under Next.js's hybrid data-fetching patterns, so the page can look server-rendered and still require a JavaScript engine to show the parts that matter.

That architectural detail matters more for competitor intelligence than for scraping in general, because the pages a team actually needs, pricing pages, feature comparison tables, job boards, product catalogs, are disproportionately built on exactly these dynamic frameworks. The data worth collecting is disproportionately the data hardest to collect.

Getting past that shell is only step one. Three separate problems stand between a developer and clean, structured competitor data: rendering the dynamic content, getting past bot detection, and turning whatever comes out the other end into records a database can actually use. These have to be solved in order, not independently, because solving rendering alone produces a pipeline that passes every test in a dev environment and then falls over in production the moment a target site notices the traffic looks automated. If you solve rendering and bot detection without a real extraction layer, you just get a bigger pile of raw HTML or messy markdown that nothing downstream can parse reliably. The rest of this piece works through each of those three layers in the order they need to be solved.

Choosing the right rendering approach for each target site

Not every page needs a headless browser, and treating "always launch Chromium" as the default is how scraping budgets quietly balloon. Routing each request by what the page actually requires is the first real decision a team has to make, and it's a cost decision as much as a technical one.

A tiered cost model from infrastructure analysis lays the pattern out clearly: a meaningful share of target pages resolve fine with a plain HTTP request, a bigger chunk need headless browser rendering, and a smaller, pricier slice need full anti-bot bypass on top of that. Each tier costs noticeably more than the one below it, so routing a request to the wrong tier isn't just inefficient, it's expensive in a way that compounds across thousands of pages a day.

The diagnostic for figuring out which tier a page belongs to is simple enough to run by hand: open the page source directly. If the body is basically empty without JavaScript running, that page needs a headless browser. If the structured content is already sitting in the initial HTML response, a static request will do the job.

For the static cases, pure HTTP tools remain the right call. Evaluation of scraping tools shows that BeautifulSoup and Requests handle static HTML targets fine, news archives, government portals, plenty of product pages, and they should stay the default there, since reaching for more machinery only adds cost and complexity.

For the JavaScript-rendered cases, you turn to Playwright or Puppeteer, the standard open-source route. They launch an actual Chromium browser, navigate to the page, wait for the JavaScript to finish rendering, then pull the DOM out. A typical Puppeteer setup for this looks something like:

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(', { waitUntil: 'networkidle0' });
const content = await page.content();
await browser.close();

That's the textbook move, and it works, right up until the target site starts paying attention to how the browser behaves rather than just what it requests. (More on that in a moment.)

Engine-level stealth modifies the browser at the C++ source level before it's even compiled, paired with lightweight Rust-based headless engines built with AI agent integration in mind. Engineering teams increasingly build Chromium cores that are modified natively at that C++ level, so they don't have to rely on the kind of fragile JavaScript injection that CDP-aware firewalls can spot. Obscura is one example of this approach: an open-source headless browser written in Rust that runs JavaScript through the V8 engine without shipping a full Chrome binary. It runs as a single self-contained binary, no Node, no Chrome, no extra dependencies, uses around 30 megabytes of memory, and starts up almost instantly. Obscura also supports MCP (Model Context Protocol), so an AI agent can call it directly as a tool over standard input and output, and you don't have to write a custom scraping script around it, which matters when you're building agent-native pipelines rather than one-off crawlers.

None of this changes the underlying argument, which is about routing, not brand loyalty toward any particular browser engine. Match the tool to what the page actually needs, and keep the expensive tiers reserved for pages that genuinely require them. The harder question, and the one that actually breaks most self-managed setups, is what happens after rendering works and the target site notices anyway.

Why bot detection defeats naive headless browser implementations

Rendering the page correctly doesn't mean getting the page's real content. Bot detection now does far more than check IP addresses and request rates. It now works at the level of browser fingerprints and behavior, so a stock headless Chromium instance gets fingerprinted and blocked before it extracts a single field.

Puppeteer and Playwright both control the browser through the Chrome DevTools Protocol, and that instrumentation leaves artifacts in the JavaScript execution environment. Stealth plugins can patch over surface-level signals, but they can't hide the CDP layer itself, because those plugins only work in user space, one level above where the real fingerprint lives.

This isn't a fringe problem limited to a handful of paranoid websites. Cloudflare alone protects a large share of the web, and adding Akamai, PerimeterX (now part of HUMAN Security), and DataDome still leaves most commercial sites without dedicated bot detection from any of those named vendors. Competitor sites at scale are not some rare exception to that pattern. And the sites that do run sophisticated detection tend to be exactly the ones competitive intelligence teams care most about: high-value e-commerce, financial data providers, SaaS pricing pages, all of which deploy the most advanced machine-learning-based risk scoring available.

The danger isn't getting blocked outright, it's getting blocked quietly. If a single IP sends requests too fast, it triggers soft blocks well before hard ones, and a soft block doesn't look like a block at all. The site just starts serving degraded content, empty product fields, missing prices, long before it cuts the connection off completely. A scraper running against a soft-blocked endpoint has no idea anything went wrong. The page loads. The fields parse. The values are stale, placeholder, or quietly missing, and the pipeline logs a success while handing a dashboard numbers that are simply false. That's a worse outcome than an outright failure, because nobody goes looking for a bug in data that appears to have arrived correctly.

The practical fix at the infrastructure level is to stop treating anti-bot handling as something to build in-house and instead hand it to a managed rendering and bypass layer built for the job. Residential proxy rotation combined with behavioral fingerprint matching addresses the TLS and device-fingerprint signals that give automated traffic away, since datacenter proxies alone no longer get past protected targets. Managed scraping APIs that bundle JS rendering, stealth configuration, and proxy rotation turn the whole arms race into the provider's problem: the rendering infrastructure, the fingerprint management, the retry logic, all of it moves off the team's plate. For agent-driven workflows in particular, most read-only competitive intelligence tasks, price checks, feature page snapshots, job posting pulls, don't need a full persistent browser session. A scraping API with JS rendering and anti-bot bypass built in is both the more appropriate tool and the cheaper one.

Structuring the extraction layer: from rendered content to schema-conformant records

Clean, unblocked, fully rendered HTML still isn't data, just a web page. Turning it into records a database or a pricing dashboard can use is the third stage, and it's where a lot of pipelines that survive rendering and bot detection quietly fall apart anyway.

The traditional method uses CSS or XPath selectors, something like div.product-price > span.value, and that works fine until the target site redesigns its page, which happens more often than most teams plan for. When it does, the selector returns nothing, or worse, it returns the wrong value, with no warning that anything changed. Selectors are giving way to objective-driven, schema-constrained extraction with large language models, and that's the biggest methodological change happening in this space right now.

Current AI data extraction analysis describes the alternative approach: send a URL and a plain-English objective to an API, let the model read the page's content, identify what matches the objective, and return it in a structured format. No selectors to write, and nothing to maintain when the page layout shifts under it. Current scraping practice treats this as a complement to selectors rather than a wholesale replacement: stick with selectors where the HTML structure is stable and well understood, and fall back to AI extraction for layouts that are unfamiliar or change often, which covers most competitor pages in practice. The numbers back the approach up at scale, too. Large benchmarks of LLM-based extraction found a 93% schema validation rate across tens of thousands of real web pages.

None of that works without a schema defined up front. Defining the JSON schema before calling the extraction API is the single highest-leverage decision in the whole pipeline, because it shapes output consistency, how usable the data is downstream, and what the whole thing costs to run. The objective fed into the extraction model needs to be specific. "Get info from this page" is a weak objective and produces inconsistent field names and gaps in the data. Naming the exact fields wanted, company name, founding year, CEO, total funding, headquarters city, is a strong one and produces output that actually matches across runs. Pre-defining typed fields, price, SKU, author, date, feature flag, forces the model to return consistently typed values that slot straight into a database or data warehouse without any manual cleanup afterward.

{
 "plan_name": "string",
 "monthly_price": "number",
 "annual_price": "number",
 "currency": "string",
 "features_included": "array",
 "user_limit": "number"
}

That schema is the contract between the extraction layer and everything downstream of it, the pricing dashboard, the demand-forecasting model, the weekly competitive intelligence report. All of those depend on field names and types staying stable from one run to the next.

Format choice is a direct line item on the bill that determines LLM token consumption at scale, not an abstract preference. Raw HTML fed to an LLM can run to many times the token count of the same content converted to clean Markdown, since the model ends up paying to process navigation menus, footers, cookie banners, and ad markup that carry no useful information at all. Markdown cuts that token usage substantially. For tabular competitor data specifically, a format called JTON (JSON Tabular Object Notation), which uses a "Zen Grid" encoding that folds column headers into a single row, cuts token counts further compared to compact JSON, a result documented across real-world domains at arxiv.org. Use Markdown for retrieval and chunking, since it keeps structure with low token overhead and chunks predictably for embeddings, and use JSON-schema extraction when the goal is typed fields like price, author, date, or SKU rather than prose.

Assembling the three layers into a production competitor intelligence pipeline

A production pipeline that puts the three layers together needs three distinct stages: finding the right URLs, rendering and cleaning the content behind them, and extracting structured records from what's left. Most brittle, one-off scraping scripts don't fail inside any single stage; they fail in the handoff between stages instead, where one script's output doesn't quite match what the next script expects, and nobody notices until the dashboard shows a gap.

A three-stage API architecture maps cleanly onto these stages: a Search function for URL discovery, an Extract function for content cleaning and rendering, and a Task function for structured output at production scale.

Stage one is URL discovery. A search API or site crawler identifies the specific competitor pages worth watching, pricing pages, product catalogs, job boards, feature comparison tables, and keeps that list current as competitor sites get reorganized, renamed, or redesigned.

Stage two is rendering and cleaning. Each URL gets fetched with JS rendering and anti-bot bypass both switched on, and the raw output gets cleaned down to dense Markdown before it ever reaches the extraction model. This is where the first two layers of the whole argument, rendering and bot detection, actually get resolved in practice rather than in theory.

Stage three is structured extraction. You pass the clean Markdown, along with a schema defined ahead of time, to the LLM extraction layer, and it returns records in the exact shape the downstream systems expect. A pricing dashboard, a demand model, or a weekly competitor report can use that output straight away, with nobody patching fields by hand.

Three stages, three separate failure modes solved in order. Rendering that gets skipped leaves the scraper seeing an empty shell. If you skip bot detection handling, the scraper sees a page that looks right and is quietly wrong. A schema that gets skipped leaves the scraper producing a pile of text nobody downstream can use. Solve all three, in that order, and what comes out the other end is a feed, not a folder full of scripts waiting for the next site redesign to break them.

Sources

  1. Web Scraping in 2026

More in Competitive Intelligence