Crawl4AI Webpage → Clean Markdown/JSON

scraping Intermediate Claude Code, Codex CLI, Gemini CLI, GitHub Copilot, Cursor
0 Upvotes
58 Views
2 Downloads
817 Words

Description

Turns webpages into clean, LLM-ready Markdown or structured JSON using the Crawl4AI Python library. Use when the user asks to scrape/convert a URL into “clean markdown for RAG”, “reference file markdown”, “structured JSON for repeated items”, or when pages are “JavaScript-heavy” / “dynamic” and requests + BeautifulSoup won’t reliably capture content. Also use when the user asks for “async crawl”, “run Crawl4AI with js_code”, or “extract with CSS/XPath/regex/LLM strategy”. Trigger keywords: crawl4ai, AsyncWebCrawler, CrawlerRunConfig, js_code, DefaultMarkdownGenerator, fit_markdown, JsonCssExtractionStrategy, JsonXPathExtractionStrategy, RegexExtractionStrategy, LLMExtractionStrategy, extracted_content, result.success. Prefer Crawl4AI over requests/BS unless the page is trivially static and the user explicitly wants lightweight HTML parsing.

When to Use

Use this skill when the agent needs Crawl4AI-driven extraction from URLs into LLM-ready Markdown or structured JSON, especially for JS/dynamic pages or when choosing between Markdown, CSS/XPath JSON schemas, regex, and LLM extraction strategies.

Use Cases

- Turn documentation/blog pages into clean Markdown for RAG/reference files
- Extract repeated entities (products, jobs, changelog entries, directory listings) into structured JSON
- Scrape JavaScript-heavy pages by running js_code and/or using a persistent browser profile
- Choose the cheapest reliable extraction strategy (CSS/XPath vs regex vs LLM) with reliability checks

About this skill

Crawl4AI Webpage → Clean Markdown/JSON is a free SKILL.md agent skill in the Scraping category for Claude Code, Codex CLI, Gemini CLI, GitHub Copilot, Cursor. The file is about 698 words long and is rated intermediate difficulty. To use it, copy or download the file and save it as crawl4ai-webpage-to-markdown-json/SKILL.md inside your agent's skills folder (for Claude Code, .claude/skills/).

New to skills? Read the SKILL.md format and manifest spec or the complete guide to AI agent skills. Browse more skills in the SKILL.md skill library.

Bundle Explorer

3 files across 1 folder. Click a file to inspect its contents.

references/crawl4ai-extraction-strategy-playbook.md
reference 8,169 chars
# Crawl4AI Extraction Strategy Playbook (v0.8.x)

## When to use Crawl4AI (vs requests/BeautifulSoup)
Use Crawl4AI when:
- You need clean, LLM-ready content from webpages (especially for research/RAG/reference files).
- The page is JavaScript-heavy and normal requests/BeautifulSoup scraping likely fails.
- You need structured JSON from repeated elements (e.g., product listings, docs sections, job posts, changelogs, directory pages).
- You want a cheaper/non-LLM extraction first, with a fallback to LLM only when needed.

## Reliability rule
- Always check `result.success` before trusting outputs.

## Prefer strategy order
1) Markdown extraction (for reference/RAG)
2) LLM-free structured JSON:
   - CSS/XPath schema extraction for repeated structured elements
   - RegexExtractionStrategy for simple fields (URLs/emails/dates/currency/phone/IDs/etc.)
3) LLMExtractionStrategy only when content is unstructured/semantic interpretation is required.
   - Note: LLM extraction can be slower and costlier than schema/regex.

## Minimal async crawler baseline (Markdown output)
```python
import asyncio
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig
from crawl4ai.markdown_generation_strategy import DefaultMarkdownGenerator

async def main():
    config = CrawlerRunConfig(markdown_generator=DefaultMarkdownGenerator())
    async with AsyncWebCrawler() as crawler:
        result = await crawler.arun("https://example.com", config=config)
        if result.success:
            print(result.markdown)
        else:
            print("Crawl failed:", result.error_message)

if __name__ == "__main__":
    asyncio.run(main())
```

## LLM-friendly Markdown vs filtered “fit_markdown”
- Crawl4AI can provide both:
  - `result.markdown` (raw/unfiltered markdown)
  - `fit_markdown` when content filters are used (filtered “fit” markdown)

(Use filtered markdown when you want fewer tokens for downstream LLM tasks; the LLM extraction docs explicitly support `input_format="fit_markdown"`.)

## LLM-Free JSON extraction (schema-based): CSS or XPath
Use when the page has repeated structured elements.

### JsonCssExtractionStrategy (CSS)
- Strategy uses:
  - `baseSelector`: container for each repeated item
  - `fields`: per-item fields with CSS selectors and `type`
- Example structure (from docs):
```python
schema = {
  "name": "Crypto Prices",
  "baseSelector": "div.crypto-row",
  "fields": [
    {"name": "coin_name", "selector": "h2.coin-name", "type": "text"},
    {"name": "price", "selector": "span.coin-price", "type": "text"},
  ],
}
extraction_strategy = JsonCssExtractionStrategy(schema, verbose=True)

config = CrawlerRunConfig(
  cache_mode=CacheMode.BYPASS,
  extraction_strategy=extraction_strategy,
)
```

### JsonXPathExtractionStrategy (XPath)
Same concept as CSS version but using XPath selectors.

### raw:// scheme (for local HTML testing)
- Crawl4AI supports passing dummy HTML directly via `raw://<html_string>`.
- This is shown in the XPath example.

## LLM-Free JSON extraction: RegexExtractionStrategy
Use when you only need fast pattern extraction (emails/phones/URLs/dates/currency/etc.).

### Built-in patterns (IntFlag)
- `RegexExtractionStrategy.Url`
- `RegexExtractionStrategy.Email`
- `RegexExtractionStrategy.PhoneUS`, `RegexExtractionStrategy.PhoneIntl`
- `RegexExtractionStrategy.DateIso`, `RegexExtractionStrategy.DateUS`
- `RegexExtractionStrategy.Currency`, `RegexExtractionStrategy.Number`, `RegexExtractionStrategy.Uuid`, etc.

### Simple example
```python
strategy = RegexExtractionStrategy(
    pattern = RegexExtractionStrategy.Email | RegexExtractionStrategy.Url
)
config = CrawlerRunConfig(extraction_strategy=strategy)

result = await crawler.arun(url="https://example.com", config=config)
if result.success:
    data = json.loads(result.extracted_content)
```

### Regex match output shape
Returns a JSON array of matches like:
```json
[
  {
    "url": "https://example.com",
    "label": "email",
    "value": "contact@example.com",
    "span": [145, 163]
  }
]
```

### LLM-assisted regex pattern generation (one-time)
- RegexExtractionStrategy includes `generate_pattern(label, html, query, llm_config)`.
- Workflow described: fetch sample HTML, generate pattern once via LLM, save to disk, then extract with regex only.

## LLM extraction: LLMExtractionStrategy
Use when:
- content is unstructured
- you need semantic interpretation / reorganization / knowledge-graph style extraction

### Key facts from docs
- Provider-agnostic via LiteLLM using `LLMConfig(provider="<provider>/<model>", api_token=..., base_url=optional)`.
- LLM-based extraction can be slower and costlier than schema-based.
- Strategy must be placed in `CrawlerRunConfig(extraction_strategy=...)` (not passed directly as a param to `arun()`).

### extraction_type
- `extraction_type="schema"`: model returns JSON conforming to a Pydantic-derived schema.
- `extraction_type="block"`: model returns freeform text or smaller JSON structures; library collects.

### input_format
Controls what the LLM receives:
- `"markdown"` (default)
- `"fit_markdown"`
- `"html"`

### chunking parameters
- `chunk_token_threshold` (max tokens per chunk)
- `overlap_rate` (e.g., 0.1 for 10% overlap)
- `apply_chunking` (bool)

### Required/typical parameters inside LLMExtractionStrategy
From the docs:
- `llm_config: LLMConfig`
- `schema: dict` (when `extraction_type="schema"`)
- `extraction_type: "schema" | "block"`
- `instruction: str`
- optional chunking fields (`chunk_token_threshold`, `overlap_rate`, `apply_chunking`)
- `input_format: "markdown" | "fit_markdown" | "html"`
- optional `extra_args`

### Minimal pattern
```python
import os, json
from pydantic import BaseModel
from crawl4ai import (
    AsyncWebCrawler, BrowserConfig, CrawlerRunConfig,
    CacheMode, LLMConfig, LLMExtractionStrategy
)

class Product(BaseModel):
    name: str
    price: str

async def main():
    llm_strategy = LLMExtractionStrategy(
        llm_config=LLMConfig(
            provider="openai/gpt-4o-mini",
            api_token=os.getenv("OPENAI_API_KEY"),
        ),
        schema=Product.model_json_schema(),
        extraction_type="schema",
        instruction="Extract all product objects with 'name' and 'price'.",
        apply_chunking=True,
        input_format="markdown",
        extra_args={"temperature": 0.0, "max_tokens": 800},
        verbose=True,
    )

    crawl_config = CrawlerRunConfig(
        extraction_strategy=llm_strategy,
        cache_mode=CacheMode.BYPASS,
    )

    async with AsyncWebCrawler(config=BrowserConfig(headless=True)) as crawler:
        result = await crawler.arun(url="https://example.com/products", config=crawl_config)

    if result.success:
        data = json.loads(result.extracted_content)
        print(data)
        llm_strategy.show_usage()
    else:
        print("Error:", result.error_message)

import asyncio
if __name__ == "__main__":
    asyncio.run(main())
```

## LLM output handling
- When `extraction_type="schema"`, the docs recommend parsing/validating the JSON output.
- `llm_strategy.show_usage()` prints token usage when provided by the provider.

## Strategy placement constraint
- Docs explicitly state: strategy definitions belong inside `CrawlerRunConfig`, not as direct parameters to `arun()`.

## Sibling layout handling for schema extraction (CSS/XPath)
If an item’s data is split across sibling elements (not descendants of a base container), schema supports per-field `source` to navigate to a sibling before applying that field’s selector.

Syntax shown:
- `"source": "+ <selector>"`

Example from docs (Hacker News):
```json
{"name": "score", "selector": "span.score", "type": "text", "source": "+ tr"}
```

## JS-heavy pages
- Crawl4AI supports JS execution and dynamic content rendering via its browser integration (JS execution described in examples using `js_code` in `CrawlerRunConfig`).
- For structured extraction on JS-rendered sections, docs show passing `js_code` to click tabs and wait.

(See separate note in install/dynamic-pages file about how to use async + js_code; the code example shown in Crawl4AI README uses `run_config = CrawlerRunConfig(js_code=[...])`.)
references/crawl4ai-install-and-dynamic-crawling.md
reference 4,843 chars
# Crawl4AI Install & Dynamic Crawling (async + JS)

## Install (Python)
- Basic install:
  - `pip install -U crawl4ai`
- If you need pre-release:
  - `pip install crawl4ai --pre`
- After installing, run post-install setup:
  - `crawl4ai-setup`
- Verify installation:
  - `crawl4ai-doctor`

## Playwright dependencies
- Crawl4AI uses Playwright for web crawling (async version).
- If Playwright-related errors occur, install browsers manually:
  - `playwright install`
  - or: `python -m playwright install chromium`

## Minimal async crawl
```python
import asyncio
from crawl4ai import AsyncWebCrawler

async def main():
    async with AsyncWebCrawler() as crawler:
        result = await crawler.arun(url="https://www.nbcnews.com/business")
        print(result.markdown)

if __name__ == "__main__":
    asyncio.run(main())
```

## JS-heavy pages: use the browser and run custom JS
Crawl4AI supports executing JavaScript and waiting for dynamic content. Docs show using `CrawlerRunConfig(js_code=[...])`.

Example: click through tabs to reveal content, then extract structured data without LLMs:
```python
import asyncio, json
from crawl4ai import (
    AsyncWebCrawler, BrowserConfig, CrawlerRunConfig, CacheMode,
    JsonCssExtractionStrategy
)

async def main():
    schema = {
        "name": "KidoCode Courses",
        "baseSelector": "section.charge-methodology .w-tab-content > div",
        "fields": [
            {"name": "section_title", "selector": "h3.heading-50", "type": "text"},
            {"name": "section_description", "selector": ".charge-content", "type": "text"},
            {"name": "course_name", "selector": ".text-block-93", "type": "text"},
            {"name": "course_description", "selector": ".course-content-text", "type": "text"},
            {"name": "course_icon", "selector": ".image-92", "type": "attribute", "attribute": "src"},
        ],
    }

    extraction_strategy = JsonCssExtractionStrategy(schema, verbose=True)

    browser_config = BrowserConfig(headless=False, verbose=True)
    run_config = CrawlerRunConfig(
        extraction_strategy=extraction_strategy,
        js_code=["""
        (async () => {
          const tabs = document.querySelectorAll(
            "section.charge-methodology .tabs-menu-3 > div"
          );
          for (let tab of tabs) {
            tab.scrollIntoView();
            tab.click();
            await new Promise(r => setTimeout(r, 500));
          }
        })();
        """],
        cache_mode=CacheMode.BYPASS,
    )

    async with AsyncWebCrawler(config=browser_config) as crawler:
        result = await crawler.arun(
            url="https://www.kidocode.com/degrees/technology",
            config=run_config,
        )

    companies = json.loads(result.extracted_content)
    print(f"Successfully extracted {len(companies)} companies")
    print(json.dumps(companies[0], indent=2))

if __name__ == "__main__":
    asyncio.run(main())
```

## Browser/session persistence (persistent user profile)
Docs show `BrowserConfig(user_data_dir=..., use_persistent_context=True)` with `magic=True` when crawling:
```python
import os
from pathlib import Path
import asyncio
from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig, CacheMode

async def test_news_crawl():
    user_data_dir = os.path.join(Path.home(), ".crawl4ai", "browser_profile")
    os.makedirs(user_data_dir, exist_ok=True)

    browser_config = BrowserConfig(
        verbose=True,
        headless=True,
        user_data_dir=user_data_dir,
        use_persistent_context=True,
    )

    run_config = CrawlerRunConfig(cache_mode=CacheMode.BYPASS)

    async with AsyncWebCrawler(config=browser_config) as crawler:
        url = "ADDRESS_OF_A_CHALLENGING_WEBSITE"
        result = await crawler.arun(url, config=run_config, magic=True)
        print(f"Content length: {len(result.markdown)}")

if __name__ == "__main__":
    asyncio.run(test_news_crawl())
```

## Docker quick test (self-hosted API)
The README describes a Docker image exposing endpoints like `/crawl` and `/task/{task_id}`.

Run container:
- `docker pull unclecode/crawl4ai:latest`
- `docker run -d -p 11235:11235 --name crawl4ai --shm-size=1g unclecode/crawl4ai:latest`

Submit crawl job (POST `/crawl`):
```python
import requests

response = requests.post(
    "http://localhost:11235/crawl",
    json={"urls": ["https://example.com"], "priority": 10}
)
if response.status_code == 200:
    if "results" in response.json():
        results = response.json()["results"]
        for result in results:
            print(result)
    else:
        task_id = response.json()["task_id"]
        result = requests.get(f"http://localhost:11235/task/{task_id}")
```

(For monitoring dashboard/playground URLs, the README states: `http://localhost:11235/dashboard` and `http://localhost:11235/playground`.)
SKILL.md
core 6,054 chars
---
name: crawl4ai-webpage-to-markdown-json
description: "Turns webpages into clean, LLM-ready Markdown or structured JSON using the Crawl4AI Python library. Use when the user asks to scrape/convert a URL into “clean markdown for RAG”, “reference file markdown”, “structured JSON for repeated items”, or when pages are “JavaScript-heavy” / “dynamic” and requests + BeautifulSoup won’t reliably capture content. Also use when the user asks for “async crawl”, “run Crawl4AI with js_code”, or “extract with CSS/XPath/regex/LLM strategy”. Trigger keywords: crawl4ai, AsyncWebCrawler, CrawlerRunConfig, js_code, DefaultMarkdownGenerator, fit_markdown, JsonCssExtractionStrategy, JsonXPathExtractionStrategy, RegexExtractionStrategy, LLMExtractionStrategy, extracted_content, result.success. Prefer Crawl4AI over requests/BS unless the page is trivially static and the user explicitly wants lightweight HTML parsing."
---
Crawl4AI guidance for converting webpages to clean Markdown or structured JSON.

Workflow
1) Decide the extraction approach.
   - If the goal is “LLM-ready Markdown / RAG / reference files”, plan to use DefaultMarkdownGenerator and return result.markdown or fit_markdown.
   - If the goal is “structured JSON for repeated items” (lists of cards/rows/sections), plan schema-based JSON first via CSS or XPath.
   - If you only need a few fast fields (emails/urls/phones/dates/currency/ids), plan RegexExtractionStrategy.
   - If the goal requires semantic interpretation/unstructured reformatting, plan LLMExtractionStrategy.
2) Prefer Crawl4AI over requests/BeautifulSoup when:
   - The page is JS-heavy/dynamic, content loads after interactions, or static HTML misses the needed text.
   - You need consistent cleaning into LLM-ready output.
   Use when the user requests “clean markdown” or “structured extraction” from a URL.
3) Install and verify before coding.
   - Run: pip install -U crawl4ai
   - Run: crawl4ai-setup
   - Run: crawl4ai-doctor
   - If Playwright issues: run playwright install (or python -m playwright install chromium).
   (Read [references/crawl4ai-install-and-dynamic-crawling.md](references/crawl4ai-install-and-dynamic-crawling.md) when setting up/installing or debugging Playwright/dynamic crawling.)
4) Use the async crawling baseline.
   - Use AsyncWebCrawler in an asyncio program.
   - Run crawler.arun(url, config=CrawlerRunConfig(...)).
   - Always check result.success before using result.markdown or result.extracted_content.
   (Read [references/crawl4ai-extraction-strategy-playbook.md](references/crawl4ai-extraction-strategy-playbook.md) when selecting Markdown vs JSON strategies, ordering strategies, and enforcing result.success checks.)
5) Generate LLM-ready Markdown for RAG/reference files.
   - Configure CrawlerRunConfig(markdown_generator=DefaultMarkdownGenerator()).
   - After arun: if result.success, return result.markdown (or use fit_markdown when token reduction/filtered content is required).
   - Prefer fit_markdown when downstream LLM context budget is tight.
6) Handle JavaScript-heavy or interaction-revealed content.
   - Use CrawlerRunConfig(js_code=[...]) to run browser-side JS (e.g., click tabs, expand accordions, scroll to trigger lazy loads).
   - If needed, use BrowserConfig(headless=..., user_data_dir=..., use_persistent_context=True) to persist sessions.
   (Read [references/crawl4ai-install-and-dynamic-crawling.md](references/crawl4ai-install-and-dynamic-crawling.md) when the page requires dynamic JS, js_code usage, or persistent browser profiles.)
7) Produce structured JSON without LLM first.
   - For repeated structured items: use JsonCssExtractionStrategy or JsonXPathExtractionStrategy with a schema defining baseSelector and fields.
   - Place extraction_strategy in CrawlerRunConfig(extraction_strategy=...).
   - After arun: if result.success, parse result.extracted_content as JSON.
8) Use regex only for simple patterns.
   - Use RegexExtractionStrategy with built-in patterns (Email/Url/Phone/Date/Currency/Number/Uuid, etc.).
   - Parse result.extracted_content as JSON when result.success.
9) Use LLMExtractionStrategy only when needed.
   - Use LLMExtractionStrategy(...), and place it in CrawlerRunConfig(extraction_strategy=...); do not pass it directly to arun().
   - Choose extraction_type:
     * “schema” when you need strict JSON conforming to a schema.
     * “block” when you want freeform text/smaller structures.
   - Choose input_format among markdown/fit_markdown/html.
   - Configure llm_config (provider/model) from your externally prepared auth environment.
   (Read [references/crawl4ai-extraction-strategy-playbook.md](references/crawl4ai-extraction-strategy-playbook.md) when deciding between CSS/XPath vs regex vs LLM, and when enforcing placement + reliability checks.)

Constraints / edge cases
- Never trust output until result.success is True. If false, return the error (result.error_message) and suggest retry with js_code or a different strategy.
- Strategy ordering rule: Markdown first for reference/RAG; schema JSON next (CSS/XPath); regex for simple fields; LLM last.
- If extraction fails on a JS page, add js_code to reveal the content before extracting.
- For repeated structured content, prefer CSS/XPath over LLM for speed/cost.

Concrete I/O expectations
- Markdown path: return a string from result.markdown (or fit_markdown if configured/used).
- JSON path: return parsed JSON from json.loads(result.extracted_content) after result.success.

Example output mapping
- User asks: “Convert this docs URL into clean markdown for my RAG reference.” → Use MarkdownGenerator; return result.markdown/fit_markdown.
- User asks: “Extract product listings (name, price, url) into JSON.” → Use JsonCssExtractionStrategy or JsonXPathExtractionStrategy; return JSON.
- User asks: “Find all emails/phones/urls on this page.” → Use RegexExtractionStrategy; return matches.
- User asks: “Summarize each section into a structured knowledge graph.” → Use LLMExtractionStrategy (schema if strict), with fit_markdown input.

SKILL.md Content

---
name: crawl4ai-webpage-to-markdown-json
description: "Turns webpages into clean, LLM-ready Markdown or structured JSON using the Crawl4AI Python library. Use when the user asks to scrape/convert a URL into “clean markdown for RAG”, “reference file markdown”, “structured JSON for repeated items”, or when pages are “JavaScript-heavy” / “dynamic” and requests + BeautifulSoup won’t reliably capture content. Also use when the user asks for “async crawl”, “run Crawl4AI with js_code”, or “extract with CSS/XPath/regex/LLM strategy”. Trigger keywords: crawl4ai, AsyncWebCrawler, CrawlerRunConfig, js_code, DefaultMarkdownGenerator, fit_markdown, JsonCssExtractionStrategy, JsonXPathExtractionStrategy, RegexExtractionStrategy, LLMExtractionStrategy, extracted_content, result.success. Prefer Crawl4AI over requests/BS unless the page is trivially static and the user explicitly wants lightweight HTML parsing."
---
Crawl4AI guidance for converting webpages to clean Markdown or structured JSON.

Workflow
1) Decide the extraction approach.
   - If the goal is “LLM-ready Markdown / RAG / reference files”, plan to use DefaultMarkdownGenerator and return result.markdown or fit_markdown.
   - If the goal is “structured JSON for repeated items” (lists of cards/rows/sections), plan schema-based JSON first via CSS or XPath.
   - If you only need a few fast fields (emails/urls/phones/dates/currency/ids), plan RegexExtractionStrategy.
   - If the goal requires semantic interpretation/unstructured reformatting, plan LLMExtractionStrategy.
2) Prefer Crawl4AI over requests/BeautifulSoup when:
   - The page is JS-heavy/dynamic, content loads after interactions, or static HTML misses the needed text.
   - You need consistent cleaning into LLM-ready output.
   Use when the user requests “clean markdown” or “structured extraction” from a URL.
3) Install and verify before coding.
   - Run: pip install -U crawl4ai
   - Run: crawl4ai-setup
   - Run: crawl4ai-doctor
   - If Playwright issues: run playwright install (or python -m playwright install chromium).
   (Read [references/crawl4ai-install-and-dynamic-crawling.md](references/crawl4ai-install-and-dynamic-crawling.md) when setting up/installing or debugging Playwright/dynamic crawling.)
4) Use the async crawling baseline.
   - Use AsyncWebCrawler in an asyncio program.
   - Run crawler.arun(url, config=CrawlerRunConfig(...)).
   - Always check result.success before using result.markdown or result.extracted_content.
   (Read [references/crawl4ai-extraction-strategy-playbook.md](references/crawl4ai-extraction-strategy-playbook.md) when selecting Markdown vs JSON strategies, ordering strategies, and enforcing result.success checks.)
5) Generate LLM-ready Markdown for RAG/reference files.
   - Configure CrawlerRunConfig(markdown_generator=DefaultMarkdownGenerator()).
   - After arun: if result.success, return result.markdown (or use fit_markdown when token reduction/filtered content is required).
   - Prefer fit_markdown when downstream LLM context budget is tight.
6) Handle JavaScript-heavy or interaction-revealed content.
   - Use CrawlerRunConfig(js_code=[...]) to run browser-side JS (e.g., click tabs, expand accordions, scroll to trigger lazy loads).
   - If needed, use BrowserConfig(headless=..., user_data_dir=..., use_persistent_context=True) to persist sessions.
   (Read [references/crawl4ai-install-and-dynamic-crawling.md](references/crawl4ai-install-and-dynamic-crawling.md) when the page requires dynamic JS, js_code usage, or persistent browser profiles.)
7) Produce structured JSON without LLM first.
   - For repeated structured items: use JsonCssExtractionStrategy or JsonXPathExtractionStrategy with a schema defining baseSelector and fields.
   - Place extraction_strategy in CrawlerRunConfig(extraction_strategy=...).
   - After arun: if result.success, parse result.extracted_content as JSON.
8) Use regex only for simple patterns.
   - Use RegexExtractionStrategy with built-in patterns (Email/Url/Phone/Date/Currency/Number/Uuid, etc.).
   - Parse result.extracted_content as JSON when result.success.
9) Use LLMExtractionStrategy only when needed.
   - Use LLMExtractionStrategy(...), and place it in CrawlerRunConfig(extraction_strategy=...); do not pass it directly to arun().
   - Choose extraction_type:
     * “schema” when you need strict JSON conforming to a schema.
     * “block” when you want freeform text/smaller structures.
   - Choose input_format among markdown/fit_markdown/html.
   - Configure llm_config (provider/model) from your externally prepared auth environment.
   (Read [references/crawl4ai-extraction-strategy-playbook.md](references/crawl4ai-extraction-strategy-playbook.md) when deciding between CSS/XPath vs regex vs LLM, and when enforcing placement + reliability checks.)

Constraints / edge cases
- Never trust output until result.success is True. If false, return the error (result.error_message) and suggest retry with js_code or a different strategy.
- Strategy ordering rule: Markdown first for reference/RAG; schema JSON next (CSS/XPath); regex for simple fields; LLM last.
- If extraction fails on a JS page, add js_code to reveal the content before extracting.
- For repeated structured content, prefer CSS/XPath over LLM for speed/cost.

Concrete I/O expectations
- Markdown path: return a string from result.markdown (or fit_markdown if configured/used).
- JSON path: return parsed JSON from json.loads(result.extracted_content) after result.success.

Example output mapping
- User asks: “Convert this docs URL into clean markdown for my RAG reference.” → Use MarkdownGenerator; return result.markdown/fit_markdown.
- User asks: “Extract product listings (name, price, url) into JSON.” → Use JsonCssExtractionStrategy or JsonXPathExtractionStrategy; return JSON.
- User asks: “Find all emails/phones/urls on this page.” → Use RegexExtractionStrategy; return matches.
- User asks: “Summarize each section into a structured knowledge graph.” → Use LLMExtractionStrategy (schema if strict), with fit_markdown input.