Onecrawler
An async Python crawling framework for discovering URLs, extracting links, and scraping structured content.
Overview
Onecrawler helps you build maintainable crawling and extraction workflows without turning every project into a custom scraping script. It provides a shared configuration model, async execution, sitemap discovery, browser-backed link extraction, heuristic content extraction, and optional GenAI extraction for typed outputs.
Recommended workflow:
- Use sitemaps first whenever possible.
- Fall back to browser link extraction when sitemap coverage is missing or dynamic.
- Scrape the final URL list with heuristic extraction by default.
- Use GenAI extraction when you need structured output in a Pydantic schema.
async with LinkExtractor(settings) as link_engine:
links = await link_engine.run("https://example.com")
async with Scraper(settings) as scraper_engine:
records = await scraper_engine.run(links) # [{"url": ..., "result": ...}, ...]
Features
| Capability | Details |
|---|---|
| Sitemap discovery | Resolves robots.txt, common sitemap paths, nested indexes, .xml.gz, feeds, and HTML fallback |
| Browser link extraction | Shallow and deep Playwright-backed discovery for JavaScript-rendered or sitemap-poor sites |
| URL filtering | Wildcard path filters with include_link_patterns |
| Content filtering | Composable post-extraction filters by date, keywords, file type, and cosine similarity with AND/OR/NOT logic |
| Async performance | Tunable concurrency, retries, timeouts, and crawl limits |
| Content extraction | Heuristic extraction with trafilatura for fast article-like content |
| GenAI extraction | Optional model-assisted extraction for strongly typed Pydantic outputs |
| Output formats | markdown, json, xml, xmltei |
| Proxy support | Single proxy or rotating proxy pools for browser and sitemap workflows |
| Browser controls | Viewport, user agent, locale, timezone, storage state, and runtime settings |
When To Use What
| Need | Use | Why |
|---|---|---|
| Fast URL discovery from a public site | UniversalSiteMap |
Simplest, fastest, and least expensive way to collect URLs |
| Links from one listing page | Shallow LinkExtractor |
Reads direct same-site links from the page |
| Recursive discovery through navigation | Deep LinkExtractor |
Follows internal links until your configured limit |
| Bulk article or page text extraction | Heuristic Scraper |
Deterministic and avoids model cost |
| Typed fields or semantic normalization | GenAI extraction | Produces schema-shaped output for downstream systems |
Installation
pip install onecrawler
Install Playwright browser binaries when you use browser-backed crawling or scraping:
python -m playwright install chromium
Install optional GenAI dependencies when you use model-assisted extraction:
pip install "onecrawler[genai]"
For local development:
git clone https://github.com/sayedshaun/onecrawler.git
cd onecrawler
python -m pip install -e ".[dev]"
python -m playwright install chromium
Docker Support
OneCrawler provides an optimized Docker image that includes all necessary browser dependencies. This is the recommended way to run the framework in production or CI/CD environments.
Build the Image
docker pull ghcr.io/sayedshaun/onecrawler:latest
You can rename it for convenience
docker tag ghcr.io/sayedshaun/onecrawler:latest onecrawler
Run a Script with Docker
docker run -it --rm -v $(pwd):/app onecrawler python your_script.py
Quick Start
from onecrawler import Settings, Crawler
from onecrawler.utils import writter
async def main():
settings = Settings(link_extraction_limit=100, concurrency=5)
async with Crawler(settings) as engine:
results = await engine.run("https://www.prothomalo.com/")
writter.dump_json(results, "output.json")
if __name__ == "__main__":
import asyncio
asyncio.run(main())
Separate Workflow
from onecrawler import Settings, LinkExtractor, Scraper
from onecrawler.utils import writter
async def main():
settings = Settings(
link_extraction_strategy="deep",
link_extraction_limit=10,
concurrency=7,
scraping_strategy="heuristic",
scraping_output_format="json",
enable_human_behaviors=True,
)
async with LinkExtractor(settings) as link_engine:
links = await link_engine.run("https://www.example.com/")
async with Scraper(settings) as scraper_engine:
results = await scraper_engine.run(links) # [{"url": ..., "result": ...}, ...]
writter.dump_json(results, "output.json")
if __name__ == "__main__":
import asyncio
asyncio.run(main())
Browser Link Extraction
Use browser extraction when sitemaps are incomplete, unavailable, or unable to expose JavaScript-rendered links.
import asyncio
from onecrawler import Settings, LinkExtractor
async def main():
settings = Settings(
link_extraction_strategy="deep",
link_extraction_limit=250,
include_link_patterns=["/news/*"],
concurrency=5,
)
async with LinkExtractor(settings) as engine:
links = await engine.run("https://example.com/news")
print(f"Collected {len(links)} links")
if __name__ == "__main__":
asyncio.run(main())
Content Filtering
Filter crawled results by date, keywords, file type, or semantic similarity. Filters are passed to Crawler.run() or Crawler.stream() and applied after content extraction.
import asyncio
from onecrawler import Crawler, Settings
from onecrawler.filters import by_date, by_keywords
from onecrawler.filters.chain import AND
async def main():
settings = Settings(
link_extraction_limit=50,
concurrency=5,
)
# Keep only pages from 2025 that mention "python" or "async"
content_filter = AND(
by_date(start="2025-01-01", end="2025-12-31"),
by_keywords(["python", "async"]),
)
async with Crawler(settings) as engine:
results = await engine.run(
"https://example.com/blog",
filters=content_filter,
)
print(f"Matched {len(results)} pages")
if __name__ == "__main__":
asyncio.run(main())
Available Filters
| Filter | Import | Purpose |
|---|---|---|
by_date(start, end) |
onecrawler.filters |
Keep items within a YYYY-MM-DD date range |
by_keywords(keywords) |
onecrawler.filters |
Keep items whose text contains any keyword |
by_files(types) |
onecrawler.filters |
Keep items by logical file type (pdf, image, docx, text) |
by_extension(extensions) |
onecrawler.filters |
Keep items by URL file extension (.pdf, .jpg) |
by_cosine_similarity(query, threshold) |
onecrawler.filters |
Keep items whose text is semantically similar to a query |
Composing Filters
Use AND, OR, and NOT from onecrawler.filters.chain to combine filters:
from onecrawler.filters import by_date, by_keywords, by_files
from onecrawler.filters.chain import AND, OR, NOT
# Pages from 2025 that mention "python" but are not PDFs
f = AND(
by_date(start="2025-01-01"),
by_keywords(["python"]),
NOT(by_files(["pdf"])),
)
# Pages that mention "AI" or are from 2025
f = OR(
by_keywords(["AI"]),
by_date(start="2025-01-01", end="2025-12-31"),
)
Streaming With Filters
Filters work with Crawler.stream() for real-time filtered output:
async with Crawler(settings) as engine:
async for item in engine.stream(
"https://example.com/news",
filters=by_cosine_similarity("climate policy", threshold=0.3),
):
print(item["title"])
GenAI Extraction With a Schema
Use GenAI extraction when you need a strongly typed response shape instead of plain content.
pip install "onecrawler[genai]"
import asyncio
from typing import Optional
from pydantic import BaseModel
from onecrawler import Settings, GenerativeAISettings, Scraper
class ArticleSummary(BaseModel):
title: str
author: Optional[str] = None
published_at: Optional[str] = None
summary: str
topics: list[str]
async def main():
settings = Settings(
scraping_strategy="genai",
scraping_output_format="json",
genai=GenerativeAISettings(
provider="openai",
model_name="gpt-4o-mini",
api_key="YOUR_API_KEY",
output_schema=ArticleSummary,
),
concurrency=2,
request_timeout=30,
)
async with Scraper(settings) as scraper:
item = await scraper.run("https://example.com/articles/story")
result = item["result"]
print(result.model_dump() if hasattr(result, "model_dump") else result)
if __name__ == "__main__":
asyncio.run(main())
Supported Providers
| Provider | Requires | Models |
|---|---|---|
| OpenAI | api_key (optional with a custom base_url) |
GPT-4o, GPT-4o-mini, or any OpenAI-compatible server |
api_key |
Gemini models | |
| Ollama | base_url (no key needed) |
Any locally hosted model |
Ollama Example
settings = Settings(
scraping_strategy="genai",
genai=GenerativeAISettings(
provider="ollama",
model_name="llama3:8b",
base_url="http://localhost:11434/",
output_schema=ArticleSummary,
),
)
Proxy Support
Attach one proxy or a rotating proxy pool directly to Settings.
from onecrawler import Settings, ProxySettings
settings = Settings(
proxies=[
ProxySettings(server="http://proxy-1.example:8080"),
ProxySettings(
server="http://proxy-2.example:8080",
username="user",
password="pass",
),
],
proxy_rotation_method="round_robin",
)
Use proxy=ProxySettings(...) for a single proxy, or proxies=[...] with proxy_rotation_method for a pool.
Production Tips
License
Released under the MIT License. See LICENSE for full terms.
Metadata
Release files for onecrawler 0.1.8
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| onecrawler-0.1.8.tar.gz | 65.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| onecrawler-0.1.8-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 132.4 kB
Release files / onecrawler-0.1.8.tar.gz
| Download URL | onecrawler-0.1.8.tar.gz |
|---|---|
| Size | 65.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d1115392e3b91bd37f36f5dd71f8e2945344f83ad3a7d58d0d3272f4f66834d9
|
|
BLAKE2b-256 checksum How to use checksums |
58e5f970d2523328be58932a604bb3cb0a51dba4641e85fc5ec09eec326d7b82
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Release files / onecrawler-0.1.8-py3-none-any.whl
| Download URL | onecrawler-0.1.8-py3-none-any.whl |
|---|---|
| Size | 67.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bd0872ceb8739a579c7210f168f17f2c7de8276e1b4b4fbd2dea415008202387
|
|
BLAKE2b-256 checksum How to use checksums |
8e10f3ba3eedca77c0636e5cf103ecdbb4c3b2098fb2e128f873281cee4de998
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|