WebGraph

Python SDK

The engine as a library - install it, read a page or a site in ten lines, and find every function, parameter and default in the pages that follow.

The engine is a plain Python package with no dependency on the API or the web UI. Everything the HTTP API does, it does by calling the functions on these pages; the API adds streaming over HTTP, per-request option validation and host-level caps, nothing else. If you are writing Python, call the library.

The package name is provisional

The code imports as webgraph and this documentation is written that way. webgraph on PyPI belongs to another project, so the distribution name (what you pip install) will change before the first release, and the import name may change with it. Until then the package installs from the repository; the snippets need only the import line changed.

Install

pip install "webgraph[render] @ git+https://github.com/BeastxD7/webgraph#subdirectory=packages/engine"
playwright install chromium

Python 3.12 or newer. The render extra installs Playwright; playwright install chromium downloads the browser it drives. Without either, everything still works from the plain HTTP fetch alone and says so (reading_order_method is dom-fallback, render_error is "rendering not available"); with them, every page is also rendered and its layout measured, which is what makes the reading order a measurement rather than a guess.

Ten lines

from webgraph import resolve_page, select_content, to_markdown

page = resolve_page("https://docs.python.org/3/tutorial/introduction.html")
print(page.strategy, page.document.reading_order_method)   # union geometric-anchored

# The whole page, in reading order. This is the default output: nothing a reader sees
# is left out, and the engine says how it read the page.
print(to_markdown(page.document))

# Opt in to the content alone: navigation, footer and boilerplate taken out.
body = select_content(page.document.blocks, title=page.document.title)
print(to_markdown(page.document.model_copy(update={"blocks": tuple(body.blocks)})))

A whole site streams as it goes:

from webgraph import SiteConfig, stream_site

config = SiteConfig(max_pages=50, within_path=True)
for event in stream_site("https://docs.python.org/3/tutorial/", config=config):
    if event["type"] == "page":
        print(event["url"], len(event["markdown"].split()), "words")
    elif event["type"] == "done":
        print(event["pages_ok"], "pages in", event["duration_seconds"], "s")

Measured as written: the first snippet resolves the tutorial page in about 3 s (both fetches), 164 blocks, and select_content keeps 116 of them; the crawl reads three pages in 11 s at the default politeness of one page a second per host.

What the web UI does, as library calls

Everything on the site is a call into this package. There is no capability the UI has that the library lacks; the UI adds a screen and the API adds HTTP.

In the UIJobLibraryPage
Extract → PageOne page as Markdown, whole and content-onlyresolve_page + to_markdown + select_contentPages, Content selection
Extract → SiteA whole site, streamed page by page, with discovery and refusalsstream_siteSites
Extract → factsFields against a JSON Schema, with provenanceextract_facts, facts_for_pageFacts against a schema
Site graph, AskThe crawl's graph of pages and sections; a bounded context for a questionGraphBuilder, ContextAssembler, GraphStoreSite graph and Ask
Stage 0 panelTechnology, render behaviour, page countanalyze_siteSite analysis and report
Site ReportWhat the site shows people and machines; agent-readiness scorebuild_site_reportSite analysis and report
WatchCrawl again, get what changed, as a list or a feedcreate_watch, run_watch, list_changes, export_changesWatch a site
WebGraph (knowledge graph)Typed entities and relations, optional LLM providers, Neo4jwebgraph.kg (behind WEBGRAPH_KG=1)WebGraph
SettingsThe per-request options and their defaultsFetchConfig, RenderConfig, SiteConfig, MainContentConfigeach reference page

What is in the box

Every name below is importable from webgraph itself, and its signature is kept stable across minor versions. Everything else in the package is importable from its module and documented there, but may move.

JobCallPage
Read one page, both ways, mergedresolve_page(url, ...) → ResolvedPagePages
Read HTML you already haveresolve_supplied(html, url)Pages
Parse HTML into blocks yourselfbuild_document(html, url, ...) → DocumentPages
Render a document as Markdownto_markdown(document, options=)Markdown and other formats
Keep the content, drop the chromeselect_content(blocks, ...) → ContentSelectionContent selection
Classify a page and pick its policydefault_router().route(document), policy_for(page_type)Content selection
Read the page's own metadataread_metadata(html, url, structured_data=) → PageMetadataTypes
Crawl a site, streamingstream_site(root, config=SiteConfig(...))Sites
Tune a fetch or a renderFetchConfig, RenderConfigPages
Tune the content boundaryMainContentConfigContent selection
Handle a refusalPageMissingError, PageDisallowedError, PageBlockedError, PageShellErrorPages
Facts against a schemaextract_facts, merge_facts, facts_for_page → Fact, PageFactsFacts against a schema
Build, query, keep and export the site graphGraphBuilder, SiteGraph, derive_entities, ContextAssembler, Budget, GraphStore, to_jsonl / write_jsonl / load_jsonl / to_cypherSite graph and Ask
Measure a site, report on itanalyze_site → SiteAnalysis; build_site_report → SiteReportSite analysis and report
Watch a sitecreate_watch, get_watch, list_watches, run_watch, stream_watch, list_changes, export_changes → Watch, RunSummaryWatch a site
The dataDocument, Block, BlockKind, Rect, ReadingOrderMethod, Strategy, PageType, RoutingTypes

webgraph.__version__ is the installed version.

Output formats

The engine produces one data structure, a Document of ordered blocks, and every format is a view of it:

FormatHowNotes
Markdown, whole pageto_markdown(document)The default. Headings, lists, tables, code, images and links preserved, nothing a reader sees left out; what the fidelity benchmarks score
Markdown, content onlyselect_content(...) then to_markdown on the copyOpt-in: navigation, footer and boilerplate removed; what the main-content benchmarks score
Plain textdocument.textBlocks joined by blank lines, no markup at all
JSONdocument.model_dump(mode="json")Every block with its kind, position, source XPath and provenance; exclude={"html"} drops the raw page
Blocksdocument.blocksThe tuple of Block objects, for anything else

How the reading is done

There is one mode today, and it is deterministic: rules and two small gradient-boosted models shipped as JSON (a page-type router and an optional per-block classifier), no language model anywhere in the path. The same page read twice gives the same output, which is what makes the benchmarks reproducible. An LLM-assisted or hybrid mode is a possible future addition and would be a separate, opt-in setting; nothing on these pages depends on one.