WebGraph

Public boards

WCXB (both splits), WCEB, Zyte article extraction, WebMainBench, CleanEval and DAnIEL — the numbers from a from-scratch run, who was run here beside the engine, and what each board cannot see.

Six public corpora, seven boards, every one re-run from a fresh clone on 21 September 2026 with the runners in benchmark/ — the morning at main@fcc2c9f (docs/research/2026-09-21-benchmarks-from-scratch.md: every corpus revision, checksum, command and log), the evening at 928cf84 after the page-type router change that the morning's numbers demanded (2026-09-21-router-canonical-and-multilingual.md). The figures below are the evening's. Two rules, both new this day:

  • Only corpora that ship their own HTML. Every number here can be produced again by anyone with the clone. Firecrawl's scrape-evals, which fetches a thousand live URLs of which ninety are already gone, is out of the boards for that reason (it is listed under "not run" on the benchmarks page with the one figure it produced here).
  • The engine's row is the production path: the page-type router, its per-type policy, the content boundary and the title — what stream_page, the API and the CLI run. Until this run the boards scored the boundary step alone and called it production. Both rows are shown; the headline is the shipped one.

Where a rival could be installed it was run here, with its defaults, through the corpus's own scorer over the same bytes — marked "run here". Other rows are their authors' published figures and say so.

What every board shares

Cached HTML, no network, one page per site. That switches off geometric reading order (every page falls back to DOM order; each runner prints the distribution) and cross-page chrome detection. What is measured is the single-page half of the engine.

What the morning run found, and what the evening changed

Off WCXB the shipped path was behind its own rules boundary — Zyte −0.015, CleanEval −1.0 word-F, WCEB −0.024, DAnIEL −0.218 — and the cause was the page-type router: it typed 987 of DAnIEL's 1,689 news articles as listings. Two things fixed most of it the same day. The router now reads the URL block from the page's declared canonical rather than the address it was fetched under (a DAnIEL page under its host root routed listing 118 times in a 200-page sample, 9 under an article-shaped path). And the router now trains on five languages: DAnIEL's 711 canonical-URL pages joined WCXB dev, grouped folds, class-weighted as before. DAnIEL 0.582 → 0.783; held out one language at a time, the unseen language routes article 81–94% of the time (2% before). Cost: WCXB dev routed out of fold 0.862 → 0.861, test 0.879 → 0.878. A prose-run guard was measured and rejected (WCXB test −0.003 at every threshold).

Web Content Extraction Benchmark (Murrough Foley): 2,008 human-reviewed pages across seven page types, the only public board that labels type. Metric: per-page word-level F1, averaged. Protocol, exactly as the corpus README prescribes: each system writes {file_id: text} and the corpus's own evaluate.py --results scores it (benchmark/wcxb/run.py --out for the engine, benchmark/wcxb/baselines.py for the rest). rs-trafilatura is the benchmark author's extractor, built from its PyPI sdist.

A corpus fact the README does not state: 139 pages are in both dev/html and test/html, byte-identical (metadata.json calls them test). The official scorer counts them in both splits, so these tables do too; the runner prints the disjoint subsets.

Dev split, 1,497 pages — the router trained here, so the engine's row uses its out-of-fold predictions:

systemF1articledocsserviceforumcollectionlistingproduct
webgraph (routed, out of fold)0.8610.9450.9210.8450.7870.6920.7180.634
rs-trafilatura, published0.8590.9320.9320.8440.8080.7160.7070.641
rs-trafilatura 0.1.1, run here0.8470.9340.9380.8250.7600.6720.6690.636
MinerU-HTML, published0.8270.9280.8380.8240.7940.5060.7100.619
trafilatura 2.2.0, run here0.8130.9280.9140.7590.6890.5640.5590.618
resiliparse 1.0.9, run here0.7710.8580.8980.7800.7130.5360.5750.530
readability-lxml 0.9, run here0.6960.8870.8050.5430.4550.3820.2910.432
jusText 3.0.2, run here0.6910.8470.6840.5830.6770.3430.3390.449

Without routing 0.837. First of the systems run here by 0.014; 0.002 over the author's published figure, which the Python build does not reproduce (forcing its page_type= to the annotated type gives 0.847 too). The morning's router scored 0.862 here; the evening's trades 0.001 of it for the five languages.

Held-out test split, 511 pages — the production path with the shipped router:

systemF1articledocsserviceforumcollectionlistingproduct
rs-trafilatura 0.1.1, run here0.8910.9510.9730.8320.8360.7750.7480.777
webgraph (production)0.8780.9420.9520.8330.8500.7290.6890.781
trafilatura 2.2.0, run here0.8580.9370.9310.8170.7670.6280.7460.721
resiliparse 1.0.9, run here0.7910.8150.9530.8260.7730.5860.6690.712
readability-lxml 0.9, run here0.7590.9210.8620.6170.6160.4650.4160.514
jusText 3.0.2, run here0.7130.8260.6530.6850.7520.3790.3820.629

Published test rows: rs-trafilatura 0.893, trafilatura 0.833, dom-smoothie 0.808, readability 0.726. Second, 0.013 behind; the annotated type would score 0.886 — the archiver stripped every <script> from these files, so the router's JSON-LD features are zero and it mis-types listings (0.689 against a 0.763 ceiling). The 0.875 this page showed before 21 September was not the production path.

Boards deliberately not run

Firecrawl's scrape-evals (live URLs; run once here, never a board), CrawlBench (LLM structured extraction, dataset not published), the Hydrafetch benchmark (the WCXB dev split under another name), the Dragnet and Boilerpipe corpora (inside WCEB), and trafilatura's own evaluation (the maintainer's corpus; WCEB covers it independently).