Web scraper extraction quality: Trafilatura, readability, Crawl4AI, Firecrawl — article mark

Web scraper extraction quality: Trafilatura, readability, Crawl4AI, Firecrawl

Trafilatura 0.84 F1 on the independent WCXB test set (0.96 article-only) — best open extractor. Run locally on ScrapingHub’s 181 pages: Trafilatura 0.955, readability 0.922, Crawl4AI fit_markdown 0.790, Firecrawl main content 0.785 — the browser readers keep the body (recall 0.97) but lose on precision (0.66).

Series — web scrapers for local LLMs: Overview · Readers compared · Token cost per page · Extraction quality · Output formats · Previous stage: Web search for local LLMs

Independent F1 — published benchmarks

Main-text extraction F1 on the independent WCXB held-out test, with Trafilatura's article-only score marked Extraction F1 (WCXB held-out test — independent, higher = cleaner) Trafilatura Dripper (MinerU-HTML 0.6B) ReaderLM-v2 (0.5–1.5B) readability 0.500.751.00 F1 0.84 0.96 article-only 0.83 0.74 0.74 Amber tick: Trafilatura on ScrapingHub's article-only set. Crawl4AI / Firecrawl: no third-party number → run locally below.
WCXB held-out-test F1 across 2,008 mixed pages. On clean article pages everyone rises — Trafilatura to 0.96.
ReaderWCXB dev F1WCXB test F1ScrapingHub (article-only)SpeedNotes
Trafilatura0.790.840.958fastpure-Python, no browser
Dripper (MinerU-HTML 0.6B)—0.83—1.57 s/pagesmall model
ReaderLM-v2 (0.5–1.5B)—0.74—10.4 s/page (slowest)transformer reader
readability0.670.740.92–0.95fastmisses <div> content
Crawl4AI / Firecrawl✗ no third-party run✗✗—published figures are competitor-run / coverage-pass → measured locally, next section
chromium MCP fetch= Trafilatura= Trafilatura= Trafilatura5.4–8.3 s/page cold, 2–3 ms cache hitruns Trafilatura on the rendered DOM; U2 5,527 tok, identical to Trafilatura

Third-party benchmarks, not run here. Sources: WCXB arXiv 2605.21097 · ScrapingHub article-extraction-benchmark (github.com/scrapinghub/article-extraction-benchmark).

Same pages, every reader — ScrapingHub benchmark, 181 pages

Run locally on my fleet.

Main-text extraction F1 of six reader configurations on the same 181 ScrapingHub benchmark pages, run locally Extraction F1 (ScrapingHub article-extraction-benchmark, 181 pages — run locally, higher = cleaner) Trafilatura readability-lxml Crawl4AI fit_markdown Firecrawl onlyMainContent Firecrawl full page Crawl4AI raw_markdown 0.500.751.00 F1 0.955 0.922 0.790 · P 0.66 0.785 · P 0.66 0.655 · P 0.49 0.513 · P 0.35 Recall 0.93–0.99 for all six; the gap is precision (boilerplate kept). chromium MCP fetch = Trafilatura.
Same 181 pages, same scorer, same Markdown stripping for every reader.
ReaderPrecisionRecallF1Empty pagesMedian ms/page
Trafilatura 2.0.00.9430.9680.955010
readability-lxml 0.8.4.10.9130.9310.92208
Crawl4AI 0.9.4 fit_markdown0.6640.9760.7902131
Firecrawl (self-host) onlyMainContent:true0.6600.9670.78502,155
Firecrawl onlyMainContent:false0.4900.9850.65501,764
Crawl4AI raw_markdown0.3470.9900.5130131
chromium MCP fetch= Trafilatura= Trafilatura= Trafilatura—not run

Scorer: the benchmark's own evaluate.py method (4-word shingles, mean precision and recall over pages, F1 of the means), reimplemented and checked against its published outputs (Trafilatura 0.958 and readability 0.922 reproduced within 0.001). Markdown syntax (links, image refs, #, *, |, emphasis) stripped from every reader's output before tokenizing. Trafilatura: favor_precision, no links, tables, Markdown (published 0.958 = default config). readability: benchmark's own text conversion. Crawl4AI: one /crawl call per page with the HTML passed as raw:, PruningContentFilter 0.48; raw and fit from the same call. Firecrawl: self-host built from source, /v1/scrape from a local static server, removed after the run. ms: in-process for the pip readers, one HTTP round trip for the browser readers.

What each reader kept on robertz.co

Run locally on my fleet.

ReaderU1 homepage (listing)U2 auth articleU3 broker postBody kept?
raw HTML29,55216,01017,947—
Trafilatura 2.0.060 ✗5,5273,928articles yes · listing no
readability-lxml 0.8.4.151 ✗5,7464,147articles yes · listing no
Crawl4AI 0.9.4 fit1,2696,5904,628yes
Firecrawl main content1,4796,3644,724yes

Tokens per page. U1 robertz.co/ · U2 /knowledge/computer-science/web-security/eight-authentication-methods-battle/ · U3 /feeds/open-source/broker-parrot-self-hosted-job-fleet/. On U1 Trafilatura kept only the hosting blurb (twice), readability only the name, tagline and email; the browser readers kept the listing.

Where every reader fails

Prior run: stackoverflow.com behind a Cloudflare JS challengeResult
Trafilaturafetch blocked
readability-lxmlHTTP 403
Crawl4AI1 char
FirecrawlHTTP 500

Separate run, same page: Camoufox ✓ and nodriver ✓ (real page, accepted answer in the DOM; Trafilatura then kept 631 tok, question + comments, answer dropped); same-run controls Crawl4AI ✗ (blocked: Cloudflare JS challenge), Trafilatura fetch ✗ (nothing downloaded). None of the four ship a stealth browser.

Caveats

Questions? Let's Chat :)

ai llm engineering