Skip to main content

A structured, resumable web crawling framework for AI-ready datasets.

Project description

Ladon

CI Lint Python 3.11+ License: AGPL-3.0-only

A Python framework for building structured, resumable web crawlers — designed for domains where data quality matters.

What is Ladon?

Ladon enforces typed domain objects at every stage of the crawl pipeline through the SES protocol (Source / Expander / Sink). The difference from Scrapy — a proven, mature tool — is structural: instead of weakly typed scrapy.Item fields, you define typed dataclasses at the protocol level (e.g. a CommentRecord with enforced field types). The output is structured and typed without a post-processing step. This matters when the destination is an LLM training pipeline or any domain where schema correctness is not optional.

The built-in HTTP layer handles retries, exponential back-off with optional full-jitter, 429/503 Retry-After respect, per-domain rate limiting, circuit breaking, static and rotating proxy support, and robots.txt enforcement — so adapter authors focus on domain logic, not infrastructure.

Quick start

The canonical example is ladon-hackernews — an adapter that crawls the HN top-stories list and writes comments to DuckDB:

pip install ladon-crawl ladon-hackernews
ladon-hackernews --top 30 --out hn.db

No authentication. No external server. 30 stories and their comments in under a minute.

The LLM training pipeline

ladon-hackernews --top 500 --out hn.db
    → export_parquet("hn.db", "hn.parquet")
        → training pipeline

HN comments are structured, human-authored, and high signal-to-noise. The full pipeline from install to Parquet takes under five minutes. Each run writes a ladon_runs audit table to the DuckDB file — re-running skips stories already marked done, giving you resumable crawls for free.

from ladon_hackernews import export_parquet
export_parquet("hn.db", "hn.parquet")

Writing your own adapter

ladon-hackernews is the canonical reference for building an adapter. Adapters implement the SES protocol structurally — no inheritance from any Ladon base class is required. The three components to implement are:

  • Source — discovers the list of root references to crawl
  • Expander — maps a reference to a domain record and child references
  • Sink — receives each leaf record for persistence or downstream use

See the adapter authoring guide and ADR-003 for the full protocol specification. The ladon-hackernews source is the worked example.

CLI reference

ladon info
ladon run --plugin MODULE:CLASS --ref URL [--respect-robots-txt]
ladon --version
command description
ladon info Print Ladon version, Python version, and platform
ladon run Run a crawl using a dynamically loaded plugin class
ladon --version Print the installed version

ladon run flags:

flag required description
--plugin MODULE:CLASS yes Dotted import path to the CrawlPlugin class
--ref URL yes Top-level reference URL passed to the plugin
--respect-robots-txt no Honour Disallow rules and Crawl-delay directives

Exit codes: 0 success · 1 fatal error · 2 partial failures · 3 data not ready (retry later)

ladon run uses default HttpClientConfig settings. For retries, rate limiting, circuit breaking, or a persistence layer, call run_crawl() directly from Python — see ladon-hackernews — Use as a library for a full example.

Cloudflare-protected targets

Standard HTTP clients fail against Cloudflare because their TLS fingerprint is identifiable as non-browser traffic. Ladon's optional curl-cffi backend impersonates a real browser's TLS ClientHello to bypass L1 and L2 challenges without a browser process.

pip install ladon-crawl[cffi]   # adds curl-cffi (binary wheel, ~10 MB)
from ladon.networking import make_http_client
from ladon.networking.config import HttpClientConfig

config = HttpClientConfig(
    backend="curl-cffi",
    impersonate="chrome136",
    timeout_seconds=20.0,
    retries=2,
)
with make_http_client(config) as client:
    result = client.get("https://protected-target.example/")

make_http_client() and make_async_http_client() dispatch on config.backend — change one field to switch backends with no call-site changes. All policies (retries, circuit breaker, proxy rotation, rate limiting) are identical to the standard backends.

See the Cloudflare bypass guide for the full layer-by-layer breakdown, impersonate target list, and L3 (IP reputation) considerations.

Async crawling

async_run_crawl() is the asyncio-native counterpart to run_crawl(). Phase 1 (expander traversal) runs sequentially; Phase 3 issues leaf fetches concurrently behind asyncio.Semaphore(config.async_concurrency) (default 10):

import asyncio
from ladon import AsyncHttpClient, async_run_crawl
from ladon.networking.config import HttpClientConfig
from ladon.runner import RunConfig

async def main() -> None:
    config = HttpClientConfig(retries=2, timeout_seconds=10)
    async with AsyncHttpClient(config) as client:
        result = await async_run_crawl(
            top_ref=my_ref,
            plugin=my_async_plugin,
            client=client,
            config=RunConfig(async_concurrency=20),
            on_leaf=my_async_persist,
        )
        print(f"consumed {result.leaves_consumed}, failed {result.leaves_failed}")

asyncio.run(main())

AsyncHttpClient mirrors all policies of HttpClient (retries, backoff, Retry-After, circuit breaker, proxy rotation, auth) using httpx as the backend. Adapters implement AsyncCrawlPlugin, AsyncSource, AsyncExpander, and AsyncSink — the same structural-protocol pattern as the sync stack.

Status

v0.2.0 — async crawling milestone. AsyncHttpClient, AsyncCrawlPlugin, and async_run_crawl() are stable and fully tested. The sync API is unchanged.

What was added in v0.3.0:

  • Cloudflare bypassCurlHttpClient / AsyncCurlHttpClient via curl-cffi, HttpClientConfig(backend="curl-cffi", impersonate="chrome136"), make_http_client() / make_async_http_client() factories (issue #107)

What was added in v0.2.0:

  • Async crawlingasync_run_crawl() + AsyncHttpClient (httpx backend)
  • Async plugin protocolsAsyncSource, AsyncExpander, AsyncSink, AsyncCrawlPlugin
  • RunConfig.async_concurrency — bounded leaf-fetch concurrency (default 10)

What was added in v0.1.0:

  • HTTP 429/503 Retry-After respect and full-jitter backoff
  • Static and rotating proxy support (ProxyPool, RoundRobinProxyPool)
  • HTTP authentication — Basic, Digest, any requests.auth.AuthBase
  • Default query parameters via HttpClientConfig(default_params=...)

What was added in v0.0.1:

  • SES protocol (Source / Expander / Sink) with structural typing
  • run_crawl() runner with leaf isolation and RunResult summary
  • HttpClient with retries, back-off, rate limiting, circuit breaker, robots.txt
  • Storage protocol with LocalFileStorage
  • Repository and RunAudit persistence protocols with NullRepository
  • ladon run / ladon info CLI

What is coming:

  • ladon-mimir — async Wikipedia adapter for LLM fine-tuning (issue #96)
  • Async robots.txt enforcement in AsyncHttpClient
  • Structured logging baseline (ADR-009)

Contributing

The plugin protocol is settled — contributions are welcome. Please read the documentation for design context (ADRs, plugin authoring guide) before sending a pull request.

A CLA signature is required for external contributors. The bot will prompt you on your first PR.

License

Ladon is released under the GNU Affero General Public License v3.0 only (AGPL-3.0-only). See LICENSE for the full text.

AGPL was chosen to ensure that improvements to the core framework — including when deployed as a networked service — remain open and available to the community. A commercial licence is available for organisations that cannot accept the AGPL terms — see LICENSE-COMMERCIAL.

ladon-hackernews is separately licensed under Apache-2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ladon_crawl-0.3.2.tar.gz (109.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ladon_crawl-0.3.2-py3-none-any.whl (90.5 kB view details)

Uploaded Python 3

File details

Details for the file ladon_crawl-0.3.2.tar.gz.

File metadata

  • Download URL: ladon_crawl-0.3.2.tar.gz
  • Upload date:
  • Size: 109.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for ladon_crawl-0.3.2.tar.gz
Algorithm Hash digest
SHA256 7c788372a9da5938cecf3ae94b61f8182167ac7e573be30e91f879204c31a540
MD5 6939f14be9f22ea3657b0735247628cd
BLAKE2b-256 693645b2ddc9a43beb4efd116255509175142f6e5dabe1238f45669272a6b7b3

See more details on using hashes here.

File details

Details for the file ladon_crawl-0.3.2-py3-none-any.whl.

File metadata

  • Download URL: ladon_crawl-0.3.2-py3-none-any.whl
  • Upload date:
  • Size: 90.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for ladon_crawl-0.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 b6b86b658b22279b72b4f47470452b6e93585c1951664f5f34218a0e8db208e7
MD5 fe3d1ce7a9e30aa542ce8f978e6bfa17
BLAKE2b-256 de9301128d452ef76649b252b1c2af88454a7e39868eeb6ffd1b7d7d7d911e5b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page