Raw web pages are terrible training data. Nav bars, cookie banners, "related articles" and ads drown the signal, and near-identical syndicated text pollutes the corpus. If you're fine-tuning a domain model — legal reasoning, medical QA, financial analysis — you want clean vertical text with provenance, not a pile of HTML. I built an actor that goes from seed URLs to a token-aware JSONL dataset in one run.
The pipeline
seed URLs → crawl (same-domain BFS) → clean (boilerplate strip)
→ dedupe (exact + near) → token-aware chunk → JSONL with provenance
Three steps matter most:
-
Boilerplate strip — drop
script/style/nav/footer/header/asideand utility blocks (breadcrumbs, shares, comments, menus). One detail that earned its keep: paragraphs where more than half the links are anchor text get dropped too — those are link farms, not content. - Dedupe, exact and near — exact duplicates collapse by normalised hash; near-duplicates by 6-gram Jaccard similarity (default 0.95), so syndicated copies of the same opinion appear once.
- Token-aware chunking — chunks split at paragraph boundaries and hard-split on sentence/word boundaries only when a single paragraph exceeds the budget (default 512 tokens). Each chunk stays coherent, which is what fine-tuning actually wants.
Every record carries source (hostname), url, domain (your vertical), title, and chunk_index — so you can filter, cite, or re-weight the corpus later.
The seed-source reality check
Not every legal text source works from a datacenter IP. CourtListener sits behind an AWS WAF JavaScript challenge, and Justia 403s datacenter egress — both unusable as plain-HTTP seeds. Cornell LII (law.cornell.edu/supremecourt/text/…) serves full opinions as clean HTML and works beautifully. The honest move was documenting that in the README and defaulting the examples to LII rather than pretending every source is reachable.
The smoke test
Three LII Supreme Court opinions (Dobbs, Bruen, WV v EPA):
- Local: 559 chunks, all 6 provenance keys present, chunk sizes 63–2,054 chars, zero duplicates after global dedupe
- Cloud (2 seeds): SUCCEEDED, 443 items, every record with non-empty text,
domain=legal, sourcelaw.cornell.edu, 0 duplicate texts - KV store:
output.jsonl— 443 lines / ~811 KB, plus aSUMMARYrecord: 443 chunks, 2 pages, ~173,000 estimated tokens, no failed sources
The honest bits
- Seed URLs must serve server-rendered HTML. JS-only SPAs and WAF'd sites need a browser-rendering actor instead.
- Chunk size is an estimate (~4 chars/token), not a byte-exact tokeniser count.
- Near-dedupe is O(n²) in chunk count — cap large crawls with
maxChunksPerPage. - The crawler follows same-domain links only; cross-domain citations aren't chased.
Try it
👉 Vertical Corpora Builder on Apify Store
More from me
While you're here, these might be worth a read:
- I Stopped Scraping Business Directories and Built an MCP Server on Official Registry Data
- Scraping Romanian Public Contracts: A Native-Language Tender & Awarded Deals Scraper
- Building a 12-City US Building Permits Scraper With Python
- Building an EU Safety Gate (RAPEX) Product Recall Scraper With Python
- How I Built a Water Utility Risk Intelligence Tool With Python and MCP
- Building an Aviation Hub API: Airports, Airlines, Live Flights & Weather From Six Keyless Sources
- I Built a Canada Product Recalls & Safety Alerts Scraper That Reads Open Government Data
- I Built a Telegram Members Scraper That Reads Public Chat Stats Without Login
- Building a WHOIS & DNS Lookup Tool: Domain Intelligence in One...
- Building an AI Web Crawler That Outputs LLM-Ready Content Chunks
- Building a Real-Time Press Release Monitor with Python and RSS...
- Building a Universal Property Listing Scraper with Python and ...
- Tracking Tech Sentiment in Real-Time with VADER and Python
- How I Built a Product Hunt Scraper That Tracks Launches in Rea...
- 5 APIs Every Developer Needs for Content Processing (RSS, Extraction, Sitemaps, AI)
- How to Extract Clean Content From Any Website Sitemap (For SEO...
- Scraping 187,000 Romanian Businesses: Building a B2B Lead Gene...
- Make Any Website AI-Readable: Generating llms.txt Files with
- I Built an RSS Aggregator That Extracts Full Article Content (...
Top comments (0)