Schema-Based Extraction
CSS schemas turn repeating HTML into JSON without an API token. They are the fastest extraction option when the DOM is stable.
Concept
When the HTML structure is reliable, schema-based extraction is the fastest and cheapest strategy. JsonCssExtractionStrategy and JsonXPathExtractionStrategy take a schema with a baseSelector, optional baseFields, and a list of fields. Each field has a name, selector, type, and optional transform, default, or attribute.
The source key is the escape hatch for sibling layouts like Hacker News, where the title and score live in adjacent rows. Pass the strategy to CrawlerRunConfig(extraction_strategy=...), then parse result.extracted_content with json.loads().
Use cases
- Extract product name, price, and image URL from an e-commerce grid.
- Pull article titles and links from a blog archive.
- Parse table-like layouts where related data sits in sibling elements.
Video walkthrough
Code example
import asyncio
import json
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig, CacheMode
from crawl4ai import JsonCssExtractionStrategy
async def main():
schema = {
"name": "Articles",
"baseSelector": "article.post",
"fields": [
{"name": "title", "selector": "h2", "type": "text"},
{"name": "url", "selector": "a", "type": "attribute", "attribute": "href"},
{"name": "summary", "selector": "p.summary", "type": "text", "default": ""},
],
}
run_cfg = CrawlerRunConfig(
cache_mode=CacheMode.BYPASS,
extraction_strategy=JsonCssExtractionStrategy(schema, verbose=True),
)
async with AsyncWebCrawler() as crawler:
result = await crawler.arun(
url="https://example.com/blog",
config=run_cfg,
)
if result.success and result.extracted_content:
data = json.loads(result.extracted_content)
print(json.dumps(data, indent=2))
else:
print("Failed:", result.error_message)
asyncio.run(main())
schema = {
"name": "HN Submissions",
"baseSelector": "tr.athing.submission",
"fields": [
{"name": "rank", "selector": "span.rank", "type": "text"},
{"name": "title", "selector": "span.titleline a", "type": "text"},
{"name": "url", "selector": "span.titleline a", "type": "attribute", "attribute": "href"},
{"name": "score", "selector": "span.score", "type": "text", "source": "+ tr"},
{"name": "author", "selector": "a.hnuser", "type": "text", "source": "+ tr"},
],
}
strategy = JsonCssExtractionStrategy(schema)
Practice lab
Practice lab
Objective: Extract a structured list from a page with repeated elements using a CSS schema.
Steps
- Choose a page with repeated items (e.g., a blog archive or documentation list).
- Inspect the HTML and identify a stable container selector and child selectors.
- Write a schema with at least three fields, including one attribute field.
- Crawl the page with
JsonCssExtractionStrategyand parse the JSON output. - Print the number of items extracted and the first two items.
Success criteria
Expected output
Hints
- Use browser dev tools to test selectors before writing the schema.
- Add
defaultvalues for optional fields so missing data does not break the extraction.
Solution
import asyncio
import json
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig, CacheMode
from crawl4ai import JsonCssExtractionStrategy
async def main():
schema = {
"name": "Blog Posts",
"baseSelector": "article.post",
"fields": [
{"name": "title", "selector": "h2 a", "type": "text"},
{"name": "url", "selector": "h2 a", "type": "attribute", "attribute": "href"},
{"name": "summary", "selector": "p", "type": "text", "default": ""},
],
}
run_cfg = CrawlerRunConfig(
cache_mode=CacheMode.BYPASS,
extraction_strategy=JsonCssExtractionStrategy(schema),
)
async with AsyncWebCrawler() as crawler:
result = await crawler.arun(
url="https://example.com/blog",
config=run_cfg,
)
if result.success and result.extracted_content:
posts = json.loads(result.extracted_content)
print(f"Extracted {len(posts)} posts")
print(json.dumps(posts[:2], indent=2))
asyncio.run(main())
Common mistakes
nth-child selectors that break when the site adds one item.json.loads() on result.extracted_content.source when a nested child selector would work, or vice versa.