JSON-LD is the invisible gold mine of the modern web. Every e-commerce product page, every news article, every recipe blog, and every business listing has structured data baked into its HTML — and most developers ignore it completely.
In this guide, I'll show you how to extract JSON-LD from any URL, what you can do with it, and why it's the fastest path from "I need website data" to "I have clean structured JSON."
JSON-LD (JavaScript Object Notation for Linked Data) is a W3C standard for embedding structured data inside HTML pages. It looks like this:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Wireless Headphones Pro",
"description": "Noise-canceling wireless headphones",
"brand": { "@type": "Brand", "name": "AudioTech" },
"offers": {
"@type": "Offer",
"price": "129.99",
"priceCurrency": "USD",
"availability": "https://schema.org/InStock"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.7",
"reviewCount": "2,341"
}
}
</script>
This is already structured data. No regex. No CSS selectors. No guessing which div contains the price. The website owner (or their CMS/SEO plugin) already formatted it for you.
JSON-LD powers Google's rich results — those product cards, recipe cards, FAQ accordions, and review stars you see in search results. According to BuiltWith (2026), over 68% of the top 100,000 websites use JSON-LD, and adoption grows every year.
HTML parsing for structured data is brittle. You write a CSS selector for .product-price, the site redesigns, your scraper breaks. JSON-LD is different:
@type fields.For one-off research, open any product page and run this in the browser console:
// Extract all JSON-LD blocks from the current page
const scripts = document.querySelectorAll('script[type="application/ld+json"]');
const jsonld = Array.from(scripts).map(s => JSON.parse(s.textContent));
console.log(JSON.stringify(jsonld, null, 2));
This works on static pages but fails on JavaScript-rendered sites where JSON-LD is injected after page load. For those, you need browser automation.
For automated extraction at scale, Playwright (or Puppeteer) gives you a real browser environment:
from playwright.sync_api import sync_playwright
def extract_jsonld(url):
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto(url, wait_until='networkidle')
jsonld = page.evaluate("""() => {
const scripts = document.querySelectorAll(
'script[type="application/ld+json"]'
);
return Array.from(scripts).map(s => {
try { return JSON.parse(s.textContent); }
catch (e) { return null; }
}).filter(Boolean);
}""")
browser.close()
return jsonld
# Usage
data = extract_jsonld('https://example.com/product/123')
for block in data:
print(f"{block['@type']}: {block.get('name', 'unnamed')}")
ExtractAPI automatically extracts all JSON-LD blocks from any URL, along with page metadata, headings, text content, images, and links — in one API call:
curl -X POST https://extractapi.app/v1/extract \
-H "Authorization: Bearer ***" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/product/123"}'
The response includes a jsonld array with every structured data block on the page:
{
"url": "https://example.com/product/123",
"data": {
"title": "Wireless Headphones Pro — AudioTech",
"jsonld": [
{
"@type": "Product",
"name": "Wireless Headphones Pro",
"offers": { "price": "129.99", "priceCurrency": "USD" },
"aggregateRating": { "ratingValue": "4.7", "reviewCount": 2341 }
},
{
"@type": "BreadcrumbList",
"itemListElement": [ /* breadcrumb items */ ]
}
],
"headings": { "h1": ["Wireless Headphones Pro"] },
"text": "Noise-canceling wireless headphones with 40-hour battery life..."
}
}
No browser management, no proxy rotation, no HTML parsing. One request, all the structured data.
Try ExtractAPI — Plans from $9/month
{
"@type": "Product",
"name": "MacBook Pro 16\"",
"sku": "MBP16-M3-2026",
"brand": { "@type": "Brand", "name": "Apple" },
"offers": {
"@type": "Offer",
"price": "2499.00",
"priceCurrency": "USD",
"availability": "https://schema.org/InStock",
"priceValidUntil": "2026-12-31"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.8",
"reviewCount": "1,247"
}
}
{
"@type": "Recipe",
"name": "Sourdough Bread",
"prepTime": "PT30M",
"cookTime": "PT45M",
"totalTime": "PT15H",
"recipeYield": "1 loaf",
"recipeIngredient": [
"500g bread flour",
"350g water",
"100g sourdough starter",
"10g salt"
],
"nutrition": {
"@type": "NutritionInformation",
"calories": "180 calories per slice"
}
}
{
"@type": "Article",
"headline": "ExtractAPI Launches Web Scraping API From $9/month",
"author": { "@type": "Person", "name": "Adam" },
"datePublished": "2026-07-22",
"publisher": { "@type": "Organization", "name": "ExtractAPI" }
}
{
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What is JSON-LD?",
"acceptedAnswer": {
"@type": "Answer",
"text": "JSON-LD is a method of encoding Linked Data using JSON..."
}
}
]
}
{
"@type": "LocalBusiness",
"name": "Greenhouse Coffee",
"address": {
"@type": "PostalAddress",
"streetAddress": "123 Main St",
"addressLocality": "Princeton",
"addressRegion": "NJ",
"postalCode": "08540"
},
"telephone": "+1-609-555-0123",
"openingHoursSpecification": [
{ "@type": "OpeningHoursSpecification", "dayOfWeek": "Monday", "opens": "07:00", "closes": "18:00" }
],
"geo": { "@type": "GeoCoordinates", "latitude": "40.3573", "longitude": "-74.6672" }
}
Many modern SPAs (React, Vue, Next.js) inject JSON-LD after the initial HTML load. If you're using a simple HTTP client (like requests or curl), you'll miss it. Use a browser-based approach — Playwright, Puppeteer, or a web scraping API that renders JavaScript.
Pages often have multiple JSON-LD blocks: one for the Product schema, one for BreadcrumbList, one for Organization. Don't assume the first block is the one you want — iterate through all of them.
Not all JSON-LD is valid JSON. A misplaced comma, unescaped quote, or broken template tag can make JSON.parse() throw. Always wrap your extraction in try/catch, and consider using a JSON repair library for production scraping.
Some sites extend Schema.org with custom properties (e.g., Shopify's @type: "Product" often includes non-standard fields like itemCondition or category). Don't assume every field exists — check with optional chaining: offer?.price.
Hitting the same domain too fast will get you blocked. Use a scraping API with built-in rate limiting per API key (ExtractAPI does per-key rate limiting, so one customer can't affect your entire account).
I've used both approaches extensively. CSS selectors work until the site changes a class name. JSON-LD works until the site stops using Schema.org — which almost never happens because it would tank their SEO. For e-commerce data, structured content, and business listings, JSON-LD extraction is the most reliable approach available today.
But you need a real browser to render JavaScript sites, a way to handle malformed JSON, and a system that doesn't fall over at scale. That's why we built ExtractAPI.
Start extracting JSON-LD — Free test with any plan
Read next: Web Scraping API Pricing Compared (2026) · ExtractAPI vs ScrapingBee — Honest Comparison