Skip to main content

Bypassing the DOM: Extracting JSON-LD and Schema.org Metadata (2026)

· 4 min read
Yassine El Haddad
Software Developer & Automation Specialist

I build production AI agents, web scrapers, and automation pipelines. Most of what I publish here comes from the actual problems they run into: proxies that get banned, anti-bot stacks that fingerprint your client, RAG that drifts when the underlying data moves. Stack: Python, TypeScript, Go, FastAPI, LangChain, Crawlee, Playwright, deployed on AWS, GCP, and Cloudflare.

HTML scraping breaks often for a boring reason: layout churn. You wire up precise CSS selectors (div.product-card > span.price-wrapper > span.value), then the site ships an A/B test or a Tailwind refactor and classes become text-sm font-bold. The scraper still runs, but the data is wrong—or empty—and that quietly poisons anything downstream.

One way to reduce that fragility is to stop depending on the visual tree and read semantic metadata that many sites already embed for search engines.

The usual format is Schema.org vocabulary serialized as JSON-LD (JSON for Linked Data).

Why JSON-LD is useful for extraction

Sites that care about SEO often expose structured product, event, or article data in the initial HTML. Large publishers and marketplaces are typical examples.

You look for script tags with type="application/ld+json". That gives you JSON objects—often closer to the backend model than whatever the UI happens to render today.

<!-- Visual DOM (changes often) -->
<div class="x-y-z-12 flex space-x-2">
<p class="font-[Inter] text-[#111]">99.50</p>
</div>

<!-- JSON-LD block (usually more stable than class names) -->
<script type="application/ld+json">
{
"@context": "https://schema.org/",
"@type": "Product",
"sku": "1098412",
"name": "Enterprise Quantum Router",
"offers": {
"@type": "Offer",
"priceCurrency": "USD",
"price": "99.50",
"availability": "https://schema.org/InStock"
}
}
</script>

Extracting JSON-LD with Crawlee (Node.js)

With Crawlee (from Apify), you can stay in Cheerio for many pages and parse those script blocks as plain JSON.

import { CheerioCrawler } from 'crawlee';

const crawler = new CheerioCrawler({
async requestHandler({ $, log, pushData, request }) {
const jsonLdScripts = $('script[type="application/ld+json"]');

jsonLdScripts.each((_, element) => {
try {
const data = JSON.parse($(element).html());

if (data['@type'] === 'Product') {
pushData({
sku: data.sku,
price_usd: data.offers?.price,
stock_status: data.offers?.availability,
source_uri: request.url,
});
}
} catch (err) {
log.error(`JSON-LD parse failed for ${request.url}`);
}
});
},
});

await crawler.run(['https://target-enterprise-store.com/item/1098412']);

Where JSON-LD extraction still bites you

Schema.org blocks are easier to parse than CSS soup, but they are not a free pass.

1. Malformed JSON in the page

Template bugs (unescaped quotes in descriptions, bad concatenation) produce invalid JSON. A bare JSON.parse() throws and can kill a worker if you do not catch it.

Use try/catch, log the URL, and consider stripping or normalizing problematic characters when you control the cleanup rules.

2. Stale JSON-LD vs live UI

Some stacks cache HTML fragments. The visible price may update via client-side code while the ld+json block still shows yesterday’s value.

If you need price accuracy, spot-check against what users see or against a second signal you trust.

3. Inflated or misleading schema

Some sites overstate ratings or availability in schema to chase rich results. Treat JSON-LD like any other untrusted field: validate ranges, cross-check when it matters, and flag anomalies.

Managed extraction

If you do not want to own parsers and edge cases for many sites, Apify Schema.org–related scrapers give you a hosted Actor workflow with sanitization and delivery options (for example S3 or webhooks into your warehouse).

Apify Affiliate Banner 728x90Apify Affiliate Banner 728x90Apify Affiliate Banner 300x50Apify Affiliate Banner 300x50
Frequently Asked Questions

JSON-LD is mainly an SEO investment. Internal apps, dashboards, and many SaaS surfaces that block indexing often skip it—there is little upside for them.

Usually yes when the block is in the first HTML response: Cheerio or BeautifulSoup can grab it without a headless browser. If the schema only appears after heavy client rendering, you may still need Playwright or similar.