Scraping Engine Package¶
The scraping package provides content extraction engines for scraping web pages: deterministic heuristic extraction, whole-page Markdown conversion, and AI-powered structured extraction.
Start with heuristic extraction
Use the heuristic strategy for bulk article, blog, documentation, or catalog text extraction. Add GenAI only when you need typed fields, summaries, normalization, or semantic interpretation.
Classes¶
Scraper¶
Main scraping engine that supports heuristic, markdownify, and GenAI content extraction strategies.
from onecrawler import Settings, Scraper
async with Scraper(settings) as scraper:
item = await scraper.run("https://example.com/article")
print(item["url"], item["result"])
Parameters¶
settings(Settings): Configuration for scraping behavior
Methods¶
run(url: str) -> dict: Extract content from the given URL, returning{"url": str, "result": Any}—result's shape depends onscraping_strategy/scraping_output_format(a dict, plain text, or a GenAIoutput_schemamodel instance)run(urls: List[str]) -> List[dict]: Extract content from multiple URLs, returning one{"url": str, "result": Any}dict per successful extraction
Strategies¶
- Heuristic: Fast, rule-based extraction using trafilatura; isolates article-like content and strips boilerplate, but can return little or nothing on non-article pages (e-commerce, dashboards, listings)
- Markdownify: Faithful whole-page HTML-to-Markdown conversion with no content extraction; never returns
Nonefor a rendered page, so it's the fallback for pages heuristic extraction can't handle - GenAI: AI-powered extraction with structured output
Single URL vs list behavior
Scraper.run() returns one {"url": ..., "result": ...} dict for a single URL and a list of them for multiple URLs. Failed extractions are omitted from list results. The url key lets you trace a result back to its source even though stream() completes in asyncio.as_completed() order rather than input order.
Heuristic strategy¶
Rule-based content extraction using the trafilatura library. Select it with Settings(scraping_strategy="heuristic") — see Heuristic Scraping below.
No direct class import
HeuristicStrategy is an internal implementation detail with no public import path or stability guarantee. Always go through Scraper.
Features¶
- Fast extraction: No model calls, deterministic results
- Multiple formats: Markdown, JSON, XML, and XML-TEI output
- Language detection: Automatic language identification
- Content cleaning: Removes boilerplate and navigation
Markdownify strategy¶
Whole-page HTML-to-Markdown conversion using html-to-markdown, with no content extraction. Select it with Settings(scraping_strategy="markdownify").
No direct class import
MarkdownifyStrategy is an internal implementation detail with no public import path or stability guarantee. Always go through Scraper.
Features¶
- Never empty: Converts the whole rendered page, so it works on pages heuristic extraction returns little or nothing for
- Faithful conversion: Keeps navigation, footers, and other page chrome — no boilerplate removal and no extracted metadata
- Deterministic filtering: Set
settings.exclude_selectors(e.g.["nav", "footer", ".cookie-banner"]) to strip known chrome before conversion, at no LLM cost
GenAI strategy¶
AI-powered content extraction using language models. Select it with Settings(scraping_strategy="genai", genai=LLMSettings(...)) — see GenAI Scraping below.
No direct class import
LLMStrategy is an internal implementation detail with no public import path or stability guarantee. Always go through Scraper.
Features¶
- Structured output: Pydantic schema-based extraction
- Semantic understanding: Context-aware content extraction
- Field normalization: Consistent data formatting
- Custom schemas: Define your own output structure
Usage Examples¶
Heuristic Scraping¶
from onecrawler import Settings, Scraper
async def scrape_heuristic():
settings = Settings(
scraping_strategy="heuristic",
scraping_output_format="json",
concurrency=10,
request_timeout=15
)
async with Scraper(settings) as scraper:
item = await scraper.run("https://example.com/article")
return item["result"]
if __name__ == "__main__":
import asyncio
asyncio.run(scrape_heuristic())
GenAI Scraping¶
from pydantic import BaseModel
from onecrawler import Settings, LLMSettings, Scraper
class Article(BaseModel):
title: str
author: str
content: str
published_date: str
async def scrape_with_ai():
settings = Settings(
scraping_strategy="genai",
scraping_output_format="json",
genai=LLMSettings(
provider="openai",
model_name="gpt-4o-mini",
api_key="your-api-key",
output_schema=Article
),
concurrency=2,
request_timeout=30
)
async with Scraper(settings) as scraper:
item = await scraper.run("https://example.com/article")
return item["result"] # an Article instance
if __name__ == "__main__":
import asyncio
asyncio.run(scrape_with_ai())
GenAI has operational cost
Model extraction is slower and can hit provider rate limits. Keep concurrency low, increase request_timeout, and monitor cost per page.
Batch Scraping¶
async def scrape_multiple():
urls = [
"https://example.com/article1",
"https://example.com/article2",
"https://example.com/article3"
]
settings = Settings(
scraping_strategy="heuristic",
concurrency=5,
max_retries=3
)
async with Scraper(settings) as scraper:
results = await scraper.run(urls) # [{"url": ..., "result": ...}, ...]
return results
Persist failed URLs
For large batches, save failed or empty URLs separately. Retrying only failures is faster than repeating discovery and scraping the whole batch. Since each result carries its own url, you can find failures by diffing the input list against [r["url"] for r in results].
Configuration¶
Scraping behavior is controlled through Settings:
| Setting | Description | Default |
|---|---|---|
scraping_strategy | "heuristic", "genai", or "markdownify" | "heuristic" |
scraping_output_format | Output format (ignored by "markdownify") | "json" |
exclude_selectors | CSS selectors to strip before conversion ("markdownify"/"genai") | None |
concurrency | Number of parallel workers | 10 |
request_timeout | Per-request timeout | 10 |
max_retries | Retry attempts | 2 |
genai | GenAI configuration | None |
Output Formats¶
Heuristic Strategy¶
- JSON: Structured data with metadata
- Markdown: Clean text formatting
- XML: Original TEI-agnostic XML structure
- XML-TEI: TEI-conformant XML output
Markdownify Strategy¶
- Markdown only:
scraping_output_formatis ignored; always returns a plain Markdown string of the whole page, with no extracted metadata
GenAI Strategy¶
- JSON: Structured output matching Pydantic schema
- Custom formats: Based on your schema definition
GenAI output is JSON-only
Configure scraping_output_format="json" when using scraping_strategy="genai". Other formats are rejected during settings validation.
Performance Considerations¶
Heuristic Scraping¶
- Fast: No model calls, deterministic timing
- Lightweight: Lower memory and CPU usage
- Scalable: Higher concurrency possible
- Consistent: Predictable performance
GenAI Scraping¶
- Slower: Model response time (10-30 seconds)
- Expensive: API costs per request
- Limited concurrency: Lower parallelism
- Variable: Response time depends on model
Split discovery and scraping
Discover URLs once, store them, then scrape in controlled batches. This makes retries, rate-limit recovery, and cost tracking much easier.
Best Practices¶
When to Use Heuristic¶
- Bulk content extraction: Large numbers of pages
- Fast processing: Time-sensitive operations
- Cost efficiency: Budget-conscious projects
- Simple content: Articles, blog posts, documentation
When to Use GenAI¶
- Structured data: Specific fields required
- Complex content: Mixed or unstructured pages
- Normalization: Consistent data formatting
- Semantic extraction: Understanding context
General Tips¶
- Start with heuristic: Faster and cheaper
- Filter URLs: Only scrape relevant pages
- Set timeouts: Handle slow pages gracefully
- Monitor errors: Track failure rates
- Batch processing: Group similar requests
Error Handling¶
The scraper handles various error conditions:
- Network errors: Automatic retries with backoff
- Parsing errors: Graceful degradation
- Timeout errors: Configurable timeouts
- Rate limiting: Respects server limits
- Model errors: Fallback strategies for GenAI
Integration Examples¶
Save to File¶
from onecrawler import Settings, Scraper
from onecrawler.utils import writter
async def scrape_and_save():
settings = Settings(scraping_strategy="heuristic")
async with Scraper(settings) as scraper:
item = await scraper.run("https://example.com/article")
writter.dump_json(item, "output.json") # includes both "url" and "result"
Database Integration¶
from onecrawler import Settings, Scraper
async def scrape_to_database():
settings = Settings(scraping_strategy="heuristic")
async with Scraper(settings) as scraper:
urls = ["https://example.com/page1", "https://example.com/page2"]
results = await scraper.run(urls)
# Save to database
for item in results:
await save_to_database(item["url"], item["result"])
Troubleshooting¶
Common Issues¶
- Empty results: Check URL accessibility and content structure
- Timeout errors: Increase
request_timeoutor reduce concurrency - GenAI failures: Verify API keys and model availability
- Memory issues: Reduce concurrency for large batches
- Rate limits: Implement delays between requests
Debug Mode¶
Enable detailed logging for troubleshooting:
Empty content is not always an error
Some pages are navigation, search, login, or media-only pages with little extractable text. Use URL filters to keep these out of scraping batches.