Skip to content

API Reference

This page is generated directly from the source docstrings, so it always matches the installed version. The guide pages explain when and why to use each object; this page is the authoritative what.

Public imports

User-facing code should prefer from onecrawler import .... Internal runtime helpers should be imported from their concrete modules only when you are extending OneCrawler itself.

from onecrawler import (
    Crawler,
    LinkExtractor,
    Scraper,
    Settings,
    SiteMap,
)

Engines

Crawler

Crawler(settings)

Bases: BaseEngine

Crawls a site and returns all content with no filtering.

Supports optional proxy configuration via Settings.proxy for anonymised crawling. Acts as the primary crawler entry-point.

Attributes:

Name Type Description
settings Settings

Configuration for the crawl.

strategy Optional[Any]

The content-extraction strategy in use (HeuristicStrategy or LLMStrategy); set on start().

browser Optional[GoogleChrome]

The shared browser instance; set on start().

Example
from onecrawler import Crawler, Settings

async with Crawler(Settings()) as crawler:
    results = await crawler.run("https://example.com")
    print(results)

Streaming:

async with Crawler(Settings()) as crawler:
    async for result in crawler.stream("https://example.com"):
        print(result)

Initializes the crawler with the given settings.

Parameters:

Name Type Description Default
settings Settings

Configuration for the crawl.

required
Source code in onecrawler/crawler/crawl.py
def __init__(self, settings):
    """Initializes the crawler with the given settings.

    Args:
        settings (Settings): Configuration for the crawl.
    """
    super().__init__()
    self.settings = settings
    self.strategy = None
    self.browser = None
    self.session = None
    self.logger.info("Crawler initialized")

start async

start()

Starts the browser and initializes the configured scraping strategy.

Raises:

Type Description
ValueError

If scraping_strategy is "genai" but settings.genai is not configured, or an unknown strategy is requested.

Source code in onecrawler/crawler/crawl.py
async def start(self):
    """Starts the browser and initializes the configured scraping strategy.

    Raises:
        ValueError: If ``scraping_strategy`` is ``"genai"`` but
            ``settings.genai`` is not configured, or an unknown strategy
            is requested.
    """
    self._closed = False

    scraping_strategy = getattr(
        self.settings, "scraping_strategy", ScrapingStrategy.HEURISTIC
    )
    if not isinstance(scraping_strategy, str):
        scraping_strategy = ScrapingStrategy.HEURISTIC

    if scraping_strategy == ScrapingStrategy.GENAI:
        if not getattr(self.settings, "genai", None):
            raise ValueError("GenAI settings is required for GenAI strategy")
    elif scraping_strategy not in (
        ScrapingStrategy.HEURISTIC,
        ScrapingStrategy.MARKDOWNIFY,
    ):
        raise ValueError(f"Unknown strategy: {scraping_strategy}")

    self.browser = GoogleChrome(
        self.settings.browser_settings,
        proxy_pool=self.settings.create_proxy_pool(),
    )
    await self.browser.start()

    if scraping_strategy == ScrapingStrategy.HEURISTIC:
        strategy = HeuristicStrategy(
            settings=self.settings,
            browser=self.browser,
        )
    elif scraping_strategy == ScrapingStrategy.MARKDOWNIFY:
        strategy = MarkdownifyStrategy(
            settings=self.settings,
            browser=self.browser,
        )
    else:
        strategy = LLMStrategy(
            provider=self.settings.genai.provider,
            model_name=self.settings.genai.model_name,
            max_retries=self.settings.max_retries,
            api_key=self.settings.genai.api_key,
            base_url=self.settings.genai.base_url,
            output_schema=self.settings.genai.output_schema,
            provider_kwargs=self.settings.genai.provider_kwargs,
            timeout=self.settings.genai.timeout,
            think=self.settings.genai.think,
            exclude_selectors=self.settings.exclude_selectors,
            browser=self.browser,
        )
        await strategy.initialize()

    self.strategy = strategy

close async

close()

Closes the scraping strategy and the browser.

Source code in onecrawler/crawler/crawl.py
async def close(self):
    """Closes the scraping strategy and the browser."""
    if self.strategy:
        await self.strategy.close()

    if self.browser:
        await self.browser.close()

run async

run(url: str, filters=None) -> list[dict]

Crawls url and collects all extracted content.

Parameters:

Name Type Description Default
url str

The starting URL; the crawl stays within its origin.

required
filters Optional[Callable[[dict], bool]]

Predicate applied to each extracted content dict post-extraction, pre-collection; items failing it are dropped. See onecrawler.filters.

None

Returns:

Type Description
list[dict]

list[dict]: Extracted content dicts, each including its source

list[dict]

url.

Source code in onecrawler/crawler/crawl.py
async def run(self, url: str, filters=None) -> list[dict]:
    """Crawls ``url`` and collects all extracted content.

    Args:
        url (str): The starting URL; the crawl stays within its origin.
        filters (Optional[Callable[[dict], bool]]): Predicate applied to
            each extracted content dict post-extraction, pre-collection;
            items failing it are dropped. See ``onecrawler.filters``.

    Returns:
        list[dict]: Extracted content dicts, each including its source
        ``url``.
    """
    self._ensure_open()
    runtime, pool = await self._create_runtime(url, content_filter=filters)
    try:
        return await runtime.run()
    finally:
        await pool.close()

stream async

stream(
    url: str, filters=None
) -> AsyncGenerator[dict, None]

Crawls url, yielding extracted content dicts as they arrive.

Parameters:

Name Type Description Default
url str

The starting URL; the crawl stays within its origin.

required
filters Optional[Callable[[dict], bool]]

Predicate applied to each extracted content dict post-extraction, pre-yield; items failing it are dropped. See onecrawler.filters.

None

Yields:

Name Type Description
dict AsyncGenerator[dict, None]

An extracted content dict, including its source url.

Source code in onecrawler/crawler/crawl.py
async def stream(self, url: str, filters=None) -> AsyncGenerator[dict, None]:
    """Crawls ``url``, yielding extracted content dicts as they arrive.

    Args:
        url (str): The starting URL; the crawl stays within its origin.
        filters (Optional[Callable[[dict], bool]]): Predicate applied to
            each extracted content dict post-extraction, pre-yield; items
            failing it are dropped. See ``onecrawler.filters``.

    Yields:
        dict: An extracted content dict, including its source ``url``.
    """
    self._ensure_open()
    runtime, pool = await self._create_runtime(
        url, streaming=True, content_filter=filters
    )
    try:
        async for item in runtime.stream():
            yield item
    finally:
        await pool.close()

LinkExtractor

LinkExtractor(settings: Settings)

Bases: BaseEngine

Engine for extracting links from websites using various strategies.

Supports both 'shallow' (single page) and 'deep' (BFS-based) extraction.

Attributes:

Name Type Description
settings Settings

Configuration settings for extraction.

Example
from onecrawler import Settings, LinkExtractor

settings = Settings(link_extraction_strategy="shallow")

async with LinkExtractor(settings) as engine:
    links = await engine.run("https://example.com")
    print(links)

Streaming:

async with LinkExtractor(settings) as engine:
    async for link in engine.stream("https://example.com"):
        print(link)
Source code in onecrawler/crawler/engine.py
def __init__(self, settings: Settings):
    super().__init__()

    self.settings = settings

    self.session = None

    self.logger.info("LinkExtractor initialized")

start async

start()

Starts the engine and initializes the browser.

Source code in onecrawler/crawler/engine.py
async def start(self):
    """Starts the engine and initializes the browser."""
    self._closed = False
    self.browser = GoogleChrome(
        self.settings.browser_settings,
        proxy_pool=self.settings.create_proxy_pool(),
    )
    await self.browser.start()

close async

close()

Closes the engine and releases browser resources.

Source code in onecrawler/crawler/engine.py
async def close(self):
    """Closes the engine and releases browser resources."""
    if hasattr(self, "browser") and self.browser:
        await self.browser.close()

run async

run(url: str) -> List[str]

Runs the link extraction for the given URL.

Parameters:

Name Type Description Default
url str

The starting URL.

required

Returns:

Type Description
List[str]

List[str]: A list of absolute URLs discovered.

Raises:

Type Description
ValueError

If an unknown strategy is configured.

Source code in onecrawler/crawler/engine.py
async def run(self, url: str) -> List[str]:
    """Runs the link extraction for the given URL.

    Args:
        url (str): The starting URL.

    Returns:
        List[str]: A list of absolute URLs discovered.

    Raises:
        ValueError: If an unknown strategy is configured.
    """
    self._ensure_open()

    strategy = self.settings.link_extraction_strategy

    self.logger.info(
        "Running link extraction on %s with strategy: %s",
        url,
        strategy,
    )

    if strategy == LinkExtractionStrategy.SHALLOW:
        return await extract_url_from_current_page(
            url=url,
            browser=self.browser,
            include_link_patterns=self.settings.include_link_patterns,
            exclude_link_patterns=self.settings.exclude_link_patterns,
            max_links=self.settings.link_extraction_limit,
        )

    if strategy != LinkExtractionStrategy.DEEP:
        raise ValueError(f"Unknown strategy: {strategy}")

    results = []
    async for link in self.stream(url):
        results.append(link)

    return results

stream async

stream(url: str) -> AsyncGenerator[str, None]

Streams discovered links incrementally (deep strategy only).

Parameters:

Name Type Description Default
url str

The starting URL.

required

Yields:

Name Type Description
str AsyncGenerator[str, None]

Discovered absolute URL.

Raises:

Type Description
AssertionError

If strategy is 'shallow'.

ValueError

If an unknown strategy is configured.

Source code in onecrawler/crawler/engine.py
async def stream(self, url: str) -> AsyncGenerator[str, None]:
    """Streams discovered links incrementally (deep strategy only).

    Args:
        url (str): The starting URL.

    Yields:
        str: Discovered absolute URL.

    Raises:
        AssertionError: If strategy is 'shallow'.
        ValueError: If an unknown strategy is configured.
    """
    self._ensure_open()

    strategy = self.settings.link_extraction_strategy

    assert (
        strategy != LinkExtractionStrategy.SHALLOW
    ), "Shallow link extraction does not support stream"

    self.logger.info(
        "Running link extraction stream on %s with strategy: %s",
        url,
        strategy,
    )

    if strategy != LinkExtractionStrategy.DEEP:
        raise ValueError(f"Unknown strategy: {strategy}")

    parsed = urlparse(url)
    base_prefix = f"{parsed.scheme}://{parsed.netloc}"

    scheduler = BFScheduler(url)
    spider = LinkSpider(base_prefix)
    pool = BrowserPool(
        self.browser,
        self.settings.concurrency,
    )

    await pool.init()

    runtime = BFSRuntime(
        scheduler=scheduler,
        pool=pool,
        spider=spider,
        base_prefix=base_prefix,
        max_links=self.settings.link_extraction_limit,
        include_pattern=self.settings.include_link_patterns,
        exclude_pattern=self.settings.exclude_link_patterns,
        enable_human_behaviors=self.settings.human_behavior_settings is not None,
        human_behavior_settings=self.settings.human_behavior_settings,
        concurrency=self.settings.concurrency,
        streaming=True,
        wait_until=self.settings.browser_settings.wait_until,
        timeout=self.settings.browser_settings.timeout,
        show_progress=self.settings.show_progress,
    )

    try:
        async for link in runtime.stream():
            yield link

    finally:
        await pool.close()

Scraper

Scraper(settings: Settings)

Bases: BaseEngine

Engine for scraping and extracting data from URLs.

Supports: - Heuristic extraction - GenAI extraction - Concurrent scraping - Streaming results

Attributes:

Name Type Description
settings Settings

Configuration settings for scraping.

strategy Optional[Any]

The content-extraction strategy in use (HeuristicStrategy or LLMStrategy); set on start().

browser Optional[GoogleChrome]

The shared browser instance; set on start() if settings.browser_settings is configured.

Example
from onecrawler import Settings, Scraper

async with Scraper(settings) as engine:
    result = await engine.run("https://example.com")
    print(result)

Streaming:

async with Scraper(settings) as engine:
    async for result in engine.stream(urls):
        print(result)
Source code in onecrawler/crawler/engine.py
def __init__(self, settings: Settings):
    super().__init__()

    self.settings = settings

    self.browser = None
    self.strategy = None

    self.semaphore = asyncio.Semaphore(settings.concurrency)

    self.retries = settings.max_retries
    self.timeout = settings.request_timeout

    self.logger.info("Scraper initialized")

start async

start()

Starts the scraper engine.

Source code in onecrawler/crawler/engine.py
async def start(self):
    """Starts the scraper engine."""
    self._closed = False

    if self.settings.browser_settings:
        self.browser = GoogleChrome(
            settings=self.settings.browser_settings,
            proxy_pool=self.settings.create_proxy_pool(),
        )
        await self.browser.start()

    if self.settings.scraping_strategy == ScrapingStrategy.HEURISTIC:
        self.strategy = HeuristicStrategy(
            settings=self.settings,
            browser=self.browser,
        )

    elif self.settings.scraping_strategy == ScrapingStrategy.MARKDOWNIFY:
        self.strategy = MarkdownifyStrategy(
            settings=self.settings,
            browser=self.browser,
        )

    elif self.settings.scraping_strategy == ScrapingStrategy.GENAI:
        if not self.settings.genai:
            raise ValueError("GenAI settings are required for GenAI strategy")

        self.strategy = LLMStrategy(
            provider=self.settings.genai.provider,
            model_name=self.settings.genai.model_name,
            max_retries=self.settings.max_retries,
            api_key=self.settings.genai.api_key,
            base_url=self.settings.genai.base_url,
            output_schema=self.settings.genai.output_schema,
            provider_kwargs=self.settings.genai.provider_kwargs,
            timeout=self.settings.genai.timeout,
            think=self.settings.genai.think,
            exclude_selectors=self.settings.exclude_selectors,
            browser=self.browser,
        )

        await self.strategy.initialize()

    else:
        raise ValueError(
            f"Unknown scraping strategy: {self.settings.scraping_strategy}"
        )

    self.logger.info(
        f"Scraper started using {self.settings.scraping_strategy} strategy"
    )

close async

close()

Closes the scraper engine.

Source code in onecrawler/crawler/engine.py
async def close(self):
    """Closes the scraper engine."""
    if self.strategy:
        await self.strategy.close()

    if self.browser:
        await self.browser.close()

    self.logger.info("Scraper closed")

stream async

stream(
    link: Union[str, List[str]],
) -> AsyncGenerator[Any, None]

Streams extracted results as they complete, in completion order.

Parameters:

Name Type Description Default
link Union[str, List[str]]

One URL or a list of URLs to scrape.

required

Yields:

Name Type Description
dict AsyncGenerator[Any, None]

{"url": str, "result": Any}, where result's shape

AsyncGenerator[Any, None]

depends on settings.scraping_strategy/scraping_output_format

AsyncGenerator[Any, None]

(a dict, plain text, or a GenAI output_schema model instance).

AsyncGenerator[Any, None]

URLs that fail extraction after retries are silently skipped.

Source code in onecrawler/crawler/engine.py
async def stream(self, link: Union[str, List[str]]) -> AsyncGenerator[Any, None]:
    """Streams extracted results as they complete, in completion order.

    Args:
        link (Union[str, List[str]]): One URL or a list of URLs to scrape.

    Yields:
        dict: ``{"url": str, "result": Any}``, where ``result``'s shape
        depends on ``settings.scraping_strategy``/``scraping_output_format``
        (a dict, plain text, or a GenAI ``output_schema`` model instance).
        URLs that fail extraction after retries are silently skipped.
    """
    self._ensure_open()
    links = link if isinstance(link, list) else [link]

    self.logger.info(
        f"Streaming scraper on {len(links)} link(s) "
        f"using {self.settings.scraping_strategy}"
    )

    tasks = [asyncio.create_task(self._process(url)) for url in links]
    show_progress = getattr(
        self.settings,
        "show_progress",
        True,
    )

    with make_progress_bar(
        total=len(tasks),
        desc="Scraping",
        unit="url",
        show_progress=show_progress,
    ) as pbar:
        for task in asyncio.as_completed(tasks):
            try:
                result = await task

                if result is not None:
                    yield result

            except asyncio.CancelledError:
                raise

            except Exception as e:
                self.logger.warning(
                    f"Streaming task failed: [{type(e).__name__}] {e}"
                )

            finally:
                pbar.update(1)

    self.logger.info("Streaming scrape completed")

run async

run(
    link: Union[str, List[str]],
) -> Union[dict, List[dict], None]

Runs the scraper and collects all results.

Parameters:

Name Type Description Default
link Union[str, List[str]]

One URL or a list of URLs to scrape.

required

Returns:

Type Description
Union[dict, List[dict], None]

Union[dict, List[dict], None]: A list of ``{"url": str, "result":

Union[dict, List[dict], None]

Any}dicts whenlink`` is a list (possibly shorter than the

Union[dict, List[dict], None]

input if some URLs failed extraction); a single such dict (or

Union[dict, List[dict], None]

None) when link is a single URL.

Source code in onecrawler/crawler/engine.py
async def run(self, link: Union[str, List[str]]) -> Union[dict, List[dict], None]:
    """Runs the scraper and collects all results.

    Args:
        link (Union[str, List[str]]): One URL or a list of URLs to scrape.

    Returns:
        Union[dict, List[dict], None]: A list of ``{"url": str, "result":
        Any}`` dicts when ``link`` is a list (possibly shorter than the
        input if some URLs failed extraction); a single such dict (or
        ``None``) when ``link`` is a single URL.
    """
    results = []
    async for result in self.stream(link):
        results.append(result)

    return results if isinstance(link, list) else (results[0] if results else None)

Sitemap discovery

SiteMap

SiteMap(settings: Settings)

The high-level orchestrator for site discovery.

Combines robots.txt parsing, common path probing, XML sitemap traversal, and HTML crawling to find all relevant URLs on a site.

Attributes:

Name Type Description
settings Settings

Configuration settings.

Initializes SiteMap.

Source code in onecrawler/crawler/map/sitemap.py
def __init__(self, settings: Settings):
    """Initializes SiteMap."""
    self.settings = settings

run async

run(url: str) -> List[str]

Runs the universal sitemap discovery process.

Parameters:

Name Type Description Default
url str

The starting URL.

required

Returns:

Type Description
List[str]

List[str]: A list of discovered absolute internal URLs.

Source code in onecrawler/crawler/map/sitemap.py
async def run(self, url: str) -> List[str]:
    """Runs the universal sitemap discovery process.

    Args:
        url (str): The starting URL.

    Returns:
        List[str]: A list of discovered absolute internal URLs.
    """
    base_url = self._normalize_base(url)

    strategies_used: list[str] = []
    all_records: list[URLRecord] = []

    pbar = make_progress_bar(
        total=self.settings.link_extraction_limit,
        desc="Sitemap Discovery",
        unit="url",
        show_progress=self.settings.show_progress,
    )

    def _bump(n: int = 1) -> None:
        remaining = pbar.total - pbar.n
        if remaining > 0:
            pbar.update(min(n, remaining))

    try:
        async with HTTPClient(
            self.settings.concurrency,
            self.settings.request_timeout,
            self.settings.sitemap.user_agent,
            self.settings.max_retries,
            self.settings.retry_delay,
            self.settings.create_proxy_pool(),
        ) as client:
            robots = RobotsParser(client)
            sitemap_parser = SitemapParser(
                client,
                self.settings.concurrency,
                follow_index=self.settings.sitemap.follow_index,
            )

            sitemap_urls = await robots.fetch_sitemaps(base_url)
            if sitemap_urls:
                strategies_used.append("robots.txt")

            probe_tasks = [
                self._probe_url(client, urljoin(base_url, path))
                for path in COMMON_SITEMAP_PATHS
            ]
            probe_results = await asyncio.gather(*probe_tasks)
            found_common = [
                urljoin(base_url, path)
                for path, ok in zip(COMMON_SITEMAP_PATHS, probe_results)
                if ok
            ]
            new_common = [u for u in found_common if u not in sitemap_urls]
            if new_common:
                strategies_used.append("common_paths")
                sitemap_urls.extend(new_common)

            if sitemap_urls:
                strategies_used.append("sitemap_xml")

                # Only let parse_all() stop early when no downstream filter
                # can unpredictably drop a large fraction of records — those
                # filters run after this point and could otherwise leave us
                # under the requested limit despite more URLs being available.
                # robots.txt disallow rules are excluded from this check: they
                # typically remove a small, roughly uniform slice of a site's
                # URL space (unlike a narrow date range or include pattern,
                # which can trivially drop the vast majority of records), so
                # the overfetch margin below is expected to absorb them too.
                filters_may_shrink_results = (
                    self.settings.sitemap.start_date is not None
                    or self.settings.sitemap.end_date is not None
                    or bool(self.settings.include_link_patterns)
                )
                target_count = (
                    None
                    if filters_may_shrink_results
                    else self.settings.link_extraction_limit
                    * _SITEMAP_LIMIT_OVERFETCH_FACTOR
                )

                records, _ = await sitemap_parser.parse_all(
                    sitemap_urls, target_count=target_count, on_records=_bump
                )
                all_records.extend(records)

            if not all_records and self.settings.sitemap.html_fallback:
                strategies_used.append("html_crawl")
                crawler = HTMLCrawler(
                    client,
                    self.settings.concurrency,
                    self.settings.sitemap.max_pages,
                    self.settings.sitemap.max_depth,
                    robots=robots if self.settings.sitemap.respect_robots else None,
                )
                crawl_records = await crawler.crawl(base_url, on_record=_bump)
                all_records.extend(crawl_records)

            # Respect robots.txt for sitemap-sourced URLs too (the HTML crawl
            # fallback above already gates each fetch as it happens).
            if self.settings.sitemap.respect_robots and all_records:
                allowed_flags = await asyncio.gather(
                    *(robots.is_allowed(rec.url, base_url) for rec in all_records)
                )
                all_records = [
                    rec for rec, ok in zip(all_records, allowed_flags) if ok
                ]
    finally:
        pbar.close()

    logging.info(f"Strategies used: {strategies_used}")

    start_date = self.settings.sitemap.start_date
    end_date = self.settings.sitemap.end_date
    strict_date_filter = self.settings.sitemap.strict_date_filter
    if start_date is not None or end_date is not None:
        filtered: list[URLRecord] = []
        for rec in all_records:
            lm = self._parse_lastmod(rec.lastmod)
            if lm is None:
                if not strict_date_filter:
                    filtered.append(rec)
                continue
            if start_date is not None and lm < start_date:
                continue
            if end_date is not None and lm > end_date:
                continue
            filtered.append(rec)
        all_records = filtered

    all_records = [r for r in all_records if not is_xml_url(r.url)]

    if self.settings.include_link_patterns:
        all_records = [
            r
            for r in all_records
            if wildcard_link_match(
                r.url, base_url, self.settings.include_link_patterns
            )
        ]

    if self.settings.sitemap.deduplicate:
        seen: set[str] = set()
        deduped: list[URLRecord] = []
        for rec in all_records:
            norm = normalize_url(rec.url)
            if norm not in seen:
                seen.add(norm)
                deduped.append(rec)
        all_records = deduped

    if len(all_records) > self.settings.link_extraction_limit:
        all_records = all_records[: self.settings.link_extraction_limit]

    return [rec.url for rec in all_records]

Settings

Settings dataclass

Settings(
    sitemap: SitemapSettings = SitemapSettings(),
    link_extraction_strategy: Literal[
        "shallow", "deep"
    ] = "deep",
    link_extraction_limit: int = 50,
    include_link_patterns: Optional[List[str]] = None,
    exclude_link_patterns: Optional[List[str]] = None,
    scraping_strategy: Literal[
        "heuristic", "genai", "markdownify"
    ] = "heuristic",
    scraping_output_format: Literal[
        "markdown", "json", "xml", "xmltei"
    ] = "json",
    exclude_selectors: Optional[List[str]] = None,
    genai: Optional[LLMSettings] = None,
    concurrency: int = 10,
    max_retries: int = 2,
    request_timeout: int = 10,
    retry_delay: int = 1,
    proxies: Optional[List[ProxySettings]] = None,
    proxy_rotation_method: Literal[
        "round_robin", "random"
    ] = "round_robin",
    browser_settings: BrowserSettings = BrowserSettings(),
    show_progress: bool = True,
    logging_level: Optional[
        Literal["DEBUG", "INFO", "WARNING", "ERROR"]
    ] = None,
    human_behavior_settings: Optional[
        HumanBehaviorSettings
    ] = None,
)

Central configuration class for OneCrawler.

This class coordinates settings for sitemap crawling, link extraction, content scraping, browser automation, and proxy management.

Attributes:

Name Type Description
sitemap SitemapSettings

Configuration for sitemap discovery.

link_extraction_strategy Literal['shallow', 'deep']

Strategy for finding links on pages.

link_extraction_limit int

Maximum number of valid links to extract per run.

include_link_patterns Optional[List[str]]

Wildcard patterns for links to include.

exclude_link_patterns Optional[List[str]]

Wildcard patterns for links to exclude.

scraping_strategy Literal['heuristic', 'genai', 'markdownify']

Strategy for content extraction.

scraping_output_format str

The desired format for scraped data.

exclude_selectors Optional[List[str]]

CSS selectors (e.g. ["nav", "footer", ".cookie-banner"]) to strip before HTML-to- Markdown conversion. Applies to the markdownify and genai strategies, which both convert whole-page HTML. None (the default) converts the page as-is.

genai Optional[LLMSettings]

Configuration for AI-powered scraping.

concurrency int

Number of concurrent workers/pages.

max_retries int

Number of retries for failed requests/actions.

request_timeout int

Timeout for requests in seconds.

retry_delay int

Delay between retries in seconds.

proxies Optional[List[ProxySettings]]

Proxies to use. A single proxy is just a one-element list; multiple proxies rotate per proxy_rotation_method.

proxy_rotation_method Literal['round_robin', 'random']

Strategy for proxy rotation.

browser_settings BrowserSettings

Configuration for the browser instance.

show_progress bool

Whether to display tqdm progress bars.

logging_level Optional[str]

Log level (e.g., "INFO"). None (the default) leaves logging unconfigured.

human_behavior_settings Optional[HumanBehaviorSettings]

Human-browsing simulation config. None (the default) disables simulation; pass a HumanBehaviorSettings to enable it.

create_proxy_pool

create_proxy_pool() -> ProxyPool

Creates a ProxyPool based on the provided proxy settings.

Returns:

Name Type Description
ProxyPool ProxyPool

An initialized proxy pool.

Source code in onecrawler/settings/crawler.py
def create_proxy_pool(self) -> ProxyPool:
    """Creates a ProxyPool based on the provided proxy settings.

    Returns:
        ProxyPool: An initialized proxy pool.
    """
    if self.proxies:
        proxies = self.proxies
    elif self.browser_settings.proxy:
        proxies = [self.browser_settings.proxy]
    else:
        proxies = []

    return ProxyPool(proxies=proxies, strategy=self.proxy_rotation_method)

BrowserSettings dataclass

BrowserSettings(
    proxy: Optional[ProxySettings] = None,
    viewport: Optional[Dict[str, int]] = (
        lambda: {"width": 1366, "height": 768}
    )(),
    screen: Optional[Dict[str, int]] = None,
    no_viewport: Optional[bool] = None,
    user_agent: Optional[
        str
    ] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
    locale: str = "en-US",
    timezone_id: str = "Asia/Dhaka",
    geolocation: Optional[Dict[str, float]] = None,
    permissions: Optional[List[str]] = None,
    extra_http_headers: Optional[Dict[str, str]] = None,
    java_script_enabled: bool = True,
    bypass_csp: bool = True,
    ignore_https_errors: bool = True,
    offline: bool = False,
    storage_state: Optional[str] = None,
    base_url: Optional[str] = None,
    headless: bool = True,
    slow_mo: int = 0,
    args: List[str] = (
        lambda: [
            "--no-sandbox",
            "--disable-blink-features=AutomationControlled",
            "--disable-dev-shm-usage",
        ]
    )(),
    executable_path: Optional[str] = None,
    channel: Optional[str] = None,
    env: Optional[Dict[str, str]] = None,
    wait_until: str = "domcontentloaded",
    timeout: int = 30000,
    settle_delay: int = 1500,
)

Settings for a browser instance.

Attributes:

Name Type Description
proxy Optional[ProxySettings]

Proxy configuration.

viewport Optional[Dict[str, int]]

The default viewport size for each page.

screen Optional[Dict[str, int]]

The screen size.

no_viewport Optional[bool]

Whether to disable the default viewport.

user_agent Optional[str]

The user agent string to use.

locale str

The locale (e.g., "en-US").

timezone_id str

The timezone ID (e.g., "Asia/Dhaka").

geolocation Optional[Dict[str, float]]

The geolocation coordinates.

permissions Optional[List[str]]

List of permissions to grant (e.g., ["geolocation"]).

extra_http_headers Optional[Dict[str, str]]

Additional HTTP headers to send with every request.

java_script_enabled bool

Whether to enable JavaScript.

bypass_csp bool

Whether to bypass Content Security Policy.

ignore_https_errors bool

Whether to ignore HTTPS errors.

offline bool

Whether to simulate being offline.

storage_state Optional[str]

Path to a file with the storage state (cookies, localStorage).

base_url Optional[str]

Base URL for navigation.

headless bool

Whether to run the browser in headless mode.

slow_mo int

Slows down Playwright operations by the specified amount of milliseconds.

args List[str]

Additional arguments to pass to the browser instance.

executable_path Optional[str]

Path to a browser executable to use instead of the bundled one.

channel Optional[str]

Browser distribution channel (e.g., "chrome", "msedge").

env Optional[Dict[str, str]]

Environment variables that will be visible to the browser.

wait_until str

When to consider navigation finished (e.g., "domcontentloaded").

timeout int

Timeout for browser operations in milliseconds.

settle_delay int

Extra time in milliseconds to wait after navigation before capturing the page, letting client-side/JS-rendered content (e.g. prices or listings on SPA sites) hydrate. Defaults to 1500ms so dynamic sites work out of the box; set 0 to disable it for faster crawls on static sites.

LLMSettings dataclass

LLMSettings(
    provider: Literal["google", "openai", "ollama"],
    model_name: str,
    api_key: Optional[str] = None,
    output_schema: Optional[Type[BaseModel]] = None,
    base_url: Optional[str] = None,
    provider_kwargs: Optional[dict[str, Any]] = None,
    timeout: Optional[float] = None,
    think: bool = False,
)

Configuration for Generative AI providers and models.

Attributes:

Name Type Description
provider Literal['google', 'openai', 'ollama']

The AI service provider. Use "openai" with a custom base_url to target any OpenAI-compatible server (llama.cpp, vLLM, LM Studio, LocalAI, ...); api_key is optional for those keyless endpoints.

model_name str

The name of the specific model to use (e.g., "gemini-1.5-flash").

api_key Optional[str]

API key for the provider. Required for the real OpenAI/Gemini endpoints; optional when base_url points at a keyless OpenAI-compatible server.

output_schema Optional[Type[BaseModel]]

Pydantic model for structured output.

base_url Optional[str]

Base URL for Ollama, an OpenAI-compatible server, or any custom endpoint.

provider_kwargs Optional[dict[str, Any]]

Provider-specific keyword arguments.

timeout Optional[float]

Per-request timeout in seconds for the provider's API call. None uses the provider client's own default.

think bool

Ollama only. Whether to let a thinking model (qwen3, deepseek-r1, ...) emit its reasoning trace. Defaults to False because thinking breaks structured output on Ollama (it returns an empty response) and makes free-form calls very slow. Ignored by the OpenAI and Gemini providers.

HumanBehaviorSettings dataclass

HumanBehaviorSettings(
    min_delay: float = 0.3,
    max_delay: float = 1.2,
    max_scrolls: int = 50,
    scroll_pause: float = 1.0,
    min_mouse_moves: int = 5,
    max_mouse_moves: int = 15,
    mouse_width: int = 1366,
    mouse_height: int = 768,
    min_mouse_sleep: float = 0.1,
    max_mouse_sleep: float = 0.3,
    min_mouse_steps: int = 5,
    max_mouse_steps: int = 20,
)

Configuration for simulating human interaction with a web page.

Attributes:

Name Type Description
min_delay float

Minimum delay in seconds between major actions.

max_delay float

Maximum delay in seconds between major actions.

max_scrolls int

Maximum number of scroll steps per page.

scroll_pause float

Pause in seconds between scroll steps.

min_mouse_moves int

Minimum number of mouse movements per action.

max_mouse_moves int

Maximum number of mouse movements per action.

mouse_width int

Width of the area for mouse movement simulation.

mouse_height int

Height of the area for mouse movement simulation.

min_mouse_sleep float

Minimum sleep time between mouse steps.

max_mouse_sleep float

Maximum sleep time between mouse steps.

min_mouse_steps int

Minimum steps per mouse movement.

max_mouse_steps int

Maximum steps per mouse movement.

ProxySettings dataclass

ProxySettings(
    server: str,
    username: Optional[str] = None,
    password: Optional[str] = None,
)

Configuration for a single proxy server.

Attributes:

Name Type Description
server str

The proxy server URL (e.g., "http://myproxy.com:8080").

username Optional[str]

Username for proxy authentication.

password Optional[str]

Password for proxy authentication.

as_playwright

as_playwright() -> Dict[str, str]

Converts proxy settings to a dictionary format expected by Playwright.

Returns:

Type Description
Dict[str, str]

Dict[str, str]: A dictionary with 'server', 'username', and 'password'.

Source code in onecrawler/settings/proxy.py
def as_playwright(self) -> Dict[str, str]:
    """Converts proxy settings to a dictionary format expected by Playwright.

    Returns:
        Dict[str, str]: A dictionary with 'server', 'username', and 'password'.
    """
    proxy = {"server": self.server}
    if self.username:
        proxy["username"] = self.username
    if self.password:
        proxy["password"] = self.password
    return proxy

as_requests_proxies

as_requests_proxies() -> Dict[str, str]

Converts proxy settings to a dictionary format expected by the requests library.

Returns:

Type Description
Dict[str, str]

Dict[str, str]: A dictionary with 'http' and 'https' keys mapping to the proxy URL.

Source code in onecrawler/settings/proxy.py
def as_requests_proxies(self) -> Dict[str, str]:
    """Converts proxy settings to a dictionary format expected by the requests
    library.

    Returns:
        Dict[str, str]: A dictionary with 'http' and 'https' keys mapping to the proxy URL.
    """
    server = self._server_with_auth()
    return {"http": server, "https": server}

Filters

Composable predicates over extracted content dicts. Build a filter, then pass it to Crawler.run() / Crawler.stream() as the filters argument.

by_date

by_date(
    start: Optional[str] = None,
    end: Optional[str] = None,
    *,
    fields: Sequence[str] = CONTENT_DATE_FIELDS
) -> Callable[[dict], bool]

Filter items by YYYY-MM-DD date range.

Reads the first present field in fields (default: publication date, then filedate). Note filedate is the extraction/download date, so date is checked first to filter by publication rather than crawl time. Items with no parseable date are excluded.

Parameters:

Name Type Description Default
start Optional[str]

Inclusive lower bound, as YYYY-MM-DD. No lower bound if omitted.

None
end Optional[str]

Inclusive upper bound, as YYYY-MM-DD. No upper bound if omitted.

None
fields Sequence[str]

Content-dict keys to check, in priority order.

CONTENT_DATE_FIELDS

Returns:

Type Description
Callable[[dict], bool]

Callable[[dict], bool]: A predicate accepting items whose resolved

Callable[[dict], bool]

date falls within [start, end].

Source code in onecrawler/filters/date.py
def by_date(
    start: Optional[str] = None,
    end: Optional[str] = None,
    *,
    fields: Sequence[str] = CONTENT_DATE_FIELDS,
) -> Callable[[dict], bool]:
    """Filter items by YYYY-MM-DD date range.

    Reads the first present field in `fields` (default: publication ``date``,
    then ``filedate``). Note ``filedate`` is the extraction/download date, so
    ``date`` is checked first to filter by publication rather than crawl time.
    Items with no parseable date are excluded.

    Args:
        start (Optional[str]): Inclusive lower bound, as ``YYYY-MM-DD``.
            No lower bound if omitted.
        end (Optional[str]): Inclusive upper bound, as ``YYYY-MM-DD``.
            No upper bound if omitted.
        fields (Sequence[str]): Content-dict keys to check, in priority order.

    Returns:
        Callable[[dict], bool]: A predicate accepting items whose resolved
        date falls within ``[start, end]``.
    """
    start_dt = datetime.datetime.strptime(start, "%Y-%m-%d") if start else None
    end_dt = datetime.datetime.strptime(end, "%Y-%m-%d") if end else None

    def _filter(item: dict) -> bool:
        date_str = resolve_field(item, fields, filter_name="by_date")
        if not date_str:
            return False

        try:
            dt = datetime.datetime.strptime(date_str, "%Y-%m-%d")
        except (ValueError, TypeError):
            return False

        if start_dt and dt < start_dt:
            return False
        if end_dt and dt > end_dt:
            return False

        return True

    return _filter

by_keywords

by_keywords(
    keywords: Iterable[str], *, field: str = "text"
) -> Callable[[dict], bool]

Keep items whose content contains ANY keyword (case-insensitive).

Reads the item's field (default text); items missing that field are dropped.

Parameters:

Name Type Description Default
keywords Iterable[str]

Keywords to match; matching is case-insensitive.

required
field str

Content-dict key to search.

'text'

Returns:

Type Description
Callable[[dict], bool]

Callable[[dict], bool]: A predicate accepting items containing at

Callable[[dict], bool]

least one keyword.

Source code in onecrawler/filters/keywords.py
def by_keywords(
    keywords: Iterable[str],
    *,
    field: str = "text",
) -> Callable[[dict], bool]:
    """Keep items whose content contains ANY keyword (case-insensitive).

    Reads the item's ``field`` (default ``text``); items missing that field
    are dropped.

    Args:
        keywords (Iterable[str]): Keywords to match; matching is case-insensitive.
        field (str): Content-dict key to search.

    Returns:
        Callable[[dict], bool]: A predicate accepting items containing at
        least one keyword.
    """
    keywords = [k.lower() for k in keywords]

    def _filter(item: dict) -> bool:
        text = resolve_field(item, (field,), filter_name="by_keywords")
        if not text:
            return False
        text = text.lower()
        return any(k in text for k in keywords)

    return _filter

by_files

by_files(
    types: Iterable[str], *, field: str = CONTENT_URL_FIELD
) -> Callable[[dict], bool]

Filter by logical file types: pdf, docx, image, etc.

Parameters:

Name Type Description Default
types Iterable[str]

Logical type names from FILE_TYPE_MAP ("pdf", "docx", "image", "text"), or a raw extension (with or without a leading dot) for anything else.

required
field str

Content-dict key holding the URL to check.

CONTENT_URL_FIELD

Returns:

Type Description
Callable[[dict], bool]

Callable[[dict], bool]: A predicate accepting items whose URL matches

Callable[[dict], bool]

one of the resolved extensions.

Source code in onecrawler/filters/files.py
def by_files(
    types: Iterable[str],
    *,
    field: str = CONTENT_URL_FIELD,
) -> Callable[[dict], bool]:
    """Filter by logical file types: pdf, docx, image, etc.

    Args:
        types (Iterable[str]): Logical type names from ``FILE_TYPE_MAP``
            (``"pdf"``, ``"docx"``, ``"image"``, ``"text"``), or a raw
            extension (with or without a leading dot) for anything else.
        field (str): Content-dict key holding the URL to check.

    Returns:
        Callable[[dict], bool]: A predicate accepting items whose URL matches
        one of the resolved extensions.
    """
    allowed_exts = set()

    for t in types:
        t = t.lower()
        if t in FILE_TYPE_MAP:
            allowed_exts.update(FILE_TYPE_MAP[t])
        else:
            allowed_exts.add(f".{t}")

    return by_extension(allowed_exts, field=field)

by_extension

by_extension(
    extensions: Iterable[str],
    *,
    field: str = CONTENT_URL_FIELD
) -> Callable[[dict], bool]

Filter by the file extension of the item's URL (field).

Parameters:

Name Type Description Default
extensions Iterable[str]

Extensions to allow, with or without a leading dot (e.g. "pdf" or ".pdf"); matching is case-insensitive.

required
field str

Content-dict key holding the URL to check.

CONTENT_URL_FIELD

Returns:

Type Description
Callable[[dict], bool]

Callable[[dict], bool]: A predicate accepting items whose URL ends

Callable[[dict], bool]

with one of extensions.

Source code in onecrawler/filters/files.py
def by_extension(
    extensions: Iterable[str],
    *,
    field: str = CONTENT_URL_FIELD,
) -> Callable[[dict], bool]:
    """Filter by the file extension of the item's URL (``field``).

    Args:
        extensions (Iterable[str]): Extensions to allow, with or without a
            leading dot (e.g. ``"pdf"`` or ``".pdf"``); matching is case-insensitive.
        field (str): Content-dict key holding the URL to check.

    Returns:
        Callable[[dict], bool]: A predicate accepting items whose URL ends
        with one of ``extensions``.
    """
    allowed = {
        ext.lower() if ext.startswith(".") else f".{ext.lower()}" for ext in extensions
    }

    def _filter(item: dict) -> bool:
        url = resolve_field(item, (field,), filter_name="by_extension")
        if not url:
            return False
        url = url.lower()
        return any(url.endswith(ext) for ext in allowed)

    return _filter

by_cosine_similarity

by_cosine_similarity(
    query: str,
    threshold: float = 0.25,
    *,
    fields: Sequence[str] = CONTENT_TEXT_FIELDS
) -> Callable[[dict], bool]

Compare query text vs document text using cosine similarity.

Scores against the first present field in fields (default: the body text, then content, then title) so the full document is compared rather than a short title when both exist.

Parameters:

Name Type Description Default
query str

Text to compare each item's content against.

required
threshold float

Minimum cosine similarity score (0-1) required to pass.

0.25
fields Sequence[str]

Content-dict keys to check, in priority order.

CONTENT_TEXT_FIELDS

Returns:

Type Description
Callable[[dict], bool]

Callable[[dict], bool]: A predicate accepting items scoring at least

Callable[[dict], bool]

threshold against query.

Source code in onecrawler/filters/similarity.py
def by_cosine_similarity(
    query: str,
    threshold: float = 0.25,
    *,
    fields: Sequence[str] = CONTENT_TEXT_FIELDS,
) -> Callable[[dict], bool]:
    """Compare query text vs document text using cosine similarity.

    Scores against the first present field in `fields` (default: the body
    ``text``, then ``content``, then ``title``) so the full document is
    compared rather than a short title when both exist.

    Args:
        query (str): Text to compare each item's content against.
        threshold (float): Minimum cosine similarity score (0-1) required to
            pass.
        fields (Sequence[str]): Content-dict keys to check, in priority order.

    Returns:
        Callable[[dict], bool]: A predicate accepting items scoring at least
        ``threshold`` against ``query``.
    """
    query_vec = _vectorize(query)

    def _filter(item: dict) -> bool:
        doc_text = resolve_field(item, fields, filter_name="by_cosine_similarity")
        if not doc_text:
            return False

        doc_vec = _vectorize(doc_text)
        score = _cosine_sim(query_vec, doc_vec)

        return score >= threshold

    return _filter

Composition

Combine filters with boolean logic.

AND

AND(*filters)

Combine filters so an item passes only if ALL of them accept it.

Parameters:

Name Type Description Default
*filters Callable[[dict], bool]

Filter predicates to combine.

()

Returns:

Name Type Description
FilterChain

A predicate accepting an item only when every filter does.

Source code in onecrawler/filters/chain.py
def AND(*filters):
    """Combine filters so an item passes only if ALL of them accept it.

    Args:
        *filters (Callable[[dict], bool]): Filter predicates to combine.

    Returns:
        FilterChain: A predicate accepting an item only when every filter does.
    """
    return FilterChain(*filters, mode="AND")

OR

OR(*filters)

Combine filters so an item passes if ANY of them accepts it.

Parameters:

Name Type Description Default
*filters Callable[[dict], bool]

Filter predicates to combine.

()

Returns:

Name Type Description
FilterChain

A predicate accepting an item when at least one filter does.

Source code in onecrawler/filters/chain.py
def OR(*filters):
    """Combine filters so an item passes if ANY of them accepts it.

    Args:
        *filters (Callable[[dict], bool]): Filter predicates to combine.

    Returns:
        FilterChain: A predicate accepting an item when at least one filter does.
    """
    return FilterChain(*filters, mode="OR")

NOT

NOT(f)

Negate a filter predicate.

Parameters:

Name Type Description Default
f Callable[[dict], bool]

The filter predicate to negate.

required

Returns:

Type Description

Callable[[dict], bool]: A predicate accepting an item only when f

rejects it.

Warning

Field-dependent filters (e.g. by_keywords, by_date) return False both when an item fails to match and when the item is missing the field entirely (fail-closed). NOT can't tell those apart, so NOT(by_keywords(...)) will include items that have no text field at all, not just items that failed to match. Add an explicit field-presence check first if that distinction matters.

Source code in onecrawler/filters/chain.py
def NOT(f):
    """Negate a filter predicate.

    Args:
        f (Callable[[dict], bool]): The filter predicate to negate.

    Returns:
        Callable[[dict], bool]: A predicate accepting an item only when ``f``
        rejects it.

    Warning:
        Field-dependent filters (e.g. ``by_keywords``, ``by_date``) return
        ``False`` both when an item fails to match *and* when the item is
        missing the field entirely (fail-closed). ``NOT`` can't tell those
        apart, so ``NOT(by_keywords(...))`` will *include* items that have no
        ``text`` field at all, not just items that failed to match. Add an
        explicit field-presence check first if that distinction matters.
    """

    def _neg(item):
        return not f(item)

    return _neg

Output writers

Helpers for persisting results, exported from onecrawler.utils.writter.

dump_json

dump_json(
    data: Any,
    filename: str | Path,
    indent: int = 2,
    ensure_ascii: bool = False,
    encoding: str = "utf-8",
) -> None

Write data to filename as a single JSON document.

Parameters:

Name Type Description Default
data Any

The object to serialize. Dataclasses/Pydantic models, Path, date/datetime, and set/frozenset values nested inside it are converted automatically.

required
filename str | Path

Destination file path; overwritten if it exists.

required
indent int

Number of spaces used to pretty-print the output.

2
ensure_ascii bool

If True, escape all non-ASCII characters.

False
encoding str

Text encoding used to open filename.

'utf-8'
Source code in onecrawler/utils/writter.py
def dump_json(
    data: Any,
    filename: str | Path,
    indent: int = 2,
    ensure_ascii: bool = False,
    encoding: str = "utf-8",
) -> None:
    """Write ``data`` to ``filename`` as a single JSON document.

    Args:
        data (Any): The object to serialize. Dataclasses/Pydantic models,
            ``Path``, ``date``/``datetime``, and ``set``/``frozenset`` values
            nested inside it are converted automatically.
        filename (str | Path): Destination file path; overwritten if it exists.
        indent (int): Number of spaces used to pretty-print the output.
        ensure_ascii (bool): If True, escape all non-ASCII characters.
        encoding (str): Text encoding used to open ``filename``.
    """
    path = Path(filename)

    with path.open("w", encoding=encoding) as f:
        json.dump(
            data,
            f,
            indent=indent,
            ensure_ascii=ensure_ascii,
            default=_serialize,
        )

dump_jsonl

dump_jsonl(
    data: Any,
    filename: str | Path,
    ensure_ascii: bool = False,
    encoding: str = "utf-8",
) -> None

Write data to filename as JSON Lines (one JSON object per line).

Parameters:

Name Type Description Default
data Any

A list of objects to serialize, one per line. A non-list value is wrapped in a single-item list.

required
filename str | Path

Destination file path; overwritten if it exists.

required
ensure_ascii bool

If True, escape all non-ASCII characters.

False
encoding str

Text encoding used to open filename.

'utf-8'
Source code in onecrawler/utils/writter.py
def dump_jsonl(
    data: Any,
    filename: str | Path,
    ensure_ascii: bool = False,
    encoding: str = "utf-8",
) -> None:
    """Write ``data`` to ``filename`` as JSON Lines (one JSON object per line).

    Args:
        data (Any): A list of objects to serialize, one per line. A
            non-list value is wrapped in a single-item list.
        filename (str | Path): Destination file path; overwritten if it exists.
        ensure_ascii (bool): If True, escape all non-ASCII characters.
        encoding (str): Text encoding used to open ``filename``.
    """
    path = Path(filename)

    if not isinstance(data, list):
        data = [data]

    with path.open("w", encoding=encoding) as f:
        for item in data:
            f.write(
                json.dumps(
                    item,
                    ensure_ascii=ensure_ascii,
                    default=_serialize,
                )
                + "\n"
            )

dump_txt

dump_txt(
    text: str, filename: str | Path, encoding: str = "utf-8"
) -> None

Write text to filename as plain text.

Parameters:

Name Type Description Default
text str

The text content to write.

required
filename str | Path

Destination file path; overwritten if it exists.

required
encoding str

Text encoding used to open filename.

'utf-8'
Source code in onecrawler/utils/writter.py
def dump_txt(
    text: str,
    filename: str | Path,
    encoding: str = "utf-8",
) -> None:
    """Write ``text`` to ``filename`` as plain text.

    Args:
        text (str): The text content to write.
        filename (str | Path): Destination file path; overwritten if it exists.
        encoding (str): Text encoding used to open ``filename``.
    """
    Path(filename).write_text(text, encoding=encoding)

dump_csv

dump_csv(
    rows: list[dict],
    filename: str | Path,
    encoding: str = "utf-8",
) -> None

Write rows to filename as CSV.

The column order follows each key's first appearance across rows, so rows with different key sets still produce one consistent header; missing values in a given row are written as empty cells. Non-dict rows (e.g. dataclasses/Pydantic models) are serialized the same way as dump_json.

Parameters:

Name Type Description Default
rows list[dict]

The rows to write. A no-op if empty.

required
filename str | Path

Destination file path; overwritten if it exists.

required
encoding str

Text encoding used to open filename.

'utf-8'
Source code in onecrawler/utils/writter.py
def dump_csv(
    rows: list[dict],
    filename: str | Path,
    encoding: str = "utf-8",
) -> None:
    """Write ``rows`` to ``filename`` as CSV.

    The column order follows each key's first appearance across ``rows``, so
    rows with different key sets still produce one consistent header; missing
    values in a given row are written as empty cells. Non-dict rows (e.g.
    dataclasses/Pydantic models) are serialized the same way as `dump_json`.

    Args:
        rows (list[dict]): The rows to write. A no-op if empty.
        filename (str | Path): Destination file path; overwritten if it exists.
        encoding (str): Text encoding used to open ``filename``.
    """
    if not rows:
        return

    rows = [row if isinstance(row, dict) else _serialize(row) for row in rows]

    fieldnames: list = []
    seen = set()
    for row in rows:
        for key in row.keys():
            if key not in seen:
                seen.add(key)
                fieldnames.append(key)

    path = Path(filename)

    with path.open("w", newline="", encoding=encoding) as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames, restval="")
        writer.writeheader()
        writer.writerows(rows)