Bypassing the DOM: Extracting JSON-LD and Schema.org Metadata (2026)
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).
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.




